/*M///////////////////////////////////////////////////////////////////////////////////////
 //
 //  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
 //
 //  By downloading, copying, installing or using the software you agree to this license.
 //  If you do not agree to this license, do not download, install,
 //  copy or use the software.
 //
 //
 //                           License Agreement
 //                For Open Source Computer Vision Library
 //
 // Copyright (C) 2013, OpenCV Foundation, all rights reserved.
 // Third party copyrights are property of their respective owners.
 //
 // Redistribution and use in source and binary forms, with or without modification,
 // are permitted provided that the following conditions are met:
 //
 //   * Redistribution's of source code must retain the above copyright notice,
 //     this list of conditions and the following disclaimer.
 //
 //   * Redistribution's in binary form must reproduce the above copyright notice,
 //     this list of conditions and the following disclaimer in the documentation
 //     and/or other materials provided with the distribution.
 //
 //   * The name of the copyright holders may not be used to endorse or promote products
 //     derived from this software without specific prior written permission.
 //
 // This software is provided by the copyright holders and contributors "as is" and
 // any express or implied warranties, including, but not limited to, the implied
 // warranties of merchantability and fitness for a particular purpose are disclaimed.
 // In no event shall the Intel Corporation or contributors be liable for any direct,
 // indirect, incidental, special, exemplary, or consequential damages
 // (including, but not limited to, procurement of substitute goods or services;
 // loss of use, data, or profits; or business interruption) however caused
 // and on any theory of liability, whether in contract, strict liability,
 // or tort (including negligence or otherwise) arising in any way out of
 // the use of this software, even if advised of the possibility of such damage.
 //
 //M*/

#include "precomp.hpp"
#include "opencv2/tracking/feature.hpp"

namespace cv
{

/*
 * TODO This implementation is based on apps/traincascade/
 * TODO Changed CvHaarEvaluator based on ADABOOSTING implementation (Grabner et al.)
 */

CvParams::CvParams() :
    name( "params" )
{
}
void CvParams::printDefaults() const
{
  std::cout << "--" << name << "--" << std::endl;
}
void CvParams::printAttrs() const
{
}
bool CvParams::scanAttr( const std::string, const std::string )
{
  return false;
}

//---------------------------- FeatureParams --------------------------------------

CvFeatureParams::CvFeatureParams() :
    maxCatCount( 0 ),
    featSize( 1 ),
    numFeatures( 1 )
{
  name = CC_FEATURE_PARAMS;
}

void CvFeatureParams::init( const CvFeatureParams& fp )
{
  maxCatCount = fp.maxCatCount;
  featSize = fp.featSize;
  numFeatures = fp.numFeatures;
}

void CvFeatureParams::write( FileStorage &fs ) const
{
  fs << CC_MAX_CAT_COUNT << maxCatCount;
  fs << CC_FEATURE_SIZE << featSize;
  fs << CC_NUM_FEATURES << numFeatures;
}

bool CvFeatureParams::read( const FileNode &node )
{
  if( node.empty() )
    return false;
  maxCatCount = node[CC_MAX_CAT_COUNT];
  featSize = node[CC_FEATURE_SIZE];
  numFeatures = node[CC_NUM_FEATURES];
  return ( maxCatCount >= 0 && featSize >= 1 );
}

Ptr<CvFeatureParams> CvFeatureParams::create( int featureType )
{
  return featureType == HAAR ? Ptr<CvFeatureParams>( new CvHaarFeatureParams ) : featureType == LBP ? Ptr<CvFeatureParams>( new CvLBPFeatureParams ) :
         featureType == HOG ? Ptr<CvFeatureParams>( new CvHOGFeatureParams ) : Ptr<CvFeatureParams>();
}

//------------------------------------- FeatureEvaluator ---------------------------------------

void CvFeatureEvaluator::init( const CvFeatureParams *_featureParams, int _maxSampleCount, Size _winSize )
{
  CV_Assert( _maxSampleCount > 0 );
  featureParams = (CvFeatureParams *) _featureParams;
  winSize = _winSize;
  numFeatures = _featureParams->numFeatures;
  cls.create( (int) _maxSampleCount, 1, CV_32FC1 );
  generateFeatures();
}

void CvFeatureEvaluator::setImage( const Mat &img, uchar clsLabel, int idx )
{
  winSize.width = img.cols;
  winSize.height = img.rows;
  //CV_Assert( img.cols == winSize.width );
  //CV_Assert( img.rows == winSize.height );
  CV_Assert( idx < cls.rows );
  cls.ptr<float>( idx )[0] = clsLabel;
}

Ptr<CvFeatureEvaluator> CvFeatureEvaluator::create( int type )
{
  return type == CvFeatureParams::HAAR ? Ptr<CvFeatureEvaluator>( new CvHaarEvaluator ) :
         type == CvFeatureParams::LBP ? Ptr<CvFeatureEvaluator>( new CvLBPEvaluator ) :
         type == CvFeatureParams::HOG ? Ptr<CvFeatureEvaluator>( new CvHOGEvaluator ) : Ptr<CvFeatureEvaluator>();
}

CvHaarFeatureParams::CvHaarFeatureParams()
{
  name = HFP_NAME;
  isIntegral = false;
}

void CvHaarFeatureParams::init( const CvFeatureParams& fp )
{
  CvFeatureParams::init( fp );
  isIntegral = ( (const CvHaarFeatureParams&) fp ).isIntegral;
}

void CvHaarFeatureParams::write( FileStorage &fs ) const
{
  CvFeatureParams::write( fs );
  fs << CC_ISINTEGRAL << isIntegral;
}

bool CvHaarFeatureParams::read( const FileNode &node )
{
  if( !CvFeatureParams::read( node ) )
    return false;

  FileNode rnode = node[CC_ISINTEGRAL];
  if( !rnode.isString() )
    return false;
  String intStr;
  rnode >> intStr;
  isIntegral = !intStr.compare( "0" ) ? false : !true;
  return true;
}

void CvHaarFeatureParams::printDefaults() const
{
  CvFeatureParams::printDefaults();
  std::cout << "isIntegral: false" << std::endl;
}

void CvHaarFeatureParams::printAttrs() const
{
  CvFeatureParams::printAttrs();
  std::string int_str = isIntegral == true ? "true" : "false";
  std::cout << "isIntegral: " << int_str << std::endl;
}

bool CvHaarFeatureParams::scanAttr( const std::string /*prmName*/, const std::string /*val*/)
{

  return true;
}

//--------------------- HaarFeatureEvaluator ----------------

void CvHaarEvaluator::init( const CvFeatureParams *_featureParams, int /*_maxSampleCount*/, Size _winSize )
{
  int cols = ( _winSize.width + 1 ) * ( _winSize.height + 1 );
  sum.create( (int) 1, cols, CV_32SC1 );
  isIntegral = ( (CvHaarFeatureParams*) _featureParams )->isIntegral;
  CvFeatureEvaluator::init( _featureParams, 1, _winSize );
}

void CvHaarEvaluator::setImage( const Mat& img, uchar /*clsLabel*/, int /*idx*/)
{
  CV_DbgAssert( !sum.empty() );

  winSize.width = img.cols;
  winSize.height = img.rows;

  CvFeatureEvaluator::setImage( img, 1, 0 );
  if( !isIntegral )
  {
    std::vector<Mat_<float> > ii_imgs;
    compute_integral( img, ii_imgs );
    _ii_img = ii_imgs[0];
  }
  else
  {
    _ii_img = img;
  }
}

void CvHaarEvaluator::writeFeatures( FileStorage &fs, const Mat& featureMap ) const
{
  _writeFeatures( features, fs, featureMap );
}

void CvHaarEvaluator::writeFeature( FileStorage &fs ) const
{
  String modeStr = isIntegral == true ? "1" : "0";
  CV_Assert( !modeStr.empty() );
  fs << "isIntegral" << modeStr;
}

void CvHaarEvaluator::generateFeatures()
{
  generateFeatures( featureParams->numFeatures );
}

void CvHaarEvaluator::generateFeatures( int nFeatures )
{
  for ( int i = 0; i < nFeatures; i++ )
  {
    CvHaarEvaluator::FeatureHaar feature( Size( winSize.width, winSize.height ) );
    features.push_back( feature );
  }

}

const std::vector<CvHaarEvaluator::FeatureHaar>& CvHaarEvaluator::getFeatures() const
{
  return features;
}

float CvHaarEvaluator::operator()( int featureIdx, int /*sampleIdx*/)
{
  /* TODO Added from MIL implementation */
  //return features[featureIdx].calc( _ii_img, Mat(), 0 );
  float res;
  features.at( featureIdx ).eval( _ii_img, Rect( 0, 0, winSize.width, winSize.height ), &res );
  return res;
}

void CvHaarEvaluator::setWinSize( Size patchSize )
{
  winSize.width = patchSize.width;
  winSize.height = patchSize.height;
}

Size CvHaarEvaluator::setWinSize() const
{
  return Size( winSize.width, winSize.height );
}

#define INITSIGMA( numAreas ) ( static_cast<float>( sqrt( 256.0f*256.0f / 12.0f * (numAreas) ) ) );

CvHaarEvaluator::FeatureHaar::FeatureHaar( Size patchSize )
{
  try
  {
    generateRandomFeature( patchSize );
  }
  catch ( ... )
  {
    throw;
  }
}

float CvHaarEvaluator::FeatureHaar::getInitMean() const
{
  return m_initMean;
}

float CvHaarEvaluator::FeatureHaar::getInitSigma() const
{
  return m_initSigma;
}

void CvHaarEvaluator::FeatureHaar::generateRandomFeature( Size patchSize )
{
  cv::Point2i position;
  Size baseDim;
  Size sizeFactor;
  int area;

  //Size minSize = Size( 3, 3 );
  int minArea = 9;

  bool valid = false;
  while ( !valid )
  {
    //choose position and scale
    position.y = rand() % ( patchSize.height );
    position.x = rand() % ( patchSize.width );

    baseDim.width = (int) ( ( 1 - sqrt( 1 - (float) rand() / RAND_MAX ) ) * patchSize.width );
    baseDim.height = (int) ( ( 1 - sqrt( 1 - (float) rand() / RAND_MAX ) ) * patchSize.height );

    //select types
    //float probType[11] = {0.0909f, 0.0909f, 0.0909f, 0.0909f, 0.0909f, 0.0909f, 0.0909f, 0.0909f, 0.0909f, 0.0909f, 0.0950f};
    float probType[11] =
    { 0.2f, 0.2f, 0.2f, 0.2f, 0.2f, 0.2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
    float prob = (float) rand() / RAND_MAX;

    if( prob < probType[0] )
    {
      //check if feature is valid
      sizeFactor.height = 2;
      sizeFactor.width = 1;
      if( position.y + baseDim.height * sizeFactor.height >= patchSize.height || position.x + baseDim.width * sizeFactor.width >= patchSize.width )
        continue;
      area = baseDim.height * sizeFactor.height * baseDim.width * sizeFactor.width;
      if( area < minArea )
        continue;

      m_type = 1;
      m_numAreas = 2;
      m_weights.resize( m_numAreas );
      m_weights[0] = 1;
      m_weights[1] = -1;
      m_areas.resize( m_numAreas );
      m_areas[0].x = position.x;
      m_areas[0].y = position.y;
      m_areas[0].height = baseDim.height;
      m_areas[0].width = baseDim.width;
      m_areas[1].x = position.x;
      m_areas[1].y = position.y + baseDim.height;
      m_areas[1].height = baseDim.height;
      m_areas[1].width = baseDim.width;
      m_initMean = 0;
      m_initSigma = INITSIGMA( m_numAreas );

      valid = true;

    }
    else if( prob < probType[0] + probType[1] )
    {
      //check if feature is valid
      sizeFactor.height = 1;
      sizeFactor.width = 2;
      if( position.y + baseDim.height * sizeFactor.height >= patchSize.height || position.x + baseDim.width * sizeFactor.width >= patchSize.width )
        continue;
      area = baseDim.height * sizeFactor.height * baseDim.width * sizeFactor.width;
      if( area < minArea )
        continue;

      m_type = 2;
      m_numAreas = 2;
      m_weights.resize( m_numAreas );
      m_weights[0] = 1;
      m_weights[1] = -1;
      m_areas.resize( m_numAreas );
      m_areas[0].x = position.x;
      m_areas[0].y = position.y;
      m_areas[0].height = baseDim.height;
      m_areas[0].width = baseDim.width;
      m_areas[1].x = position.x + baseDim.width;
      m_areas[1].y = position.y;
      m_areas[1].height = baseDim.height;
      m_areas[1].width = baseDim.width;
      m_initMean = 0;
      m_initSigma = INITSIGMA( m_numAreas );
      valid = true;

    }
    else if( prob < probType[0] + probType[1] + probType[2] )
    {
      //check if feature is valid
      sizeFactor.height = 4;
      sizeFactor.width = 1;
      if( position.y + baseDim.height * sizeFactor.height >= patchSize.height || position.x + baseDim.width * sizeFactor.width >= patchSize.width )
        continue;
      area = baseDim.height * sizeFactor.height * baseDim.width * sizeFactor.width;
      if( area < minArea )
        continue;

      m_type = 3;
      m_numAreas = 3;
      m_weights.resize( m_numAreas );
      m_weights[0] = 1;
      m_weights[1] = -2;
      m_weights[2] = 1;
      m_areas.resize( m_numAreas );
      m_areas[0].x = position.x;
      m_areas[0].y = position.y;
      m_areas[0].height = baseDim.height;
      m_areas[0].width = baseDim.width;
      m_areas[1].x = position.x;
      m_areas[1].y = position.y + baseDim.height;
      m_areas[1].height = 2 * baseDim.height;
      m_areas[1].width = baseDim.width;
      m_areas[2].y = position.y + 3 * baseDim.height;
      m_areas[2].x = position.x;
      m_areas[2].height = baseDim.height;
      m_areas[2].width = baseDim.width;
      m_initMean = 0;
      m_initSigma = INITSIGMA( m_numAreas );
      valid = true;
    }
    else if( prob < probType[0] + probType[1] + probType[2] + probType[3] )
    {
      //check if feature is valid
      sizeFactor.height = 1;
      sizeFactor.width = 4;
      if( position.y + baseDim.height * sizeFactor.height >= patchSize.height || position.x + baseDim.width * sizeFactor.width >= patchSize.width )
        continue;
      area = baseDim.height * sizeFactor.height * baseDim.width * sizeFactor.width;
      if( area < minArea )
        continue;

      m_type = 3;
      m_numAreas = 3;
      m_weights.resize( m_numAreas );
      m_weights[0] = 1;
      m_weights[1] = -2;
      m_weights[2] = 1;
      m_areas.resize( m_numAreas );
      m_areas[0].x = position.x;
      m_areas[0].y = position.y;
      m_areas[0].height = baseDim.height;
      m_areas[0].width = baseDim.width;
      m_areas[1].x = position.x + baseDim.width;
      m_areas[1].y = position.y;
      m_areas[1].height = baseDim.height;
      m_areas[1].width = 2 * baseDim.width;
      m_areas[2].y = position.y;
      m_areas[2].x = position.x + 3 * baseDim.width;
      m_areas[2].height = baseDim.height;
      m_areas[2].width = baseDim.width;
      m_initMean = 0;
      m_initSigma = INITSIGMA( m_numAreas );
      valid = true;
    }
    else if( prob < probType[0] + probType[1] + probType[2] + probType[3] + probType[4] )
    {
      //check if feature is valid
      sizeFactor.height = 2;
      sizeFactor.width = 2;
      if( position.y + baseDim.height * sizeFactor.height >= patchSize.height || position.x + baseDim.width * sizeFactor.width >= patchSize.width )
        continue;
      area = baseDim.height * sizeFactor.height * baseDim.width * sizeFactor.width;
      if( area < minArea )
        continue;

      m_type = 5;
      m_numAreas = 4;
      m_weights.resize( m_numAreas );
      m_weights[0] = 1;
      m_weights[1] = -1;
      m_weights[2] = -1;
      m_weights[3] = 1;
      m_areas.resize( m_numAreas );
      m_areas[0].x = position.x;
      m_areas[0].y = position.y;
      m_areas[0].height = baseDim.height;
      m_areas[0].width = baseDim.width;
      m_areas[1].x = position.x + baseDim.width;
      m_areas[1].y = position.y;
      m_areas[1].height = baseDim.height;
      m_areas[1].width = baseDim.width;
      m_areas[2].y = position.y + baseDim.height;
      m_areas[2].x = position.x;
      m_areas[2].height = baseDim.height;
      m_areas[2].width = baseDim.width;
      m_areas[3].y = position.y + baseDim.height;
      m_areas[3].x = position.x + baseDim.width;
      m_areas[3].height = baseDim.height;
      m_areas[3].width = baseDim.width;
      m_initMean = 0;
      m_initSigma = INITSIGMA( m_numAreas );
      valid = true;
    }
    else if( prob < probType[0] + probType[1] + probType[2] + probType[3] + probType[4] + probType[5] )
    {
      //check if feature is valid
      sizeFactor.height = 3;
      sizeFactor.width = 3;
      if( position.y + baseDim.height * sizeFactor.height >= patchSize.height || position.x + baseDim.width * sizeFactor.width >= patchSize.width )
        continue;
      area = baseDim.height * sizeFactor.height * baseDim.width * sizeFactor.width;
      if( area < minArea )
        continue;

      m_type = 6;
      m_numAreas = 2;
      m_weights.resize( m_numAreas );
      m_weights[0] = 1;
      m_weights[1] = -9;
      m_areas.resize( m_numAreas );
      m_areas[0].x = position.x;
      m_areas[0].y = position.y;
      m_areas[0].height = 3 * baseDim.height;
      m_areas[0].width = 3 * baseDim.width;
      m_areas[1].x = position.x + baseDim.width;
      m_areas[1].y = position.y + baseDim.height;
      m_areas[1].height = baseDim.height;
      m_areas[1].width = baseDim.width;
      m_initMean = -8 * 128;
      m_initSigma = INITSIGMA( m_numAreas );
      valid = true;
    }
    else if( prob < probType[0] + probType[1] + probType[2] + probType[3] + probType[4] + probType[5] + probType[6] )
    {
      //check if feature is valid
      sizeFactor.height = 3;
      sizeFactor.width = 1;
      if( position.y + baseDim.height * sizeFactor.height >= patchSize.height || position.x + baseDim.width * sizeFactor.width >= patchSize.width )
        continue;
      area = baseDim.height * sizeFactor.height * baseDim.width * sizeFactor.width;
      if( area < minArea )
        continue;

      m_type = 7;
      m_numAreas = 3;
      m_weights.resize( m_numAreas );
      m_weights[0] = 1;
      m_weights[1] = -2;
      m_weights[2] = 1;
      m_areas.resize( m_numAreas );
      m_areas[0].x = position.x;
      m_areas[0].y = position.y;
      m_areas[0].height = baseDim.height;
      m_areas[0].width = baseDim.width;
      m_areas[1].x = position.x;
      m_areas[1].y = position.y + baseDim.height;
      m_areas[1].height = baseDim.height;
      m_areas[1].width = baseDim.width;
      m_areas[2].y = position.y + baseDim.height * 2;
      m_areas[2].x = position.x;
      m_areas[2].height = baseDim.height;
      m_areas[2].width = baseDim.width;
      m_initMean = 0;
      m_initSigma = INITSIGMA( m_numAreas );
      valid = true;
    }
    else if( prob < probType[0] + probType[1] + probType[2] + probType[3] + probType[4] + probType[5] + probType[6] + probType[7] )
    {
      //check if feature is valid
      sizeFactor.height = 1;
      sizeFactor.width = 3;
      if( position.y + baseDim.height * sizeFactor.height >= patchSize.height || position.x + baseDim.width * sizeFactor.width >= patchSize.width )
        continue;

      area = baseDim.height * sizeFactor.height * baseDim.width * sizeFactor.width;

      if( area < minArea )
        continue;

      m_type = 8;
      m_numAreas = 3;
      m_weights.resize( m_numAreas );
      m_weights[0] = 1;
      m_weights[1] = -2;
      m_weights[2] = 1;
      m_areas.resize( m_numAreas );
      m_areas[0].x = position.x;
      m_areas[0].y = position.y;
      m_areas[0].height = baseDim.height;
      m_areas[0].width = baseDim.width;
      m_areas[1].x = position.x + baseDim.width;
      m_areas[1].y = position.y;
      m_areas[1].height = baseDim.height;
      m_areas[1].width = baseDim.width;
      m_areas[2].y = position.y;
      m_areas[2].x = position.x + 2 * baseDim.width;
      m_areas[2].height = baseDim.height;
      m_areas[2].width = baseDim.width;
      m_initMean = 0;
      m_initSigma = INITSIGMA( m_numAreas );
      valid = true;
    }
    else if( prob < probType[0] + probType[1] + probType[2] + probType[3] + probType[4] + probType[5] + probType[6] + probType[7] + probType[8] )
    {
      //check if feature is valid
      sizeFactor.height = 3;
      sizeFactor.width = 3;
      if( position.y + baseDim.height * sizeFactor.height >= patchSize.height || position.x + baseDim.width * sizeFactor.width >= patchSize.width )
        continue;
      area = baseDim.height * sizeFactor.height * baseDim.width * sizeFactor.width;
      if( area < minArea )
        continue;

      m_type = 9;
      m_numAreas = 2;
      m_weights.resize( m_numAreas );
      m_weights[0] = 1;
      m_weights[1] = -2;
      m_areas.resize( m_numAreas );
      m_areas[0].x = position.x;
      m_areas[0].y = position.y;
      m_areas[0].height = 3 * baseDim.height;
      m_areas[0].width = 3 * baseDim.width;
      m_areas[1].x = position.x + baseDim.width;
      m_areas[1].y = position.y + baseDim.height;
      m_areas[1].height = baseDim.height;
      m_areas[1].width = baseDim.width;
      m_initMean = 0;
      m_initSigma = INITSIGMA( m_numAreas );
      valid = true;
    }
    else if( prob
        < probType[0] + probType[1] + probType[2] + probType[3] + probType[4] + probType[5] + probType[6] + probType[7] + probType[8] + probType[9] )
    {
      //check if feature is valid
      sizeFactor.height = 3;
      sizeFactor.width = 1;
      if( position.y + baseDim.height * sizeFactor.height >= patchSize.height || position.x + baseDim.width * sizeFactor.width >= patchSize.width )
        continue;
      area = baseDim.height * sizeFactor.height * baseDim.width * sizeFactor.width;
      if( area < minArea )
        continue;

      m_type = 10;
      m_numAreas = 3;
      m_weights.resize( m_numAreas );
      m_weights[0] = 1;
      m_weights[1] = -1;
      m_weights[2] = 1;
      m_areas.resize( m_numAreas );
      m_areas[0].x = position.x;
      m_areas[0].y = position.y;
      m_areas[0].height = baseDim.height;
      m_areas[0].width = baseDim.width;
      m_areas[1].x = position.x;
      m_areas[1].y = position.y + baseDim.height;
      m_areas[1].height = baseDim.height;
      m_areas[1].width = baseDim.width;
      m_areas[2].y = position.y + baseDim.height * 2;
      m_areas[2].x = position.x;
      m_areas[2].height = baseDim.height;
      m_areas[2].width = baseDim.width;
      m_initMean = 128;
      m_initSigma = INITSIGMA( m_numAreas );
      valid = true;
    }
    else if( prob
        < probType[0] + probType[1] + probType[2] + probType[3] + probType[4] + probType[5] + probType[6] + probType[7] + probType[8] + probType[9]
            + probType[10] )
    {
      //check if feature is valid
      sizeFactor.height = 1;
      sizeFactor.width = 3;
      if( position.y + baseDim.height * sizeFactor.height >= patchSize.height || position.x + baseDim.width * sizeFactor.width >= patchSize.width )
        continue;
      area = baseDim.height * sizeFactor.height * baseDim.width * sizeFactor.width;
      if( area < minArea )
        continue;

      m_type = 11;
      m_numAreas = 3;
      m_weights.resize( m_numAreas );
      m_weights[0] = 1;
      m_weights[1] = -1;
      m_weights[2] = 1;
      m_areas.resize( m_numAreas );
      m_areas[0].x = position.x;
      m_areas[0].y = position.y;
      m_areas[0].height = baseDim.height;
      m_areas[0].width = baseDim.width;
      m_areas[1].x = position.x + baseDim.width;
      m_areas[1].y = position.y;
      m_areas[1].height = baseDim.height;
      m_areas[1].width = baseDim.width;
      m_areas[2].y = position.y;
      m_areas[2].x = position.x + 2 * baseDim.width;
      m_areas[2].height = baseDim.height;
      m_areas[2].width = baseDim.width;
      m_initMean = 128;
      m_initSigma = INITSIGMA( m_numAreas );
      valid = true;
    }
    else
    CV_Error(CV_StsAssert, "");
  }

  m_initSize = patchSize;
  m_curSize = m_initSize;
  m_scaleFactorWidth = m_scaleFactorHeight = 1.0f;
  m_scaleAreas.resize( m_numAreas );
  m_scaleWeights.resize( m_numAreas );
  for ( int curArea = 0; curArea < m_numAreas; curArea++ )
  {
    m_scaleAreas[curArea] = m_areas[curArea];
    m_scaleWeights[curArea] = (float) m_weights[curArea] / (float) ( m_areas[curArea].width * m_areas[curArea].height );
  }
}

bool CvHaarEvaluator::FeatureHaar::eval( const Mat& image, Rect /*ROI*/, float* result ) const
{

  *result = 0.0f;

  for ( int curArea = 0; curArea < m_numAreas; curArea++ )
  {
    *result += (float) getSum( image, Rect( m_areas[curArea].x, m_areas[curArea].y, m_areas[curArea].width, m_areas[curArea].height ) )
        * m_scaleWeights[curArea];
  }

  /*
   if( image->getUseVariance() )
   {
   float variance = (float) image->getVariance( ROI );
   *result /= variance;
   }
   */

  return true;
}

float CvHaarEvaluator::FeatureHaar::getSum( const Mat& image, Rect imageROI ) const
{
// left upper Origin
  int OriginX = imageROI.x;
  int OriginY = imageROI.y;

// Check and fix width and height
  int Width = imageROI.width;
  int Height = imageROI.height;

  if( OriginX + Width >= image.cols - 1 )
    Width = ( image.cols - 1 ) - OriginX;
  if( OriginY + Height >= image.rows - 1 )
    Height = ( image.rows - 1 ) - OriginY;

  float value = 0;
  int depth = image.depth();

  if( depth == CV_8U || depth == CV_32S )
    value = static_cast<float>(image.at<int>( OriginY + Height, OriginX + Width ) + image.at<int>( OriginY, OriginX ) - image.at<int>( OriginY, OriginX + Width )
        - image.at<int>( OriginY + Height, OriginX ));
  else if( depth == CV_64F )
    value = static_cast<float>(image.at<double>( OriginY + Height, OriginX + Width ) + image.at<double>( OriginY, OriginX )
        - image.at<double>( OriginY, OriginX + Width ) - image.at<double>( OriginY + Height, OriginX ));
  else if( depth == CV_32F )
    value = static_cast<float>(image.at<float>( OriginY + Height, OriginX + Width ) + image.at<float>( OriginY, OriginX ) - image.at<float>( OriginY, OriginX + Width )
        - image.at<float>( OriginY + Height, OriginX ));

  return value;
}

int CvHaarEvaluator::FeatureHaar::getNumAreas()
{
  return m_numAreas;
}

const std::vector<float>& CvHaarEvaluator::FeatureHaar::getWeights() const
{
  return m_weights;
}

const std::vector<Rect>& CvHaarEvaluator::FeatureHaar::getAreas() const
{
  return m_areas;
}

CvHOGFeatureParams::CvHOGFeatureParams()
{
  maxCatCount = 0;
  name = HOGF_NAME;
  featSize = N_BINS * N_CELLS;
}

void CvHOGEvaluator::init( const CvFeatureParams *_featureParams, int _maxSampleCount, Size _winSize )
{
  CV_Assert( _maxSampleCount > 0 );
  int cols = ( _winSize.width + 1 ) * ( _winSize.height + 1 );
  for ( int bin = 0; bin < N_BINS; bin++ )
  {
    hist.push_back( Mat( _maxSampleCount, cols, CV_32FC1 ) );
  }
  normSum.create( (int) _maxSampleCount, cols, CV_32FC1 );
  CvFeatureEvaluator::init( _featureParams, _maxSampleCount, _winSize );
}

void CvHOGEvaluator::setImage( const Mat &img, uchar clsLabel, int idx )
{
  CV_DbgAssert( !hist.empty());
  CvFeatureEvaluator::setImage( img, clsLabel, idx );
  std::vector<Mat> integralHist;
  for ( int bin = 0; bin < N_BINS; bin++ )
  {
    integralHist.push_back( Mat( winSize.height + 1, winSize.width + 1, hist[bin].type(), hist[bin].ptr<float>( (int) idx ) ) );
  }
  Mat integralNorm( winSize.height + 1, winSize.width + 1, normSum.type(), normSum.ptr<float>( (int) idx ) );
  integralHistogram( img, integralHist, integralNorm, (int) N_BINS );
}

//void CvHOGEvaluator::writeFeatures( FileStorage &fs, const Mat& featureMap ) const
//{
//    _writeFeatures( features, fs, featureMap );
//}

void CvHOGEvaluator::writeFeatures( FileStorage &fs, const Mat& featureMap ) const
{
  int featIdx;
  int componentIdx;
  const Mat_<int>& featureMap_ = (const Mat_<int>&) featureMap;
  fs << FEATURES << "[";
  for ( int fi = 0; fi < featureMap.cols; fi++ )
    if( featureMap_( 0, fi ) >= 0 )
    {
      fs << "{";
      featIdx = fi / getFeatureSize();
      componentIdx = fi % getFeatureSize();
      features[featIdx].write( fs, componentIdx );
      fs << "}";
    }
  fs << "]";
}

void CvHOGEvaluator::generateFeatures()
{
  int offset = winSize.width + 1;
  Size blockStep;
  int x, y, t, w, h;

  for ( t = 8; t <= winSize.width / 2; t += 8 )  //t = size of a cell. blocksize = 4*cellSize
  {
    blockStep = Size( 4, 4 );
    w = 2 * t;  //width of a block
    h = 2 * t;  //height of a block
    for ( x = 0; x <= winSize.width - w; x += blockStep.width )
    {
      for ( y = 0; y <= winSize.height - h; y += blockStep.height )
      {
        features.push_back( Feature( offset, x, y, t, t ) );
      }
    }
    w = 2 * t;
    h = 4 * t;
    for ( x = 0; x <= winSize.width - w; x += blockStep.width )
    {
      for ( y = 0; y <= winSize.height - h; y += blockStep.height )
      {
        features.push_back( Feature( offset, x, y, t, 2 * t ) );
      }
    }
    w = 4 * t;
    h = 2 * t;
    for ( x = 0; x <= winSize.width - w; x += blockStep.width )
    {
      for ( y = 0; y <= winSize.height - h; y += blockStep.height )
      {
        features.push_back( Feature( offset, x, y, 2 * t, t ) );
      }
    }
  }

  numFeatures = (int) features.size();
}

CvHOGEvaluator::Feature::Feature()
{
  for ( int i = 0; i < N_CELLS; i++ )
  {
    rect[i] = Rect( 0, 0, 0, 0 );
  }
}

CvHOGEvaluator::Feature::Feature( int offset, int x, int y, int cellW, int cellH )
{
  rect[0] = Rect( x, y, cellW, cellH );  //cell0
  rect[1] = Rect( x + cellW, y, cellW, cellH );  //cell1
  rect[2] = Rect( x, y + cellH, cellW, cellH );  //cell2
  rect[3] = Rect( x + cellW, y + cellH, cellW, cellH );  //cell3

  for ( int i = 0; i < N_CELLS; i++ )
  {
    CV_SUM_OFFSETS( fastRect[i].p0, fastRect[i].p1, fastRect[i].p2, fastRect[i].p3, rect[i], offset );
  }
}

void CvHOGEvaluator::Feature::write( FileStorage &fs ) const
{
  fs << CC_RECTS << "[";
  for ( int i = 0; i < N_CELLS; i++ )
  {
    fs << "[:" << rect[i].x << rect[i].y << rect[i].width << rect[i].height << "]";
  }
  fs << "]";
}

//cell and bin idx writing
//void CvHOGEvaluator::Feature::write(FileStorage &fs, int varIdx) const
//{
//    int featComponent = varIdx % (N_CELLS * N_BINS);
//    int cellIdx = featComponent / N_BINS;
//    int binIdx = featComponent % N_BINS;
//
//    fs << CC_RECTS << "[:" << rect[cellIdx].x << rect[cellIdx].y <<
//        rect[cellIdx].width << rect[cellIdx].height << binIdx << "]";
//}

//cell[0] and featComponent idx writing. By cell[0] it's possible to recover all block
//All block is nessesary for block normalization
void CvHOGEvaluator::Feature::write( FileStorage &fs, int featComponentIdx ) const
{
  fs << CC_RECT << "[:" << rect[0].x << rect[0].y << rect[0].width << rect[0].height << featComponentIdx << "]";
}

void CvHOGEvaluator::integralHistogram( const Mat &img, std::vector<Mat> &histogram, Mat &norm, int nbins ) const
{
  CV_Assert( img.type() == CV_8U || img.type() == CV_8UC3 );
  int x, y, binIdx;

  Size gradSize( img.size() );
  Size histSize( histogram[0].size() );
  Mat grad( gradSize, CV_32F );
  Mat qangle( gradSize, CV_8U );

  AutoBuffer<int> mapbuf( gradSize.width + gradSize.height + 4 );
  int* xmap = (int*) mapbuf + 1;
  int* ymap = xmap + gradSize.width + 2;

  const int borderType = (int) BORDER_REPLICATE;

  for ( x = -1; x < gradSize.width + 1; x++ )
    xmap[x] = borderInterpolate( x, gradSize.width, borderType );
  for ( y = -1; y < gradSize.height + 1; y++ )
    ymap[y] = borderInterpolate( y, gradSize.height, borderType );

  int width = gradSize.width;
  AutoBuffer<float> _dbuf( width * 4 );
  float* dbuf = _dbuf;
  Mat Dx( 1, width, CV_32F, dbuf );
  Mat Dy( 1, width, CV_32F, dbuf + width );
  Mat Mag( 1, width, CV_32F, dbuf + width * 2 );
  Mat Angle( 1, width, CV_32F, dbuf + width * 3 );

  float angleScale = (float) ( nbins / CV_PI );

  for ( y = 0; y < gradSize.height; y++ )
  {
    const uchar* currPtr = img.data + img.step * ymap[y];
    const uchar* prevPtr = img.data + img.step * ymap[y - 1];
    const uchar* nextPtr = img.data + img.step * ymap[y + 1];
    float* gradPtr = (float*) grad.ptr( y );
    uchar* qanglePtr = (uchar*) qangle.ptr( y );

    for ( x = 0; x < width; x++ )
    {
      dbuf[x] = (float) ( currPtr[xmap[x + 1]] - currPtr[xmap[x - 1]] );
      dbuf[width + x] = (float) ( nextPtr[xmap[x]] - prevPtr[xmap[x]] );
    }
    cartToPolar( Dx, Dy, Mag, Angle, false );
    for ( x = 0; x < width; x++ )
    {
      float mag = dbuf[x + width * 2];
      float angle = dbuf[x + width * 3];
      angle = angle * angleScale - 0.5f;
      int bidx = cvFloor( angle );
      angle -= bidx;
      if( bidx < 0 )
        bidx += nbins;
      else if( bidx >= nbins )
        bidx -= nbins;

      qanglePtr[x] = (uchar) bidx;
      gradPtr[x] = mag;
    }
  }
  integral( grad, norm, grad.depth() );

  float* histBuf;
  const float* magBuf;
  const uchar* binsBuf;

  int binsStep = (int) ( qangle.step / sizeof(uchar) );
  int histStep = (int) ( histogram[0].step / sizeof(float) );
  int magStep = (int) ( grad.step / sizeof(float) );
  for ( binIdx = 0; binIdx < nbins; binIdx++ )
  {
    histBuf = (float*) histogram[binIdx].data;
    magBuf = (const float*) grad.data;
    binsBuf = (const uchar*) qangle.data;

    memset( histBuf, 0, histSize.width * sizeof ( histBuf[0] ) );
    histBuf += histStep + 1;
    for ( y = 0; y < qangle.rows; y++ )
    {
      histBuf[-1] = 0.f;
      float strSum = 0.f;
      for ( x = 0; x < qangle.cols; x++ )
      {
        if( binsBuf[x] == binIdx )
          strSum += magBuf[x];
        histBuf[x] = histBuf[-histStep + x] + strSum;
      }
      histBuf += histStep;
      binsBuf += binsStep;
      magBuf += magStep;
    }
  }
}

CvLBPFeatureParams::CvLBPFeatureParams()
{
  maxCatCount = 256;
  name = LBPF_NAME;
}

void CvLBPEvaluator::init( const CvFeatureParams *_featureParams, int _maxSampleCount, Size _winSize )
{
  CV_Assert( _maxSampleCount > 0 );
  sum.create( (int) _maxSampleCount, ( _winSize.width + 1 ) * ( _winSize.height + 1 ), CV_32SC1 );
  CvFeatureEvaluator::init( _featureParams, _maxSampleCount, _winSize );
}

void CvLBPEvaluator::setImage( const Mat &img, uchar clsLabel, int idx )
{
  CV_DbgAssert( !sum.empty() );
  CvFeatureEvaluator::setImage( img, clsLabel, idx );
  Mat innSum( winSize.height + 1, winSize.width + 1, sum.type(), sum.ptr<int>( (int) idx ) );
  integral( img, innSum );
}

void CvLBPEvaluator::writeFeatures( FileStorage &fs, const Mat& featureMap ) const
{
  _writeFeatures( features, fs, featureMap );
}

void CvLBPEvaluator::generateFeatures()
{
  int offset = winSize.width + 1;
  for ( int x = 0; x < winSize.width; x++ )
    for ( int y = 0; y < winSize.height; y++ )
      for ( int w = 1; w <= winSize.width / 3; w++ )
        for ( int h = 1; h <= winSize.height / 3; h++ )
          if( ( x + 3 * w <= winSize.width ) && ( y + 3 * h <= winSize.height ) )
            features.push_back( Feature( offset, x, y, w, h ) );
  numFeatures = (int) features.size();
}

CvLBPEvaluator::Feature::Feature()
{
  rect = Rect( 0, 0, 0, 0 );
}

CvLBPEvaluator::Feature::Feature( int offset, int x, int y, int _blockWidth, int _blockHeight )
{
  Rect tr = rect = Rect( x, y, _blockWidth, _blockHeight );
  CV_SUM_OFFSETS( p[0], p[1], p[4], p[5], tr, offset )
  tr.x += 2 * rect.width;
  CV_SUM_OFFSETS( p[2], p[3], p[6], p[7], tr, offset )
  tr.y += 2 * rect.height;
  CV_SUM_OFFSETS( p[10], p[11], p[14], p[15], tr, offset )
  tr.x -= 2 * rect.width;
  CV_SUM_OFFSETS( p[8], p[9], p[12], p[13], tr, offset )
}

void CvLBPEvaluator::Feature::write( FileStorage &fs ) const
{
  fs << CC_RECT << "[:" << rect.x << rect.y << rect.width << rect.height << "]";
}

} /* namespace cv */
