Coverage Report

Created: 2026-07-16 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/work/vvenc/source/Lib/EncoderLib/SEIFilmGrainAnalyzer.cpp
Line
Count
Source
1
/* -----------------------------------------------------------------------------
2
The copyright in this software is being made available under the Clear BSD
3
License, included below. No patent rights, trademark rights and/or
4
other Intellectual Property Rights other than the copyrights concerning
5
the Software are granted under this license.
6
7
The Clear BSD License
8
9
Copyright (c) 2019-2026, Fraunhofer-Gesellschaft zur F�rderung der angewandten Forschung e.V. & The VVenC Authors.
10
All rights reserved.
11
12
Redistribution and use in source and binary forms, with or without modification,
13
are permitted (subject to the limitations in the disclaimer below) provided that
14
the following conditions are met:
15
16
     * Redistributions of source code must retain the above copyright notice,
17
     this list of conditions and the following disclaimer.
18
19
     * Redistributions in binary form must reproduce the above copyright
20
     notice, this list of conditions and the following disclaimer in the
21
     documentation and/or other materials provided with the distribution.
22
23
     * Neither the name of the copyright holder nor the names of its
24
     contributors may be used to endorse or promote products derived from this
25
     software without specific prior written permission.
26
27
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
28
THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
29
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
31
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
32
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
33
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
34
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
35
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
36
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38
POSSIBILITY OF SUCH DAMAGE.
39
40
41
------------------------------------------------------------------------------------------- */
42
43
#include "SEIFilmGrainAnalyzer.h"
44
45
#include "CommonLib/MCTF.h"
46
#include "TrQuant_EMT.h"
47
48
using namespace vvenc;
49
50
// POLYFIT
51
static constexpr int      MAXORDER = 8;                                   // maximum order of polynomial fitting
52
static constexpr int      MAX_REAL_SCALE = 16;
53
static constexpr int      ORDER = 4;                                      // order of polynomial function
54
static constexpr int      QUANT_LEVELS = 4;                               // number of quantization levels in lloyd max quantization
55
56
static constexpr int      MIN_ELEMENT_NUMBER_PER_INTENSITY_INTERVAL = 8;
57
static constexpr int      MIN_POINTS_FOR_INTENSITY_ESTIMATION = 40;       // 5*8 = 40; 5 intervals with at least 8 points
58
static constexpr int      MIN_BLOCKS_FOR_CUTOFF_ESTIMATION = 2;           // 2 blocks of 64 x 64 size
59
static constexpr int      POINT_STEP = 16;                                // step size in point extension
60
static constexpr int      MAX_NUM_POINT_TO_EXTEND = 4;                    // max point in extension
61
static constexpr double   POINT_SCALE = 1.25;                             // scaling in point extension
62
static constexpr double   VAR_SCALE_DOWN = 1.2;                           // filter out large points
63
static constexpr double   VAR_SCALE_UP = 0.6;                             // filter out large points
64
static constexpr int      NUM_PASSES = 2;                                 // number of passes when fitting the function
65
static constexpr int      NBRS = 1;                                       // minimum number of surrounding points in order to keep it for further analysis (within the widnow range)
66
static constexpr int      WINDOW = 1;                                     // window to check surrounding points
67
static constexpr int      MIN_INTENSITY = 40;
68
static constexpr int      MAX_INTENSITY = 950;
69
70
static constexpr int      MAX_ALLOWED_MODEL_VALUES = 3;
71
static constexpr int      MAX_NUM_MODEL_VALUES = 6;                       // Maximum number of model values supported in FGC SEI
72
73
static constexpr int      BLK_8 = 8;
74
static constexpr int      BLK_16 = 16;
75
static constexpr int      BLK_32 = 32;
76
static constexpr int      BIT_DEPTH_8 = 8;
77
78
79
static constexpr int      MAX_BLOCKS = 40000;                             // higher than (3840*2160)/(16*16)
80
81
const int m_gx[CONV_HEIGHT_S][CONV_WIDTH_S]{ { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } };
82
const int m_gy[CONV_HEIGHT_S][CONV_WIDTH_S]{ { -1, -2, -1 }, { 0, 0, 0 }, { 1, 2, 1 } };
83
84
constexpr double FGAnalyzer::m_tapFilter[3];
85
86
void gradient_core ( PelStorage *buff1,
87
                     PelStorage *buff2,
88
                     PelStorage *tmpBuf1,
89
                     PelStorage *tmpBuf2,
90
                     uint32_t width,
91
                     uint32_t height,
92
                     uint32_t bitDepth,
93
                     ComponentID compID )
94
0
{
95
  // buff1 - magnitude; buff2 - orientation (Only luma in buff2)
96
0
  const uint32_t convWidthS = CONV_WIDTH_S;
97
0
  const uint32_t convHeightS = CONV_HEIGHT_S;
98
0
  const int maxClpRange = (1 << bitDepth) - 1;
99
0
  const int padding     = convWidthS / 2;
100
101
0
  buff1->get(compID).extendBorderPel( padding,
102
0
                                      padding );
103
104
  // Gx
105
0
  for (int i = 0; i < width; i++)
106
0
  {
107
0
    for (int j = 0; j < height; j++)
108
0
    {
109
0
      int acc = 0;
110
0
      int xOffset = i - convWidthS / 2;
111
0
      int yOffset = j - convHeightS / 2;
112
0
      for (int x = 0; x < convWidthS; x++)
113
0
      {
114
0
        for (int y = 0; y < convHeightS; y++)
115
0
        {
116
0
          acc += ( buff1->get(compID).at( x + xOffset, y + yOffset ) * m_gx[x][y] );
117
0
        }
118
0
      }
119
0
      tmpBuf1->Y().at(i, j) = acc;
120
0
    }
121
0
  }
122
123
  // Gy
124
0
  for ( int i = 0; i < width; i++ )
125
0
  {
126
0
    for ( int j = 0; j < height; j++ )
127
0
    {
128
0
      int acc = 0;
129
0
      for ( int x = 0; x < convWidthS; x++ )
130
0
      {
131
0
        for ( int y = 0; y < convHeightS; y++ )
132
0
        {
133
0
          acc += (buff1->get(compID).at(x - convWidthS / 2 + i, y - convHeightS / 2 + j) * m_gy[x][y]);
134
0
        }
135
0
      }
136
0
      tmpBuf2->Y().at(i, j) = acc;
137
0
    }
138
0
  }
139
140
  // magnitude
141
0
  for ( int i = 0; i < width; i++ )
142
0
  {
143
0
    for ( int j = 0; j < height; j++ )
144
0
    {
145
0
      Pel tmp                     = static_cast<Pel>((abs(tmpBuf1->Y().at(i, j)) + abs(tmpBuf2->Y().at(i, j))) / 2);
146
0
      buff1->get(compID).at(i, j) = static_cast<Pel>( Clip3((Pel) 0, (Pel) maxClpRange, tmp) );
147
0
    }
148
0
  }
149
150
  // Loop through each pixel
151
0
  for ( int i = 0; i < width; i++ )
152
0
  {
153
0
    for ( int j = 0; j < height; j++ )
154
0
    {
155
      // Calculate edge direction angle
156
0
      Pel Dx = tmpBuf1->Y().at( i, j );
157
0
      Pel Dy = tmpBuf2->Y().at( i, j );
158
0
      float theta = 0.0;
159
0
      int quantized_direction = 0;
160
161
0
      if ( Dx == 0 )
162
0
      {
163
0
        if ( Dy == 0 )
164
0
          quantized_direction = 0;
165
0
        else
166
0
          quantized_direction = 90;
167
0
      }
168
0
      else
169
0
      {
170
0
        theta= ( atan( static_cast<double>( Dy )/(double)static_cast<double>( Dx ) ) ) ;
171
0
        if ( Dx < 0 )
172
0
        {
173
0
          if ( Dy >= 0 )
174
0
            theta += static_cast<float>( PI );
175
0
          else
176
0
            theta -= static_cast<float>( PI );
177
0
        }
178
0
        theta = std::fabs( theta );
179
        /* Convert actual edge direction to approximate value - quantize directions */
180
0
        if (( theta <= pi_8 ) || ( pi_7_8 < theta ))
181
0
        {
182
0
          quantized_direction = 0;
183
0
        }
184
0
        if (( pi_8 < theta ) && ( theta <= pi_3_8 ))
185
0
        {
186
0
          if ( Dy > 0 )
187
0
            quantized_direction = 45;
188
0
          else
189
0
            quantized_direction = 135;
190
0
        }
191
0
        if (( pi_3_8 < theta ) && ( theta <= pi_5_8 ))
192
0
        {
193
0
          quantized_direction = 90;
194
0
        }
195
0
        if (( pi_5_8 < theta ) && ( theta <= pi_7_8 ))
196
0
        {
197
0
          if ( Dy > 0 )
198
0
            quantized_direction = 135;
199
0
          else
200
0
            quantized_direction = 45;
201
0
        }
202
0
      }
203
0
      buff2->get(ComponentID(0)).at( i, j ) = quantized_direction;
204
0
    }
205
0
  }
206
0
  buff1->get(compID).extendBorderPel( padding, 
207
0
                                      padding );   // extend border for the next steps
208
0
}
209
210
// ====================================================================================================================
211
// Edge detection - Canny
212
// ====================================================================================================================
213
214
Canny::Canny()
215
1.21k
{
216
  // init();
217
1.21k
  gradient=gradient_core;
218
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_X86 )
219
  initFGACannyX86();
220
#endif
221
1.21k
}
222
223
Canny::~Canny()
224
1.21k
{
225
  // uninit();
226
1.21k
}
227
228
void Canny::init ( uint32_t width,
229
                   uint32_t height,
230
                   ChromaFormat inputChroma )
231
0
{
232
0
  if (!m_orientationBuf)
233
0
  {
234
0
    m_orientationBuf = new PelStorage;
235
0
    m_orientationBuf->create( inputChroma,
236
0
                              Area(0, 0, width, height) );
237
0
  }
238
239
0
  if ( !m_gradientBufX )
240
0
  {
241
0
    m_gradientBufX = new PelStorage;
242
0
    m_gradientBufX->create ( inputChroma,
243
0
                             Area(0, 0, width, height) );
244
0
  }
245
246
0
  if ( !m_gradientBufY )
247
0
  {
248
0
    m_gradientBufY = new PelStorage;
249
0
    m_gradientBufY->create ( inputChroma,
250
0
                             Area(0, 0, width, height) );
251
0
  }
252
0
}
253
254
void Canny::destroy()
255
0
{
256
0
  if ( m_orientationBuf )
257
0
  {
258
0
    m_orientationBuf->destroy();
259
0
    delete m_orientationBuf;
260
0
    m_orientationBuf = nullptr;
261
0
  }
262
263
0
  if ( m_gradientBufX )
264
0
  {
265
0
    m_gradientBufX->destroy();
266
0
    delete m_gradientBufX;
267
0
    m_gradientBufX = nullptr;
268
0
  }
269
270
0
  if ( m_gradientBufY )
271
0
  {
272
0
    m_gradientBufY->destroy();
273
0
    delete m_gradientBufY;
274
0
    m_gradientBufY = nullptr;
275
0
  }
276
0
}
277
278
void Canny::suppressNonMax ( PelStorage *buff1,
279
                             PelStorage *buff2,
280
                             uint32_t width,
281
                             uint32_t height,
282
                             ComponentID compID )
283
0
{
284
0
  for ( int i = 0; i < width; i++ )
285
0
  {
286
0
    for ( int j = 0; j < height; j++ )
287
0
    {
288
0
      int rowShift = 0, colShift = 0;
289
0
      switch ( buff2->get( ComponentID(0) ).at( i, j ) )
290
0
      {
291
0
      case 0:
292
0
        rowShift = 1;
293
0
        colShift = 0;
294
0
        break;
295
0
      case 45:
296
0
        rowShift = 1;
297
0
        colShift = 1;
298
0
        break;
299
0
      case 90:
300
0
        rowShift = 0;
301
0
        colShift = 1;
302
0
        break;
303
0
      case 135:
304
0
        rowShift = -1;
305
0
        colShift = 1;
306
0
        break;
307
0
      default: THROW("Unsupported gradient direction."); break;
308
0
      }
309
310
0
      Pel pelCurrent             = buff1->get(compID).at( i, j );
311
0
      Pel pelEdgeDirectionTop    = buff1->get(compID).at( i + rowShift, j + colShift );
312
0
      Pel pelEdgeDirectionBottom = buff1->get(compID).at( i - rowShift, j - colShift );
313
0
      if (( pelCurrent < pelEdgeDirectionTop ) || ( pelCurrent < pelEdgeDirectionBottom ))
314
0
      {
315
0
        buff2->get(ComponentID(0)).at( i, j ) = 0;   // supress
316
0
      }
317
0
      else
318
0
      {
319
0
        buff2->get(ComponentID(0)).at( i, j ) = buff1->get(compID).at( i, j );   // keep
320
0
      }
321
0
    }
322
0
  }
323
0
  buff1->get(compID).copyFrom( buff2->get( ComponentID(0) ) );
324
0
}
325
326
void Canny::doubleThreshold ( PelStorage *buff,
327
                              uint32_t width,
328
                              uint32_t height,
329
                              uint32_t bitDepth,
330
                              ComponentID compID )
331
0
{
332
0
  Pel strongPel = ( static_cast<Pel>( 1 ) << bitDepth) - 1;
333
0
  Pel weekPel   = ( static_cast<Pel>( 1 ) << (bitDepth - 1)) - 1;
334
335
0
  Pel highThreshold = 0;
336
0
  Pel lowThreshold  = strongPel;
337
0
  for ( int i = 0; i < width; i++ )
338
0
  {
339
0
    for ( int j = 0; j < height; j++ )
340
0
    {
341
0
      highThreshold = std::max<Pel>( highThreshold,
342
0
                                     buff->get(compID).at( i, j ) );
343
0
    }
344
0
  }
345
346
  // global low and high threshold
347
0
  lowThreshold = static_cast<Pel>( m_lowThresholdRatio * highThreshold );
348
0
  highThreshold = Clip3( 0,
349
0
                         (1 << bitDepth) - 1,
350
0
                         m_highThresholdRatio * lowThreshold);   // Canny recommended a upper:lower ratio between 2:1 and 3:1.
351
352
  // strong, week, supressed
353
0
  for ( int i = 0; i < width; i++ )
354
0
  {
355
0
    for ( int j = 0; j < height; j++ )
356
0
    {
357
0
      if ( buff->get(compID).at( i, j ) > highThreshold )
358
0
      {
359
0
        buff->get(compID).at( i, j ) = strongPel;
360
0
      }
361
0
      else if ( buff->get(compID).at( i, j ) <= highThreshold && buff->get(compID).at( i, j ) > lowThreshold )
362
0
      {
363
0
        buff->get(compID).at( i, j ) = weekPel;
364
0
      }
365
0
      else
366
0
      {
367
0
        buff->get(compID).at( i, j ) = 0;
368
0
      }
369
0
    }
370
0
  }
371
372
0
  buff->get(compID).extendBorderPel ( 1, 1 );   // extend one pixel on each side for the next step
373
0
}
374
375
void Canny::edgeTracking ( PelStorage *buff,
376
                           uint32_t width,
377
                           uint32_t height,
378
                           uint32_t windowWidth,
379
                           uint32_t windowHeight,
380
                           uint32_t bitDepth,
381
                           ComponentID compID )
382
0
{
383
0
  Pel strongPel = (static_cast<Pel>(1) << bitDepth) - 1;
384
0
  Pel weakPel   = (static_cast<Pel>(1) << (bitDepth - 1)) - 1;
385
386
0
  for ( int i = 0; i < width; i++ )
387
0
  {
388
0
    for ( int j = 0; j < height; j++ )
389
0
    {
390
0
      if ( buff->get(compID).at( i, j ) == weakPel )
391
0
      {
392
0
        bool strong = false;
393
394
0
        for ( int x = 0; x < windowWidth; x++ )
395
0
        {
396
0
          for ( int y = 0; y < windowHeight; y++ )
397
0
          {
398
0
            if ( buff->get(compID).at( x - windowWidth / 2 + i, y - windowHeight / 2 + j ) == strongPel )
399
0
            {
400
0
              strong = true;
401
0
              break;
402
0
            }
403
0
          }
404
0
        }
405
406
0
        if ( strong )
407
0
        {
408
0
          buff->get(compID).at( i, j ) = strongPel;
409
0
        }
410
0
        else
411
0
        {
412
0
          buff->get(compID).at( i, j ) = 0;   // supress
413
0
        }
414
0
      }
415
0
    }
416
0
  }
417
0
}
418
419
void Canny::detect_edges ( const PelStorage *orig,
420
                           PelStorage *dest,
421
                           uint32_t uiBitDepth,
422
                           ComponentID compID )
423
0
{
424
  /* No noise reduction - Gaussian blur is skipped;
425
   Gradient calculation;
426
   Non-maximum suppression;
427
   Double threshold;
428
   Edge Tracking by Hysteresis.*/
429
430
0
  uint32_t width      = orig->get( compID ).width,
431
0
           height     = orig->get( compID ).height;       // Width and Height of current frame
432
0
  uint32_t convWidthS  = CONV_WIDTH_S,
433
0
           convHeightS = CONV_HEIGHT_S;                 // Pixel's row and col positions for Sobel filtering
434
0
  uint32_t bitDepth    = uiBitDepth;
435
436
0
  dest->get(compID).copyFrom( orig->getBuf( compID ) );   // we skip blur in canny detector to catch as much as possible edges and textures
437
438
  /* Gradient calculation */
439
0
  gradient ( dest,
440
0
             m_orientationBuf,
441
0
             m_gradientBufX,
442
0
             m_gradientBufY,
443
0
             width,
444
0
             height,
445
0
             bitDepth,
446
0
             compID );
447
448
  /* Non - maximum suppression */
449
0
  suppressNonMax ( dest,
450
0
                   m_orientationBuf,
451
0
                   width,
452
0
                   height,
453
0
                   compID );
454
455
  /* Double threshold */
456
0
  doubleThreshold ( dest,
457
0
                    width,
458
0
                    height, 
459
0
                    bitDepth,
460
0
                    compID );
461
462
  /* Edge Tracking by Hysteresis */
463
0
  edgeTracking ( dest,
464
0
                 width,
465
0
                 height,
466
0
                 convWidthS,
467
0
                 convHeightS,
468
0
                 bitDepth,
469
0
                 compID ); 
470
0
}
471
472
// ====================================================================================================================
473
// Morphologigal operations - Dilation and Erosion
474
// ====================================================================================================================
475
int dilation_core ( PelStorage *buff,
476
                    PelStorage *Wbuf,
477
                    uint32_t bitDepth,
478
                    ComponentID compID,
479
                    int numIter,
480
                    int iter,
481
                    Pel Value )
482
0
{
483
0
  if ( iter == numIter )
484
0
  {
485
0
    return iter;
486
0
  }
487
0
  uint32_t width      = buff->get( compID ).width;
488
0
  uint32_t height     = buff->get( compID ).height;   // Width and Height of current frame
489
0
  uint32_t windowSize = KERNELSIZE;
490
0
  uint32_t padding    = windowSize / 2;
491
492
0
  Wbuf->bufs[0].copyFrom( buff->get( compID ) );
493
494
0
  buff->get(compID).extendBorderPel( padding,
495
0
                                     padding );
496
497
0
  for ( int i = 0; i < width; i++ )
498
0
  {
499
0
    for ( int j = 0; j < height; j++ )
500
0
    {
501
0
      bool strong = false;
502
0
      for ( int x = 0; x < windowSize; x++ )
503
0
      {
504
0
        for ( int y = 0; y < windowSize; y++ )
505
0
        {
506
0
          if ( buff->get( compID ).at( x - windowSize / 2 + i, y - windowSize / 2 + j ) == Value )
507
0
          {
508
0
            strong = true;
509
0
            break;
510
0
          }
511
0
        }
512
0
        if ( strong ) break;
513
0
      }
514
0
      if ( strong )
515
0
      {
516
0
        Wbuf->get(ComponentID(0)).at( i, j ) = Value;
517
0
      }
518
0
    }
519
0
  }
520
521
0
  buff->get(compID).copyFrom( Wbuf->bufs[0] );
522
523
0
  return dilation_core ( buff,
524
0
                         Wbuf,
525
0
                         bitDepth,
526
0
                         compID,
527
0
                         numIter,
528
0
                         ++iter,
529
0
                         Value );
530
531
0
}
532
533
Morph::Morph()
534
1.21k
{
535
  // init();
536
1.21k
  dilation=dilation_core;
537
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_X86 )
538
  initFGAMorphX86();
539
#endif
540
1.21k
}
541
542
Morph::~Morph()
543
1.21k
{
544
  // uninit();
545
1.21k
}
546
547
void Morph::init ( uint32_t width,
548
                   uint32_t height )
549
0
{
550
0
  if ( !m_dilationBuf )
551
0
  {
552
0
    m_dilationBuf = new PelStorage;
553
0
    m_dilationBuf->create ( VVENC_CHROMA_400,
554
0
                            Area( 0, 0, width, height ) );
555
0
  }
556
0
  if ( !m_dilationBuf2 )
557
0
  {
558
0
    m_dilationBuf2 = new PelStorage;
559
0
    m_dilationBuf2->create ( VVENC_CHROMA_400,
560
0
                             Area( 0, 0, width >> 1, height >> 1 ) );
561
0
  }
562
0
  if ( !m_dilationBuf4 )
563
0
  {
564
0
    m_dilationBuf4 = new PelStorage;
565
0
    m_dilationBuf4->create( VVENC_CHROMA_400,
566
0
                              Area( 0, 0, width >> 2, height >> 2 ) );
567
0
  }
568
0
}
569
570
void Morph::destroy ()
571
0
{
572
0
  if ( m_dilationBuf )
573
0
  {
574
0
    m_dilationBuf->destroy();
575
0
    delete m_dilationBuf;
576
0
    m_dilationBuf = nullptr;
577
0
  }
578
0
  if ( m_dilationBuf2 )
579
0
  {
580
0
    m_dilationBuf2->destroy();
581
0
    delete m_dilationBuf2;
582
0
    m_dilationBuf2 = nullptr;
583
0
  }
584
0
  if ( m_dilationBuf4 )
585
0
  {
586
0
    m_dilationBuf4->destroy();
587
0
    delete m_dilationBuf4;
588
0
    m_dilationBuf4 = nullptr;
589
0
  }
590
0
}
591
592
int calcMeanCore ( const Pel* org,
593
                   const ptrdiff_t origStride,
594
                   const int w,
595
                   const int h )
596
0
{
597
  // calculate average
598
0
  int avg = 0;
599
0
  for( int y1 = 0; y1 < h; y1++ )
600
0
  {
601
0
    for( int x1 = 0; x1 < w; x1++ )
602
0
    {
603
0
      avg = avg + *( org + x1 + y1 * origStride );
604
0
    }
605
0
  }
606
0
  return avg;
607
0
}
608
609
// ====================================================================================================================
610
// Film Grain Analysis Functions
611
// ====================================================================================================================
612
FGAnalyzer::FGAnalyzer( bool enableOpt )
613
1.21k
{
614
1.21k
  calcVar     = calcVarCore;
615
1.21k
  calcMean    = calcMeanCore;
616
1.21k
  fastDCT2_64 = fastForwardDCT2_B64;
617
618
1.21k
  if( enableOpt )
619
1.21k
  {
620
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_X86 )
621
    initFGAnalyzerX86();
622
#endif
623
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_ARM )
624
    initFGAnalyzerARM();
625
#endif
626
1.21k
  }
627
1.21k
}
628
629
FGAnalyzer::~FGAnalyzer()
630
1.21k
{
631
1.21k
}
632
633
// initialize film grain parameters
634
void FGAnalyzer::init ( const int width,
635
                        const int height,
636
                        const ChromaFormat inputChroma,
637
                        const int *outputBitDepths,
638
                        const bool doAnalysis[] )
639
0
{
640
0
  m_log2ScaleFactor = 2;
641
0
  for (int i = 0; i < ComponentID::MAX_NUM_COMP; i++)
642
0
  {
643
0
    m_compModel[i].presentFlag           = true;
644
0
    m_compModel[i].numModelValues        = 3;
645
0
    m_compModel[i].numIntensityIntervals = 1;
646
0
    m_compModel[i].intensityValues.resize(VVENC_MAX_NUM_INTENSITIES);
647
0
    for ( int j = 0; j < VVENC_MAX_NUM_INTENSITIES; j++ )
648
0
    {
649
0
      m_compModel[i].intensityValues[j].intensityIntervalLowerBound = 10;
650
0
      m_compModel[i].intensityValues[j].intensityIntervalUpperBound = 250;
651
0
      m_compModel[i].intensityValues[j].compModelValue.resize( MAX_ALLOWED_MODEL_VALUES );
652
0
      for ( int k = 0; k < m_compModel[i].numModelValues; k++ )
653
0
      {
654
        // half intensity for chroma. Provided value is default value, manually tuned.
655
0
        m_compModel[i].intensityValues[j].compModelValue[k] = i == 0 ? 26 : 13;
656
0
      }
657
0
    }
658
0
    m_doAnalysis[i] = doAnalysis[i];
659
0
  }
660
661
  // initialize picture parameters and create buffers
662
0
  m_bitDepths                   = const_cast<int*>( outputBitDepths );
663
0
  m_inputChromaFormat           = inputChroma;
664
  // Allocate memory for m_coeffBuf and m_dctGrainBlockList
665
0
  m_coeffBuf = (TCoeff*)xMalloc( TCoeff, width * height );
666
0
  int N = (width * height) / (DATA_BASE_SIZE * DATA_BASE_SIZE);
667
0
  m_dctGrainBlockList = new CoeffBuf[N];
668
669
0
  std::fill( std::begin(vecMean), std::end(vecMean), 0 );
670
0
  std::fill( std::begin(vecVar), std::end(vecVar), 0 );
671
672
  // Connect portions of m_coeffBuf memory with m_dctGrainBlockList
673
0
  for ( int i = 0; i < N; ++i )
674
0
  {
675
0
    m_dctGrainBlockList[i].buf = m_coeffBuf + i * ( DATA_BASE_SIZE * DATA_BASE_SIZE );
676
0
    m_dctGrainBlockList[i].stride = DATA_BASE_SIZE;
677
0
    m_dctGrainBlockList[i].height = m_dctGrainBlockList[i].width = DATA_BASE_SIZE;
678
0
  }
679
680
0
  m_edgeDetector.init ( width,
681
0
                        height,
682
0
                        inputChroma );
683
684
0
  m_morphOperation.init ( width,
685
0
                          height );
686
687
0
  int margin = m_edgeDetector.m_convWidthG / 2;   // set margin for padding for filtering
688
0
  int      newWidth2 = width / 2;
689
0
  int      newHeight2 = height / 2;
690
0
  int      newWidth4 = width / 4;
691
0
  int      newHeight4 = height / 4;
692
693
0
  if ( !m_maskBuf )
694
0
  {
695
0
    m_maskBuf = new PelStorage;
696
0
    m_maskBuf->create ( inputChroma,
697
0
                        Area(0, 0, width, height),
698
0
                        0, margin,
699
0
                        0, false );
700
0
  }
701
702
0
  if ( !m_grainEstimateBuf )
703
0
  {
704
0
     m_grainEstimateBuf = new PelStorage;
705
0
     m_grainEstimateBuf->create( inputChroma,
706
0
                                Area(0, 0, width, height),
707
0
                                0, 0,
708
0
                                0, false );
709
0
  }
710
711
0
  if ( !m_workingBufSubsampled2 )
712
0
  {
713
0
    m_workingBufSubsampled2 = new PelStorage;
714
0
    m_workingBufSubsampled2->create( inputChroma,
715
0
                                     Area(0, 0, newWidth2, newHeight2),
716
0
                                     0, margin,
717
0
                                     0, false );
718
0
  }
719
720
0
  if ( !m_maskSubsampled2 )
721
0
  {
722
0
    m_maskSubsampled2 = new PelStorage;
723
0
    m_maskSubsampled2->create( inputChroma,
724
0
                               Area(0, 0, newWidth2, newHeight2),
725
0
                               0, margin,
726
0
                               0, false );
727
0
  }
728
0
  if ( !m_workingBufSubsampled4 )
729
0
  {
730
0
    m_workingBufSubsampled4 = new PelStorage;
731
0
    m_workingBufSubsampled4->create( inputChroma,
732
0
                                     Area(0, 0, newWidth4, newHeight4),
733
0
                                     0, margin,
734
0
                                     0, false );
735
0
  }
736
737
0
  if ( !m_maskSubsampled4 )
738
0
  {
739
0
    m_maskSubsampled4 = new PelStorage;
740
0
    m_maskSubsampled4->create( inputChroma,
741
0
                               Area(0, 0, newWidth4, newHeight4),
742
0
                               0, margin,
743
0
                               0, false );
744
0
  }
745
0
  if ( !m_maskUpsampled )
746
0
  {
747
0
    m_maskUpsampled = new PelStorage;
748
0
    m_maskUpsampled->create( inputChroma,
749
0
                             Area(0, 0, width, height),
750
0
                             0, margin,
751
0
                             0, false );
752
0
  }
753
0
  if ( !m_DCTinout )
754
0
  {
755
0
    m_DCTinout = ( TCoeff* ) xMalloc( TCoeff, DATA_BASE_SIZE * DATA_BASE_SIZE );
756
0
  }
757
0
  if ( !m_DCTtemp )
758
0
  {
759
0
    m_DCTtemp = ( TCoeff* ) xMalloc( TCoeff, DATA_BASE_SIZE * DATA_BASE_SIZE );
760
0
  }
761
762
0
}
763
764
// delete picture buffers
765
void FGAnalyzer::destroy()
766
0
{
767
0
  if ( m_maskBuf != nullptr )
768
0
  {
769
0
    m_maskBuf->destroy();
770
0
    delete m_maskBuf;
771
0
    m_maskBuf = nullptr;
772
0
  }
773
774
0
  if ( m_grainEstimateBuf )
775
0
  {
776
0
    m_grainEstimateBuf->destroy();
777
0
    delete m_grainEstimateBuf;
778
0
    m_grainEstimateBuf = nullptr;
779
0
  }
780
781
0
  if ( m_workingBufSubsampled2 )
782
0
  {
783
0
    m_workingBufSubsampled2->destroy();
784
0
    delete m_workingBufSubsampled2;
785
0
    m_workingBufSubsampled2 = nullptr;
786
0
  }
787
0
  if ( m_maskSubsampled2 )
788
0
  {
789
0
    m_maskSubsampled2->destroy();
790
0
    delete m_maskSubsampled2;
791
0
    m_maskSubsampled2 = nullptr;
792
0
  }
793
  
794
0
  if ( m_workingBufSubsampled4 )
795
0
  {
796
0
    m_workingBufSubsampled4->destroy();
797
0
    delete m_workingBufSubsampled4;
798
0
    m_workingBufSubsampled4 = nullptr;
799
0
  }
800
0
  if ( m_maskSubsampled4 )
801
0
  {
802
0
    m_maskSubsampled4->destroy();
803
0
    delete m_maskSubsampled4;
804
0
    m_maskSubsampled4 = nullptr;
805
0
  }
806
0
  if ( m_maskUpsampled )
807
0
  {
808
0
    m_maskUpsampled->destroy();
809
0
    delete m_maskUpsampled;
810
0
    m_maskUpsampled = nullptr;
811
0
  }
812
0
  if ( m_DCTinout )
813
0
  {
814
0
    xFree( m_DCTinout );
815
0
    m_DCTinout = nullptr;
816
0
  }
817
0
  if ( m_DCTtemp )
818
0
  {
819
0
    xFree( m_DCTtemp );
820
0
    m_DCTtemp = nullptr;
821
0
  }
822
823
0
  xFree ( m_coeffBuf );
824
825
0
  if ( m_dctGrainBlockList )
826
0
  {
827
0
    delete[] m_dctGrainBlockList;
828
0
    m_dctGrainBlockList = nullptr;
829
0
  }
830
831
  // Clear vectors to release memory
832
0
  finalIntervalsandScalingFactors.clear();
833
0
  vec_mean_intensity.clear();
834
0
  vec_variance_intensity.clear();
835
0
  element_number_per_interval.clear();
836
0
  vecMean.clear();
837
0
  vecVar.clear();
838
0
  tmp_data_x.clear();
839
0
  tmp_data_y.clear();
840
0
  scalingVec.clear();
841
0
  quantVec.clear();
842
0
  coeffs.clear();
843
844
0
  m_edgeDetector.destroy ();
845
0
  m_morphOperation.destroy ();
846
0
}
847
848
// find flat and low complexity regions of the frame
849
void FGAnalyzer::findMask( ComponentID compId )
850
0
{
851
0
  const unsigned padding    = m_edgeDetector.m_convWidthG / 2;   // for filtering
852
0
  int bitDepth  = m_bitDepths[toChannelType( compId )];
853
854
  // Step 1: Subsample the original picture to two lower resolutions.
855
0
  subsample ( *m_workingBufSubsampled2,
856
0
              2,
857
0
              padding,
858
0
              compId );
859
0
  subsample ( *m_workingBufSubsampled4,
860
0
              4,
861
0
              padding,
862
0
              compId );
863
864
  /* Step 2: Full Resolution processing:
865
   * For each component(luma and chroma), detect edges and suppress low intensity regions.
866
   * Apply dilation to each component.*/
867
0
  m_edgeDetector.detect_edges ( m_workingBuf,
868
0
                                m_maskBuf,
869
0
                                bitDepth,
870
0
                                compId );
871
0
  suppressLowIntensity ( *m_workingBuf,
872
0
                         *m_maskBuf,
873
0
                         bitDepth,
874
0
                         compId );
875
  
876
0
  Pel strongPel = ( static_cast<Pel>( 1 ) << bitDepth ) - 1;
877
0
  m_morphOperation.dilation ( m_maskBuf,
878
0
                              m_morphOperation.m_dilationBuf,
879
0
                              bitDepth,
880
0
                              compId,
881
0
                              4,
882
0
                              0,
883
0
                              strongPel );
884
  
885
  
886
  /* Step 3: Subsampled 2 processing:
887
   * Detect edges and suppresses low intensity regions for each component.
888
   * Apply dilation to each component.
889
   * Upsample the result and combine it with the full-resolution mask.*/
890
0
  m_edgeDetector.detect_edges ( m_workingBufSubsampled2,
891
0
                                m_maskSubsampled2,
892
0
                                bitDepth,
893
0
                                compId );
894
0
  suppressLowIntensity ( *m_workingBufSubsampled2,
895
0
                         *m_maskSubsampled2,
896
0
                         bitDepth,
897
0
                         compId );
898
  
899
    
900
0
  m_morphOperation.dilation ( m_maskSubsampled2,
901
0
                              m_morphOperation.m_dilationBuf2,
902
0
                              bitDepth,
903
0
                              compId,
904
0
                              3,
905
0
                              0,
906
0
                              strongPel );
907
908
909
  // upsample, combine maskBuf and maskUpsampled
910
0
  upsample ( *m_maskSubsampled2,
911
0
             2,
912
0
             compId );
913
0
  combineMasks ( compId );
914
915
  /* Step 4: Subsampled 4 processing:
916
   * Detect edges and suppresses low intensity regions for each component.
917
   * Apply dilation to each component.
918
   * Upsample the result and combine it with the full-resolution mask.*/
919
0
  m_edgeDetector.detect_edges ( m_workingBufSubsampled4,
920
0
                                m_maskSubsampled4,
921
0
                                bitDepth,
922
0
                                compId );
923
0
  suppressLowIntensity ( *m_workingBufSubsampled4,
924
0
                         *m_maskSubsampled4,
925
0
                         bitDepth,
926
0
                         compId );
927
928
0
  m_morphOperation.dilation ( m_maskSubsampled4,
929
0
                              m_morphOperation.m_dilationBuf4,
930
0
                              bitDepth,
931
0
                              compId,
932
0
                              2,
933
0
                              0,
934
0
                              strongPel );
935
936
  // upsample, combine maskBuf and maskUpsampled
937
0
  upsample ( *m_maskSubsampled4,
938
0
             4,
939
0
             compId );
940
0
  combineMasks ( compId );
941
942
  /* Step 5: Final dilation and erosion
943
   * Apply final dilation to fill the holes and erosion for each component. */
944
0
  m_morphOperation.dilation ( m_maskBuf,
945
0
                              m_morphOperation.m_dilationBuf,
946
0
                              bitDepth,
947
0
                              compId,
948
0
                              2,
949
0
                              0,
950
0
                              strongPel );
951
  // erosion -> dilation with value 0
952
0
  m_morphOperation.dilation ( m_maskBuf,
953
0
                              m_morphOperation.m_dilationBuf,
954
0
                              bitDepth,
955
0
                              compId,
956
0
                              1,
957
0
                              0,
958
0
                              0 );
959
0
}
960
961
void FGAnalyzer::suppressLowIntensity ( const PelStorage &buff1,
962
                                        PelStorage &buff2,
963
                                        uint32_t bitDepth, 
964
                                        ComponentID compId )
965
0
{
966
  // buff1 - intensity values ( luma or chroma samples); buff2 - mask
967
968
0
  int width                 = buff2.get( compId ).width;
969
0
  int height                = buff2.get( compId ).height;
970
0
  Pel maxIntensity          = static_cast <Pel>( 1 << bitDepth ) - 1;
971
0
  Pel lowIntensityThreshold = static_cast<Pel>( m_lowIntensityRatio * maxIntensity );
972
973
  // strong, weak, supressed
974
0
  for ( int i = 0; i < width; i++ )
975
0
  {
976
0
    for ( int j = 0; j < height; j++ )
977
0
    {
978
      // Check if the intensity is below the threshold
979
0
      if ( buff1.get( compId ).at( i, j ) < lowIntensityThreshold )
980
0
      {
981
        // Set the corresponding mask value to maxIntensity
982
0
        buff2.get( compId ).at( i, j ) = maxIntensity;
983
0
      }
984
0
    }
985
0
  }
986
0
}
987
988
void FGAnalyzer::subsample ( PelStorage &output,
989
                             const int factor,
990
                             const int padding,
991
                             ComponentID compId ) const
992
0
{
993
0
  const int newWidth  = m_workingBuf->get( compId ).width / factor;
994
0
  const int newHeight = m_workingBuf->get( compId ).height / factor;
995
996
0
  const Pel *srcRow    = m_workingBuf->get( compId ).buf;
997
0
  const ptrdiff_t srcStride = m_workingBuf->get( compId ).stride;
998
0
  Pel *dstRow    = output.get( compId ).buf;   // output is tmp buffer with only one component for binary mask
999
0
  const ptrdiff_t dstStride = output.get( compId ).stride;
1000
1001
0
  for ( int y = 0; y < newHeight; y++, srcRow += factor * srcStride, dstRow += dstStride )
1002
0
  {
1003
0
    const Pel *inRow      = srcRow;
1004
0
    const Pel *inRowBelow = srcRow + srcStride;
1005
0
    Pel *      target     = dstRow;
1006
1007
0
    for ( int x = 0; x < newWidth; x++ )
1008
0
    {
1009
0
      target[x] = ( inRow[0] + inRowBelow[0] + inRow[1] + inRowBelow[1] + 2 ) >> 2;
1010
0
      inRow += factor;
1011
0
      inRowBelow += factor;
1012
0
    }
1013
0
  }
1014
1015
0
  if ( padding )
1016
0
  {
1017
    // Extend border with padding
1018
0
    output.get( compId ).extendBorderPel ( padding,
1019
0
                                           padding );
1020
0
  }
1021
0
}
1022
1023
void FGAnalyzer::upsample ( const PelStorage &input,
1024
                            const int factor,
1025
                            const int padding,
1026
                            ComponentID compId ) const
1027
0
{
1028
  // binary mask upsampling
1029
  // use simple replication of pixels
1030
1031
0
  const int width  = input.get(compId).width;
1032
0
  const int height = input.get(compId).height;
1033
1034
0
  for ( int i = 0; i < width; i++ )
1035
0
  {
1036
0
    for ( int j = 0; j < height; j++ )
1037
0
    {
1038
0
      Pel currentPel = input.get( compId ).at( i, j );
1039
1040
0
      for ( int x = 0; x < factor; x++ )
1041
0
      {
1042
0
        for ( int y = 0; y < factor; y++ )
1043
0
        {
1044
0
          m_maskUpsampled->get( compId ).at( i * factor + x, j * factor + y ) = currentPel;
1045
0
        }
1046
0
      }
1047
0
    }
1048
0
  }
1049
1050
0
  if ( padding )
1051
0
  {
1052
0
    m_maskUpsampled->get( compId ).extendBorderPel( padding,
1053
0
                                                    padding );
1054
0
  }
1055
0
}
1056
1057
void FGAnalyzer::combineMasks( ComponentID compId )
1058
0
{
1059
0
  const int width = m_maskBuf->get( compId ).width;
1060
0
  const int height = m_maskBuf->get( compId ).height;
1061
1062
0
  for ( int i = 0; i < width; i++ )
1063
0
  {
1064
0
    for ( int j = 0; j < height; j++ )
1065
0
    {
1066
0
      m_maskBuf->get( compId ).at( i, j ) = ( m_maskBuf->get( compId ).at( i, j ) | m_maskUpsampled->get( compId ).at( i, j ) );
1067
0
    }
1068
0
  }
1069
0
}
1070
1071
// estimate cut-off frequencies and scaling factors for different intensity intervals
1072
void FGAnalyzer::estimateGrainParameters ( Picture *pic )
1073
0
{
1074
0
  m_originalBuf = &pic->getOrigBuffer();                                   // original frame
1075
0
  m_workingBuf = &pic->getFilteredOrigBuffer();                            // mctf filtered frame
1076
1077
  // Determine blockSize dynamically based on the frame resolution
1078
0
  int blockSize = BLK_8;
1079
0
  uint32_t picSizeInLumaSamples = m_workingBuf->Y().height * m_workingBuf->Y().width;
1080
0
  if ( picSizeInLumaSamples >= 7680 * 4320 )
1081
0
  {
1082
    // 8K resolution
1083
0
    blockSize = BLK_32;
1084
0
  }
1085
0
  else if ( picSizeInLumaSamples >= 3840 * 2160 )
1086
0
  {
1087
    // 4K resolution
1088
0
    blockSize = BLK_16;
1089
0
  }
1090
0
  else
1091
0
  {
1092
0
    blockSize = BLK_8;
1093
0
  }
1094
1095
0
  findMask( COMP_Y );                                                       // Generate mask for luma only
1096
1097
  // find difference between original and filtered/reconstructed frame => film grain estimate
1098
0
  m_grainEstimateBuf->subtract( pic->getOrigBuffer(),
1099
0
                                pic->getFilteredOrigBuffer() );
1100
1101
0
  for ( int compIdx = 0; compIdx < getNumberValidComponents( m_inputChromaFormat ); compIdx++ )
1102
0
  {
1103
0
    ComponentID  compID          = ComponentID( compIdx );
1104
0
    uint32_t     width           = m_workingBuf->getBuf( compID ).width;    // Width of current frame
1105
0
    uint32_t     height          = m_workingBuf->getBuf( compID ).height;   // Height of current frame
1106
0
    uint32_t     windowSize      = DATA_BASE_SIZE;                          // Size for Film Grain block
1107
0
    int          bitDepth        = m_bitDepths[toChannelType( compID )];
1108
0
    int          detect_edges    = 0;
1109
0
    int          mean            = 0;
1110
0
    int          var             = 0;
1111
0
    m_numDctGrainBlocks          = 0;
1112
1113
    // Clear vectors before computing for each component
1114
0
    vecMean.clear();
1115
0
    vecVar.clear();
1116
0
    tmp_data_x.clear();
1117
0
    tmp_data_y.clear();
1118
0
    scalingVec.clear();
1119
0
    quantVec.clear();
1120
0
    coeffs.clear();
1121
1122
0
    for ( int i = 0; i <= width - windowSize; i += windowSize )
1123
0
    { // loop over windowSize x windowSize blocks
1124
0
      for ( int j = 0; j <= height - windowSize; j += windowSize )
1125
0
      {
1126
0
        if ( compID == COMP_Y )
1127
0
        {
1128
0
          detect_edges = countEdges ( windowSize,
1129
0
                                      i,
1130
0
                                      j,
1131
0
                                      compID );  // for flat region without edges
1132
0
        }
1133
0
        else
1134
0
        {
1135
0
          detect_edges = 1;                      // always process for chroma
1136
0
        }
1137
0
        if ( detect_edges )   // selection of uniform, flat and low-complexity area; extend to other features, e.g., variance.
1138
0
        { // find transformed blocks; cut-off frequency estimation is done on 64 x 64 blocks as low-pass filtering on synthesis side is done on 64 x 64 blocks.
1139
0
          CoeffBuf& currentCoeffBuf = m_dctGrainBlockList[m_numDctGrainBlocks++];
1140
0
          blockTransform ( currentCoeffBuf,
1141
0
                           i,
1142
0
                           j,
1143
0
                           bitDepth,
1144
0
                           compID );
1145
0
        }
1146
1147
0
        int step = windowSize / blockSize;
1148
0
        for ( int k = 0; k < step; k++ )
1149
0
        {
1150
0
          for ( int m = 0; m < step; m++ )
1151
0
          {
1152
0
            if ( compID == COMP_Y )
1153
0
            {
1154
0
              detect_edges = countEdges ( blockSize,
1155
0
                                          i + k * blockSize,
1156
0
                                          j + m * blockSize,
1157
0
                                          compID );   // for flat region without edges
1158
0
            }
1159
0
            else
1160
0
            {
1161
0
              detect_edges = 1;  // always process for chroma
1162
0
            }
1163
0
            if ( detect_edges )   // selection of uniform, flat and low-complexity area; extend to other features, e.g., variance.
1164
0
            {
1165
              // collect all data for parameter estimation; mean and variance are caluclated on blockSize x blockSize blocks
1166
0
              uint32_t stride = m_grainEstimateBuf->get( compID ).stride;
1167
0
              double varD = calcVar ( m_grainEstimateBuf->get( compID ).buf + ( ( j + m * blockSize ) * stride ) + i + ( k * blockSize ),
1168
0
                                      stride,
1169
0
                                      blockSize,
1170
0
                                      blockSize );
1171
0
              varD = varD / (( blockSize * blockSize ));
1172
0
              var = static_cast<int>( varD + 0.5 );
1173
0
              stride = m_workingBuf->get( compID ).stride;
1174
0
              mean = calcMean ( m_workingBuf->get( compID ).buf + ( ( j + m * blockSize ) * stride ) + i + ( k * blockSize ),
1175
0
                                stride,
1176
0
                                blockSize,
1177
0
                                blockSize );
1178
0
              mean = static_cast<int>(static_cast<double>( mean ) / ( blockSize * blockSize ) + 0.5 );
1179
1180
              // regularize high variations; controls excessively fluctuating points
1181
0
              double tmp = 2.75 * pow( static_cast<double>( var ), 0.5 ) + 0.5;
1182
0
              var = static_cast<int>( tmp ); 
1183
              // limit data points to meaningful values. higher variance can be result of not perfect mask estimation (non-flat regions fall in estimation process)
1184
0
              if ( var < ( MAX_REAL_SCALE << ( bitDepth - BIT_DEPTH_8 ) ) )
1185
0
              {
1186
0
                vecMean.push_back( mean );    // mean of the filtered frame
1187
0
                vecVar.push_back( var );      // variance of the film grain estimate
1188
0
              }
1189
0
            }
1190
0
          }
1191
0
        }
1192
0
      }
1193
0
    }
1194
1195
    // calculate film grain parameters
1196
0
    estimateCutoffFreqAdaptive( compID );
1197
0
    estimateScalingFactors ( bitDepth,
1198
0
                             compID );
1199
1200
    // Clear vectors after estimation
1201
0
    vecMean.clear();
1202
0
    vecVar.clear();
1203
0
    finalIntervalsandScalingFactors.clear();
1204
0
  }
1205
0
}
1206
1207
/* This function calculates the scaling factors for film grain by analyzing the variance of intensity intervals.
1208
 * The primary steps include fitting a polynomial regression function to the intensity - variance data points,
1209
 * smoothing the resulting scaling function, and performing Lloyd - Max quantization to derive the final scaling factors.
1210
 * The estimated parameters are then set for each intensity interval.*/
1211
void FGAnalyzer::estimateScalingFactors ( uint32_t bitDepth,
1212
                                          ComponentID compId )
1213
0
{
1214
  // if cutoff frequencies are not estimated previously, do not proceed since presentFlag is set to false in a previous step
1215
0
  if ( !m_compModel[compId].presentFlag || vecMean.size() < MIN_POINTS_FOR_INTENSITY_ESTIMATION )
1216
0
  {
1217
0
    return;   // If there is no enough points to estimate film grain intensities, default or previously estimated
1218
              // parameters are used
1219
0
  }
1220
1221
0
  double              distortion = 0.0;
1222
1223
  // Fit the points with the curve and perform Lloyd Max quantization.
1224
0
  bool valid;
1225
0
  for ( int i = 0; i < NUM_PASSES; i++ )   // if num_passes = 2, filtering of the dataset points is performed
1226
0
  {
1227
0
    valid = fitFunction ( ORDER,
1228
0
                          bitDepth,
1229
0
                          i );   // n-th order polynomial regression for scaling function estimation
1230
0
    if ( !valid )
1231
0
    {
1232
0
      coeffs.clear();
1233
0
      scalingVec.clear();
1234
0
      quantVec.clear();
1235
0
      break;
1236
0
    }
1237
0
  }
1238
1239
0
  if ( valid )
1240
0
  {
1241
0
    avgScalingVec ( bitDepth );   // scale with previously fitted function to smooth the intensity
1242
0
    valid = lloydMax ( distortion,
1243
0
                       bitDepth );   // train quantizer and quantize curve using Lloyd Max
1244
0
  }
1245
1246
  // Based on quantized intervals, set intensity region and scaling parameter
1247
0
  if ( valid )   // if not valid, reuse previous parameters (for example, if var is all zero)
1248
0
  {
1249
0
    setEstimatedParameters ( bitDepth,
1250
0
                             compId );
1251
0
  }
1252
1253
0
  coeffs.clear();
1254
0
  scalingVec.clear();
1255
0
  quantVec.clear();
1256
1257
0
}
1258
1259
/*This function divides the specified range(rows or columns) of the `meanSquaredDctGrain` matrix into bins
1260
* and calculates the average value of each bin.If the average value of a bin exceeds the given threshold,
1261
* the bin is considered significant and its starting index is recorded in the `significantIndices` vector.
1262
* The function can be used to adaptively refine the search for significant values in the matrix by focusing
1263
* on specific rows or columns iteratively.*/
1264
void FGAnalyzer::adaptiveSampling ( int bins,
1265
                                    double threshold,
1266
                                    std::vector<int>& significantIndices,
1267
                                    bool isRow,
1268
                                    int startIdx )
1269
0
{
1270
0
  int binSize = DATA_BASE_SIZE / bins;
1271
0
  for ( int i = 0; i < bins; i++ )
1272
0
  {
1273
0
    double sum = 0;
1274
0
    for ( int j = 0; j < binSize; j++ )
1275
0
    {
1276
0
      int idx = startIdx + i * binSize + j;
1277
0
      if ( idx >= DATA_BASE_SIZE )
1278
0
          break;  // Ensure we don't go out of bounds
1279
0
      sum += isRow ? meanSquaredDctGrain[idx][0] : meanSquaredDctGrain[0][idx];
1280
0
    }
1281
0
    sum /= binSize;
1282
0
    if ( sum > threshold )
1283
0
    {
1284
0
      significantIndices.push_back( startIdx + i * binSize );
1285
0
    }
1286
0
  }
1287
0
}
1288
1289
1290
/*This function refines the cutoff frequency estimation by adaptively sampling the mean squared DCT grain values
1291
 * matrix. Instead of analyzing every row and column, it focuses on significant bins determined by the adaptive sampling
1292
 * method. The horizontal and vertical cutoff frequencies are estimated by examining the mean values of these significant
1293
 * bins, making the process more efficient and reducing computational overhead.
1294
 * The function performs the following steps :
1295
 * 1. Initializes mean squared DCT grain matrix and mean vectors for rows and columns.
1296
 * 2. Iterates through the DCT grain blocks to calculate the average block for each coefficient.
1297
 * 3. Uses the adaptive sampling method to identify significant rows and columns.
1298
 * 4. Estimates the cutoff frequencies based on the mean values of the significant rows and columns.
1299
 * 5. Updates the component model with the estimated cutoff frequencies.*/
1300
void FGAnalyzer::estimateCutoffFreqAdaptive( ComponentID compId )
1301
0
{
1302
0
  const int coarseBins = 8; // Initial coarse sampling bins
1303
0
  const int refineBins = 4; // Bins for each refinement step
1304
0
  const int maxIterations = 3; // Maximum refinement iterations
1305
0
  const double threshold = 0.1; // Threshold to identify significant bins
1306
1307
0
  std::memset( meanSquaredDctGrain, 0, sizeof( meanSquaredDctGrain ) );
1308
1309
  // Calculate mean squared DCT grain values
1310
0
  for ( int x = 0; x < DATA_BASE_SIZE; x++ )
1311
0
  {
1312
0
    for ( int y = 0; y < DATA_BASE_SIZE; y++ )
1313
0
    {
1314
0
      for ( int i = 0; i < m_numDctGrainBlocks; i++ )
1315
0
      {
1316
0
        meanSquaredDctGrain[x][y] += m_dctGrainBlockList[i].at( x, y );
1317
0
      }
1318
0
      meanSquaredDctGrain[x][y] /= m_numDctGrainBlocks;
1319
0
    }
1320
0
  }
1321
1322
  // Identify initial coarse bins with significant grain values
1323
0
  std::vector<int> significantRows, significantCols;
1324
0
  adaptiveSampling ( coarseBins,
1325
0
                     threshold,
1326
0
                     significantRows,
1327
0
                     true,
1328
0
                     0 ); // Rows
1329
0
  adaptiveSampling ( coarseBins,
1330
0
                     threshold,
1331
0
                     significantCols,
1332
0
                     false,
1333
0
                     0 );  // Columns
1334
1335
  // Iterative Refinement
1336
0
  for ( int iter = 0; iter < maxIterations; iter++ )
1337
0
  {
1338
0
    std::vector<int> refinedRows, refinedCols;
1339
0
    for ( int row : significantRows )
1340
0
    {
1341
0
      adaptiveSampling ( refineBins,
1342
0
                         threshold,
1343
0
                         refinedRows,
1344
0
                         true,
1345
0
                         row );
1346
0
    }
1347
0
    for ( int col : significantCols )
1348
0
    {
1349
0
      adaptiveSampling ( refineBins,
1350
0
                         threshold,
1351
0
                         refinedCols,
1352
0
                         false,
1353
0
                         col );
1354
0
    }
1355
0
    significantRows = refinedRows;
1356
0
    significantCols = refinedCols;
1357
0
  }
1358
1359
  // Determine cut-off frequencies from the refined significant bins
1360
0
  int cutoffVertical = significantRows.empty() ? 0 : significantRows.back() / ( DATA_BASE_SIZE / 16 );
1361
0
  int cutoffHorizontal = significantCols.empty() ? 0 : significantCols.back() / ( DATA_BASE_SIZE / 16 );
1362
1363
  // Set the cut-off frequencies in the model
1364
0
  if ( cutoffVertical && cutoffHorizontal )
1365
0
  {
1366
0
    m_compModel[compId].presentFlag = true;
1367
0
    m_compModel[compId].numModelValues = 3;
1368
0
    m_compModel[compId].intensityValues[0].compModelValue[1] = cutoffHorizontal;
1369
0
    m_compModel[compId].intensityValues[0].compModelValue[2] = cutoffVertical;
1370
0
  }
1371
0
  else
1372
0
  {
1373
0
    m_compModel[compId].presentFlag = false;
1374
0
  }
1375
0
}
1376
1377
// DCT-2 64x64 as defined in VVC
1378
void FGAnalyzer::blockTransform ( CoeffBuf &currentCoeffBuf,
1379
                                  int offsetX,
1380
                                  int offsetY,
1381
                                  uint32_t bitDepth,
1382
                                  ComponentID compId )
1383
0
{
1384
0
  uint32_t      windowSize      = DATA_BASE_SIZE;   // Size for Film Grain block
1385
0
  const int     transform_scale = 9;                // upscaling of original transform as specified in VVC (for 64x64 block)
1386
1387
  // copy input -> 32 Bit
1388
0
  for ( uint32_t y = 0; y < DATA_BASE_SIZE; y++ )
1389
0
  {
1390
0
    for ( uint32_t x = 0; x < DATA_BASE_SIZE; x++ )
1391
0
    {
1392
0
      m_DCTinout[x + DATA_BASE_SIZE * y] = m_grainEstimateBuf->get( compId ).at( offsetX + x,
1393
0
                                                                                 offsetY + y );
1394
0
    }
1395
0
  }
1396
1397
0
  fastForwardDCT2_B64 ( m_DCTinout,
1398
0
                        m_DCTtemp,
1399
0
                        transform_scale,
1400
0
                        windowSize,
1401
0
                        0,
1402
0
                        0 );
1403
0
  fastForwardDCT2_B64 ( m_DCTtemp,
1404
0
                        m_DCTinout,
1405
0
                        transform_scale,
1406
0
                        windowSize,
1407
0
                        0,
1408
0
                        0 );
1409
1410
  // Calculate squared transformed block
1411
0
  for ( int y = 0; y < DATA_BASE_SIZE; y++ )
1412
0
  {
1413
0
    for ( int x = 0; x < DATA_BASE_SIZE; x++ )
1414
0
    {
1415
0
      currentCoeffBuf.at( x, y ) = m_DCTinout[x + DATA_BASE_SIZE * y] * m_DCTinout[x + DATA_BASE_SIZE * y];
1416
0
    }
1417
0
  }
1418
0
}
1419
1420
// check edges
1421
int FGAnalyzer::countEdges ( int windowSize,
1422
                             int offsetX,
1423
                             int offsetY, 
1424
                             ComponentID compId )
1425
0
{
1426
0
  for ( int x = 0; x < windowSize; x++ )
1427
0
  {
1428
0
    for ( int y = 0; y < windowSize; y++ )
1429
0
    {
1430
0
      if ( m_maskBuf->get( compId ).at( offsetX + x,
1431
0
                                        offsetY + y ) )
1432
0
      {
1433
0
        return 0;
1434
0
      }
1435
0
    }
1436
0
  }
1437
1438
0
  return 1;
1439
0
}
1440
1441
// Fit data to a function using n-th order polynomial interpolation
1442
bool FGAnalyzer::fitFunction ( int order,
1443
                               int bitDepth,
1444
                               bool second_pass )
1445
0
{
1446
0
  long double         a[MAXPAIRS + 1][MAXPAIRS + 1];
1447
0
  long double         B[MAXPAIRS + 1], C[MAXPAIRS + 1], S[MAXPAIRS + 1];
1448
0
  long double         A1 = 0.0, A2 = 0.0, Y1 = 0.0, m = 0.0, S1 = 0.0, x1 = 0.0;
1449
0
  long double         xscale = 0.0, yscale = 0.0;
1450
0
  long double         xmin = 0.0, xmax = 0.0, ymin = 0.0, ymax = 0.0;
1451
0
  long double         polycoefs[MAXORDER + 1];
1452
0
  int i, j, k, L, R;
1453
1454
  // several data filtering and data manipulations before fitting the function
1455
  // create interval points for function fitting
1456
0
  int INTENSITY_INTERVAL_NUMBER = (1 << bitDepth) / INTERVAL_SIZE;
1457
0
  vec_mean_intensity.resize( INTENSITY_INTERVAL_NUMBER, 0 );
1458
0
  vec_variance_intensity.resize( INTENSITY_INTERVAL_NUMBER, 0 );
1459
0
  element_number_per_interval.resize( INTENSITY_INTERVAL_NUMBER, 0 );
1460
1461
0
  double              mn = 0.0, sd = 0.0;
1462
1463
0
  std::memset( a, 0, sizeof(a) );
1464
0
  std::memset( B, 0, sizeof(B) );
1465
0
  std::memset( C, 0, sizeof(C) );
1466
0
  std::memset( S, 0, sizeof(S) );
1467
0
  std::memset( polycoefs, 0, sizeof(polycoefs) );
1468
1469
0
  if ( second_pass )   // in second pass, filter based on the variance of the data_y. remove all high and low points
1470
0
  {
1471
0
    xmin = scalingVec.back();
1472
0
    scalingVec.pop_back();
1473
0
    xmax = scalingVec.back();
1474
0
    scalingVec.pop_back();
1475
0
    int n = static_cast<int>( vecVar.size() );
1476
0
    if ( n != 0 )
1477
0
    {
1478
0
      mn = std::accumulate ( vecVar.begin(), vecVar.end(), 0.0 ) / n;
1479
0
      for ( int cnt = 0; cnt < n; cnt++ )
1480
0
      {
1481
0
        sd += ( vecVar[cnt] - mn ) * ( vecVar[cnt] - mn );
1482
0
      }
1483
0
      sd /= n;
1484
0
      sd = std::sqrt( sd );
1485
0
    }
1486
0
  }
1487
1488
0
  for ( int cnt = 0; cnt < vecMean.size(); cnt++ )
1489
0
  {
1490
0
    if ( second_pass )
1491
0
    {
1492
0
      if ( vecMean[cnt] >= xmin && vecMean[cnt] <= xmax )
1493
0
      {
1494
0
        if (( vecVar[cnt] < scalingVec[vecMean[cnt] - static_cast<int>(xmin)] + sd * VAR_SCALE_UP ) && ( vecVar[cnt] > scalingVec[vecMean[cnt] - static_cast<int>(xmin)] - sd * VAR_SCALE_DOWN ))
1495
0
        {
1496
0
          int block_index = vecMean[cnt] / INTERVAL_SIZE;
1497
0
          vec_mean_intensity[block_index] += vecMean[cnt];
1498
0
          vec_variance_intensity[block_index] += vecVar[cnt];
1499
0
          element_number_per_interval[block_index]++;
1500
0
        }
1501
0
      }
1502
0
    }
1503
0
    else
1504
0
    {
1505
0
      int block_index = vecMean[cnt] / INTERVAL_SIZE;
1506
0
      vec_mean_intensity[block_index] += vecMean[cnt];
1507
0
      vec_variance_intensity[block_index] += vecVar[cnt];
1508
0
      element_number_per_interval[block_index]++;
1509
0
    }
1510
0
  }
1511
1512
  // create points per intensity interval
1513
0
  for ( int block_idx = 0; block_idx < INTENSITY_INTERVAL_NUMBER; block_idx++ )
1514
0
  {
1515
0
    if ( element_number_per_interval[block_idx] >= MIN_ELEMENT_NUMBER_PER_INTENSITY_INTERVAL )
1516
0
    {
1517
0
      tmp_data_x.push_back ( vec_mean_intensity[block_idx] / element_number_per_interval[block_idx] );
1518
0
      tmp_data_y.push_back( vec_variance_intensity[block_idx] / element_number_per_interval[block_idx] );
1519
0
    }
1520
0
  }
1521
1522
  // There needs to be at least ORDER+1 points to fit the function
1523
0
  if ( tmp_data_x.size() < ( order + 1 ) )
1524
0
  {
1525
0
    return false;   // if there is no enough blocks to estimate film grain parameters, default or previously estimated
1526
                    // parameters are used
1527
0
  }
1528
1529
0
  for ( i = 0; i < tmp_data_x.size(); i++ ) // remove single points before extending and fitting
1530
0
  {
1531
0
    int check = 0;
1532
0
    for ( j = -WINDOW; j <= WINDOW; j++ )
1533
0
    {
1534
0
      int idx = i + j;
1535
0
      if ( idx >= 0 && idx < tmp_data_x.size() && j != 0 )
1536
0
      {
1537
0
        check += abs( tmp_data_x[i] / INTERVAL_SIZE - tmp_data_x[idx] / INTERVAL_SIZE ) <= WINDOW ? 1 : 0;
1538
0
      }
1539
0
    }
1540
1541
0
    if ( check < NBRS )
1542
0
    {
1543
0
      for ( int k = i; k < tmp_data_x.size() - 1; k++ )
1544
0
      {
1545
0
        tmp_data_x[k] = tmp_data_x[k + 1];
1546
0
        tmp_data_y[k] = tmp_data_y[k + 1];
1547
0
      }
1548
0
      tmp_data_x.pop_back();
1549
0
      tmp_data_y.pop_back();
1550
0
      i--;
1551
0
    }
1552
0
  }
1553
1554
0
  extendPoints( bitDepth );     // find the most left and the most right point, and extend edges
1555
1556
0
  CHECK( tmp_data_x.size() > MAXPAIRS, "Maximum dataset size exceeded." );
1557
1558
  // fitting the function starts here
1559
0
  xmin = tmp_data_x[0];
1560
0
  xmax = tmp_data_x[0];
1561
0
  ymin = tmp_data_y[0];
1562
0
  ymax = tmp_data_y[0];
1563
0
  for ( i = 0; i < tmp_data_x.size(); i++ )
1564
0
  {
1565
0
    if ( tmp_data_x[i] < xmin )
1566
0
    {
1567
0
      xmin = tmp_data_x[i];
1568
0
    }
1569
0
    if ( tmp_data_x[i] > xmax )
1570
0
    {
1571
0
      xmax = tmp_data_x[i];
1572
0
    }
1573
0
    if ( tmp_data_y[i] < ymin )
1574
0
    {
1575
0
      ymin = tmp_data_y[i];
1576
0
    }
1577
0
    if ( tmp_data_y[i] > ymax )
1578
0
    {
1579
0
      ymax = tmp_data_y[i];
1580
0
    }
1581
0
  }
1582
1583
0
  long double xlow = xmax;
1584
0
  long double ylow = ymax;
1585
1586
0
  int data_pairs = static_cast<int>( tmp_data_x.size() );
1587
1588
0
  double data_array[2][MAXPAIRS + 1];
1589
0
  std::memset( data_array, 0, sizeof(data_array) );
1590
0
  for ( i = 0; i < data_pairs; i++ )
1591
0
  {
1592
0
    data_array[0][i + 1] = static_cast<double>( tmp_data_x[i] );
1593
0
    data_array[1][i + 1] = static_cast<double>( tmp_data_y[i] );
1594
0
  }
1595
1596
  // Clear previous vectors by resizing them to 0
1597
0
  tmp_data_x.clear();
1598
0
  tmp_data_y.clear();
1599
1600
0
  if ( second_pass )
1601
0
  {
1602
0
    coeffs.resize( 0 );
1603
0
    scalingVec.resize( 0 );
1604
0
  }
1605
1606
0
  for ( i = 1; i <= data_pairs; i++ )
1607
0
  {
1608
0
    if ( data_array[0][i] < xlow && data_array[0][i] != 0 )
1609
0
    {
1610
0
      xlow = data_array[0][i];
1611
0
    }
1612
0
    if ( data_array[1][i] < ylow && data_array[1][i] != 0 )
1613
0
    {
1614
0
      ylow = data_array[1][i];
1615
0
    }
1616
0
  }
1617
1618
0
  if ( xlow < .001 && xmax < 1000 )
1619
0
  {
1620
0
    xscale = 1 / xlow;
1621
0
  }
1622
0
  else if ( xmax > 1000 && xlow > .001 )
1623
0
  {
1624
0
    xscale = 1 / xmax;
1625
0
  }
1626
0
  else
1627
0
  {
1628
0
    xscale = 1;
1629
0
  }
1630
1631
0
  if ( ylow < .001 && ymax < 1000 )
1632
0
  {
1633
0
    yscale = 1 / ylow;
1634
0
  }
1635
0
  else if ( ymax > 1000 && ylow > .001 )
1636
0
  {
1637
0
    yscale = 1 / ymax;
1638
0
  }
1639
0
  else
1640
0
  {
1641
0
    yscale = 1;
1642
0
  }
1643
1644
  // initialise array variables
1645
0
  for ( j = 1; j <= data_pairs; j++ )
1646
0
  {
1647
0
    for ( i = 1; i <= order; i++ )
1648
0
    {
1649
0
      B[i] = B[i] + data_array[1][j] * yscale * ldpow( data_array[0][j] * xscale, i );
1650
0
      if ( B[i] == std::numeric_limits<long double>::max() )
1651
0
      {
1652
0
        return false;
1653
0
      }
1654
0
      for ( k = 1; k <= order; k++ )
1655
0
      {
1656
0
        a[i][k] = a[i][k] + ldpow( data_array[0][j] * xscale, ( i + k ) );
1657
0
        if ( a[i][k] == std::numeric_limits<long double>::max() )
1658
0
        {
1659
0
          return false;
1660
0
        }
1661
0
      }
1662
0
      S[i] = S[i] + ldpow( data_array[0][j] * xscale, i );
1663
0
      if ( S[i] == std::numeric_limits<long double>::max() )
1664
0
      {
1665
0
        return false;
1666
0
      }
1667
0
    }
1668
0
    Y1 = Y1 + data_array[1][j] * yscale;
1669
0
    if ( Y1 == std::numeric_limits<long double>::max() )
1670
0
    {
1671
0
      return false;
1672
0
    }
1673
0
  }
1674
1675
0
  for ( i = 1; i <= order; i++ )
1676
0
  {
1677
0
    for ( j = 1; j <= order; j++ )
1678
0
    {
1679
0
      a[i][j] = a[i][j] - S[i] * S[j] / static_cast<long double>( data_pairs );
1680
0
      if (a[i][j] == std::numeric_limits<long double>::max())
1681
0
      {
1682
0
        return false;
1683
0
      }
1684
0
    }
1685
0
    B[i] = B[i] - Y1 * S[i] / static_cast<long double>( data_pairs );
1686
0
    if ( B[i] == std::numeric_limits<long double>::max() )
1687
0
    {
1688
0
      return false;
1689
0
    }
1690
0
  }
1691
1692
0
  for ( k = 1; k <= order; k++ )
1693
0
  {
1694
0
    R  = k;
1695
0
    A1 = 0;
1696
0
    for ( L = k; L <= order; L++ )
1697
0
    {
1698
0
      A2 = fabsl( a[L][k] );
1699
0
      if ( A2 > A1 )
1700
0
      {
1701
0
        A1 = A2;
1702
0
        R  = L;
1703
0
      }
1704
0
    }
1705
0
    if ( A1 == 0 )
1706
0
    {
1707
0
      return false;
1708
0
    }
1709
0
    if ( R != k )
1710
0
    {
1711
0
      for ( j = k; j <= order; j++ )
1712
0
      {
1713
0
        x1      = a[R][j];
1714
0
        a[R][j] = a[k][j];
1715
0
        a[k][j] = x1;
1716
0
      }
1717
0
      x1   = B[R];
1718
0
      B[R] = B[k];
1719
0
      B[k] = x1;
1720
0
    }
1721
0
    for ( i = k; i <= order; i++ )
1722
0
    {
1723
0
      m = a[i][k];
1724
0
      for ( j = k; j <= order; j++ )
1725
0
      {
1726
0
        if ( i == k )
1727
0
        {
1728
0
          a[i][j] = a[i][j] / m;
1729
0
        }
1730
0
        else
1731
0
        {
1732
0
          a[i][j] = a[i][j] - m * a[k][j];
1733
0
        }
1734
0
      }
1735
0
      if ( i == k )
1736
0
      {
1737
0
        B[i] = B[i] / m;
1738
0
      }
1739
0
      else
1740
0
      {
1741
0
        B[i] = B[i] - m * B[k];
1742
0
      }
1743
0
    }
1744
0
  }
1745
1746
0
  polycoefs[order] = B[order];
1747
0
  for ( k = 1; k <= order - 1; k++ )
1748
0
  {
1749
0
    i  = order - k;
1750
0
    S1 = 0;
1751
0
    for ( j = 1; j <= order; j++ )
1752
0
    {
1753
0
      S1 = S1 + a[i][j] * polycoefs[j];
1754
0
      if ( S1 == std::numeric_limits<long double>::max() )
1755
0
      {
1756
0
        return false;
1757
0
      }
1758
0
    }
1759
0
    polycoefs[i] = B[i] - S1;
1760
0
  }
1761
1762
0
  S1 = 0;
1763
0
  for ( i = 1; i <= order; i++ )
1764
0
  {
1765
0
    S1 = S1 + polycoefs[i] * S[i] / static_cast<long double>( data_pairs );
1766
0
    if ( S1 == std::numeric_limits<long double>::max() )
1767
0
    {
1768
0
      return false;
1769
0
    }
1770
0
  }
1771
0
  polycoefs[0] = (Y1 / static_cast<long double>( data_pairs ) - S1);
1772
1773
  // zero all coeficient values smaller than +/- .00000000001 (avoids -0)
1774
0
  for ( i = 0; i <= order; i++ )
1775
0
  {
1776
0
    if ( fabsl(polycoefs[i] * 100000000000) < 1 )
1777
0
    {
1778
0
      polycoefs[i] = 0;
1779
0
    }
1780
0
  }
1781
1782
  // rescale parameters
1783
0
  for ( i = 0; i <= order; i++ )
1784
0
  {
1785
0
    polycoefs[i] = (1 / yscale) * polycoefs[i] * ldpow( xscale, i );
1786
0
    coeffs.push_back( polycoefs[i] );
1787
0
  }
1788
1789
  // create fg scaling function. interpolation based on coeffs which returns lookup table from 0 - 2^B-1. n-th order polinomial regression
1790
0
  for ( i = static_cast<int>( xmin ); i <= static_cast<int>( xmax ); i++ )
1791
0
  {
1792
0
    double val = coeffs[0];
1793
0
    for ( j = 1; j < coeffs.size(); j++ )
1794
0
    {
1795
0
      val += (coeffs[j] * ldpow( i, j ));
1796
0
    }
1797
1798
0
    val = Clip3( 0.0,
1799
0
                 static_cast<double>( 1 << bitDepth ) - 1,
1800
0
                 val );
1801
0
    scalingVec.push_back( val );
1802
0
  }
1803
1804
  // save in scalingVec min and max value for further use
1805
0
  scalingVec.push_back( xmax );
1806
0
  scalingVec.push_back( xmin );
1807
1808
0
  vec_mean_intensity.clear();
1809
0
  vec_variance_intensity.clear();
1810
0
  element_number_per_interval.clear();
1811
0
  tmp_data_x.clear();
1812
0
  tmp_data_y.clear();
1813
1814
0
  return true;
1815
0
}
1816
1817
// avg scaling vector with previous result to smooth transition betweeen frames
1818
void FGAnalyzer::avgScalingVec ( int bitDepth )
1819
0
{
1820
0
  int xmin = static_cast<int>( scalingVec.back() );
1821
0
  scalingVec.pop_back();
1822
0
  int xmax = static_cast<int>( scalingVec.back() );
1823
0
  scalingVec.pop_back();
1824
1825
0
  std::vector<double> scalingVecAvg( static_cast<int>( 1 << bitDepth ) );
1826
0
  bool isFirstScalingEst = true;
1827
1828
0
  if ( isFirstScalingEst )
1829
0
  {
1830
0
    for (int i = xmin; i <= xmax; i++)
1831
0
    {
1832
0
      scalingVecAvg[i] = scalingVec[i - xmin];
1833
0
    }
1834
0
    isFirstScalingEst = false;
1835
0
  }
1836
0
  else
1837
0
  {
1838
0
    for ( int i = xmin; i <= xmax; i++ )
1839
0
    {
1840
0
      scalingVecAvg[i] = ( scalingVecAvg[i] + scalingVec[i - xmin] ) / 2.0;
1841
0
    }
1842
0
  }
1843
1844
0
  int new_xmin = 0;
1845
0
  while ( new_xmin <= xmax && scalingVecAvg[new_xmin] == 0 )
1846
0
  {
1847
0
    new_xmin++;
1848
0
  }
1849
1850
0
  int new_xmax = static_cast<int>( scalingVecAvg.size() ) - 1;
1851
0
  while ( new_xmax >= 0 && scalingVecAvg[new_xmax] == 0 )
1852
0
  {
1853
0
    new_xmax--;
1854
0
  }
1855
1856
0
  if ( new_xmax < new_xmin )
1857
0
  {
1858
    // Handle the case where all entries are zero
1859
0
    scalingVec.clear();
1860
0
    scalingVec.push_back( 0 ); // Minimum value
1861
0
    scalingVec.push_back( 0 ); // Maximum value
1862
0
    return;
1863
0
  }
1864
1865
0
  scalingVec.assign( scalingVecAvg.begin() + new_xmin,
1866
0
                     scalingVecAvg.begin() + new_xmax + 1 );
1867
0
  scalingVec.push_back( new_xmax );
1868
0
  scalingVec.push_back( new_xmin );
1869
0
}
1870
1871
1872
// Lloyd Max quantizer
1873
bool FGAnalyzer::lloydMax ( double &distortion,
1874
                            int bitDepth )
1875
0
{
1876
0
  if ( !scalingVec.size() )
1877
0
  {
1878
    // Film grain parameter estimation is not performed. Default or previously estimated parameters are reused.
1879
0
    return false;
1880
0
  }
1881
1882
0
  int xmin = static_cast<int>( scalingVec.back() );
1883
0
  scalingVec.pop_back();
1884
0
  scalingVec.pop_back();   // dummy pop_pack ==> int xmax = (int)scalingVec.back();
1885
1886
0
  double ymin          = 0.0;
1887
0
  double ymax          = 0.0;
1888
0
  double init_training = 0.0;
1889
0
  double tolerance     = 0.0000001;
1890
0
  double last_distor   = 0.0;
1891
0
  double rel_distor    = 0.0;
1892
1893
0
  double codebook[QUANT_LEVELS];
1894
0
  double partition[QUANT_LEVELS - 1];
1895
1896
0
  std::vector<double> tmpVec( scalingVec.size(), 0.0 );
1897
0
  distortion = 0.0;
1898
1899
0
  ymin = scalingVec[0];
1900
0
  ymax = scalingVec[0];
1901
0
  for ( int i = 0; i < scalingVec.size(); i++ )
1902
0
  {
1903
0
    if ( scalingVec[i] < ymin )
1904
0
    {
1905
0
      ymin = scalingVec[i];
1906
0
    }
1907
0
    if ( scalingVec[i] > ymax )
1908
0
    {
1909
0
      ymax = scalingVec[i];
1910
0
    }
1911
0
  }
1912
1913
0
  init_training = ( ymax - ymin ) / QUANT_LEVELS;
1914
1915
0
  if ( init_training <= 0 )
1916
0
  {
1917
    // msg(WARNING, "Invalid training dataset. Film grain parameter estimation is not performed. Default or previously estimated parameters are reused.\n");
1918
0
    return false;
1919
0
  }
1920
1921
  // initial codebook
1922
0
  double step = init_training / 2;
1923
0
  for ( int i = 0; i < QUANT_LEVELS; i++ )
1924
0
  {
1925
0
    codebook[i] = ymin + i * init_training + step;
1926
0
  }
1927
1928
  // initial partition
1929
0
  for ( int i = 0; i < QUANT_LEVELS - 1; i++ )
1930
0
  {
1931
0
    partition[i] = (codebook[i] + codebook[i + 1]) / 2;
1932
0
  }
1933
1934
  // quantizer initialization
1935
0
  quantize ( tmpVec,
1936
0
             distortion,
1937
0
             partition,
1938
0
             codebook );
1939
1940
0
  double tolerance2 = std::numeric_limits<double>::epsilon() * ymax;
1941
0
  if ( distortion > tolerance2 )
1942
0
  {
1943
0
    rel_distor = std::fabs( distortion - last_distor ) / distortion;
1944
0
  }
1945
0
  else
1946
0
  {
1947
0
    rel_distor = distortion;
1948
0
  }
1949
1950
  // optimization: find optimal codebook and partition
1951
0
  while ( ( rel_distor > tolerance ) && ( rel_distor > tolerance2 ) )
1952
0
  {
1953
0
    for ( int i = 0; i < QUANT_LEVELS; i++ )
1954
0
    {
1955
0
      int count = 0;
1956
0
      double sum = 0.0;
1957
1958
0
      for ( int j = 0; j < tmpVec.size(); j++ )
1959
0
      {
1960
0
        if ( codebook[i] == tmpVec[j] )
1961
0
        {
1962
0
          count++;
1963
0
          sum += scalingVec[j];
1964
0
        }
1965
0
      }
1966
1967
0
      if ( count )
1968
0
      {
1969
0
        codebook[i] = sum / static_cast<double>( count );
1970
0
      }
1971
0
      else
1972
0
      {
1973
0
        sum   = 0.0;
1974
0
        count = 0;
1975
0
        if ( i == 0 )
1976
0
        {
1977
0
          for ( int j = 0; j < tmpVec.size(); j++ )
1978
0
          {
1979
0
            if ( scalingVec[j] <= partition[i] )
1980
0
            {
1981
0
              count++;
1982
0
              sum += scalingVec[j];
1983
0
            }
1984
0
          }
1985
0
          if ( count )
1986
0
          {
1987
0
            codebook[i] = sum / static_cast<double>( count );
1988
0
          }
1989
0
          else
1990
0
          {
1991
0
            codebook[i] = ( partition[i] + ymin ) / 2;
1992
0
          }
1993
0
        }
1994
0
        else if ( i == QUANT_LEVELS - 1 )
1995
0
        {
1996
0
          for ( int j = 0; j < tmpVec.size(); j++ )
1997
0
          {
1998
0
            if (scalingVec[j] >= partition[i - 1])
1999
0
            {
2000
0
              count++;
2001
0
              sum += scalingVec[j];
2002
0
            }
2003
0
          }
2004
0
          if ( count )
2005
0
          {
2006
0
            codebook[i] = sum / static_cast<double>( count );
2007
0
          }
2008
0
          else
2009
0
          {
2010
0
            codebook[i] = ( partition[i - 1] + ymax ) / 2;
2011
0
          }
2012
0
        }
2013
0
        else
2014
0
        {
2015
0
          for ( int j = 0; j < tmpVec.size(); j++ )
2016
0
          {
2017
0
            if ( scalingVec[j] >= partition[i - 1] && scalingVec[j] <= partition[i] )
2018
0
            {
2019
0
              count++;
2020
0
              sum += scalingVec[j];
2021
0
            }
2022
0
          }
2023
0
          if ( count )
2024
0
          {
2025
0
            codebook[i] = sum / static_cast<double>( count );
2026
0
          }
2027
0
          else
2028
0
          {
2029
0
            codebook[i] = ( partition[i - 1] + partition[i] ) / 2;
2030
0
          }
2031
0
        }
2032
0
      }
2033
0
    }
2034
2035
    // compute and sort partition
2036
0
    for ( int i = 0; i < QUANT_LEVELS - 1; i++ )
2037
0
    {
2038
0
      partition[i] = ( codebook[i] + codebook[i + 1] ) / 2;
2039
0
    }
2040
0
    std::sort( partition, partition + QUANT_LEVELS - 1 );
2041
2042
    // final quantization - testing condition
2043
0
    last_distor = distortion;
2044
0
    quantize ( tmpVec,
2045
0
               distortion,
2046
0
               partition,
2047
0
               codebook );
2048
2049
0
    if ( distortion > tolerance2 )
2050
0
    {
2051
0
      rel_distor = std::fabs( distortion - last_distor ) / distortion;
2052
0
    }
2053
0
    else
2054
0
    {
2055
0
      rel_distor = distortion;
2056
0
    }
2057
0
  }
2058
2059
  // fill the final quantized vector
2060
0
  int maxVal = ( 1 << bitDepth ) - 1;  // Full range max value for given bit depth
2061
0
  quantVec.resize( static_cast<int>( 1 << bitDepth ), 0 );
2062
0
  for ( int i = 0; i < tmpVec.size(); i++ )
2063
0
  {
2064
0
    quantVec[i + xmin] = Clip3( 0, 
2065
0
                                maxVal,                                    
2066
0
                                static_cast<int>( tmpVec[i] + 0.5 ) );
2067
0
  }
2068
2069
0
  return true;
2070
0
}
2071
2072
void FGAnalyzer::quantize ( std::vector<double>& quantizedVec,
2073
                            double& distortion,
2074
                            double partition[],
2075
                            double codebook[] )
2076
0
{
2077
  // Reset previous quantizedVec to 0 and distortion to 0
2078
0
  std::fill(quantizedVec.begin(), quantizedVec.end(), 0.0);
2079
0
  distortion = 0.0;
2080
2081
  // Quantize input vector
2082
0
  for ( int i = 0; i < scalingVec.size(); i++ )
2083
0
  {
2084
0
    double quantizedValue = 0.0;
2085
0
    for ( int j = 0; j < QUANT_LEVELS - 1; j++ )
2086
0
    {
2087
0
      quantizedValue += ( scalingVec[i] > partition[j] );
2088
0
    }
2089
0
    quantizedVec[i] = codebook[static_cast<int>( quantizedValue )];
2090
0
  }
2091
2092
  // Compute distortion (MSE)
2093
0
  for ( int i = 0; i < scalingVec.size(); i++ )
2094
0
  {
2095
0
    double error = scalingVec[i] - quantizedVec[i];
2096
0
    distortion += ( error * error );
2097
0
  }
2098
0
  distortion /= scalingVec.size();
2099
0
}
2100
2101
// Set correctlly SEI parameters based on the quantized curve
2102
void FGAnalyzer::setEstimatedParameters ( uint32_t bitDepth,
2103
                                          ComponentID compId )
2104
0
{
2105
  // calculate intervals and scaling factors
2106
0
  defineIntervalsAndScalings ( bitDepth );
2107
2108
  // Merge small intervals with left or right interval
2109
0
  for ( size_t i = 0; i < finalIntervalsandScalingFactors.size(); ++i )
2110
0
  {
2111
0
    int tmp1 = finalIntervalsandScalingFactors[i][1] - finalIntervalsandScalingFactors[i][0];
2112
2113
0
    if ( tmp1 < ( 2 << ( bitDepth - BIT_DEPTH_8 ) ) )
2114
0
    {
2115
0
      int diffRight = ( i == finalIntervalsandScalingFactors.size() - 1 ) || ( finalIntervalsandScalingFactors[i + 1][2] == 0 )
2116
0
          ? std::numeric_limits<int>::max()
2117
0
          : abs( finalIntervalsandScalingFactors[i][2] - finalIntervalsandScalingFactors[i + 1][2] );
2118
0
      int diffLeft = ( i == 0 ) || ( finalIntervalsandScalingFactors[i - 1][2] == 0 )
2119
0
          ? std::numeric_limits<int>::max()
2120
0
          : abs( finalIntervalsandScalingFactors[i][2] - finalIntervalsandScalingFactors[i - 1][2] );
2121
2122
0
      if ( diffLeft < diffRight )
2123
0
      {
2124
0
        int tmp2 = finalIntervalsandScalingFactors[i - 1][1] - finalIntervalsandScalingFactors[i - 1][0];
2125
0
        int newScale = ( tmp2 * finalIntervalsandScalingFactors[i - 1][2] + tmp1 * finalIntervalsandScalingFactors[i][2] ) / ( tmp2 + tmp1 );
2126
2127
0
        finalIntervalsandScalingFactors[i - 1][1] = finalIntervalsandScalingFactors[i][1];
2128
0
        finalIntervalsandScalingFactors[i - 1][2] = newScale;
2129
0
        finalIntervalsandScalingFactors.erase( finalIntervalsandScalingFactors.begin() + i );
2130
0
        --i;
2131
0
      }
2132
0
      else
2133
0
      {
2134
0
        int tmp2 = finalIntervalsandScalingFactors[i + 1][1] - finalIntervalsandScalingFactors[i + 1][0];
2135
0
        int newScale = ( tmp2 * finalIntervalsandScalingFactors[i + 1][2] + tmp1 * finalIntervalsandScalingFactors[i][2] ) / ( tmp2 + tmp1 );
2136
2137
0
        finalIntervalsandScalingFactors[i][1] = finalIntervalsandScalingFactors[i + 1][1];
2138
0
        finalIntervalsandScalingFactors[i][2] = newScale;
2139
0
        finalIntervalsandScalingFactors.erase( finalIntervalsandScalingFactors.begin() + i + 1 );
2140
0
        --i;
2141
0
      }
2142
0
    }
2143
0
  }
2144
2145
  // scale to 8-bit range as supported by current sei and rdd5
2146
0
  scaleDown ( bitDepth );
2147
2148
  // because of scaling in previous step, some intervals may overlap. Check intervals for errors.
2149
0
  confirmIntervals ( );
2150
2151
  // Set number of intervals; exclude intervals with scaling factor 0.
2152
0
  m_compModel[compId].numIntensityIntervals =
2153
0
      static_cast<uint8_t>( finalIntervalsandScalingFactors.size() - std::count_if ( finalIntervalsandScalingFactors.begin(),
2154
0
                                                                                     finalIntervalsandScalingFactors.end(),
2155
0
                                                                                     []( const std::array<int, 3>& interval )
2156
0
                                                                                     {
2157
0
                                                                                       return interval[2] == 0;
2158
0
                                                                                     }
2159
0
                                                                                   ) );
2160
2161
  // check if all intervals are 0, and if yes set presentFlag to false
2162
0
  if ( m_compModel[compId].numIntensityIntervals == 0 )
2163
0
  { 
2164
0
    m_compModel[compId].presentFlag = false;
2165
0
    return;
2166
0
  }
2167
2168
  // Set final interval boundaries and scaling factors.
2169
  // Check if some interval has scaling factor 0, and do not encode them within SEI.
2170
0
  int j = 0;
2171
0
  for ( const auto& interval : finalIntervalsandScalingFactors )
2172
0
  {
2173
0
    if ( interval[2] != 0 )
2174
0
    {
2175
0
      m_compModel[compId].intensityValues[j].intensityIntervalLowerBound = interval[0];
2176
0
      m_compModel[compId].intensityValues[j].intensityIntervalUpperBound = interval[1];
2177
0
      m_compModel[compId].intensityValues[j].compModelValue[0] = interval[2];
2178
0
      m_compModel[compId].intensityValues[j].compModelValue[1] = m_compModel[compId].intensityValues[0].compModelValue[1];
2179
0
      m_compModel[compId].intensityValues[j].compModelValue[2] = m_compModel[compId].intensityValues[0].compModelValue[2];
2180
0
      ++j;
2181
0
    }
2182
0
  }
2183
0
  CHECK( j != m_compModel[compId].numIntensityIntervals, "Check film grain intensity levels" );
2184
0
}
2185
2186
long double FGAnalyzer::ldpow ( long double n,
2187
                                unsigned p )
2188
0
{
2189
0
  long double result = 1.0;
2190
2191
  // Handle special cases for p = 0 and p = 1
2192
0
  if ( p == 0 ) return 1.0;
2193
0
  if ( p == 1 ) return n;
2194
2195
  // Exponentiation by squaring
2196
0
  while ( p > 0 )
2197
0
  {
2198
0
    if ( p % 2 == 1 )
2199
0
      result *= n;
2200
0
    n *= n;
2201
0
    p /= 2;
2202
0
  }
2203
0
  return result;
2204
0
}
2205
2206
// find bounds of intensity intervals and scaling factors for each interval
2207
void FGAnalyzer::defineIntervalsAndScalings ( int bitDepth )
2208
0
{
2209
0
  finalIntervalsandScalingFactors.clear();
2210
0
  std::array<int, 3> interval = { 0, 0, quantVec[0] };
2211
2212
0
  for ( int i = 0; i < (1 << bitDepth) - 1; ++i )
2213
0
  {
2214
0
    if ( quantVec[i] != quantVec[i + 1] )
2215
0
    {
2216
0
      interval[1] = i;
2217
0
      finalIntervalsandScalingFactors.push_back ( interval );
2218
0
      interval[0] = i + 1;
2219
0
      interval[2] = quantVec[i + 1];
2220
0
    }
2221
0
  }
2222
0
  interval[1] = ( 1 << bitDepth ) - 1;
2223
0
  finalIntervalsandScalingFactors.push_back ( interval );
2224
0
}
2225
2226
// scale everything to 8-bit ranges as supported by SEI message
2227
void FGAnalyzer::scaleDown ( int bitDepth )
2228
0
{
2229
0
  for ( auto& interval : finalIntervalsandScalingFactors )
2230
0
  {
2231
0
    interval[0] >>= ( bitDepth - BIT_DEPTH_8 );
2232
0
    interval[1] >>= ( bitDepth - BIT_DEPTH_8 );
2233
0
    interval[2] <<= m_log2ScaleFactor;
2234
0
    interval[2] >>= ( bitDepth - BIT_DEPTH_8 );
2235
0
  }
2236
0
}
2237
2238
// check if intervals are properly set after scaling to 8-bit representation
2239
void FGAnalyzer::confirmIntervals ( )
2240
0
{
2241
0
  for ( size_t i = 0; i < finalIntervalsandScalingFactors.size() - 1; ++i )
2242
0
  {
2243
0
    if ( finalIntervalsandScalingFactors[i][1] >= finalIntervalsandScalingFactors[i + 1][0] )
2244
0
    {
2245
0
      finalIntervalsandScalingFactors[i][1] = finalIntervalsandScalingFactors[i + 1][0] - 1;
2246
0
    }
2247
0
  }
2248
0
}
2249
2250
void FGAnalyzer::extendPoints ( int bitDepth )
2251
0
{
2252
0
  int xmin = tmp_data_x[0];
2253
0
  int xmax = tmp_data_x[0];
2254
0
  int ymin = tmp_data_y[0];
2255
0
  int ymax = tmp_data_y[0];
2256
0
  for ( int i = 0; i < tmp_data_x.size(); i++ )
2257
0
  {
2258
0
    if ( tmp_data_x[i] < xmin )
2259
0
    {
2260
0
      xmin = tmp_data_x[i];
2261
0
      ymin = tmp_data_y[i];   // not real ymin
2262
0
    }
2263
0
    if ( tmp_data_x[i] > xmax )
2264
0
    {
2265
0
      xmax = tmp_data_x[i];
2266
0
      ymax = tmp_data_y[i];   // not real ymax
2267
0
    }
2268
0
  }
2269
2270
  // extend points to the left
2271
0
  int    step = POINT_STEP;
2272
0
  double scale = POINT_SCALE;
2273
0
  int num_extra_point_left = MAX_NUM_POINT_TO_EXTEND;
2274
0
  int num_extra_point_right = MAX_NUM_POINT_TO_EXTEND;
2275
0
  while ( xmin >= step && ymin > 1 && num_extra_point_left > 0 )
2276
0
  {
2277
0
    xmin -= step;
2278
0
    ymin = static_cast<int>( ymin / scale );
2279
0
    tmp_data_x.push_back( xmin );
2280
0
    tmp_data_y.push_back( ymin );
2281
0
    num_extra_point_left--;
2282
0
  }
2283
2284
  // extend points to the right
2285
0
  while ( xmax + step <= ((1 << bitDepth) - 1) && ymax > 1 && num_extra_point_right > 0 )
2286
0
  {
2287
0
    xmax += step;
2288
0
    ymax = static_cast<int>( ymax / scale );
2289
0
    tmp_data_x.push_back( xmax );
2290
0
    tmp_data_y.push_back( ymax );
2291
0
    num_extra_point_right--;
2292
0
  }
2293
2294
  // filter out points outside the range
2295
0
  auto isValid = []( int x )
2296
0
  {
2297
0
    return x >= MIN_INTENSITY && x <= MAX_INTENSITY;
2298
0
  };
2299
2300
0
  std::vector<int> valid_x, valid_y;
2301
0
  for ( int i = 0; i < tmp_data_x.size(); i++ )
2302
0
  {
2303
0
    if ( isValid( tmp_data_x[i] ) )
2304
0
    {
2305
0
      valid_x.push_back( tmp_data_x[i] );
2306
0
      valid_y.push_back( tmp_data_y[i] );
2307
0
    }
2308
0
  }
2309
0
  tmp_data_x = std::move( valid_x );
2310
0
  tmp_data_y = std::move( valid_y );
2311
0
}
2312