/** \file     EncCNNFilter.cpp
    \brief    encoder convolutional neural network-based filter class
*/

#include "EncCNNFilter.h"
#if CNN_FILTERING
#include "UnitTools.h"
#include "UnitPartitioner.h"
#include "CodingStructure.h"
#include "CommonLib/dtrace_codingstruct.h"
#include "CommonLib/dtrace_buffer.h"
#include <chrono>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <ctime>
#include <algorithm>
#include <numeric>
//! \ingroup CommonLib
//! \{

#define CNNLFCtx(c) SubCtx( Ctx::cnnlfParamIdx, c )

EncCNNFilter::EncCNNFilter()
{
  m_CABACEstimator = NULL;
  m_singleModelISlice = false;
}

EncCNNFilter::~EncCNNFilter()
{
}
void EncCNNFilter::initCABACEstimator( CABACEncoder* cabacEncoder, CtxCache* ctxCache, Slice* pcSlice )
{
  m_CABACEstimator = cabacEncoder->getCABACEstimator( pcSlice->getSPS() );
  m_CtxCache       = ctxCache;
  m_CABACEstimator->initCtxModels( *pcSlice );
  m_CABACEstimator->resetBits();
}

double getDistortion(PelBuf buf1, PelBuf buf2, int width, int height)
{
  Pel* p1 = buf1.buf;
  int stride1 = buf1.stride;
  Pel* p2 = buf2.buf;
  int stride2 = buf2.stride;
  double dist = 0;
  for (int y = 0; y < height; y++)
  {
    for (int x = 0; x < width; x++)
    {
      int diff = p1[y * stride1 + x] - p2[y * stride2 + x];
      dist += diff * diff;
    }
  }
  return dist;
}
void setCnnlfInferGranularity(Picture* pic)
{
  Slice* pcSlice                  = pic->cs->slice;
  CnnlfInferGranularity cnnlfInferGranularityLuma = CNNLF_INFER_GRANULARITY_BASE;
  CnnlfInferGranularity cnnlfInferGranularityChroma = CNNLF_INFER_GRANULARITY_BASE;
  if (pcSlice ->getSliceType() == I_SLICE)
  {
    pcSlice->setCnnlfInferGranularity(CHANNEL_TYPE_LUMA, CNNLF_INFER_GRANULARITY_LARGE);
    pcSlice->setCnnlfInferGranularity(CHANNEL_TYPE_CHROMA, CNNLF_INFER_GRANULARITY_LARGE);
    return;
  }
  if (pcSlice->getSliceQp() < 23)
  {
    cnnlfInferGranularityLuma = CNNLF_INFER_GRANULARITY_BASE;
    cnnlfInferGranularityChroma = CNNLF_INFER_GRANULARITY_BASE;
  }
  else if (pcSlice->getSliceQp() < 29)
  {
    cnnlfInferGranularityLuma = CNNLF_INFER_GRANULARITY_BASE;
    cnnlfInferGranularityChroma = CNNLF_INFER_GRANULARITY_LARGE;
  }
  else
  {
    cnnlfInferGranularityLuma = pic->getPicWidthInLumaSamples() <=832 ? CNNLF_INFER_GRANULARITY_BASE : CNNLF_INFER_GRANULARITY_LARGE;
    cnnlfInferGranularityChroma = CNNLF_INFER_GRANULARITY_LARGE;
  }
  pcSlice->setCnnlfInferGranularity(CHANNEL_TYPE_LUMA, cnnlfInferGranularityLuma);
  pcSlice->setCnnlfInferGranularity(CHANNEL_TYPE_CHROMA, cnnlfInferGranularityChroma);
}

void EncCNNFilter::cnnFilterPicture(Picture* pic, int numParams)
{
  CodingStructure&  cs            = *pic->cs;
  Slice* pcSlice                  = cs.slice;
  const PreCalcValues& pcv        = *cs.pcv;
  int extension = cs.sps->getCnnlfInferSizeExtension();
  const int  numValidChannels = getNumberValidChannels( cs.area.chromaFormat );
  for (int paramIdx = 0; paramIdx < numParams; paramIdx ++)
  {
    for( int chal = 0; chal < numValidChannels; chal++ )
    {
      const ChannelType chType = ChannelType( chal );
      CnnlfInferGranularity cnnlfInferGranularity = pcSlice->getCnnlfInferGranularity(chType);
      int blockSize = cs.sps->getCnnlfInferSize (cnnlfInferGranularity);
      for( int blockIdx = 0; blockIdx < pcv.sizeInCnnlfInferSize[cnnlfInferGranularity]; blockIdx++ )
      {
        int xPosInBlocks        = blockIdx % pcv.widthInCnnlfInferSize[cnnlfInferGranularity];
        int yPosInBlocks        = blockIdx / pcv.widthInCnnlfInferSize[cnnlfInferGranularity];
        int xPos = xPosInBlocks * blockSize;
        int yPos = yPosInBlocks * blockSize;
        int width = (xPos + blockSize > pcv.lumaWidth) ? (pcv.lumaWidth - xPos) : blockSize;
        int height = (yPos + blockSize > pcv.lumaHeight) ? (pcv.lumaHeight - yPos) : blockSize;
        
        int extLeft = xPos > 0 ? extension : 0;
        int extRight = (xPos + width + extension > pcv.lumaWidth) ? (pcv.lumaWidth - xPos - width) : extension;
        int extTop = yPos > 0 ? extension : 0;
        int extBottom = (yPos + height + extension > pcv.lumaHeight) ? (pcv.lumaHeight - yPos - height) : extension;
        
        int extXPos = xPos - extLeft;
        int extYPos = yPos - extTop;
        int extWidth = width + extLeft + extRight;
        int extHeight =  height + extTop + extBottom;
        
        const UnitArea extBlock( cs.area.chromaFormat, Area( extXPos, extYPos, extWidth, extHeight ) );

        if (chal == 0)
        {
#if NN_FIXED_POINT_IMPLEMENTATION
          cnnFilterLumaBlock<int16_t>(pic, extBlock, extLeft, extRight, extTop, extBottom, paramIdx, pcSlice->getSliceType() != I_SLICE);
#else
          cnnFilterLumaBlock<float>(pic, extBlock, extLeft, extRight, extTop, extBottom, paramIdx, pcSlice->getSliceType() != I_SLICE);
#endif
        }
        else
        {
#if NN_FIXED_POINT_IMPLEMENTATION
          cnnFilterChromaBlock<int16_t>(pic, extBlock, extLeft >> 1, extRight >> 1, extTop >> 1, extBottom >> 1, paramIdx, pcSlice->getSliceType() != I_SLICE);
#else
          cnnFilterChromaBlock<float>(pic, extBlock, extLeft >> 1, extRight >> 1, extTop >> 1, extBottom >> 1, paramIdx, pcSlice->getSliceType() != I_SLICE);
#endif
        }
      }
    }
  }
}
#if SCALE_NN_RESIDUE
void EncCNNFilter::calcRDCost(Picture *pic, std::vector<PelStorage>& tempBuf, int numParams, double* minCost, bool scaled)
#else
void EncCNNFilter::calcRDCost(Picture *pic, std::vector<PelStorage>& tempBuf, int numParams, double* minCost)
#endif
{
  CodingStructure&  cs            = *pic->cs;
  Slice* pcSlice                  = cs.slice;
  const PreCalcValues& pcv        = *cs.pcv;
  const int  numValidChannels = getNumberValidChannels( cs.area.chromaFormat );
  for( int chal = 0; chal < numValidChannels; chal++ )
  {
    const ChannelType chType = ChannelType( chal );
    CnnlfInferGranularity cnnlfInferGranularity = pcSlice->getCnnlfInferGranularity(chType);
    int blockSize = cs.sps->getCnnlfInferSize (cnnlfInferGranularity);
    std::vector<uint8_t>* cnnlfParamIdx = pic->getCnnlfParamIdx(cnnlfInferGranularity);
    
    int64_t distSliceLevelOff = 0, distCtuLevelOn = 0, distSliceLevelOn[numParams];
    for (int paramIdx = 0; paramIdx < numParams; paramIdx ++)
    {
      distSliceLevelOn[paramIdx] = 0;
    }
    
    // calculate distortion
    for( int blockIdx = 0; blockIdx < pcv.sizeInCnnlfInferSize[cnnlfInferGranularity]; blockIdx++ )
    {
      int xPosInBlocks        = blockIdx % pcv.widthInCnnlfInferSize[cnnlfInferGranularity];
      int yPosInBlocks        = blockIdx / pcv.widthInCnnlfInferSize[cnnlfInferGranularity];
      int xPos = xPosInBlocks * blockSize;
      int yPos = yPosInBlocks * blockSize;
      int width = (xPos + blockSize > pcv.lumaWidth) ? (pcv.lumaWidth - xPos) : blockSize;
      int height = (yPos + blockSize > pcv.lumaHeight) ? (pcv.lumaHeight - yPos) : blockSize;
      
      const UnitArea block( cs.area.chromaFormat, Area( xPos, yPos, width, height ) );

      // block distortion w/o cnnlf
      double distRec = chal == 0 ? getDistortion(pic->getOrigBuf(block).get(COMPONENT_Y), pic->getRecoBuf(block).get(COMPONENT_Y), width, height) : getDistortion(pic->getOrigBuf(block).get(COMPONENT_Cb), pic->getRecoBuf(block).get(COMPONENT_Cb), width >> 1, height >> 1) + getDistortion(pic->getOrigBuf(block).get(COMPONENT_Cr), pic->getRecoBuf(block).get(COMPONENT_Cr), width >> 1, height >> 1);
      
      // slice distortion w/o cnnlf
      distSliceLevelOff += distRec;
      
      double distBest = distRec;
      int paramIdxBest = -1;
      for (int paramIdx = 0; paramIdx < numParams; paramIdx ++)
      {
        // block distortion w/ cnnlf paramIdx
        double distTemp = chal == 0 ? getDistortion(pic->getOrigBuf(block).get(COMPONENT_Y), tempBuf[paramIdx].getBuf(block).get(COMPONENT_Y), width, height) : getDistortion(pic->getOrigBuf(block).get(COMPONENT_Cb), tempBuf[paramIdx].getBuf(block).get(COMPONENT_Cb), width >> 1, height >> 1) + getDistortion(pic->getOrigBuf(block).get(COMPONENT_Cr), tempBuf[paramIdx].getBuf(block).get(COMPONENT_Cr), width >> 1, height >> 1);
        
        // slice distortion w/ cnnlf paramIdx
        distSliceLevelOn[paramIdx] += distTemp;
        
        // the best for the block
        paramIdxBest = distTemp < distBest ? paramIdx : paramIdxBest;
        distBest = distTemp < distBest ? distTemp : distBest;
      }
      
      // the best for the slice
      distCtuLevelOn += distBest;
      cnnlfParamIdx[chal][blockIdx] = paramIdxBest + 1;
    }
    
    // calculate RD cost
    const TempCtx  ctxStart ( m_CtxCache, CNNLFCtx( m_CABACEstimator->getCtx() ) );
    m_CABACEstimator->getCtx() = CNNLFCtx( ctxStart );
    m_CABACEstimator->resetBits();
    m_CABACEstimator->codeCnnlfParamIdx(cs, chType);
    double rate = FRAC_BITS_SCALE * m_CABACEstimator->getEstFracBits();
    double costSliceLevelOff = (double)distSliceLevelOff;
    double costCtuLevelOn, costSliceLevelOn[numParams];
#if SCALE_NN_RESIDUE
    if (scaled)
    {
      costCtuLevelOn         = (double) distCtuLevelOn + m_lambda[chal] * (rate + 3* (NN_RESIDUE_SCALE_SHIFT + 1));
      for (int paramIdx = 0; paramIdx < numParams; paramIdx ++)
      {
        costSliceLevelOn[paramIdx] = (double) distSliceLevelOn[paramIdx] + m_lambda[chal] * (NN_RESIDUE_SCALE_SHIFT + 1);
      }
    }
    else
#endif
    {
      costCtuLevelOn = (double)distCtuLevelOn + m_lambda[chal]*rate;
      for (int paramIdx = 0; paramIdx < numParams; paramIdx ++)
      {
        costSliceLevelOn[paramIdx] = (double)distSliceLevelOn[paramIdx];
      }
    }
    
    // find the best cost
    double costBest;
    if (costSliceLevelOff < costCtuLevelOn)
    {
      costBest = costSliceLevelOff;
      pcSlice->setCnnlfMode(chType, 0);
    }
    else
    {
      costBest = costCtuLevelOn;
      pcSlice->setCnnlfMode(chType, numParams + 1);
    }
    
    for (int paramIdx = 0; paramIdx < numParams; paramIdx ++)
    {
      if (costSliceLevelOn[paramIdx] < costBest)
      {
        costBest = costSliceLevelOn[paramIdx];
        pcSlice->setCnnlfMode(chType, paramIdx + 1);
      }
    }
    
    minCost[chal] = costBest;
  }
}
void EncCNNFilter::cnnFilterEncoder(Picture *pic, const double *lambdas)
{
  CodingStructure&  cs            = *pic->cs;
  Slice* pcSlice                  = cs.slice;
  const PreCalcValues& pcv        = *cs.pcv;
  memcpy(m_lambda, lambdas, sizeof(m_lambda));
  const int  numValidComponents = getNumberValidComponents( cs.area.chromaFormat );
  const int  numValidChannels = getNumberValidChannels( cs.area.chromaFormat );
  
  setCnnlfInferGranularity(pic);
  
  int numParams = cs.sps->getCnnlfMaxNumParams();
  
  cnnFilterPicture(pic, numParams);

  double minCost[2]       = {MAX_DOUBLE, MAX_DOUBLE};
  
#if SCALE_NN_RESIDUE
  calcRDCost(pic, m_tempBuf, numParams, minCost, false);
  scalePicture(pic, numParams);
  double minScaledCost[2] = {MAX_DOUBLE, MAX_DOUBLE};
  double sliceCnnlfMode[2];
  for( int chal = 0; chal < numValidChannels; chal++ )
  {
    const ChannelType chType = ChannelType( chal );
    CnnlfInferGranularity cnnlfInferGranularity = pcSlice->getCnnlfInferGranularity(chType);
    std::vector<uint8_t> *cnnlfParamIdx = pic->getCnnlfParamIdx(cnnlfInferGranularity);
    std::vector<uint8_t> *cnnlfBackupParamIdx = pic->getCnnlfBackupParamIdx(cnnlfInferGranularity);
    for (int blockIdx = 0; blockIdx < pcv.sizeInCnnlfInferSize[cnnlfInferGranularity]; blockIdx++)
    {
      cnnlfBackupParamIdx[chal][blockIdx] = cnnlfParamIdx[chal][blockIdx];
    }
    sliceCnnlfMode[chal] = pcSlice->getCnnlfMode(chType);
  }
  calcRDCost(pic, m_tempScaledBuf, numParams, minScaledCost, true);
  for (int chal = 0; chal < numValidChannels; chal++)
  {
    const ChannelType chType = ChannelType( chal );
    CnnlfInferGranularity cnnlfInferGranularity = pcSlice->getCnnlfInferGranularity(chType);
    if (minCost[chal] <= minScaledCost[chal])
    {
      std::vector<uint8_t> *cnnlfParamIdx       = pic->getCnnlfParamIdx(cnnlfInferGranularity);
      std::vector<uint8_t> *cnnlfBackupParamIdx = pic->getCnnlfBackupParamIdx(cnnlfInferGranularity);
      for (int blockIdx = 0; blockIdx < pcv.sizeInCnnlfInferSize[cnnlfInferGranularity]; blockIdx++)
      {
        cnnlfParamIdx[chal][blockIdx] = cnnlfBackupParamIdx[chal][blockIdx];
      }
      pcSlice->setCnnlfMode(chType, sliceCnnlfMode[chal]);
      for (int paramIdx = 0; paramIdx < numParams; paramIdx ++)
      {
        pcSlice->setNnScaleFlag(false, paramIdx, chType);
      }
    }
    else
    {
      for (int paramIdx = 0; paramIdx < numParams; paramIdx ++)
      {
        pcSlice->setNnScaleFlag(true, paramIdx, chType);
      }
    }
  }
#else
  calcRDCost(pic, m_tempBuf, numParams, minCost);
#endif

  for( int comp = 0; comp < numValidComponents; comp++ )
  {
    const ComponentID compID = ComponentID( comp );
    int chal = toChannelType(compID);
    CnnlfInferGranularity cnnlfInferGranularity = pcSlice->getCnnlfInferGranularity(toChannelType(compID));
    int blockSize = cs.sps->getCnnlfInferSize (cnnlfInferGranularity);
    int sliceCnnlfMode = pcSlice->getCnnlfMode(toChannelType(compID));

    for( int blockIdx = 0; blockIdx < pcv.sizeInCnnlfInferSize[cnnlfInferGranularity]; blockIdx++ )
    {
      int xPosInBlocks        = blockIdx % pcv.widthInCnnlfInferSize[cnnlfInferGranularity];
      int yPosInBlocks        = blockIdx / pcv.widthInCnnlfInferSize[cnnlfInferGranularity];
      int xPos = xPosInBlocks * blockSize;
      int yPos = yPosInBlocks * blockSize;
      int width = (xPos + blockSize > pcv.lumaWidth) ? (pcv.lumaWidth - xPos) : blockSize;
      int height = (yPos + blockSize > pcv.lumaHeight) ? (pcv.lumaHeight - yPos) : blockSize;
      
      const UnitArea block( cs.area.chromaFormat, Area( xPos, yPos, width, height ) );
      
      std::vector<uint8_t>* cnnlfParamIdx = pic->getCnnlfParamIdx(cnnlfInferGranularity);
      if (sliceCnnlfMode < (numParams + 1))
        cnnlfParamIdx[chal][blockIdx] = sliceCnnlfMode;
      if (cnnlfParamIdx[chal][blockIdx])
      {
#if SCALE_NN_RESIDUE
        if (pcSlice->getNnScaleFlag(cnnlfParamIdx[chal][blockIdx]-1, toChannelType(compID)))
          pic->getRecoBuf(block).get(compID).copyFrom(m_tempScaledBuf[cnnlfParamIdx[chal][blockIdx] - 1].getBuf(block).get(compID));
        else
#endif
          pic->getRecoBuf(block).get(compID).copyFrom(m_tempBuf[cnnlfParamIdx[chal][blockIdx] - 1].getBuf(block).get(compID));
      }
    }
  }
}


#if SCALE_NN_RESIDUE
#define NN_SCALE_STABLIZING_FACTOR (0.1 * (1 << NN_RESIDUE_ADDITIONAL_SHIFT))
void EncCNNFilter::scaleFactorDerivation(Picture *pic, int paramIdx)
{
  CodingStructure &cs      = *pic->cs;
  Slice *          pcSlice = cs.slice;

  PelUnitBuf recoBuf = pic->getRecoBuf();
  PelUnitBuf origBuf = pic->getOrigBuf();
  PelUnitBuf filteredBuf = m_tempScaledBuf[paramIdx];

  PelBuf origBufY        = origBuf.get(COMPONENT_Y);
  PelBuf origBufCb       = origBuf.get(COMPONENT_Cb);
  PelBuf origBufCr       = origBuf.get(COMPONENT_Cr);
  PelBuf reconBufY       = recoBuf.get(COMPONENT_Y);
  PelBuf reconBufCb      = recoBuf.get(COMPONENT_Cb);
  PelBuf reconBufCr      = recoBuf.get(COMPONENT_Cr);
  PelBuf nnFilteredBufY  = filteredBuf.get(COMPONENT_Y);
  PelBuf nnFilteredBufCb = filteredBuf.get(COMPONENT_Cb);
  PelBuf nnFilteredBufCr = filteredBuf.get(COMPONENT_Cr);

  int pic_w  = reconBufY.width;
  int pic_h  = reconBufY.height;
  int pic_wh = pic_w * pic_h;

  int scaleY, scaleC;
  int shiftY  = NN_RESIDUE_SCALE_SHIFT;
  int shiftC = NN_RESIDUE_SCALE_SHIFT;

  int scaleUpBoundY  = int(NN_RESIDUE_SCALE_DEVIATION_UP_BOUND * (1 << shiftY));
  int scaleLowBoundY = int(NN_RESIDUE_SCALE_DEVIATION_BOT_BOUND * (1 << shiftY));

  int scaleUpBoundC  = int(NN_RESIDUE_SCALE_DEVIATION_UP_BOUND * (1 << shiftC));
  int scaleLowBoundC = int(NN_RESIDUE_SCALE_DEVIATION_BOT_BOUND * (1 << shiftC));

  double selfMulti[MAX_NUM_CHANNEL_TYPE]  = { 0., 0.};
  double crossMulti[MAX_NUM_CHANNEL_TYPE] = { 0., 0.};
  double sumOriResi[MAX_NUM_CHANNEL_TYPE] = { 0., 0.};
  double sumNnResi[MAX_NUM_CHANNEL_TYPE]  = { 0., 0.};

  for (int y = 0; y < pic_h; y++)
  {
    for (int x = 0; x < pic_w; x++)
    {
        int oriResi = (origBufY.at(x, y) - reconBufY.at(x, y)) << NN_RESIDUE_ADDITIONAL_SHIFT;
        int nnResi  = nnFilteredBufY.at(x, y) - (reconBufY.at(x, y) << NN_RESIDUE_ADDITIONAL_SHIFT);

        selfMulti[CHANNEL_TYPE_LUMA] += nnResi * nnResi;
        crossMulti[CHANNEL_TYPE_LUMA] += nnResi * oriResi;
        sumOriResi[CHANNEL_TYPE_LUMA] += oriResi;
        sumNnResi[CHANNEL_TYPE_LUMA] += nnResi;
    }
  }

  scaleY = int(((pic_wh * crossMulti[CHANNEL_TYPE_LUMA] - sumOriResi[CHANNEL_TYPE_LUMA] * sumNnResi[CHANNEL_TYPE_LUMA]
                 + pic_wh * pic_wh * NN_SCALE_STABLIZING_FACTOR)
                / (pic_wh * selfMulti[CHANNEL_TYPE_LUMA] - sumNnResi[CHANNEL_TYPE_LUMA] * sumNnResi[CHANNEL_TYPE_LUMA]
                   + pic_wh * pic_wh * NN_SCALE_STABLIZING_FACTOR))
                 * (1 << shiftY)
               + 0.5);

  if (scaleY > scaleUpBoundY)
  {
    scaleY = scaleUpBoundY;
  }
  if (scaleY < scaleLowBoundY)
  {
    scaleY = scaleLowBoundY;
  }

  for (int y = 0; y < pic_h / 2; y++)
  {
    for (int x = 0; x < pic_w / 2; x++)
    {

        int oriResiCb = (origBufCb.at(x, y) - reconBufCb.at(x, y)) << NN_RESIDUE_ADDITIONAL_SHIFT;
        int nnResiCb  = nnFilteredBufCb.at(x, y) - (reconBufCb.at(x, y) << NN_RESIDUE_ADDITIONAL_SHIFT);
      
        int oriResiCr = (origBufCr.at(x, y) - reconBufCr.at(x, y)) << NN_RESIDUE_ADDITIONAL_SHIFT;
        int nnResiCr  = nnFilteredBufCr.at(x, y) - (reconBufCr.at(x, y) << NN_RESIDUE_ADDITIONAL_SHIFT);

        selfMulti[CHANNEL_TYPE_CHROMA] += nnResiCb * nnResiCb + nnResiCr * nnResiCr;
        crossMulti[CHANNEL_TYPE_CHROMA] += nnResiCb * oriResiCb + nnResiCr * oriResiCr;
        sumOriResi[CHANNEL_TYPE_CHROMA] += oriResiCb + oriResiCr;
        sumNnResi[CHANNEL_TYPE_CHROMA] += nnResiCb + nnResiCr;
    }
  }

  scaleC = int(((pic_wh / 4 * 2 * crossMulti[CHANNEL_TYPE_CHROMA] - sumOriResi[CHANNEL_TYPE_CHROMA] * sumNnResi[CHANNEL_TYPE_CHROMA]
                  + pic_wh / 4 * 2 * pic_wh / 4 * 2 * NN_SCALE_STABLIZING_FACTOR)
                 / (pic_wh / 4 * 2 * selfMulti[CHANNEL_TYPE_CHROMA] - sumNnResi[CHANNEL_TYPE_CHROMA] * sumNnResi[CHANNEL_TYPE_CHROMA]
                    + pic_wh / 4 * 2 * pic_wh / 4 * 2 * CHANNEL_TYPE_CHROMA))
                  * (1 << shiftC)
                + 0.5);

  if (scaleC > scaleUpBoundC)
  {
    scaleC = scaleUpBoundC;
  }
  if (scaleC < scaleLowBoundC)
  {
    scaleC = scaleLowBoundC;
  }

  pcSlice->setNnScale(scaleY, paramIdx, CHANNEL_TYPE_LUMA);

  pcSlice->setNnScale(scaleC, paramIdx, CHANNEL_TYPE_CHROMA);

}

void EncCNNFilter::scalePicture(Picture* pic, int numParams)
{
  CodingStructure&  cs            = *pic->cs;
  Slice* pcSlice                  = cs.slice;
  const PreCalcValues& pcv        = *cs.pcv;
  const int  numValidComponents = getNumberValidComponents( cs.area.chromaFormat );
  for (int paramIdx = 0; paramIdx < numParams; paramIdx++)
  {
    scaleFactorDerivation(pic, paramIdx);
  }

  for (int paramIdx = 0; paramIdx < numParams; paramIdx++)
  {
    for (int comp = 0; comp < numValidComponents; comp++)
    {
      const ComponentID compID              = ComponentID(comp);
      CnnlfInferGranularity cnnlfInferGranularity = pcSlice->getCnnlfInferGranularity(toChannelType(compID));
      int blockSize = cs.sps->getCnnlfInferSize (cnnlfInferGranularity);
      for (int blockIdx = 0; blockIdx < pcv.sizeInCnnlfInferSize[cnnlfInferGranularity]; blockIdx++)
      {
        int xPosInBlocks = blockIdx % pcv.widthInCnnlfInferSize[cnnlfInferGranularity];
        int yPosInBlocks = blockIdx / pcv.widthInCnnlfInferSize[cnnlfInferGranularity];
        int xPos            = xPosInBlocks * blockSize;
        int yPos            = yPosInBlocks * blockSize;
        int width           = (xPos + blockSize > pcv.lumaWidth) ? (pcv.lumaWidth - xPos) : blockSize;
        int height          = (yPos + blockSize > pcv.lumaHeight) ? (pcv.lumaHeight - yPos) : blockSize;
        const UnitArea block( cs.area.chromaFormat, Area( xPos, yPos, width, height ) );
        scaleResidualBlock(pic, block, paramIdx, compID);
      }
    }

  }
}
#endif
#endif

//! \}
