Coverage Report

Created: 2026-08-31 06:22

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.20k
{
216
  // init();
217
1.20k
  gradient=gradient_core;
218
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_X86 )
219
  initFGACannyX86();
220
#endif
221
1.20k
}
222
223
Canny::~Canny()
224
1.20k
{
225
  // uninit();
226
1.20k
}
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( bool enableOpt )
534
1.20k
{
535
  // init();
536
1.20k
  dilation=dilation_core;
537
538
1.20k
  if( enableOpt )
539
1.20k
  {
540
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_X86 )
541
    initFGAMorphX86();
542
#endif
543
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_ARM )
544
    initFGAMorphARM();
545
#endif
546
1.20k
  }
547
1.20k
}
548
549
Morph::~Morph()
550
1.20k
{
551
  // uninit();
552
1.20k
}
553
554
void Morph::init ( uint32_t width,
555
                   uint32_t height )
556
0
{
557
0
  if ( !m_dilationBuf )
558
0
  {
559
0
    m_dilationBuf = new PelStorage;
560
0
    m_dilationBuf->create ( VVENC_CHROMA_400,
561
0
                            Area( 0, 0, width, height ) );
562
0
  }
563
0
  if ( !m_dilationBuf2 )
564
0
  {
565
0
    m_dilationBuf2 = new PelStorage;
566
0
    m_dilationBuf2->create ( VVENC_CHROMA_400,
567
0
                             Area( 0, 0, width >> 1, height >> 1 ) );
568
0
  }
569
0
  if ( !m_dilationBuf4 )
570
0
  {
571
0
    m_dilationBuf4 = new PelStorage;
572
0
    m_dilationBuf4->create( VVENC_CHROMA_400,
573
0
                              Area( 0, 0, width >> 2, height >> 2 ) );
574
0
  }
575
0
}
576
577
void Morph::destroy ()
578
0
{
579
0
  if ( m_dilationBuf )
580
0
  {
581
0
    m_dilationBuf->destroy();
582
0
    delete m_dilationBuf;
583
0
    m_dilationBuf = nullptr;
584
0
  }
585
0
  if ( m_dilationBuf2 )
586
0
  {
587
0
    m_dilationBuf2->destroy();
588
0
    delete m_dilationBuf2;
589
0
    m_dilationBuf2 = nullptr;
590
0
  }
591
0
  if ( m_dilationBuf4 )
592
0
  {
593
0
    m_dilationBuf4->destroy();
594
0
    delete m_dilationBuf4;
595
0
    m_dilationBuf4 = nullptr;
596
0
  }
597
0
}
598
599
int calcMeanCore ( const Pel* org,
600
                   const ptrdiff_t origStride,
601
                   const int w,
602
                   const int h )
603
0
{
604
  // calculate average
605
0
  int avg = 0;
606
0
  for( int y1 = 0; y1 < h; y1++ )
607
0
  {
608
0
    for( int x1 = 0; x1 < w; x1++ )
609
0
    {
610
0
      avg = avg + *( org + x1 + y1 * origStride );
611
0
    }
612
0
  }
613
0
  return avg;
614
0
}
615
616
// ====================================================================================================================
617
// Film Grain Analysis Functions
618
// ====================================================================================================================
619
FGAnalyzer::FGAnalyzer( bool enableOpt )
620
1.20k
  : m_morphOperation( enableOpt )
621
1.20k
{
622
1.20k
  calcVar     = calcVarCore;
623
1.20k
  calcMean    = calcMeanCore;
624
1.20k
  fastDCT2_64 = fastForwardDCT2_B64;
625
626
1.20k
  if( enableOpt )
627
1.20k
  {
628
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_X86 )
629
    initFGAnalyzerX86();
630
#endif
631
#if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_ARM )
632
    initFGAnalyzerARM();
633
#endif
634
1.20k
  }
635
1.20k
}
636
637
FGAnalyzer::~FGAnalyzer()
638
1.20k
{
639
1.20k
}
640
641
// initialize film grain parameters
642
void FGAnalyzer::init ( const int width,
643
                        const int height,
644
                        const ChromaFormat inputChroma,
645
                        const int *outputBitDepths,
646
                        const bool doAnalysis[] )
647
0
{
648
0
  m_log2ScaleFactor = 2;
649
0
  for (int i = 0; i < ComponentID::MAX_NUM_COMP; i++)
650
0
  {
651
0
    m_compModel[i].presentFlag           = true;
652
0
    m_compModel[i].numModelValues        = 3;
653
0
    m_compModel[i].numIntensityIntervals = 1;
654
0
    m_compModel[i].intensityValues.resize(VVENC_MAX_NUM_INTENSITIES);
655
0
    for ( int j = 0; j < VVENC_MAX_NUM_INTENSITIES; j++ )
656
0
    {
657
0
      m_compModel[i].intensityValues[j].intensityIntervalLowerBound = 10;
658
0
      m_compModel[i].intensityValues[j].intensityIntervalUpperBound = 250;
659
0
      m_compModel[i].intensityValues[j].compModelValue.resize( MAX_ALLOWED_MODEL_VALUES );
660
0
      for ( int k = 0; k < m_compModel[i].numModelValues; k++ )
661
0
      {
662
        // half intensity for chroma. Provided value is default value, manually tuned.
663
0
        m_compModel[i].intensityValues[j].compModelValue[k] = i == 0 ? 26 : 13;
664
0
      }
665
0
    }
666
0
    m_doAnalysis[i] = doAnalysis[i];
667
0
  }
668
669
  // initialize picture parameters and create buffers
670
0
  m_bitDepths                   = const_cast<int*>( outputBitDepths );
671
0
  m_inputChromaFormat           = inputChroma;
672
  // Allocate memory for m_coeffBuf and m_dctGrainBlockList
673
0
  m_coeffBuf = (TCoeff*)xMalloc( TCoeff, width * height );
674
0
  int N = (width * height) / (DATA_BASE_SIZE * DATA_BASE_SIZE);
675
0
  m_dctGrainBlockList = new CoeffBuf[N];
676
677
0
  std::fill( std::begin(vecMean), std::end(vecMean), 0 );
678
0
  std::fill( std::begin(vecVar), std::end(vecVar), 0 );
679
680
  // Connect portions of m_coeffBuf memory with m_dctGrainBlockList
681
0
  for ( int i = 0; i < N; ++i )
682
0
  {
683
0
    m_dctGrainBlockList[i].buf = m_coeffBuf + i * ( DATA_BASE_SIZE * DATA_BASE_SIZE );
684
0
    m_dctGrainBlockList[i].stride = DATA_BASE_SIZE;
685
0
    m_dctGrainBlockList[i].height = m_dctGrainBlockList[i].width = DATA_BASE_SIZE;
686
0
  }
687
688
0
  m_edgeDetector.init ( width,
689
0
                        height,
690
0
                        inputChroma );
691
692
0
  m_morphOperation.init ( width,
693
0
                          height );
694
695
0
  int margin = m_edgeDetector.m_convWidthG / 2;   // set margin for padding for filtering
696
0
  int      newWidth2 = width / 2;
697
0
  int      newHeight2 = height / 2;
698
0
  int      newWidth4 = width / 4;
699
0
  int      newHeight4 = height / 4;
700
701
0
  if ( !m_maskBuf )
702
0
  {
703
0
    m_maskBuf = new PelStorage;
704
0
    m_maskBuf->create ( inputChroma,
705
0
                        Area(0, 0, width, height),
706
0
                        0, margin,
707
0
                        0, false );
708
0
  }
709
710
0
  if ( !m_grainEstimateBuf )
711
0
  {
712
0
     m_grainEstimateBuf = new PelStorage;
713
0
     m_grainEstimateBuf->create( inputChroma,
714
0
                                Area(0, 0, width, height),
715
0
                                0, 0,
716
0
                                0, false );
717
0
  }
718
719
0
  if ( !m_workingBufSubsampled2 )
720
0
  {
721
0
    m_workingBufSubsampled2 = new PelStorage;
722
0
    m_workingBufSubsampled2->create( inputChroma,
723
0
                                     Area(0, 0, newWidth2, newHeight2),
724
0
                                     0, margin,
725
0
                                     0, false );
726
0
  }
727
728
0
  if ( !m_maskSubsampled2 )
729
0
  {
730
0
    m_maskSubsampled2 = new PelStorage;
731
0
    m_maskSubsampled2->create( inputChroma,
732
0
                               Area(0, 0, newWidth2, newHeight2),
733
0
                               0, margin,
734
0
                               0, false );
735
0
  }
736
0
  if ( !m_workingBufSubsampled4 )
737
0
  {
738
0
    m_workingBufSubsampled4 = new PelStorage;
739
0
    m_workingBufSubsampled4->create( inputChroma,
740
0
                                     Area(0, 0, newWidth4, newHeight4),
741
0
                                     0, margin,
742
0
                                     0, false );
743
0
  }
744
745
0
  if ( !m_maskSubsampled4 )
746
0
  {
747
0
    m_maskSubsampled4 = new PelStorage;
748
0
    m_maskSubsampled4->create( inputChroma,
749
0
                               Area(0, 0, newWidth4, newHeight4),
750
0
                               0, margin,
751
0
                               0, false );
752
0
  }
753
0
  if ( !m_maskUpsampled )
754
0
  {
755
0
    m_maskUpsampled = new PelStorage;
756
0
    m_maskUpsampled->create( inputChroma,
757
0
                             Area(0, 0, width, height),
758
0
                             0, margin,
759
0
                             0, false );
760
0
  }
761
0
  if ( !m_DCTinout )
762
0
  {
763
0
    m_DCTinout = ( TCoeff* ) xMalloc( TCoeff, DATA_BASE_SIZE * DATA_BASE_SIZE );
764
0
  }
765
0
  if ( !m_DCTtemp )
766
0
  {
767
0
    m_DCTtemp = ( TCoeff* ) xMalloc( TCoeff, DATA_BASE_SIZE * DATA_BASE_SIZE );
768
0
  }
769
770
0
}
771
772
// delete picture buffers
773
void FGAnalyzer::destroy()
774
0
{
775
0
  if ( m_maskBuf != nullptr )
776
0
  {
777
0
    m_maskBuf->destroy();
778
0
    delete m_maskBuf;
779
0
    m_maskBuf = nullptr;
780
0
  }
781
782
0
  if ( m_grainEstimateBuf )
783
0
  {
784
0
    m_grainEstimateBuf->destroy();
785
0
    delete m_grainEstimateBuf;
786
0
    m_grainEstimateBuf = nullptr;
787
0
  }
788
789
0
  if ( m_workingBufSubsampled2 )
790
0
  {
791
0
    m_workingBufSubsampled2->destroy();
792
0
    delete m_workingBufSubsampled2;
793
0
    m_workingBufSubsampled2 = nullptr;
794
0
  }
795
0
  if ( m_maskSubsampled2 )
796
0
  {
797
0
    m_maskSubsampled2->destroy();
798
0
    delete m_maskSubsampled2;
799
0
    m_maskSubsampled2 = nullptr;
800
0
  }
801
  
802
0
  if ( m_workingBufSubsampled4 )
803
0
  {
804
0
    m_workingBufSubsampled4->destroy();
805
0
    delete m_workingBufSubsampled4;
806
0
    m_workingBufSubsampled4 = nullptr;
807
0
  }
808
0
  if ( m_maskSubsampled4 )
809
0
  {
810
0
    m_maskSubsampled4->destroy();
811
0
    delete m_maskSubsampled4;
812
0
    m_maskSubsampled4 = nullptr;
813
0
  }
814
0
  if ( m_maskUpsampled )
815
0
  {
816
0
    m_maskUpsampled->destroy();
817
0
    delete m_maskUpsampled;
818
0
    m_maskUpsampled = nullptr;
819
0
  }
820
0
  if ( m_DCTinout )
821
0
  {
822
0
    xFree( m_DCTinout );
823
0
    m_DCTinout = nullptr;
824
0
  }
825
0
  if ( m_DCTtemp )
826
0
  {
827
0
    xFree( m_DCTtemp );
828
0
    m_DCTtemp = nullptr;
829
0
  }
830
831
0
  xFree ( m_coeffBuf );
832
833
0
  if ( m_dctGrainBlockList )
834
0
  {
835
0
    delete[] m_dctGrainBlockList;
836
0
    m_dctGrainBlockList = nullptr;
837
0
  }
838
839
  // Clear vectors to release memory
840
0
  finalIntervalsandScalingFactors.clear();
841
0
  vec_mean_intensity.clear();
842
0
  vec_variance_intensity.clear();
843
0
  element_number_per_interval.clear();
844
0
  vecMean.clear();
845
0
  vecVar.clear();
846
0
  tmp_data_x.clear();
847
0
  tmp_data_y.clear();
848
0
  scalingVec.clear();
849
0
  quantVec.clear();
850
0
  coeffs.clear();
851
852
0
  m_edgeDetector.destroy ();
853
0
  m_morphOperation.destroy ();
854
0
}
855
856
// find flat and low complexity regions of the frame
857
void FGAnalyzer::findMask( ComponentID compId )
858
0
{
859
0
  const unsigned padding    = m_edgeDetector.m_convWidthG / 2;   // for filtering
860
0
  int bitDepth  = m_bitDepths[toChannelType( compId )];
861
862
  // Step 1: Subsample the original picture to two lower resolutions.
863
0
  subsample ( *m_workingBufSubsampled2,
864
0
              2,
865
0
              padding,
866
0
              compId );
867
0
  subsample ( *m_workingBufSubsampled4,
868
0
              4,
869
0
              padding,
870
0
              compId );
871
872
  /* Step 2: Full Resolution processing:
873
   * For each component(luma and chroma), detect edges and suppress low intensity regions.
874
   * Apply dilation to each component.*/
875
0
  m_edgeDetector.detect_edges ( m_workingBuf,
876
0
                                m_maskBuf,
877
0
                                bitDepth,
878
0
                                compId );
879
0
  suppressLowIntensity ( *m_workingBuf,
880
0
                         *m_maskBuf,
881
0
                         bitDepth,
882
0
                         compId );
883
  
884
0
  Pel strongPel = ( static_cast<Pel>( 1 ) << bitDepth ) - 1;
885
0
  m_morphOperation.dilation ( m_maskBuf,
886
0
                              m_morphOperation.m_dilationBuf,
887
0
                              bitDepth,
888
0
                              compId,
889
0
                              4,
890
0
                              0,
891
0
                              strongPel );
892
  
893
  
894
  /* Step 3: Subsampled 2 processing:
895
   * Detect edges and suppresses low intensity regions for each component.
896
   * Apply dilation to each component.
897
   * Upsample the result and combine it with the full-resolution mask.*/
898
0
  m_edgeDetector.detect_edges ( m_workingBufSubsampled2,
899
0
                                m_maskSubsampled2,
900
0
                                bitDepth,
901
0
                                compId );
902
0
  suppressLowIntensity ( *m_workingBufSubsampled2,
903
0
                         *m_maskSubsampled2,
904
0
                         bitDepth,
905
0
                         compId );
906
  
907
    
908
0
  m_morphOperation.dilation ( m_maskSubsampled2,
909
0
                              m_morphOperation.m_dilationBuf2,
910
0
                              bitDepth,
911
0
                              compId,
912
0
                              3,
913
0
                              0,
914
0
                              strongPel );
915
916
917
  // upsample, combine maskBuf and maskUpsampled
918
0
  upsample ( *m_maskSubsampled2,
919
0
             2,
920
0
             compId );
921
0
  combineMasks ( compId );
922
923
  /* Step 4: Subsampled 4 processing:
924
   * Detect edges and suppresses low intensity regions for each component.
925
   * Apply dilation to each component.
926
   * Upsample the result and combine it with the full-resolution mask.*/
927
0
  m_edgeDetector.detect_edges ( m_workingBufSubsampled4,
928
0
                                m_maskSubsampled4,
929
0
                                bitDepth,
930
0
                                compId );
931
0
  suppressLowIntensity ( *m_workingBufSubsampled4,
932
0
                         *m_maskSubsampled4,
933
0
                         bitDepth,
934
0
                         compId );
935
936
0
  m_morphOperation.dilation ( m_maskSubsampled4,
937
0
                              m_morphOperation.m_dilationBuf4,
938
0
                              bitDepth,
939
0
                              compId,
940
0
                              2,
941
0
                              0,
942
0
                              strongPel );
943
944
  // upsample, combine maskBuf and maskUpsampled
945
0
  upsample ( *m_maskSubsampled4,
946
0
             4,
947
0
             compId );
948
0
  combineMasks ( compId );
949
950
  /* Step 5: Final dilation and erosion
951
   * Apply final dilation to fill the holes and erosion for each component. */
952
0
  m_morphOperation.dilation ( m_maskBuf,
953
0
                              m_morphOperation.m_dilationBuf,
954
0
                              bitDepth,
955
0
                              compId,
956
0
                              2,
957
0
                              0,
958
0
                              strongPel );
959
  // erosion -> dilation with value 0
960
0
  m_morphOperation.dilation ( m_maskBuf,
961
0
                              m_morphOperation.m_dilationBuf,
962
0
                              bitDepth,
963
0
                              compId,
964
0
                              1,
965
0
                              0,
966
0
                              0 );
967
0
}
968
969
void FGAnalyzer::suppressLowIntensity ( const PelStorage &buff1,
970
                                        PelStorage &buff2,
971
                                        uint32_t bitDepth, 
972
                                        ComponentID compId )
973
0
{
974
  // buff1 - intensity values ( luma or chroma samples); buff2 - mask
975
976
0
  int width                 = buff2.get( compId ).width;
977
0
  int height                = buff2.get( compId ).height;
978
0
  Pel maxIntensity          = static_cast <Pel>( 1 << bitDepth ) - 1;
979
0
  Pel lowIntensityThreshold = static_cast<Pel>( m_lowIntensityRatio * maxIntensity );
980
981
  // strong, weak, supressed
982
0
  for ( int i = 0; i < width; i++ )
983
0
  {
984
0
    for ( int j = 0; j < height; j++ )
985
0
    {
986
      // Check if the intensity is below the threshold
987
0
      if ( buff1.get( compId ).at( i, j ) < lowIntensityThreshold )
988
0
      {
989
        // Set the corresponding mask value to maxIntensity
990
0
        buff2.get( compId ).at( i, j ) = maxIntensity;
991
0
      }
992
0
    }
993
0
  }
994
0
}
995
996
void FGAnalyzer::subsample ( PelStorage &output,
997
                             const int factor,
998
                             const int padding,
999
                             ComponentID compId ) const
1000
0
{
1001
0
  const int newWidth  = m_workingBuf->get( compId ).width / factor;
1002
0
  const int newHeight = m_workingBuf->get( compId ).height / factor;
1003
1004
0
  const Pel *srcRow    = m_workingBuf->get( compId ).buf;
1005
0
  const ptrdiff_t srcStride = m_workingBuf->get( compId ).stride;
1006
0
  Pel *dstRow    = output.get( compId ).buf;   // output is tmp buffer with only one component for binary mask
1007
0
  const ptrdiff_t dstStride = output.get( compId ).stride;
1008
1009
0
  for ( int y = 0; y < newHeight; y++, srcRow += factor * srcStride, dstRow += dstStride )
1010
0
  {
1011
0
    const Pel *inRow      = srcRow;
1012
0
    const Pel *inRowBelow = srcRow + srcStride;
1013
0
    Pel *      target     = dstRow;
1014
1015
0
    for ( int x = 0; x < newWidth; x++ )
1016
0
    {
1017
0
      target[x] = ( inRow[0] + inRowBelow[0] + inRow[1] + inRowBelow[1] + 2 ) >> 2;
1018
0
      inRow += factor;
1019
0
      inRowBelow += factor;
1020
0
    }
1021
0
  }
1022
1023
0
  if ( padding )
1024
0
  {
1025
    // Extend border with padding
1026
0
    output.get( compId ).extendBorderPel ( padding,
1027
0
                                           padding );
1028
0
  }
1029
0
}
1030
1031
void FGAnalyzer::upsample ( const PelStorage &input,
1032
                            const int factor,
1033
                            const int padding,
1034
                            ComponentID compId ) const
1035
0
{
1036
  // binary mask upsampling
1037
  // use simple replication of pixels
1038
1039
0
  const int width  = input.get(compId).width;
1040
0
  const int height = input.get(compId).height;
1041
1042
0
  for ( int i = 0; i < width; i++ )
1043
0
  {
1044
0
    for ( int j = 0; j < height; j++ )
1045
0
    {
1046
0
      Pel currentPel = input.get( compId ).at( i, j );
1047
1048
0
      for ( int x = 0; x < factor; x++ )
1049
0
      {
1050
0
        for ( int y = 0; y < factor; y++ )
1051
0
        {
1052
0
          m_maskUpsampled->get( compId ).at( i * factor + x, j * factor + y ) = currentPel;
1053
0
        }
1054
0
      }
1055
0
    }
1056
0
  }
1057
1058
0
  if ( padding )
1059
0
  {
1060
0
    m_maskUpsampled->get( compId ).extendBorderPel( padding,
1061
0
                                                    padding );
1062
0
  }
1063
0
}
1064
1065
void FGAnalyzer::combineMasks( ComponentID compId )
1066
0
{
1067
0
  const int width = m_maskBuf->get( compId ).width;
1068
0
  const int height = m_maskBuf->get( compId ).height;
1069
1070
0
  for ( int i = 0; i < width; i++ )
1071
0
  {
1072
0
    for ( int j = 0; j < height; j++ )
1073
0
    {
1074
0
      m_maskBuf->get( compId ).at( i, j ) = ( m_maskBuf->get( compId ).at( i, j ) | m_maskUpsampled->get( compId ).at( i, j ) );
1075
0
    }
1076
0
  }
1077
0
}
1078
1079
// estimate cut-off frequencies and scaling factors for different intensity intervals
1080
void FGAnalyzer::estimateGrainParameters ( Picture *pic )
1081
0
{
1082
0
  m_originalBuf = &pic->getOrigBuffer();                                   // original frame
1083
0
  m_workingBuf = &pic->getFilteredOrigBuffer();                            // mctf filtered frame
1084
1085
  // Determine blockSize dynamically based on the frame resolution
1086
0
  int blockSize = BLK_8;
1087
0
  uint32_t picSizeInLumaSamples = m_workingBuf->Y().height * m_workingBuf->Y().width;
1088
0
  if ( picSizeInLumaSamples >= 7680 * 4320 )
1089
0
  {
1090
    // 8K resolution
1091
0
    blockSize = BLK_32;
1092
0
  }
1093
0
  else if ( picSizeInLumaSamples >= 3840 * 2160 )
1094
0
  {
1095
    // 4K resolution
1096
0
    blockSize = BLK_16;
1097
0
  }
1098
0
  else
1099
0
  {
1100
0
    blockSize = BLK_8;
1101
0
  }
1102
1103
0
  findMask( COMP_Y );                                                       // Generate mask for luma only
1104
1105
  // find difference between original and filtered/reconstructed frame => film grain estimate
1106
0
  m_grainEstimateBuf->subtract( pic->getOrigBuffer(),
1107
0
                                pic->getFilteredOrigBuffer() );
1108
1109
0
  for ( int compIdx = 0; compIdx < getNumberValidComponents( m_inputChromaFormat ); compIdx++ )
1110
0
  {
1111
0
    ComponentID  compID          = ComponentID( compIdx );
1112
0
    uint32_t     width           = m_workingBuf->getBuf( compID ).width;    // Width of current frame
1113
0
    uint32_t     height          = m_workingBuf->getBuf( compID ).height;   // Height of current frame
1114
0
    uint32_t     windowSize      = DATA_BASE_SIZE;                          // Size for Film Grain block
1115
0
    int          bitDepth        = m_bitDepths[toChannelType( compID )];
1116
0
    int          detect_edges    = 0;
1117
0
    int          mean            = 0;
1118
0
    int          var             = 0;
1119
0
    m_numDctGrainBlocks          = 0;
1120
1121
    // Clear vectors before computing for each component
1122
0
    vecMean.clear();
1123
0
    vecVar.clear();
1124
0
    tmp_data_x.clear();
1125
0
    tmp_data_y.clear();
1126
0
    scalingVec.clear();
1127
0
    quantVec.clear();
1128
0
    coeffs.clear();
1129
1130
0
    for ( int i = 0; i <= width - windowSize; i += windowSize )
1131
0
    { // loop over windowSize x windowSize blocks
1132
0
      for ( int j = 0; j <= height - windowSize; j += windowSize )
1133
0
      {
1134
0
        if ( compID == COMP_Y )
1135
0
        {
1136
0
          detect_edges = countEdges ( windowSize,
1137
0
                                      i,
1138
0
                                      j,
1139
0
                                      compID );  // for flat region without edges
1140
0
        }
1141
0
        else
1142
0
        {
1143
0
          detect_edges = 1;                      // always process for chroma
1144
0
        }
1145
0
        if ( detect_edges )   // selection of uniform, flat and low-complexity area; extend to other features, e.g., variance.
1146
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.
1147
0
          CoeffBuf& currentCoeffBuf = m_dctGrainBlockList[m_numDctGrainBlocks++];
1148
0
          blockTransform ( currentCoeffBuf,
1149
0
                           i,
1150
0
                           j,
1151
0
                           bitDepth,
1152
0
                           compID );
1153
0
        }
1154
1155
0
        int step = windowSize / blockSize;
1156
0
        for ( int k = 0; k < step; k++ )
1157
0
        {
1158
0
          for ( int m = 0; m < step; m++ )
1159
0
          {
1160
0
            if ( compID == COMP_Y )
1161
0
            {
1162
0
              detect_edges = countEdges ( blockSize,
1163
0
                                          i + k * blockSize,
1164
0
                                          j + m * blockSize,
1165
0
                                          compID );   // for flat region without edges
1166
0
            }
1167
0
            else
1168
0
            {
1169
0
              detect_edges = 1;  // always process for chroma
1170
0
            }
1171
0
            if ( detect_edges )   // selection of uniform, flat and low-complexity area; extend to other features, e.g., variance.
1172
0
            {
1173
              // collect all data for parameter estimation; mean and variance are caluclated on blockSize x blockSize blocks
1174
0
              uint32_t stride = m_grainEstimateBuf->get( compID ).stride;
1175
0
              double varD = calcVar ( m_grainEstimateBuf->get( compID ).buf + ( ( j + m * blockSize ) * stride ) + i + ( k * blockSize ),
1176
0
                                      stride,
1177
0
                                      blockSize,
1178
0
                                      blockSize );
1179
0
              varD = varD / (( blockSize * blockSize ));
1180
0
              var = static_cast<int>( varD + 0.5 );
1181
0
              stride = m_workingBuf->get( compID ).stride;
1182
0
              mean = calcMean ( m_workingBuf->get( compID ).buf + ( ( j + m * blockSize ) * stride ) + i + ( k * blockSize ),
1183
0
                                stride,
1184
0
                                blockSize,
1185
0
                                blockSize );
1186
0
              mean = static_cast<int>(static_cast<double>( mean ) / ( blockSize * blockSize ) + 0.5 );
1187
1188
              // regularize high variations; controls excessively fluctuating points
1189
0
              double tmp = 2.75 * pow( static_cast<double>( var ), 0.5 ) + 0.5;
1190
0
              var = static_cast<int>( tmp ); 
1191
              // limit data points to meaningful values. higher variance can be result of not perfect mask estimation (non-flat regions fall in estimation process)
1192
0
              if ( var < ( MAX_REAL_SCALE << ( bitDepth - BIT_DEPTH_8 ) ) )
1193
0
              {
1194
0
                vecMean.push_back( mean );    // mean of the filtered frame
1195
0
                vecVar.push_back( var );      // variance of the film grain estimate
1196
0
              }
1197
0
            }
1198
0
          }
1199
0
        }
1200
0
      }
1201
0
    }
1202
1203
    // calculate film grain parameters
1204
0
    estimateCutoffFreqAdaptive( compID );
1205
0
    estimateScalingFactors ( bitDepth,
1206
0
                             compID );
1207
1208
    // Clear vectors after estimation
1209
0
    vecMean.clear();
1210
0
    vecVar.clear();
1211
0
    finalIntervalsandScalingFactors.clear();
1212
0
  }
1213
0
}
1214
1215
/* This function calculates the scaling factors for film grain by analyzing the variance of intensity intervals.
1216
 * The primary steps include fitting a polynomial regression function to the intensity - variance data points,
1217
 * smoothing the resulting scaling function, and performing Lloyd - Max quantization to derive the final scaling factors.
1218
 * The estimated parameters are then set for each intensity interval.*/
1219
void FGAnalyzer::estimateScalingFactors ( uint32_t bitDepth,
1220
                                          ComponentID compId )
1221
0
{
1222
  // if cutoff frequencies are not estimated previously, do not proceed since presentFlag is set to false in a previous step
1223
0
  if ( !m_compModel[compId].presentFlag || vecMean.size() < MIN_POINTS_FOR_INTENSITY_ESTIMATION )
1224
0
  {
1225
0
    return;   // If there is no enough points to estimate film grain intensities, default or previously estimated
1226
              // parameters are used
1227
0
  }
1228
1229
0
  double              distortion = 0.0;
1230
1231
  // Fit the points with the curve and perform Lloyd Max quantization.
1232
0
  bool valid;
1233
0
  for ( int i = 0; i < NUM_PASSES; i++ )   // if num_passes = 2, filtering of the dataset points is performed
1234
0
  {
1235
0
    valid = fitFunction ( ORDER,
1236
0
                          bitDepth,
1237
0
                          i );   // n-th order polynomial regression for scaling function estimation
1238
0
    if ( !valid )
1239
0
    {
1240
0
      coeffs.clear();
1241
0
      scalingVec.clear();
1242
0
      quantVec.clear();
1243
0
      break;
1244
0
    }
1245
0
  }
1246
1247
0
  if ( valid )
1248
0
  {
1249
0
    avgScalingVec ( bitDepth );   // scale with previously fitted function to smooth the intensity
1250
0
    valid = lloydMax ( distortion,
1251
0
                       bitDepth );   // train quantizer and quantize curve using Lloyd Max
1252
0
  }
1253
1254
  // Based on quantized intervals, set intensity region and scaling parameter
1255
0
  if ( valid )   // if not valid, reuse previous parameters (for example, if var is all zero)
1256
0
  {
1257
0
    setEstimatedParameters ( bitDepth,
1258
0
                             compId );
1259
0
  }
1260
1261
0
  coeffs.clear();
1262
0
  scalingVec.clear();
1263
0
  quantVec.clear();
1264
1265
0
}
1266
1267
/*This function divides the specified range(rows or columns) of the `meanSquaredDctGrain` matrix into bins
1268
* and calculates the average value of each bin.If the average value of a bin exceeds the given threshold,
1269
* the bin is considered significant and its starting index is recorded in the `significantIndices` vector.
1270
* The function can be used to adaptively refine the search for significant values in the matrix by focusing
1271
* on specific rows or columns iteratively.*/
1272
void FGAnalyzer::adaptiveSampling ( int bins,
1273
                                    double threshold,
1274
                                    std::vector<int>& significantIndices,
1275
                                    bool isRow,
1276
                                    int startIdx )
1277
0
{
1278
0
  int binSize = DATA_BASE_SIZE / bins;
1279
0
  for ( int i = 0; i < bins; i++ )
1280
0
  {
1281
0
    double sum = 0;
1282
0
    for ( int j = 0; j < binSize; j++ )
1283
0
    {
1284
0
      int idx = startIdx + i * binSize + j;
1285
0
      if ( idx >= DATA_BASE_SIZE )
1286
0
          break;  // Ensure we don't go out of bounds
1287
0
      sum += isRow ? meanSquaredDctGrain[idx][0] : meanSquaredDctGrain[0][idx];
1288
0
    }
1289
0
    sum /= binSize;
1290
0
    if ( sum > threshold )
1291
0
    {
1292
0
      significantIndices.push_back( startIdx + i * binSize );
1293
0
    }
1294
0
  }
1295
0
}
1296
1297
1298
/*This function refines the cutoff frequency estimation by adaptively sampling the mean squared DCT grain values
1299
 * matrix. Instead of analyzing every row and column, it focuses on significant bins determined by the adaptive sampling
1300
 * method. The horizontal and vertical cutoff frequencies are estimated by examining the mean values of these significant
1301
 * bins, making the process more efficient and reducing computational overhead.
1302
 * The function performs the following steps :
1303
 * 1. Initializes mean squared DCT grain matrix and mean vectors for rows and columns.
1304
 * 2. Iterates through the DCT grain blocks to calculate the average block for each coefficient.
1305
 * 3. Uses the adaptive sampling method to identify significant rows and columns.
1306
 * 4. Estimates the cutoff frequencies based on the mean values of the significant rows and columns.
1307
 * 5. Updates the component model with the estimated cutoff frequencies.*/
1308
void FGAnalyzer::estimateCutoffFreqAdaptive( ComponentID compId )
1309
0
{
1310
0
  const int coarseBins = 8; // Initial coarse sampling bins
1311
0
  const int refineBins = 4; // Bins for each refinement step
1312
0
  const int maxIterations = 3; // Maximum refinement iterations
1313
0
  const double threshold = 0.1; // Threshold to identify significant bins
1314
1315
0
  std::memset( meanSquaredDctGrain, 0, sizeof( meanSquaredDctGrain ) );
1316
1317
  // Calculate mean squared DCT grain values
1318
0
  for ( int x = 0; x < DATA_BASE_SIZE; x++ )
1319
0
  {
1320
0
    for ( int y = 0; y < DATA_BASE_SIZE; y++ )
1321
0
    {
1322
0
      for ( int i = 0; i < m_numDctGrainBlocks; i++ )
1323
0
      {
1324
0
        meanSquaredDctGrain[x][y] += m_dctGrainBlockList[i].at( x, y );
1325
0
      }
1326
0
      meanSquaredDctGrain[x][y] /= m_numDctGrainBlocks;
1327
0
    }
1328
0
  }
1329
1330
  // Identify initial coarse bins with significant grain values
1331
0
  std::vector<int> significantRows, significantCols;
1332
0
  adaptiveSampling ( coarseBins,
1333
0
                     threshold,
1334
0
                     significantRows,
1335
0
                     true,
1336
0
                     0 ); // Rows
1337
0
  adaptiveSampling ( coarseBins,
1338
0
                     threshold,
1339
0
                     significantCols,
1340
0
                     false,
1341
0
                     0 );  // Columns
1342
1343
  // Iterative Refinement
1344
0
  for ( int iter = 0; iter < maxIterations; iter++ )
1345
0
  {
1346
0
    std::vector<int> refinedRows, refinedCols;
1347
0
    for ( int row : significantRows )
1348
0
    {
1349
0
      adaptiveSampling ( refineBins,
1350
0
                         threshold,
1351
0
                         refinedRows,
1352
0
                         true,
1353
0
                         row );
1354
0
    }
1355
0
    for ( int col : significantCols )
1356
0
    {
1357
0
      adaptiveSampling ( refineBins,
1358
0
                         threshold,
1359
0
                         refinedCols,
1360
0
                         false,
1361
0
                         col );
1362
0
    }
1363
0
    significantRows = refinedRows;
1364
0
    significantCols = refinedCols;
1365
0
  }
1366
1367
  // Determine cut-off frequencies from the refined significant bins
1368
0
  int cutoffVertical = significantRows.empty() ? 0 : significantRows.back() / ( DATA_BASE_SIZE / 16 );
1369
0
  int cutoffHorizontal = significantCols.empty() ? 0 : significantCols.back() / ( DATA_BASE_SIZE / 16 );
1370
1371
  // Set the cut-off frequencies in the model
1372
0
  if ( cutoffVertical && cutoffHorizontal )
1373
0
  {
1374
0
    m_compModel[compId].presentFlag = true;
1375
0
    m_compModel[compId].numModelValues = 3;
1376
0
    m_compModel[compId].intensityValues[0].compModelValue[1] = cutoffHorizontal;
1377
0
    m_compModel[compId].intensityValues[0].compModelValue[2] = cutoffVertical;
1378
0
  }
1379
0
  else
1380
0
  {
1381
0
    m_compModel[compId].presentFlag = false;
1382
0
  }
1383
0
}
1384
1385
// DCT-2 64x64 as defined in VVC
1386
void FGAnalyzer::blockTransform ( CoeffBuf &currentCoeffBuf,
1387
                                  int offsetX,
1388
                                  int offsetY,
1389
                                  uint32_t bitDepth,
1390
                                  ComponentID compId )
1391
0
{
1392
0
  uint32_t      windowSize      = DATA_BASE_SIZE;   // Size for Film Grain block
1393
0
  const int     transform_scale = 9;                // upscaling of original transform as specified in VVC (for 64x64 block)
1394
1395
  // copy input -> 32 Bit
1396
0
  for ( uint32_t y = 0; y < DATA_BASE_SIZE; y++ )
1397
0
  {
1398
0
    for ( uint32_t x = 0; x < DATA_BASE_SIZE; x++ )
1399
0
    {
1400
0
      m_DCTinout[x + DATA_BASE_SIZE * y] = m_grainEstimateBuf->get( compId ).at( offsetX + x,
1401
0
                                                                                 offsetY + y );
1402
0
    }
1403
0
  }
1404
1405
0
  fastForwardDCT2_B64 ( m_DCTinout,
1406
0
                        m_DCTtemp,
1407
0
                        transform_scale,
1408
0
                        windowSize,
1409
0
                        0,
1410
0
                        0 );
1411
0
  fastForwardDCT2_B64 ( m_DCTtemp,
1412
0
                        m_DCTinout,
1413
0
                        transform_scale,
1414
0
                        windowSize,
1415
0
                        0,
1416
0
                        0 );
1417
1418
  // Calculate squared transformed block
1419
0
  for ( int y = 0; y < DATA_BASE_SIZE; y++ )
1420
0
  {
1421
0
    for ( int x = 0; x < DATA_BASE_SIZE; x++ )
1422
0
    {
1423
0
      currentCoeffBuf.at( x, y ) = m_DCTinout[x + DATA_BASE_SIZE * y] * m_DCTinout[x + DATA_BASE_SIZE * y];
1424
0
    }
1425
0
  }
1426
0
}
1427
1428
// check edges
1429
int FGAnalyzer::countEdges ( int windowSize,
1430
                             int offsetX,
1431
                             int offsetY, 
1432
                             ComponentID compId )
1433
0
{
1434
0
  for ( int x = 0; x < windowSize; x++ )
1435
0
  {
1436
0
    for ( int y = 0; y < windowSize; y++ )
1437
0
    {
1438
0
      if ( m_maskBuf->get( compId ).at( offsetX + x,
1439
0
                                        offsetY + y ) )
1440
0
      {
1441
0
        return 0;
1442
0
      }
1443
0
    }
1444
0
  }
1445
1446
0
  return 1;
1447
0
}
1448
1449
// Fit data to a function using n-th order polynomial interpolation
1450
bool FGAnalyzer::fitFunction ( int order,
1451
                               int bitDepth,
1452
                               bool second_pass )
1453
0
{
1454
0
  long double         a[MAXPAIRS + 1][MAXPAIRS + 1];
1455
0
  long double         B[MAXPAIRS + 1], C[MAXPAIRS + 1], S[MAXPAIRS + 1];
1456
0
  long double         A1 = 0.0, A2 = 0.0, Y1 = 0.0, m = 0.0, S1 = 0.0, x1 = 0.0;
1457
0
  long double         xscale = 0.0, yscale = 0.0;
1458
0
  long double         xmin = 0.0, xmax = 0.0, ymin = 0.0, ymax = 0.0;
1459
0
  long double         polycoefs[MAXORDER + 1];
1460
0
  int i, j, k, L, R;
1461
1462
  // several data filtering and data manipulations before fitting the function
1463
  // create interval points for function fitting
1464
0
  int INTENSITY_INTERVAL_NUMBER = (1 << bitDepth) / INTERVAL_SIZE;
1465
0
  vec_mean_intensity.resize( INTENSITY_INTERVAL_NUMBER, 0 );
1466
0
  vec_variance_intensity.resize( INTENSITY_INTERVAL_NUMBER, 0 );
1467
0
  element_number_per_interval.resize( INTENSITY_INTERVAL_NUMBER, 0 );
1468
1469
0
  double              mn = 0.0, sd = 0.0;
1470
1471
0
  std::memset( a, 0, sizeof(a) );
1472
0
  std::memset( B, 0, sizeof(B) );
1473
0
  std::memset( C, 0, sizeof(C) );
1474
0
  std::memset( S, 0, sizeof(S) );
1475
0
  std::memset( polycoefs, 0, sizeof(polycoefs) );
1476
1477
0
  if ( second_pass )   // in second pass, filter based on the variance of the data_y. remove all high and low points
1478
0
  {
1479
0
    xmin = scalingVec.back();
1480
0
    scalingVec.pop_back();
1481
0
    xmax = scalingVec.back();
1482
0
    scalingVec.pop_back();
1483
0
    int n = static_cast<int>( vecVar.size() );
1484
0
    if ( n != 0 )
1485
0
    {
1486
0
      mn = std::accumulate ( vecVar.begin(), vecVar.end(), 0.0 ) / n;
1487
0
      for ( int cnt = 0; cnt < n; cnt++ )
1488
0
      {
1489
0
        sd += ( vecVar[cnt] - mn ) * ( vecVar[cnt] - mn );
1490
0
      }
1491
0
      sd /= n;
1492
0
      sd = std::sqrt( sd );
1493
0
    }
1494
0
  }
1495
1496
0
  for ( int cnt = 0; cnt < vecMean.size(); cnt++ )
1497
0
  {
1498
0
    if ( second_pass )
1499
0
    {
1500
0
      if ( vecMean[cnt] >= xmin && vecMean[cnt] <= xmax )
1501
0
      {
1502
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 ))
1503
0
        {
1504
0
          int block_index = vecMean[cnt] / INTERVAL_SIZE;
1505
0
          vec_mean_intensity[block_index] += vecMean[cnt];
1506
0
          vec_variance_intensity[block_index] += vecVar[cnt];
1507
0
          element_number_per_interval[block_index]++;
1508
0
        }
1509
0
      }
1510
0
    }
1511
0
    else
1512
0
    {
1513
0
      int block_index = vecMean[cnt] / INTERVAL_SIZE;
1514
0
      vec_mean_intensity[block_index] += vecMean[cnt];
1515
0
      vec_variance_intensity[block_index] += vecVar[cnt];
1516
0
      element_number_per_interval[block_index]++;
1517
0
    }
1518
0
  }
1519
1520
  // create points per intensity interval
1521
0
  for ( int block_idx = 0; block_idx < INTENSITY_INTERVAL_NUMBER; block_idx++ )
1522
0
  {
1523
0
    if ( element_number_per_interval[block_idx] >= MIN_ELEMENT_NUMBER_PER_INTENSITY_INTERVAL )
1524
0
    {
1525
0
      tmp_data_x.push_back ( vec_mean_intensity[block_idx] / element_number_per_interval[block_idx] );
1526
0
      tmp_data_y.push_back( vec_variance_intensity[block_idx] / element_number_per_interval[block_idx] );
1527
0
    }
1528
0
  }
1529
1530
  // There needs to be at least ORDER+1 points to fit the function
1531
0
  if ( tmp_data_x.size() < ( order + 1 ) )
1532
0
  {
1533
0
    return false;   // if there is no enough blocks to estimate film grain parameters, default or previously estimated
1534
                    // parameters are used
1535
0
  }
1536
1537
0
  for ( i = 0; i < tmp_data_x.size(); i++ ) // remove single points before extending and fitting
1538
0
  {
1539
0
    int check = 0;
1540
0
    for ( j = -WINDOW; j <= WINDOW; j++ )
1541
0
    {
1542
0
      int idx = i + j;
1543
0
      if ( idx >= 0 && idx < tmp_data_x.size() && j != 0 )
1544
0
      {
1545
0
        check += abs( tmp_data_x[i] / INTERVAL_SIZE - tmp_data_x[idx] / INTERVAL_SIZE ) <= WINDOW ? 1 : 0;
1546
0
      }
1547
0
    }
1548
1549
0
    if ( check < NBRS )
1550
0
    {
1551
0
      for ( int k = i; k < tmp_data_x.size() - 1; k++ )
1552
0
      {
1553
0
        tmp_data_x[k] = tmp_data_x[k + 1];
1554
0
        tmp_data_y[k] = tmp_data_y[k + 1];
1555
0
      }
1556
0
      tmp_data_x.pop_back();
1557
0
      tmp_data_y.pop_back();
1558
0
      i--;
1559
0
    }
1560
0
  }
1561
1562
0
  extendPoints( bitDepth );     // find the most left and the most right point, and extend edges
1563
1564
0
  CHECK( tmp_data_x.size() > MAXPAIRS, "Maximum dataset size exceeded." );
1565
1566
  // fitting the function starts here
1567
0
  xmin = tmp_data_x[0];
1568
0
  xmax = tmp_data_x[0];
1569
0
  ymin = tmp_data_y[0];
1570
0
  ymax = tmp_data_y[0];
1571
0
  for ( i = 0; i < tmp_data_x.size(); i++ )
1572
0
  {
1573
0
    if ( tmp_data_x[i] < xmin )
1574
0
    {
1575
0
      xmin = tmp_data_x[i];
1576
0
    }
1577
0
    if ( tmp_data_x[i] > xmax )
1578
0
    {
1579
0
      xmax = tmp_data_x[i];
1580
0
    }
1581
0
    if ( tmp_data_y[i] < ymin )
1582
0
    {
1583
0
      ymin = tmp_data_y[i];
1584
0
    }
1585
0
    if ( tmp_data_y[i] > ymax )
1586
0
    {
1587
0
      ymax = tmp_data_y[i];
1588
0
    }
1589
0
  }
1590
1591
0
  long double xlow = xmax;
1592
0
  long double ylow = ymax;
1593
1594
0
  int data_pairs = static_cast<int>( tmp_data_x.size() );
1595
1596
0
  double data_array[2][MAXPAIRS + 1];
1597
0
  std::memset( data_array, 0, sizeof(data_array) );
1598
0
  for ( i = 0; i < data_pairs; i++ )
1599
0
  {
1600
0
    data_array[0][i + 1] = static_cast<double>( tmp_data_x[i] );
1601
0
    data_array[1][i + 1] = static_cast<double>( tmp_data_y[i] );
1602
0
  }
1603
1604
  // Clear previous vectors by resizing them to 0
1605
0
  tmp_data_x.clear();
1606
0
  tmp_data_y.clear();
1607
1608
0
  if ( second_pass )
1609
0
  {
1610
0
    coeffs.resize( 0 );
1611
0
    scalingVec.resize( 0 );
1612
0
  }
1613
1614
0
  for ( i = 1; i <= data_pairs; i++ )
1615
0
  {
1616
0
    if ( data_array[0][i] < xlow && data_array[0][i] != 0 )
1617
0
    {
1618
0
      xlow = data_array[0][i];
1619
0
    }
1620
0
    if ( data_array[1][i] < ylow && data_array[1][i] != 0 )
1621
0
    {
1622
0
      ylow = data_array[1][i];
1623
0
    }
1624
0
  }
1625
1626
0
  if ( xlow < .001 && xmax < 1000 )
1627
0
  {
1628
0
    xscale = 1 / xlow;
1629
0
  }
1630
0
  else if ( xmax > 1000 && xlow > .001 )
1631
0
  {
1632
0
    xscale = 1 / xmax;
1633
0
  }
1634
0
  else
1635
0
  {
1636
0
    xscale = 1;
1637
0
  }
1638
1639
0
  if ( ylow < .001 && ymax < 1000 )
1640
0
  {
1641
0
    yscale = 1 / ylow;
1642
0
  }
1643
0
  else if ( ymax > 1000 && ylow > .001 )
1644
0
  {
1645
0
    yscale = 1 / ymax;
1646
0
  }
1647
0
  else
1648
0
  {
1649
0
    yscale = 1;
1650
0
  }
1651
1652
  // initialise array variables
1653
0
  for ( j = 1; j <= data_pairs; j++ )
1654
0
  {
1655
0
    for ( i = 1; i <= order; i++ )
1656
0
    {
1657
0
      B[i] = B[i] + data_array[1][j] * yscale * ldpow( data_array[0][j] * xscale, i );
1658
0
      if ( B[i] == std::numeric_limits<long double>::max() )
1659
0
      {
1660
0
        return false;
1661
0
      }
1662
0
      for ( k = 1; k <= order; k++ )
1663
0
      {
1664
0
        a[i][k] = a[i][k] + ldpow( data_array[0][j] * xscale, ( i + k ) );
1665
0
        if ( a[i][k] == std::numeric_limits<long double>::max() )
1666
0
        {
1667
0
          return false;
1668
0
        }
1669
0
      }
1670
0
      S[i] = S[i] + ldpow( data_array[0][j] * xscale, i );
1671
0
      if ( S[i] == std::numeric_limits<long double>::max() )
1672
0
      {
1673
0
        return false;
1674
0
      }
1675
0
    }
1676
0
    Y1 = Y1 + data_array[1][j] * yscale;
1677
0
    if ( Y1 == std::numeric_limits<long double>::max() )
1678
0
    {
1679
0
      return false;
1680
0
    }
1681
0
  }
1682
1683
0
  for ( i = 1; i <= order; i++ )
1684
0
  {
1685
0
    for ( j = 1; j <= order; j++ )
1686
0
    {
1687
0
      a[i][j] = a[i][j] - S[i] * S[j] / static_cast<long double>( data_pairs );
1688
0
      if (a[i][j] == std::numeric_limits<long double>::max())
1689
0
      {
1690
0
        return false;
1691
0
      }
1692
0
    }
1693
0
    B[i] = B[i] - Y1 * S[i] / static_cast<long double>( data_pairs );
1694
0
    if ( B[i] == std::numeric_limits<long double>::max() )
1695
0
    {
1696
0
      return false;
1697
0
    }
1698
0
  }
1699
1700
0
  for ( k = 1; k <= order; k++ )
1701
0
  {
1702
0
    R  = k;
1703
0
    A1 = 0;
1704
0
    for ( L = k; L <= order; L++ )
1705
0
    {
1706
0
      A2 = fabsl( a[L][k] );
1707
0
      if ( A2 > A1 )
1708
0
      {
1709
0
        A1 = A2;
1710
0
        R  = L;
1711
0
      }
1712
0
    }
1713
0
    if ( A1 == 0 )
1714
0
    {
1715
0
      return false;
1716
0
    }
1717
0
    if ( R != k )
1718
0
    {
1719
0
      for ( j = k; j <= order; j++ )
1720
0
      {
1721
0
        x1      = a[R][j];
1722
0
        a[R][j] = a[k][j];
1723
0
        a[k][j] = x1;
1724
0
      }
1725
0
      x1   = B[R];
1726
0
      B[R] = B[k];
1727
0
      B[k] = x1;
1728
0
    }
1729
0
    for ( i = k; i <= order; i++ )
1730
0
    {
1731
0
      m = a[i][k];
1732
0
      for ( j = k; j <= order; j++ )
1733
0
      {
1734
0
        if ( i == k )
1735
0
        {
1736
0
          a[i][j] = a[i][j] / m;
1737
0
        }
1738
0
        else
1739
0
        {
1740
0
          a[i][j] = a[i][j] - m * a[k][j];
1741
0
        }
1742
0
      }
1743
0
      if ( i == k )
1744
0
      {
1745
0
        B[i] = B[i] / m;
1746
0
      }
1747
0
      else
1748
0
      {
1749
0
        B[i] = B[i] - m * B[k];
1750
0
      }
1751
0
    }
1752
0
  }
1753
1754
0
  polycoefs[order] = B[order];
1755
0
  for ( k = 1; k <= order - 1; k++ )
1756
0
  {
1757
0
    i  = order - k;
1758
0
    S1 = 0;
1759
0
    for ( j = 1; j <= order; j++ )
1760
0
    {
1761
0
      S1 = S1 + a[i][j] * polycoefs[j];
1762
0
      if ( S1 == std::numeric_limits<long double>::max() )
1763
0
      {
1764
0
        return false;
1765
0
      }
1766
0
    }
1767
0
    polycoefs[i] = B[i] - S1;
1768
0
  }
1769
1770
0
  S1 = 0;
1771
0
  for ( i = 1; i <= order; i++ )
1772
0
  {
1773
0
    S1 = S1 + polycoefs[i] * S[i] / static_cast<long double>( data_pairs );
1774
0
    if ( S1 == std::numeric_limits<long double>::max() )
1775
0
    {
1776
0
      return false;
1777
0
    }
1778
0
  }
1779
0
  polycoefs[0] = (Y1 / static_cast<long double>( data_pairs ) - S1);
1780
1781
  // zero all coeficient values smaller than +/- .00000000001 (avoids -0)
1782
0
  for ( i = 0; i <= order; i++ )
1783
0
  {
1784
0
    if ( fabsl(polycoefs[i] * 100000000000) < 1 )
1785
0
    {
1786
0
      polycoefs[i] = 0;
1787
0
    }
1788
0
  }
1789
1790
  // rescale parameters
1791
0
  for ( i = 0; i <= order; i++ )
1792
0
  {
1793
0
    polycoefs[i] = (1 / yscale) * polycoefs[i] * ldpow( xscale, i );
1794
0
    coeffs.push_back( polycoefs[i] );
1795
0
  }
1796
1797
  // create fg scaling function. interpolation based on coeffs which returns lookup table from 0 - 2^B-1. n-th order polinomial regression
1798
0
  for ( i = static_cast<int>( xmin ); i <= static_cast<int>( xmax ); i++ )
1799
0
  {
1800
0
    double val = coeffs[0];
1801
0
    for ( j = 1; j < coeffs.size(); j++ )
1802
0
    {
1803
0
      val += (coeffs[j] * ldpow( i, j ));
1804
0
    }
1805
1806
0
    val = Clip3( 0.0,
1807
0
                 static_cast<double>( 1 << bitDepth ) - 1,
1808
0
                 val );
1809
0
    scalingVec.push_back( val );
1810
0
  }
1811
1812
  // save in scalingVec min and max value for further use
1813
0
  scalingVec.push_back( xmax );
1814
0
  scalingVec.push_back( xmin );
1815
1816
0
  vec_mean_intensity.clear();
1817
0
  vec_variance_intensity.clear();
1818
0
  element_number_per_interval.clear();
1819
0
  tmp_data_x.clear();
1820
0
  tmp_data_y.clear();
1821
1822
0
  return true;
1823
0
}
1824
1825
// avg scaling vector with previous result to smooth transition betweeen frames
1826
void FGAnalyzer::avgScalingVec ( int bitDepth )
1827
0
{
1828
0
  int xmin = static_cast<int>( scalingVec.back() );
1829
0
  scalingVec.pop_back();
1830
0
  int xmax = static_cast<int>( scalingVec.back() );
1831
0
  scalingVec.pop_back();
1832
1833
0
  std::vector<double> scalingVecAvg( static_cast<int>( 1 << bitDepth ) );
1834
0
  bool isFirstScalingEst = true;
1835
1836
0
  if ( isFirstScalingEst )
1837
0
  {
1838
0
    for (int i = xmin; i <= xmax; i++)
1839
0
    {
1840
0
      scalingVecAvg[i] = scalingVec[i - xmin];
1841
0
    }
1842
0
    isFirstScalingEst = false;
1843
0
  }
1844
0
  else
1845
0
  {
1846
0
    for ( int i = xmin; i <= xmax; i++ )
1847
0
    {
1848
0
      scalingVecAvg[i] = ( scalingVecAvg[i] + scalingVec[i - xmin] ) / 2.0;
1849
0
    }
1850
0
  }
1851
1852
0
  int new_xmin = 0;
1853
0
  while ( new_xmin <= xmax && scalingVecAvg[new_xmin] == 0 )
1854
0
  {
1855
0
    new_xmin++;
1856
0
  }
1857
1858
0
  int new_xmax = static_cast<int>( scalingVecAvg.size() ) - 1;
1859
0
  while ( new_xmax >= 0 && scalingVecAvg[new_xmax] == 0 )
1860
0
  {
1861
0
    new_xmax--;
1862
0
  }
1863
1864
0
  if ( new_xmax < new_xmin )
1865
0
  {
1866
    // Handle the case where all entries are zero
1867
0
    scalingVec.clear();
1868
0
    scalingVec.push_back( 0 ); // Minimum value
1869
0
    scalingVec.push_back( 0 ); // Maximum value
1870
0
    return;
1871
0
  }
1872
1873
0
  scalingVec.assign( scalingVecAvg.begin() + new_xmin,
1874
0
                     scalingVecAvg.begin() + new_xmax + 1 );
1875
0
  scalingVec.push_back( new_xmax );
1876
0
  scalingVec.push_back( new_xmin );
1877
0
}
1878
1879
1880
// Lloyd Max quantizer
1881
bool FGAnalyzer::lloydMax ( double &distortion,
1882
                            int bitDepth )
1883
0
{
1884
0
  if ( !scalingVec.size() )
1885
0
  {
1886
    // Film grain parameter estimation is not performed. Default or previously estimated parameters are reused.
1887
0
    return false;
1888
0
  }
1889
1890
0
  int xmin = static_cast<int>( scalingVec.back() );
1891
0
  scalingVec.pop_back();
1892
0
  scalingVec.pop_back();   // dummy pop_pack ==> int xmax = (int)scalingVec.back();
1893
1894
0
  double ymin          = 0.0;
1895
0
  double ymax          = 0.0;
1896
0
  double init_training = 0.0;
1897
0
  double tolerance     = 0.0000001;
1898
0
  double last_distor   = 0.0;
1899
0
  double rel_distor    = 0.0;
1900
1901
0
  double codebook[QUANT_LEVELS];
1902
0
  double partition[QUANT_LEVELS - 1];
1903
1904
0
  std::vector<double> tmpVec( scalingVec.size(), 0.0 );
1905
0
  distortion = 0.0;
1906
1907
0
  ymin = scalingVec[0];
1908
0
  ymax = scalingVec[0];
1909
0
  for ( int i = 0; i < scalingVec.size(); i++ )
1910
0
  {
1911
0
    if ( scalingVec[i] < ymin )
1912
0
    {
1913
0
      ymin = scalingVec[i];
1914
0
    }
1915
0
    if ( scalingVec[i] > ymax )
1916
0
    {
1917
0
      ymax = scalingVec[i];
1918
0
    }
1919
0
  }
1920
1921
0
  init_training = ( ymax - ymin ) / QUANT_LEVELS;
1922
1923
0
  if ( init_training <= 0 )
1924
0
  {
1925
    // msg(WARNING, "Invalid training dataset. Film grain parameter estimation is not performed. Default or previously estimated parameters are reused.\n");
1926
0
    return false;
1927
0
  }
1928
1929
  // initial codebook
1930
0
  double step = init_training / 2;
1931
0
  for ( int i = 0; i < QUANT_LEVELS; i++ )
1932
0
  {
1933
0
    codebook[i] = ymin + i * init_training + step;
1934
0
  }
1935
1936
  // initial partition
1937
0
  for ( int i = 0; i < QUANT_LEVELS - 1; i++ )
1938
0
  {
1939
0
    partition[i] = (codebook[i] + codebook[i + 1]) / 2;
1940
0
  }
1941
1942
  // quantizer initialization
1943
0
  quantize ( tmpVec,
1944
0
             distortion,
1945
0
             partition,
1946
0
             codebook );
1947
1948
0
  double tolerance2 = std::numeric_limits<double>::epsilon() * ymax;
1949
0
  if ( distortion > tolerance2 )
1950
0
  {
1951
0
    rel_distor = std::fabs( distortion - last_distor ) / distortion;
1952
0
  }
1953
0
  else
1954
0
  {
1955
0
    rel_distor = distortion;
1956
0
  }
1957
1958
  // optimization: find optimal codebook and partition
1959
0
  while ( ( rel_distor > tolerance ) && ( rel_distor > tolerance2 ) )
1960
0
  {
1961
0
    for ( int i = 0; i < QUANT_LEVELS; i++ )
1962
0
    {
1963
0
      int count = 0;
1964
0
      double sum = 0.0;
1965
1966
0
      for ( int j = 0; j < tmpVec.size(); j++ )
1967
0
      {
1968
0
        if ( codebook[i] == tmpVec[j] )
1969
0
        {
1970
0
          count++;
1971
0
          sum += scalingVec[j];
1972
0
        }
1973
0
      }
1974
1975
0
      if ( count )
1976
0
      {
1977
0
        codebook[i] = sum / static_cast<double>( count );
1978
0
      }
1979
0
      else
1980
0
      {
1981
0
        sum   = 0.0;
1982
0
        count = 0;
1983
0
        if ( i == 0 )
1984
0
        {
1985
0
          for ( int j = 0; j < tmpVec.size(); j++ )
1986
0
          {
1987
0
            if ( scalingVec[j] <= partition[i] )
1988
0
            {
1989
0
              count++;
1990
0
              sum += scalingVec[j];
1991
0
            }
1992
0
          }
1993
0
          if ( count )
1994
0
          {
1995
0
            codebook[i] = sum / static_cast<double>( count );
1996
0
          }
1997
0
          else
1998
0
          {
1999
0
            codebook[i] = ( partition[i] + ymin ) / 2;
2000
0
          }
2001
0
        }
2002
0
        else if ( i == QUANT_LEVELS - 1 )
2003
0
        {
2004
0
          for ( int j = 0; j < tmpVec.size(); j++ )
2005
0
          {
2006
0
            if (scalingVec[j] >= partition[i - 1])
2007
0
            {
2008
0
              count++;
2009
0
              sum += scalingVec[j];
2010
0
            }
2011
0
          }
2012
0
          if ( count )
2013
0
          {
2014
0
            codebook[i] = sum / static_cast<double>( count );
2015
0
          }
2016
0
          else
2017
0
          {
2018
0
            codebook[i] = ( partition[i - 1] + ymax ) / 2;
2019
0
          }
2020
0
        }
2021
0
        else
2022
0
        {
2023
0
          for ( int j = 0; j < tmpVec.size(); j++ )
2024
0
          {
2025
0
            if ( scalingVec[j] >= partition[i - 1] && scalingVec[j] <= partition[i] )
2026
0
            {
2027
0
              count++;
2028
0
              sum += scalingVec[j];
2029
0
            }
2030
0
          }
2031
0
          if ( count )
2032
0
          {
2033
0
            codebook[i] = sum / static_cast<double>( count );
2034
0
          }
2035
0
          else
2036
0
          {
2037
0
            codebook[i] = ( partition[i - 1] + partition[i] ) / 2;
2038
0
          }
2039
0
        }
2040
0
      }
2041
0
    }
2042
2043
    // compute and sort partition
2044
0
    for ( int i = 0; i < QUANT_LEVELS - 1; i++ )
2045
0
    {
2046
0
      partition[i] = ( codebook[i] + codebook[i + 1] ) / 2;
2047
0
    }
2048
0
    std::sort( partition, partition + QUANT_LEVELS - 1 );
2049
2050
    // final quantization - testing condition
2051
0
    last_distor = distortion;
2052
0
    quantize ( tmpVec,
2053
0
               distortion,
2054
0
               partition,
2055
0
               codebook );
2056
2057
0
    if ( distortion > tolerance2 )
2058
0
    {
2059
0
      rel_distor = std::fabs( distortion - last_distor ) / distortion;
2060
0
    }
2061
0
    else
2062
0
    {
2063
0
      rel_distor = distortion;
2064
0
    }
2065
0
  }
2066
2067
  // fill the final quantized vector
2068
0
  int maxVal = ( 1 << bitDepth ) - 1;  // Full range max value for given bit depth
2069
0
  quantVec.resize( static_cast<int>( 1 << bitDepth ), 0 );
2070
0
  for ( int i = 0; i < tmpVec.size(); i++ )
2071
0
  {
2072
0
    quantVec[i + xmin] = Clip3( 0, 
2073
0
                                maxVal,                                    
2074
0
                                static_cast<int>( tmpVec[i] + 0.5 ) );
2075
0
  }
2076
2077
0
  return true;
2078
0
}
2079
2080
void FGAnalyzer::quantize ( std::vector<double>& quantizedVec,
2081
                            double& distortion,
2082
                            double partition[],
2083
                            double codebook[] )
2084
0
{
2085
  // Reset previous quantizedVec to 0 and distortion to 0
2086
0
  std::fill(quantizedVec.begin(), quantizedVec.end(), 0.0);
2087
0
  distortion = 0.0;
2088
2089
  // Quantize input vector
2090
0
  for ( int i = 0; i < scalingVec.size(); i++ )
2091
0
  {
2092
0
    double quantizedValue = 0.0;
2093
0
    for ( int j = 0; j < QUANT_LEVELS - 1; j++ )
2094
0
    {
2095
0
      quantizedValue += ( scalingVec[i] > partition[j] );
2096
0
    }
2097
0
    quantizedVec[i] = codebook[static_cast<int>( quantizedValue )];
2098
0
  }
2099
2100
  // Compute distortion (MSE)
2101
0
  for ( int i = 0; i < scalingVec.size(); i++ )
2102
0
  {
2103
0
    double error = scalingVec[i] - quantizedVec[i];
2104
0
    distortion += ( error * error );
2105
0
  }
2106
0
  distortion /= scalingVec.size();
2107
0
}
2108
2109
// Set correctlly SEI parameters based on the quantized curve
2110
void FGAnalyzer::setEstimatedParameters ( uint32_t bitDepth,
2111
                                          ComponentID compId )
2112
0
{
2113
  // calculate intervals and scaling factors
2114
0
  defineIntervalsAndScalings ( bitDepth );
2115
2116
  // Merge small intervals with left or right interval
2117
0
  for ( size_t i = 0; i < finalIntervalsandScalingFactors.size(); ++i )
2118
0
  {
2119
0
    int tmp1 = finalIntervalsandScalingFactors[i][1] - finalIntervalsandScalingFactors[i][0];
2120
2121
0
    if ( tmp1 < ( 2 << ( bitDepth - BIT_DEPTH_8 ) ) )
2122
0
    {
2123
0
      int diffRight = ( i == finalIntervalsandScalingFactors.size() - 1 ) || ( finalIntervalsandScalingFactors[i + 1][2] == 0 )
2124
0
          ? std::numeric_limits<int>::max()
2125
0
          : abs( finalIntervalsandScalingFactors[i][2] - finalIntervalsandScalingFactors[i + 1][2] );
2126
0
      int diffLeft = ( i == 0 ) || ( finalIntervalsandScalingFactors[i - 1][2] == 0 )
2127
0
          ? std::numeric_limits<int>::max()
2128
0
          : abs( finalIntervalsandScalingFactors[i][2] - finalIntervalsandScalingFactors[i - 1][2] );
2129
2130
0
      if ( diffLeft < diffRight )
2131
0
      {
2132
0
        int tmp2 = finalIntervalsandScalingFactors[i - 1][1] - finalIntervalsandScalingFactors[i - 1][0];
2133
0
        int newScale = ( tmp2 * finalIntervalsandScalingFactors[i - 1][2] + tmp1 * finalIntervalsandScalingFactors[i][2] ) / ( tmp2 + tmp1 );
2134
2135
0
        finalIntervalsandScalingFactors[i - 1][1] = finalIntervalsandScalingFactors[i][1];
2136
0
        finalIntervalsandScalingFactors[i - 1][2] = newScale;
2137
0
        finalIntervalsandScalingFactors.erase( finalIntervalsandScalingFactors.begin() + i );
2138
0
        --i;
2139
0
      }
2140
0
      else
2141
0
      {
2142
0
        int tmp2 = finalIntervalsandScalingFactors[i + 1][1] - finalIntervalsandScalingFactors[i + 1][0];
2143
0
        int newScale = ( tmp2 * finalIntervalsandScalingFactors[i + 1][2] + tmp1 * finalIntervalsandScalingFactors[i][2] ) / ( tmp2 + tmp1 );
2144
2145
0
        finalIntervalsandScalingFactors[i][1] = finalIntervalsandScalingFactors[i + 1][1];
2146
0
        finalIntervalsandScalingFactors[i][2] = newScale;
2147
0
        finalIntervalsandScalingFactors.erase( finalIntervalsandScalingFactors.begin() + i + 1 );
2148
0
        --i;
2149
0
      }
2150
0
    }
2151
0
  }
2152
2153
  // scale to 8-bit range as supported by current sei and rdd5
2154
0
  scaleDown ( bitDepth );
2155
2156
  // because of scaling in previous step, some intervals may overlap. Check intervals for errors.
2157
0
  confirmIntervals ( );
2158
2159
  // Set number of intervals; exclude intervals with scaling factor 0.
2160
0
  m_compModel[compId].numIntensityIntervals =
2161
0
      static_cast<uint8_t>( finalIntervalsandScalingFactors.size() - std::count_if ( finalIntervalsandScalingFactors.begin(),
2162
0
                                                                                     finalIntervalsandScalingFactors.end(),
2163
0
                                                                                     []( const std::array<int, 3>& interval )
2164
0
                                                                                     {
2165
0
                                                                                       return interval[2] == 0;
2166
0
                                                                                     }
2167
0
                                                                                   ) );
2168
2169
  // check if all intervals are 0, and if yes set presentFlag to false
2170
0
  if ( m_compModel[compId].numIntensityIntervals == 0 )
2171
0
  { 
2172
0
    m_compModel[compId].presentFlag = false;
2173
0
    return;
2174
0
  }
2175
2176
  // Set final interval boundaries and scaling factors.
2177
  // Check if some interval has scaling factor 0, and do not encode them within SEI.
2178
0
  int j = 0;
2179
0
  for ( const auto& interval : finalIntervalsandScalingFactors )
2180
0
  {
2181
0
    if ( interval[2] != 0 )
2182
0
    {
2183
0
      m_compModel[compId].intensityValues[j].intensityIntervalLowerBound = interval[0];
2184
0
      m_compModel[compId].intensityValues[j].intensityIntervalUpperBound = interval[1];
2185
0
      m_compModel[compId].intensityValues[j].compModelValue[0] = interval[2];
2186
0
      m_compModel[compId].intensityValues[j].compModelValue[1] = m_compModel[compId].intensityValues[0].compModelValue[1];
2187
0
      m_compModel[compId].intensityValues[j].compModelValue[2] = m_compModel[compId].intensityValues[0].compModelValue[2];
2188
0
      ++j;
2189
0
    }
2190
0
  }
2191
0
  CHECK( j != m_compModel[compId].numIntensityIntervals, "Check film grain intensity levels" );
2192
0
}
2193
2194
long double FGAnalyzer::ldpow ( long double n,
2195
                                unsigned p )
2196
0
{
2197
0
  long double result = 1.0;
2198
2199
  // Handle special cases for p = 0 and p = 1
2200
0
  if ( p == 0 ) return 1.0;
2201
0
  if ( p == 1 ) return n;
2202
2203
  // Exponentiation by squaring
2204
0
  while ( p > 0 )
2205
0
  {
2206
0
    if ( p % 2 == 1 )
2207
0
      result *= n;
2208
0
    n *= n;
2209
0
    p /= 2;
2210
0
  }
2211
0
  return result;
2212
0
}
2213
2214
// find bounds of intensity intervals and scaling factors for each interval
2215
void FGAnalyzer::defineIntervalsAndScalings ( int bitDepth )
2216
0
{
2217
0
  finalIntervalsandScalingFactors.clear();
2218
0
  std::array<int, 3> interval = { 0, 0, quantVec[0] };
2219
2220
0
  for ( int i = 0; i < (1 << bitDepth) - 1; ++i )
2221
0
  {
2222
0
    if ( quantVec[i] != quantVec[i + 1] )
2223
0
    {
2224
0
      interval[1] = i;
2225
0
      finalIntervalsandScalingFactors.push_back ( interval );
2226
0
      interval[0] = i + 1;
2227
0
      interval[2] = quantVec[i + 1];
2228
0
    }
2229
0
  }
2230
0
  interval[1] = ( 1 << bitDepth ) - 1;
2231
0
  finalIntervalsandScalingFactors.push_back ( interval );
2232
0
}
2233
2234
// scale everything to 8-bit ranges as supported by SEI message
2235
void FGAnalyzer::scaleDown ( int bitDepth )
2236
0
{
2237
0
  for ( auto& interval : finalIntervalsandScalingFactors )
2238
0
  {
2239
0
    interval[0] >>= ( bitDepth - BIT_DEPTH_8 );
2240
0
    interval[1] >>= ( bitDepth - BIT_DEPTH_8 );
2241
0
    interval[2] <<= m_log2ScaleFactor;
2242
0
    interval[2] >>= ( bitDepth - BIT_DEPTH_8 );
2243
0
  }
2244
0
}
2245
2246
// check if intervals are properly set after scaling to 8-bit representation
2247
void FGAnalyzer::confirmIntervals ( )
2248
0
{
2249
0
  for ( size_t i = 0; i < finalIntervalsandScalingFactors.size() - 1; ++i )
2250
0
  {
2251
0
    if ( finalIntervalsandScalingFactors[i][1] >= finalIntervalsandScalingFactors[i + 1][0] )
2252
0
    {
2253
0
      finalIntervalsandScalingFactors[i][1] = finalIntervalsandScalingFactors[i + 1][0] - 1;
2254
0
    }
2255
0
  }
2256
0
}
2257
2258
void FGAnalyzer::extendPoints ( int bitDepth )
2259
0
{
2260
0
  int xmin = tmp_data_x[0];
2261
0
  int xmax = tmp_data_x[0];
2262
0
  int ymin = tmp_data_y[0];
2263
0
  int ymax = tmp_data_y[0];
2264
0
  for ( int i = 0; i < tmp_data_x.size(); i++ )
2265
0
  {
2266
0
    if ( tmp_data_x[i] < xmin )
2267
0
    {
2268
0
      xmin = tmp_data_x[i];
2269
0
      ymin = tmp_data_y[i];   // not real ymin
2270
0
    }
2271
0
    if ( tmp_data_x[i] > xmax )
2272
0
    {
2273
0
      xmax = tmp_data_x[i];
2274
0
      ymax = tmp_data_y[i];   // not real ymax
2275
0
    }
2276
0
  }
2277
2278
  // extend points to the left
2279
0
  int    step = POINT_STEP;
2280
0
  double scale = POINT_SCALE;
2281
0
  int num_extra_point_left = MAX_NUM_POINT_TO_EXTEND;
2282
0
  int num_extra_point_right = MAX_NUM_POINT_TO_EXTEND;
2283
0
  while ( xmin >= step && ymin > 1 && num_extra_point_left > 0 )
2284
0
  {
2285
0
    xmin -= step;
2286
0
    ymin = static_cast<int>( ymin / scale );
2287
0
    tmp_data_x.push_back( xmin );
2288
0
    tmp_data_y.push_back( ymin );
2289
0
    num_extra_point_left--;
2290
0
  }
2291
2292
  // extend points to the right
2293
0
  while ( xmax + step <= ((1 << bitDepth) - 1) && ymax > 1 && num_extra_point_right > 0 )
2294
0
  {
2295
0
    xmax += step;
2296
0
    ymax = static_cast<int>( ymax / scale );
2297
0
    tmp_data_x.push_back( xmax );
2298
0
    tmp_data_y.push_back( ymax );
2299
0
    num_extra_point_right--;
2300
0
  }
2301
2302
  // filter out points outside the range
2303
0
  auto isValid = []( int x )
2304
0
  {
2305
0
    return x >= MIN_INTENSITY && x <= MAX_INTENSITY;
2306
0
  };
2307
2308
0
  std::vector<int> valid_x, valid_y;
2309
0
  for ( int i = 0; i < tmp_data_x.size(); i++ )
2310
0
  {
2311
0
    if ( isValid( tmp_data_x[i] ) )
2312
0
    {
2313
0
      valid_x.push_back( tmp_data_x[i] );
2314
0
      valid_y.push_back( tmp_data_y[i] );
2315
0
    }
2316
0
  }
2317
0
  tmp_data_x = std::move( valid_x );
2318
0
  tmp_data_y = std::move( valid_y );
2319
0
}
2320