/*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.
//
//
//                        Intel License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, 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 Intel Corporation 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*/

/*
 * cvhaartraining.cpp
 *
 * training of cascade of boosted classifiers based on haar features
 */

#include "cvhaartraining.h"
#include "_cvhaartraining.h"

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <ctype.h>

#include "highgui.h"

#ifdef CV_VERBOSE
#include <ctime>

#ifdef _WIN32
/* use clock() function insted of time() */
#define TIME( arg ) (((double) clock()) / CLOCKS_PER_SEC)
#else
#define TIME( arg ) (time( arg ))
#endif /* _WIN32 */

#endif /* CV_VERBOSE */

#if defined CV_OPENMP && (defined _MSC_VER || defined CV_ICC)
#define CV_OPENMP 1
#else
#undef CV_OPENMP
#endif

typedef struct CvBackgroundData
{
    int    count;
    char** filename;
    int    last;
    int    round;
    CvSize winsize;
} CvBackgroundData;

typedef struct CvBackgroundReader
{
    CvMat   src;
    CvMat   img;
    CvPoint offset;
    float   scale;
    float   scalefactor;
    float   stepfactor;
    CvPoint point;
} CvBackgroundReader;

/*
 * Background reader
 * Created in each thread
 */
CvBackgroundReader* cvbgreader = NULL;

#if defined CV_OPENMP
#pragma omp threadprivate(cvbgreader)
#endif

CvBackgroundData* cvbgdata = NULL;


/*
 * get sum image offsets for <rect> corner points
 * step - row step (measured in image pixels!) of sum image
 */
#define CV_SUM_OFFSETS( p0, p1, p2, p3, rect, step )                      \
    /* (x, y) */                                                          \
    (p0) = (rect).x + (step) * (rect).y;                                  \
    /* (x + w, y) */                                                      \
    (p1) = (rect).x + (rect).width + (step) * (rect).y;                   \
    /* (x + w, y) */                                                      \
    (p2) = (rect).x + (step) * ((rect).y + (rect).height);                \
    /* (x + w, y + h) */                                                  \
    (p3) = (rect).x + (rect).width + (step) * ((rect).y + (rect).height);

/*
 * get tilted image offsets for <rect> corner points
 * step - row step (measured in image pixels!) of tilted image
 */
#define CV_TILTED_OFFSETS( p0, p1, p2, p3, rect, step )                   \
    /* (x, y) */                                                          \
    (p0) = (rect).x + (step) * (rect).y;                                  \
    /* (x - h, y + h) */                                                  \
    (p1) = (rect).x - (rect).height + (step) * ((rect).y + (rect).height);\
    /* (x + w, y + w) */                                                  \
    (p2) = (rect).x + (rect).width + (step) * ((rect).y + (rect).width);  \
    /* (x + w - h, y + w + h) */                                          \
    (p3) = (rect).x + (rect).width - (rect).height                        \
           + (step) * ((rect).y + (rect).width + (rect).height);


/*
 * icvCreateIntHaarFeatures
 *
 * Create internal representation of haar features
 *
 * mode:
 *  0 - BASIC = Viola
 *  1 - CORE  = All upright
 *  2 - ALL   = All features
 */
static
CvIntHaarFeatures* icvCreateIntHaarFeatures( CvSize winsize,
                                             int mode,
                                             int symmetric )
{
    CvIntHaarFeatures* features = NULL;
    CvTHaarFeature haarFeature;

    CvMemStorage* storage = NULL;
    CvSeq* seq = NULL;
    CvSeqWriter writer;

    int s0 = 36; /* minimum total area size of basic haar feature     */
    int s1 = 12; /* minimum total area size of tilted haar features 2 */
    int s2 = 18; /* minimum total area size of tilted haar features 3 */
    int s3 = 24; /* minimum total area size of tilted haar features 4 */

    int x  = 0;
    int y  = 0;
    int dx = 0;
    int dy = 0;

#if 0
    float factor = 1.0F;

    factor = ((float) winsize.width) * winsize.height / (24 * 24);

    s0 = (int) (s0 * factor);
    s1 = (int) (s1 * factor);
    s2 = (int) (s2 * factor);
    s3 = (int) (s3 * factor);
#else
    s0 = 1;
    s1 = 1;
    s2 = 1;
    s3 = 1;
#endif

    /* CV_VECTOR_CREATE( vec, CvIntHaarFeature, size, maxsize ) */
    storage = cvCreateMemStorage();
    cvStartWriteSeq( 0, sizeof( CvSeq ), sizeof( haarFeature ), storage, &writer );

    for( x = 0; x < winsize.width; x++ )
    {
        for( y = 0; y < winsize.height; y++ )
        {
            for( dx = 1; dx <= winsize.width; dx++ )
            {
                for( dy = 1; dy <= winsize.height; dy++ )
                {
                    // haar_x2
                    if ( (x+dx*2 <= winsize.width) && (y+dy <= winsize.height) ) {
                        if (dx*2*dy < s0) continue;
                        if (!symmetric || (x+x+dx*2 <=winsize.width)) {
                            haarFeature = cvHaarFeature( "haar_x2",
                                x,    y, dx*2, dy, -1,
                                x+dx, y, dx  , dy, +2 );
                            /* CV_VECTOR_PUSH( vec, CvIntHaarFeature, haarFeature, size, maxsize, step ) */
                            CV_WRITE_SEQ_ELEM( haarFeature, writer );
                        }
                    }

                    // haar_y2
                    if ( (x+dx <= winsize.width) && (y+dy*2 <= winsize.height) ) {
                        if (dx*2*dy < s0) continue;
                        if (!symmetric || (x+x+dx <= winsize.width)) {
                            haarFeature = cvHaarFeature( "haar_y2",
                                x, y,    dx, dy*2, -1,
                                x, y+dy, dx, dy,   +2 );
                            CV_WRITE_SEQ_ELEM( haarFeature, writer );
                        }
                    }

                    // haar_x3
                    if ( (x+dx*3 <= winsize.width) && (y+dy <= winsize.height) ) {
                        if (dx*3*dy < s0) continue;
                        if (!symmetric || (x+x+dx*3 <=winsize.width)) {
                            haarFeature = cvHaarFeature( "haar_x3",
                                x,    y, dx*3, dy, -1,
                                x+dx, y, dx,   dy, +3 );
                            CV_WRITE_SEQ_ELEM( haarFeature, writer );
                        }
                    }

                    // haar_y3
                    if ( (x+dx <= winsize.width) && (y+dy*3 <= winsize.height) ) {
                        if (dx*3*dy < s0) continue;
                        if (!symmetric || (x+x+dx <= winsize.width)) {
                            haarFeature = cvHaarFeature( "haar_y3",
                                x, y,    dx, dy*3, -1,
                                x, y+dy, dx, dy,   +3 );
                            CV_WRITE_SEQ_ELEM( haarFeature, writer );
                        }
                    }

                    if( mode != 0 /*BASIC*/ ) {
                        // haar_x4
                        if ( (x+dx*4 <= winsize.width) && (y+dy <= winsize.height) ) {
                            if (dx*4*dy < s0) continue;
                            if (!symmetric || (x+x+dx*4 <=winsize.width)) {
                                haarFeature = cvHaarFeature( "haar_x4",
                                    x,    y, dx*4, dy, -1,
                                    x+dx, y, dx*2, dy, +2 );
                                CV_WRITE_SEQ_ELEM( haarFeature, writer );
                            }
                        }

                        // haar_y4
                        if ( (x+dx <= winsize.width ) && (y+dy*4 <= winsize.height) ) {
                            if (dx*4*dy < s0) continue;
                            if (!symmetric || (x+x+dx   <=winsize.width)) {
                                haarFeature = cvHaarFeature( "haar_y4",
                                    x, y,    dx, dy*4, -1,
                                    x, y+dy, dx, dy*2, +2 );
                                CV_WRITE_SEQ_ELEM( haarFeature, writer );
                            }
                        }
                    }

                    // x2_y2
                    if ( (x+dx*2 <= winsize.width) && (y+dy*2 <= winsize.height) ) {
                        if (dx*4*dy < s0) continue;
                        if (!symmetric || (x+x+dx*2 <=winsize.width)) {
                            haarFeature = cvHaarFeature( "haar_x2_y2",
                                x   , y,    dx*2, dy*2, -1,
                                x   , y   , dx  , dy,   +2,
                                x+dx, y+dy, dx  , dy,   +2 );
                            CV_WRITE_SEQ_ELEM( haarFeature, writer );
                        }
                    }

                    if (mode != 0 /*BASIC*/) {
                        // point
                        if ( (x+dx*3 <= winsize.width) && (y+dy*3 <= winsize.height) ) {
                            if (dx*9*dy < s0) continue;
                            if (!symmetric || (x+x+dx*3 <=winsize.width))  {
                                haarFeature = cvHaarFeature( "haar_point",
                                    x   , y,    dx*3, dy*3, -1,
                                    x+dx, y+dy, dx  , dy  , +9);
                                CV_WRITE_SEQ_ELEM( haarFeature, writer );
                            }
                        }
                    }

                    if (mode == 2 /*ALL*/) {
                        // tilted haar_x2                                      (x, y, w, h, b, weight)
                        if ( (x+2*dx <= winsize.width) && (y+2*dx+dy <= winsize.height) && (x-dy>= 0) ) {
                            if (dx*2*dy < s1) continue;

                            if (!symmetric || (x <= (winsize.width / 2) )) {
                                haarFeature = cvHaarFeature( "tilted_haar_x2",
                                    x, y, dx*2, dy, -1,
                                    x, y, dx  , dy, +2 );
                                CV_WRITE_SEQ_ELEM( haarFeature, writer );
                            }
                        }

                        // tilted haar_y2                                      (x, y, w, h, b, weight)
                        if ( (x+dx <= winsize.width) && (y+dx+2*dy <= winsize.height) && (x-2*dy>= 0) ) {
                            if (dx*2*dy < s1) continue;

                            if (!symmetric || (x <= (winsize.width / 2) )) {
                                haarFeature = cvHaarFeature( "tilted_haar_y2",
                                    x, y, dx, 2*dy, -1,
                                    x, y, dx,   dy, +2 );
                                CV_WRITE_SEQ_ELEM( haarFeature, writer );
                            }
                        }

                        // tilted haar_x3                                   (x, y, w, h, b, weight)
                        if ( (x+3*dx <= winsize.width) && (y+3*dx+dy <= winsize.height) && (x-dy>= 0) ) {
                            if (dx*3*dy < s2) continue;

                            if (!symmetric || (x <= (winsize.width / 2) )) {
                                haarFeature = cvHaarFeature( "tilted_haar_x3",
                                    x,    y,    dx*3, dy, -1,
                                    x+dx, y+dx, dx  , dy, +3 );
                                CV_WRITE_SEQ_ELEM( haarFeature, writer );
                            }
                        }

                        // tilted haar_y3                                      (x, y, w, h, b, weight)
                        if ( (x+dx <= winsize.width) && (y+dx+3*dy <= winsize.height) && (x-3*dy>= 0) ) {
                            if (dx*3*dy < s2) continue;

                            if (!symmetric || (x <= (winsize.width / 2) )) {
                                haarFeature = cvHaarFeature( "tilted_haar_y3",
                                    x,    y,    dx, 3*dy, -1,
                                    x-dy, y+dy, dx,   dy, +3 );
                                CV_WRITE_SEQ_ELEM( haarFeature, writer );
                            }
                        }


                        // tilted haar_x4                                   (x, y, w, h, b, weight)
                        if ( (x+4*dx <= winsize.width) && (y+4*dx+dy <= winsize.height) && (x-dy>= 0) ) {
                            if (dx*4*dy < s3) continue;

                            if (!symmetric || (x <= (winsize.width / 2) )) {
                                haarFeature = cvHaarFeature( "tilted_haar_x4",


                                    x,    y,    dx*4, dy, -1,
                                    x+dx, y+dx, dx*2, dy, +2 );
                                CV_WRITE_SEQ_ELEM( haarFeature, writer );
                            }
                        }

                        // tilted haar_y4                                      (x, y, w, h, b, weight)
                        if ( (x+dx <= winsize.width) && (y+dx+4*dy <= winsize.height) && (x-4*dy>= 0) ) {
                            if (dx*4*dy < s3) continue;

                            if (!symmetric || (x <= (winsize.width / 2) )) {
                                haarFeature = cvHaarFeature( "tilted_haar_y4",
                                    x,    y,    dx, 4*dy, -1,
                                    x-dy, y+dy, dx, 2*dy, +2 );
                                CV_WRITE_SEQ_ELEM( haarFeature, writer );
                            }
                        }


                        /*

                          // tilted point
                          if ( (x+dx*3 <= winsize.width - 1) && (y+dy*3 <= winsize.height - 1) && (x-3*dy>= 0)) {
                          if (dx*9*dy < 36) continue;
                          if (!symmetric || (x <= (winsize.width / 2) ))  {
                            haarFeature = cvHaarFeature( "tilted_haar_point",
                                x, y,    dx*3, dy*3, -1,
                                x, y+dy, dx  , dy,   +9 );
                                CV_WRITE_SEQ_ELEM( haarFeature, writer );
                          }
                          }
                        */
                    }
                }
            }
        }
    }

    seq = cvEndWriteSeq( &writer );
    features = (CvIntHaarFeatures*) cvAlloc( sizeof( CvIntHaarFeatures ) +
        ( sizeof( CvTHaarFeature ) + sizeof( CvFastHaarFeature ) ) * seq->total );
    features->feature = (CvTHaarFeature*) (features + 1);
    features->fastfeature = (CvFastHaarFeature*) ( features->feature + seq->total );
    features->count = seq->total;
    features->winsize = winsize;
    cvCvtSeqToArray( seq, (CvArr*) features->feature );
    cvReleaseMemStorage( &storage );

    icvConvertToFastHaarFeature( features->feature, features->fastfeature,
                                 features->count, (winsize.width + 1) );

    return features;
}

static
void icvReleaseIntHaarFeatures( CvIntHaarFeatures** intHaarFeatures )
{
    if( intHaarFeatures != NULL && (*intHaarFeatures) != NULL )
    {
        cvFree( intHaarFeatures );
        (*intHaarFeatures) = NULL;
    }
}


void icvConvertToFastHaarFeature( CvTHaarFeature* haarFeature,
                                  CvFastHaarFeature* fastHaarFeature,
                                  int size, int step )
{
    int i = 0;
    int j = 0;

    for( i = 0; i < size; i++ )
    {
        fastHaarFeature[i].tilted = haarFeature[i].tilted;
        if( !fastHaarFeature[i].tilted )
        {
            for( j = 0; j < CV_HAAR_FEATURE_MAX; j++ )
            {
                fastHaarFeature[i].rect[j].weight = haarFeature[i].rect[j].weight;
                if( fastHaarFeature[i].rect[j].weight == 0.0F )
                {
                    break;
                }
                CV_SUM_OFFSETS( fastHaarFeature[i].rect[j].p0,
                                fastHaarFeature[i].rect[j].p1,
                                fastHaarFeature[i].rect[j].p2,
                                fastHaarFeature[i].rect[j].p3,
                                haarFeature[i].rect[j].r, step )
            }

        }
        else
        {
            for( j = 0; j < CV_HAAR_FEATURE_MAX; j++ )
            {
                fastHaarFeature[i].rect[j].weight = haarFeature[i].rect[j].weight;
                if( fastHaarFeature[i].rect[j].weight == 0.0F )
                {
                    break;
                }
                CV_TILTED_OFFSETS( fastHaarFeature[i].rect[j].p0,
                                   fastHaarFeature[i].rect[j].p1,
                                   fastHaarFeature[i].rect[j].p2,
                                   fastHaarFeature[i].rect[j].p3,
                                   haarFeature[i].rect[j].r, step )
            }
        }
    }
}


/*
 * icvCreateHaarTrainingData
 *
 * Create haar training data used in stage training
 */
static
CvHaarTrainigData* icvCreateHaarTrainingData( CvSize winsize, int maxnumsamples )
{
    CvHaarTrainigData* data;

    CV_FUNCNAME( "icvCreateHaarTrainingData" );

    __BEGIN__;

    data = NULL;
    uchar* ptr = NULL;
    size_t datasize = 0;

    datasize = sizeof( CvHaarTrainigData ) +
          /* sum and tilted */
        ( 2 * (winsize.width + 1) * (winsize.height + 1) * sizeof( sum_type ) +
          sizeof( float ) +      /* normfactor */
          sizeof( float ) +      /* cls */
          sizeof( float )        /* weight */
        ) * maxnumsamples;

    CV_CALL( data = (CvHaarTrainigData*) cvAlloc( datasize ) );
    memset( (void*)data, 0, datasize );
    data->maxnum = maxnumsamples;
    data->winsize = winsize;
    ptr = (uchar*)(data + 1);
    data->sum = cvMat( maxnumsamples, (winsize.width + 1) * (winsize.height + 1),
                       CV_SUM_MAT_TYPE, (void*) ptr );
    ptr += sizeof( sum_type ) * maxnumsamples * (winsize.width+1) * (winsize.height+1);
    data->tilted = cvMat( maxnumsamples, (winsize.width + 1) * (winsize.height + 1),
                       CV_SUM_MAT_TYPE, (void*) ptr );
    ptr += sizeof( sum_type ) * maxnumsamples * (winsize.width+1) * (winsize.height+1);
    data->normfactor = cvMat( 1, maxnumsamples, CV_32FC1, (void*) ptr );
    ptr += sizeof( float ) * maxnumsamples;
    data->cls = cvMat( 1, maxnumsamples, CV_32FC1, (void*) ptr );
    ptr += sizeof( float ) * maxnumsamples;
    data->weights = cvMat( 1, maxnumsamples, CV_32FC1, (void*) ptr );

    data->valcache = NULL;
    data->idxcache = NULL;

    __END__;

    return data;
}

static
void icvReleaseHaarTrainingDataCache( CvHaarTrainigData** haarTrainingData )
{
    if( haarTrainingData != NULL && (*haarTrainingData) != NULL )
    {
        if( (*haarTrainingData)->valcache != NULL )
        {
            cvReleaseMat( &(*haarTrainingData)->valcache );
            (*haarTrainingData)->valcache = NULL;
        }
        if( (*haarTrainingData)->idxcache != NULL )
        {
            cvReleaseMat( &(*haarTrainingData)->idxcache );
            (*haarTrainingData)->idxcache = NULL;
        }
    }
}

static
void icvReleaseHaarTrainingData( CvHaarTrainigData** haarTrainingData )
{
    if( haarTrainingData != NULL && (*haarTrainingData) != NULL )
    {
        icvReleaseHaarTrainingDataCache( haarTrainingData );

        cvFree( haarTrainingData );
    }
}

static
void icvGetTrainingDataCallback( CvMat* mat, CvMat* sampleIdx, CvMat*,
                                 int first, int num, void* userdata )
{
    int i = 0;
    int j = 0;
    float val = 0.0F;
    float normfactor = 0.0F;

    CvHaarTrainingData* training_data;
    CvIntHaarFeatures* haar_features;

#ifdef CV_COL_ARRANGEMENT
    assert( mat->rows >= num );
#else
    assert( mat->cols >= num );
#endif

    training_data = ((CvUserdata*) userdata)->trainingData;
    haar_features = ((CvUserdata*) userdata)->haarFeatures;
    if( sampleIdx == NULL )
    {
        int num_samples;

#ifdef CV_COL_ARRANGEMENT
        num_samples = mat->cols;
#else
        num_samples = mat->rows;
#endif
        for( i = 0; i < num_samples; i++ )
        {
            for( j = 0; j < num; j++ )
            {
                val = cvEvalFastHaarFeature(
                        ( haar_features->fastfeature
                            + first + j ),
                        (sum_type*) (training_data->sum.data.ptr
                            + i * training_data->sum.step),
                        (sum_type*) (training_data->tilted.data.ptr
                            + i * training_data->tilted.step) );
                normfactor = training_data->normfactor.data.fl[i];
                val = ( normfactor == 0.0F ) ? 0.0F : (val / normfactor);

#ifdef CV_COL_ARRANGEMENT
                CV_MAT_ELEM( *mat, float, j, i ) = val;
#else
                CV_MAT_ELEM( *mat, float, i, j ) = val;
#endif
            }
        }
    }
    else
    {
        uchar* idxdata = NULL;
        size_t step    = 0;
        int    numidx  = 0;
        int    idx     = 0;

        assert( CV_MAT_TYPE( sampleIdx->type ) == CV_32FC1 );

        idxdata = sampleIdx->data.ptr;
        if( sampleIdx->rows == 1 )
        {
            step = sizeof( float );
            numidx = sampleIdx->cols;
        }
        else
        {
            step = sampleIdx->step;
            numidx = sampleIdx->rows;
        }

        for( i = 0; i < numidx; i++ )
        {
            for( j = 0; j < num; j++ )
            {
                idx = (int)( *((float*) (idxdata + i * step)) );
                val = cvEvalFastHaarFeature(
                        ( haar_features->fastfeature
                            + first + j ),
                        (sum_type*) (training_data->sum.data.ptr
                            + idx * training_data->sum.step),
                        (sum_type*) (training_data->tilted.data.ptr
                            + idx * training_data->tilted.step) );
                normfactor = training_data->normfactor.data.fl[idx];
                val = ( normfactor == 0.0F ) ? 0.0F : (val / normfactor);

#ifdef CV_COL_ARRANGEMENT
                CV_MAT_ELEM( *mat, float, j, idx ) = val;
#else
                CV_MAT_ELEM( *mat, float, idx, j ) = val;
#endif

            }
        }
    }
#if 0 /*def CV_VERBOSE*/
    if( first % 5000 == 0 )
    {
        fprintf( stderr, "%3d%%\r", (int) (100.0 * first /
            haar_features->count) );
        fflush( stderr );
    }
#endif /* CV_VERBOSE */
}

static
void icvPrecalculate( CvHaarTrainingData* data, CvIntHaarFeatures* haarFeatures,
                      int numprecalculated )
{
    CV_FUNCNAME( "icvPrecalculate" );

    __BEGIN__;

    icvReleaseHaarTrainingDataCache( &data );

    numprecalculated -= numprecalculated % CV_STUMP_TRAIN_PORTION;
    numprecalculated = MIN( numprecalculated, haarFeatures->count );

    if( numprecalculated > 0 )
    {
        //size_t datasize;
        int m;
        CvUserdata userdata;

        /* private variables */
        #ifdef CV_OPENMP
        CvMat t_data;
        CvMat t_idx;
        int first;
        int t_portion;
        int portion = CV_STUMP_TRAIN_PORTION;
        #endif /* CV_OPENMP */

        m = data->sum.rows;

#ifdef CV_COL_ARRANGEMENT
        CV_CALL( data->valcache = cvCreateMat( numprecalculated, m, CV_32FC1 ) );
#else
        CV_CALL( data->valcache = cvCreateMat( m, numprecalculated, CV_32FC1 ) );
#endif
        CV_CALL( data->idxcache = cvCreateMat( numprecalculated, m, CV_IDX_MAT_TYPE ) );

        userdata = cvUserdata( data, haarFeatures );

        #ifdef CV_OPENMP
        #pragma omp parallel for private(t_data, t_idx, first, t_portion)
        for( first = 0; first < numprecalculated; first += portion )
        {
            t_data = *data->valcache;
            t_idx = *data->idxcache;
            t_portion = MIN( portion, (numprecalculated - first) );

            /* indices */
            t_idx.rows = t_portion;
            t_idx.data.ptr = data->idxcache->data.ptr + first * ((size_t)t_idx.step);

            /* feature values */
#ifdef CV_COL_ARRANGEMENT
            t_data.rows = t_portion;
            t_data.data.ptr = data->valcache->data.ptr +
                first * ((size_t) t_data.step );
#else
            t_data.cols = t_portion;
            t_data.data.ptr = data->valcache->data.ptr +
                first * ((size_t) CV_ELEM_SIZE( t_data.type ));
#endif
            icvGetTrainingDataCallback( &t_data, NULL, NULL, first, t_portion,
                                        &userdata );
#ifdef CV_COL_ARRANGEMENT
            cvGetSortedIndices( &t_data, &t_idx, 0 );
#else
            cvGetSortedIndices( &t_data, &t_idx, 1 );
#endif

#ifdef CV_VERBOSE
            putc( '.', stderr );
            fflush( stderr );
#endif /* CV_VERBOSE */

        }

#ifdef CV_VERBOSE
        fprintf( stderr, "\n" );
        fflush( stderr );
#endif /* CV_VERBOSE */

        #else
        icvGetTrainingDataCallback( data->valcache, NULL, NULL, 0, numprecalculated,
                                    &userdata );
#ifdef CV_COL_ARRANGEMENT
        cvGetSortedIndices( data->valcache, data->idxcache, 0 );
#else
        cvGetSortedIndices( data->valcache, data->idxcache, 1 );
#endif
        #endif /* CV_OPENMP */
    }

    __END__;
}

static
void icvSplitIndicesCallback( int compidx, float threshold,
                              CvMat* idx, CvMat** left, CvMat** right,
                              void* userdata )
{
    CvHaarTrainingData* data;
    CvIntHaarFeatures* haar_features;
    int i;
    int m;
    CvFastHaarFeature* fastfeature;

    data = ((CvUserdata*) userdata)->trainingData;
    haar_features = ((CvUserdata*) userdata)->haarFeatures;
    fastfeature = &haar_features->fastfeature[compidx];

    m = data->sum.rows;
    *left = cvCreateMat( 1, m, CV_32FC1 );
    *right = cvCreateMat( 1, m, CV_32FC1 );
    (*left)->cols = (*right)->cols = 0;
    if( idx == NULL )
    {
        for( i = 0; i < m; i++ )
        {
            if( cvEvalFastHaarFeature( fastfeature,
                    (sum_type*) (data->sum.data.ptr + i * data->sum.step),
                    (sum_type*) (data->tilted.data.ptr + i * data->tilted.step) )
                < threshold * data->normfactor.data.fl[i] )
            {
                (*left)->data.fl[(*left)->cols++] = (float) i;
            }
            else
            {
                (*right)->data.fl[(*right)->cols++] = (float) i;
            }
        }
    }
    else
    {
        uchar* idxdata;
        int    idxnum;
        size_t idxstep;
        int    index;

        idxdata = idx->data.ptr;
        idxnum = (idx->rows == 1) ? idx->cols : idx->rows;
        idxstep = (idx->rows == 1) ? CV_ELEM_SIZE( idx->type ) : idx->step;
        for( i = 0; i < idxnum; i++ )
        {
            index = (int) *((float*) (idxdata + i * idxstep));
            if( cvEvalFastHaarFeature( fastfeature,
                    (sum_type*) (data->sum.data.ptr + index * data->sum.step),
                    (sum_type*) (data->tilted.data.ptr + index * data->tilted.step) )
                < threshold * data->normfactor.data.fl[index] )
            {
                (*left)->data.fl[(*left)->cols++] = (float) index;
            }
            else
            {
                (*right)->data.fl[(*right)->cols++] = (float) index;
            }
        }
    }
}

/*
 * icvCreateCARTStageClassifier
 *
 * Create stage classifier with trees as weak classifiers
 * data             - haar training data. It must be created and filled before call
 * minhitrate       - desired min hit rate
 * maxfalsealarm    - desired max false alarm rate
 * symmetric        - if not 0 it is assumed that samples are vertically symmetric
 * numprecalculated - number of features that will be precalculated. Each precalculated
 *   feature need (number_of_samples*(sizeof( float ) + sizeof( short ))) bytes of memory
 * weightfraction   - weight trimming parameter
 * numsplits        - number of binary splits in each tree
 * boosttype        - type of applied boosting algorithm
 * stumperror       - type of used error if Discrete AdaBoost algorithm is applied
 * maxsplits        - maximum total number of splits in all weak classifiers.
 *   If it is not 0 then NULL returned if total number of splits exceeds <maxsplits>.
 */
static
CvIntHaarClassifier* icvCreateCARTStageClassifier( CvHaarTrainingData* data,
                                                   CvMat* sampleIdx,
                                                   CvIntHaarFeatures* haarFeatures,
                                                   float minhitrate,
                                                   float maxfalsealarm,
                                                   int   symmetric,
                                                   float weightfraction,
                                                   int numsplits,
                                                   CvBoostType boosttype,
                                                   CvStumpError stumperror,
                                                   int maxsplits )
{

#ifdef CV_COL_ARRANGEMENT
    int flags = CV_COL_SAMPLE;
#else
    int flags = CV_ROW_SAMPLE;
#endif

    CvStageHaarClassifier* stage = NULL;
    CvBoostTrainer* trainer;
    CvCARTClassifier* cart = NULL;
    CvCARTTrainParams trainParams;
    CvMTStumpTrainParams stumpTrainParams;
    //CvMat* trainData = NULL;
    //CvMat* sortedIdx = NULL;
    CvMat eval;
    int n = 0;
    int m = 0;
    int numpos = 0;
    int numneg = 0;
    int numfalse = 0;
    float sum_stage = 0.0F;
    float threshold = 0.0F;
    float falsealarm = 0.0F;

    //CvMat* sampleIdx = NULL;
    CvMat* trimmedIdx;
    //float* idxdata = NULL;
    //float* tempweights = NULL;
    //int    idxcount = 0;
    CvUserdata userdata;

    int i = 0;
    int j = 0;
    int idx;
    int numsamples;
    int numtrimmed;

    CvCARTHaarClassifier* classifier;
    CvSeq* seq = NULL;
    CvMemStorage* storage = NULL;
    CvMat* weakTrainVals;
    float alpha;
    float sumalpha;
    int num_splits; /* total number of splits in all weak classifiers */

#ifdef CV_VERBOSE
    printf( "+----+----+-+---------+---------+---------+---------+\n" );
    printf( "|  N |%%SMP|F|  ST.THR |    HR   |    FA   | EXP. ERR|\n" );
    printf( "+----+----+-+---------+---------+---------+---------+\n" );
#endif /* CV_VERBOSE */

    n = haarFeatures->count;
    m = data->sum.rows;
    numsamples = (sampleIdx) ? MAX( sampleIdx->rows, sampleIdx->cols ) : m;

    userdata = cvUserdata( data, haarFeatures );

    stumpTrainParams.type = ( boosttype == CV_DABCLASS )
        ? CV_CLASSIFICATION_CLASS : CV_REGRESSION;
    stumpTrainParams.error = ( boosttype == CV_LBCLASS || boosttype == CV_GABCLASS )
        ? CV_SQUARE : stumperror;
    stumpTrainParams.portion = CV_STUMP_TRAIN_PORTION;
    stumpTrainParams.getTrainData = icvGetTrainingDataCallback;
    stumpTrainParams.numcomp = n;
    stumpTrainParams.userdata = &userdata;
    stumpTrainParams.sortedIdx = data->idxcache;

    trainParams.count = numsplits;
    trainParams.stumpTrainParams = (CvClassifierTrainParams*) &stumpTrainParams;
    trainParams.stumpConstructor = cvCreateMTStumpClassifier;
    trainParams.splitIdx = icvSplitIndicesCallback;
    trainParams.userdata = &userdata;

    eval = cvMat( 1, m, CV_32FC1, cvAlloc( sizeof( float ) * m ) );

    storage = cvCreateMemStorage();
    seq = cvCreateSeq( 0, sizeof( *seq ), sizeof( classifier ), storage );

    weakTrainVals = cvCreateMat( 1, m, CV_32FC1 );
    trainer = cvBoostStartTraining( &data->cls, weakTrainVals, &data->weights,
                                    sampleIdx, boosttype );
    num_splits = 0;
    sumalpha = 0.0F;
    do
    {

#ifdef CV_VERBOSE
        int v_wt = 0;
        int v_flipped = 0;
#endif /* CV_VERBOSE */

        trimmedIdx = cvTrimWeights( &data->weights, sampleIdx, weightfraction );
        numtrimmed = (trimmedIdx) ? MAX( trimmedIdx->rows, trimmedIdx->cols ) : m;

#ifdef CV_VERBOSE
        v_wt = 100 * numtrimmed / numsamples;
        v_flipped = 0;

#endif /* CV_VERBOSE */

        cart = (CvCARTClassifier*) cvCreateCARTClassifier( data->valcache,
                        flags,
                        weakTrainVals, 0, 0, 0, trimmedIdx,
                        &(data->weights),
                        (CvClassifierTrainParams*) &trainParams );

        classifier = (CvCARTHaarClassifier*) icvCreateCARTHaarClassifier( numsplits );
        icvInitCARTHaarClassifier( classifier, cart, haarFeatures );

        num_splits += classifier->count;

        cart->release( (CvClassifier**) &cart );

        if( symmetric && (seq->total % 2) )
        {
            float normfactor = 0.0F;
            CvStumpClassifier* stump;

            /* flip haar features */
            for( i = 0; i < classifier->count; i++ )
            {
                if( classifier->feature[i].desc[0] == 'h' )
                {
                    for( j = 0; j < CV_HAAR_FEATURE_MAX &&
                                    classifier->feature[i].rect[j].weight != 0.0F; j++ )
                    {
                        classifier->feature[i].rect[j].r.x = data->winsize.width -
                            classifier->feature[i].rect[j].r.x -
                            classifier->feature[i].rect[j].r.width;
                    }
                }
                else
                {
                    int tmp = 0;

                    /* (x,y) -> (24-x,y) */
                    /* w -> h; h -> w    */
                    for( j = 0; j < CV_HAAR_FEATURE_MAX &&
                                    classifier->feature[i].rect[j].weight != 0.0F; j++ )
                    {
                        classifier->feature[i].rect[j].r.x = data->winsize.width -
                            classifier->feature[i].rect[j].r.x;
                        CV_SWAP( classifier->feature[i].rect[j].r.width,
                                 classifier->feature[i].rect[j].r.height, tmp );
                    }
                }
            }
            icvConvertToFastHaarFeature( classifier->feature,
                                         classifier->fastfeature,
                                         classifier->count, data->winsize.width + 1 );

            stumpTrainParams.getTrainData = NULL;
            stumpTrainParams.numcomp = 1;
            stumpTrainParams.userdata = NULL;
            stumpTrainParams.sortedIdx = NULL;

            for( i = 0; i < classifier->count; i++ )
            {
                for( j = 0; j < numtrimmed; j++ )
                {
                    idx = icvGetIdxAt( trimmedIdx, j );

                    eval.data.fl[idx] = cvEvalFastHaarFeature( &classifier->fastfeature[i],
                        (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                        (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step) );
                    normfactor = data->normfactor.data.fl[idx];
                    eval.data.fl[idx] = ( normfactor == 0.0F )
                        ? 0.0F : (eval.data.fl[idx] / normfactor);
                }

                stump = (CvStumpClassifier*) trainParams.stumpConstructor( &eval,
                    CV_COL_SAMPLE,
                    weakTrainVals, 0, 0, 0, trimmedIdx,
                    &(data->weights),
                    trainParams.stumpTrainParams );

                classifier->threshold[i] = stump->threshold;
                if( classifier->left[i] <= 0 )
                {
                    classifier->val[-classifier->left[i]] = stump->left;
                }
                if( classifier->right[i] <= 0 )
                {
                    classifier->val[-classifier->right[i]] = stump->right;
                }

                stump->release( (CvClassifier**) &stump );

            }

            stumpTrainParams.getTrainData = icvGetTrainingDataCallback;
            stumpTrainParams.numcomp = n;
            stumpTrainParams.userdata = &userdata;
            stumpTrainParams.sortedIdx = data->idxcache;

#ifdef CV_VERBOSE
            v_flipped = 1;
#endif /* CV_VERBOSE */

        } /* if symmetric */
        if( trimmedIdx != sampleIdx )
        {
            cvReleaseMat( &trimmedIdx );
            trimmedIdx = NULL;
        }

        for( i = 0; i < numsamples; i++ )
        {
            idx = icvGetIdxAt( sampleIdx, i );

            eval.data.fl[idx] = classifier->eval( (CvIntHaarClassifier*) classifier,
                (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
                data->normfactor.data.fl[idx] );
        }

        alpha = cvBoostNextWeakClassifier( &eval, &data->cls, weakTrainVals,
                                           &data->weights, trainer );
        sumalpha += alpha;

        for( i = 0; i <= classifier->count; i++ )
        {
            if( boosttype == CV_RABCLASS )
            {
                classifier->val[i] = cvLogRatio( classifier->val[i] );
            }
            classifier->val[i] *= alpha;
        }

        cvSeqPush( seq, (void*) &classifier );

        numpos = 0;
        for( i = 0; i < numsamples; i++ )
        {
            idx = icvGetIdxAt( sampleIdx, i );

            if( data->cls.data.fl[idx] == 1.0F )
            {
                eval.data.fl[numpos] = 0.0F;
                for( j = 0; j < seq->total; j++ )
                {
                    classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j ));
                    eval.data.fl[numpos] += classifier->eval(
                        (CvIntHaarClassifier*) classifier,
                        (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                        (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
                        data->normfactor.data.fl[idx] );
                }
                /* eval.data.fl[numpos] = 2.0F * eval.data.fl[numpos] - seq->total; */
                numpos++;
            }
        }
        icvSort_32f( eval.data.fl, numpos, 0 );
        threshold = eval.data.fl[(int) ((1.0F - minhitrate) * numpos)];

        numneg = 0;
        numfalse = 0;
        for( i = 0; i < numsamples; i++ )
        {
            idx = icvGetIdxAt( sampleIdx, i );

            if( data->cls.data.fl[idx] == 0.0F )
            {
                numneg++;
                sum_stage = 0.0F;
                for( j = 0; j < seq->total; j++ )
                {
                   classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j ));
                   sum_stage += classifier->eval( (CvIntHaarClassifier*) classifier,
                        (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                        (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
                        data->normfactor.data.fl[idx] );
                }
                /* sum_stage = 2.0F * sum_stage - seq->total; */
                if( sum_stage >= (threshold - CV_THRESHOLD_EPS) )
                {
                    numfalse++;
                }
            }
        }
        falsealarm = ((float) numfalse) / ((float) numneg);

#ifdef CV_VERBOSE
        {
            float v_hitrate    = 0.0F;
            float v_falsealarm = 0.0F;
            /* expected error of stage classifier regardless threshold */
            float v_experr = 0.0F;

            for( i = 0; i < numsamples; i++ )
            {
                idx = icvGetIdxAt( sampleIdx, i );

                sum_stage = 0.0F;
                for( j = 0; j < seq->total; j++ )
                {
                    classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j ));
                    sum_stage += classifier->eval( (CvIntHaarClassifier*) classifier,
                        (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                        (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
                        data->normfactor.data.fl[idx] );
                }
                /* sum_stage = 2.0F * sum_stage - seq->total; */
                if( sum_stage >= (threshold - CV_THRESHOLD_EPS) )
                {
                    if( data->cls.data.fl[idx] == 1.0F )
                    {
                        v_hitrate += 1.0F;
                    }
                    else
                    {
                        v_falsealarm += 1.0F;
                    }
                }
                if( ( sum_stage >= 0.0F ) != (data->cls.data.fl[idx] == 1.0F) )
                {
                    v_experr += 1.0F;
                }
            }
            v_experr /= numsamples;
            printf( "|%4d|%3d%%|%c|%9f|%9f|%9f|%9f|\n",
                seq->total, v_wt, ( (v_flipped) ? '+' : '-' ),
                threshold, v_hitrate / numpos, v_falsealarm / numneg,
                v_experr );
            printf( "+----+----+-+---------+---------+---------+---------+\n" );
            fflush( stdout );
        }
#endif /* CV_VERBOSE */

    } while( falsealarm > maxfalsealarm && (!maxsplits || (num_splits < maxsplits) ) );
    cvBoostEndTraining( &trainer );

    if( falsealarm > maxfalsealarm )
    {
        stage = NULL;
    }
    else
    {
        stage = (CvStageHaarClassifier*) icvCreateStageHaarClassifier( seq->total,
                                                                       threshold );
        cvCvtSeqToArray( seq, (CvArr*) stage->classifier );
    }

    /* CLEANUP */
    cvReleaseMemStorage( &storage );
    cvReleaseMat( &weakTrainVals );
    cvFree( &(eval.data.ptr) );

    return (CvIntHaarClassifier*) stage;
}


static
CvBackgroundData* icvCreateBackgroundData( const char* filename, CvSize winsize )
{
    CvBackgroundData* data = NULL;

    const char* dir = NULL;
    char full[PATH_MAX];
    char* imgfilename = NULL;
    size_t datasize = 0;
    int    count = 0;
    FILE*  input = NULL;
    char*  tmp   = NULL;
    int    len   = 0;

    assert( filename != NULL );

    dir = strrchr( filename, '\\' );
    if( dir == NULL )
    {
        dir = strrchr( filename, '/' );
    }
    if( dir == NULL )
    {
        imgfilename = &(full[0]);
    }
    else
    {
        strncpy( &(full[0]), filename, (dir - filename + 1) );
        imgfilename = &(full[(dir - filename + 1)]);
    }

    input = fopen( filename, "r" );
    if( input != NULL )
    {
        count = 0;
        datasize = 0;

        /* count */
        while( !feof( input ) )
        {
            *imgfilename = '\0';
            if( !fgets( imgfilename, PATH_MAX - (int)(imgfilename - full) - 1, input ))
                break;
            len = (int)strlen( imgfilename );
            for( ; len > 0 && isspace(imgfilename[len-1]); len-- )
                imgfilename[len-1] = '\0';
            if( len > 0 )
            {
                if( (*imgfilename) == '#' ) continue; /* comment */
                count++;
                datasize += sizeof( char ) * (strlen( &(full[0]) ) + 1);
            }
        }
        if( count > 0 )
        {
            //rewind( input );
            fseek( input, 0, SEEK_SET );
            datasize += sizeof( *data ) + sizeof( char* ) * count;
            data = (CvBackgroundData*) cvAlloc( datasize );
            memset( (void*) data, 0, datasize );
            data->count = count;
            data->filename = (char**) (data + 1);
            data->last = 0;
            data->round = 0;
            data->winsize = winsize;
            tmp = (char*) (data->filename + data->count);
            count = 0;
            while( !feof( input ) )
            {
                *imgfilename = '\0';
                if( !fgets( imgfilename, PATH_MAX - (int)(imgfilename - full) - 1, input ))
                    break;
                len = (int)strlen( imgfilename );
                if( len > 0 && imgfilename[len-1] == '\n' )
                    imgfilename[len-1] = 0, len--;
                if( len > 0 )
                {
                    if( (*imgfilename) == '#' ) continue; /* comment */
                    data->filename[count++] = tmp;
                    strcpy( tmp, &(full[0]) );
                    tmp += strlen( &(full[0]) ) + 1;
                }
            }
        }
        fclose( input );
    }

    return data;
}

static
void icvReleaseBackgroundData( CvBackgroundData** data )
{
    assert( data != NULL && (*data) != NULL );

    cvFree( data );
}

static
CvBackgroundReader* icvCreateBackgroundReader()
{
    CvBackgroundReader* reader = NULL;

    reader = (CvBackgroundReader*) cvAlloc( sizeof( *reader ) );
    memset( (void*) reader, 0, sizeof( *reader ) );
    reader->src = cvMat( 0, 0, CV_8UC1, NULL );
    reader->img = cvMat( 0, 0, CV_8UC1, NULL );
    reader->offset = cvPoint( 0, 0 );
    reader->scale       = 1.0F;
    reader->scalefactor = 1.4142135623730950488016887242097F;
    reader->stepfactor  = 0.5F;
    reader->point = reader->offset;

    return reader;
}

static
void icvReleaseBackgroundReader( CvBackgroundReader** reader )
{
    assert( reader != NULL && (*reader) != NULL );

    if( (*reader)->src.data.ptr != NULL )
    {
        cvFree( &((*reader)->src.data.ptr) );
    }
    if( (*reader)->img.data.ptr != NULL )
    {
        cvFree( &((*reader)->img.data.ptr) );
    }

    cvFree( reader );
}

static
void icvGetNextFromBackgroundData( CvBackgroundData* data,
                                   CvBackgroundReader* reader )
{
    IplImage* img = NULL;
    size_t datasize = 0;
    int round = 0;
    int i = 0;
    CvPoint offset = cvPoint(0,0);

    assert( data != NULL && reader != NULL );

    if( reader->src.data.ptr != NULL )
    {
        cvFree( &(reader->src.data.ptr) );
        reader->src.data.ptr = NULL;
    }
    if( reader->img.data.ptr != NULL )
    {
        cvFree( &(reader->img.data.ptr) );
        reader->img.data.ptr = NULL;
    }

    #ifdef CV_OPENMP
    #pragma omp critical(c_background_data)
    #endif /* CV_OPENMP */
    {
        for( i = 0; i < data->count; i++ )
        {
            round = data->round;

//#ifdef CV_VERBOSE
//            printf( "Open background image: %s\n", data->filename[data->last] );
//#endif /* CV_VERBOSE */

            data->last = rand() % data->count;
            data->last %= data->count;
            img = cvLoadImage( data->filename[data->last], 0 );
            if( !img )
                continue;
            data->round += data->last / data->count;
            data->round = data->round % (data->winsize.width * data->winsize.height);

            offset.x = round % data->winsize.width;
            offset.y = round / data->winsize.width;

            offset.x = MIN( offset.x, img->width - data->winsize.width );
            offset.y = MIN( offset.y, img->height - data->winsize.height );

            if( img != NULL && img->depth == IPL_DEPTH_8U && img->nChannels == 1 &&
                offset.x >= 0 && offset.y >= 0 )
            {
                break;
            }
            if( img != NULL )
                cvReleaseImage( &img );
            img = NULL;
        }
    }
    if( img == NULL )
    {
        /* no appropriate image */

#ifdef CV_VERBOSE
        printf( "Invalid background description file.\n" );
#endif /* CV_VERBOSE */

        assert( 0 );
        exit( 1 );
    }
    datasize = sizeof( uchar ) * img->width * img->height;
    reader->src = cvMat( img->height, img->width, CV_8UC1, (void*) cvAlloc( datasize ) );
    cvCopy( img, &reader->src, NULL );
    cvReleaseImage( &img );
    img = NULL;

    //reader->offset.x = round % data->winsize.width;
    //reader->offset.y = round / data->winsize.width;
    reader->offset = offset;
    reader->point = reader->offset;
    reader->scale = MAX(
        ((float) data->winsize.width + reader->point.x) / ((float) reader->src.cols),
        ((float) data->winsize.height + reader->point.y) / ((float) reader->src.rows) );

    reader->img = cvMat( (int) (reader->scale * reader->src.rows + 0.5F),
                         (int) (reader->scale * reader->src.cols + 0.5F),
                          CV_8UC1, (void*) cvAlloc( datasize ) );
    cvResize( &(reader->src), &(reader->img) );
}


/*
 * icvGetBackgroundImage
 *
 * Get an image from background
 * <img> must be allocated and have size, previously passed to icvInitBackgroundReaders
 *
 * Usage example:
 * icvInitBackgroundReaders( "bg.txt", cvSize( 24, 24 ) );
 * ...
 * #pragma omp parallel
 * {
 *     ...
 *     icvGetBackgourndImage( cvbgdata, cvbgreader, img );
 *     ...
 * }
 * ...
 * icvDestroyBackgroundReaders();
 */
static
void icvGetBackgroundImage( CvBackgroundData* data,
                            CvBackgroundReader* reader,
                            CvMat* img )
{
    CvMat mat;

    assert( data != NULL && reader != NULL && img != NULL );
    assert( CV_MAT_TYPE( img->type ) == CV_8UC1 );
    assert( img->cols == data->winsize.width );
    assert( img->rows == data->winsize.height );

    if( reader->img.data.ptr == NULL )
    {
        icvGetNextFromBackgroundData( data, reader );
    }

    mat = cvMat( data->winsize.height, data->winsize.width, CV_8UC1 );
    cvSetData( &mat, (void*) (reader->img.data.ptr + reader->point.y * reader->img.step
                              + reader->point.x * sizeof( uchar )), reader->img.step );

    cvCopy( &mat, img, 0 );
    if( (int) ( reader->point.x + (1.0F + reader->stepfactor ) * data->winsize.width )
            < reader->img.cols )
    {
        reader->point.x += (int) (reader->stepfactor * data->winsize.width);
    }
    else
    {
        reader->point.x = reader->offset.x;
        if( (int) ( reader->point.y + (1.0F + reader->stepfactor ) * data->winsize.height )
                < reader->img.rows )
        {
            reader->point.y += (int) (reader->stepfactor * data->winsize.height);
        }
        else
        {
            reader->point.y = reader->offset.y;
            reader->scale *= reader->scalefactor;
            if( reader->scale <= 1.0F )
            {
                reader->img = cvMat( (int) (reader->scale * reader->src.rows),
                                     (int) (reader->scale * reader->src.cols),
                                      CV_8UC1, (void*) (reader->img.data.ptr) );
                cvResize( &(reader->src), &(reader->img) );
            }
            else
            {
                icvGetNextFromBackgroundData( data, reader );
            }
        }
    }
}


/*
 * icvInitBackgroundReaders
 *
 * Initialize background reading process.
 * <cvbgreader> and <cvbgdata> are initialized.
 * Must be called before any usage of background
 *
 * filename - name of background description file
 * winsize  - size of images will be obtained from background
 *
 * return 1 on success, 0 otherwise.
 */
static
int icvInitBackgroundReaders( const char* filename, CvSize winsize )
{
    if( cvbgdata == NULL && filename != NULL )
    {
        cvbgdata = icvCreateBackgroundData( filename, winsize );
    }

    if( cvbgdata )
    {

        #ifdef CV_OPENMP
        #pragma omp parallel
        #endif /* CV_OPENMP */
        {
            #ifdef CV_OPENMP
            #pragma omp critical(c_create_bg_data)
            #endif /* CV_OPENMP */
            {
                if( cvbgreader == NULL )
                {
                    cvbgreader = icvCreateBackgroundReader();
                }
            }
        }

    }

    return (cvbgdata != NULL);
}


/*
 * icvDestroyBackgroundReaders
 *
 * Finish backgournd reading process
 */
static
void icvDestroyBackgroundReaders()
{
    /* release background reader in each thread */
    #ifdef CV_OPENMP
    #pragma omp parallel
    #endif /* CV_OPENMP */
    {
        #ifdef CV_OPENMP
        #pragma omp critical(c_release_bg_data)
        #endif /* CV_OPENMP */
        {
            if( cvbgreader != NULL )
            {
                icvReleaseBackgroundReader( &cvbgreader );
                cvbgreader = NULL;
            }
        }
    }

    if( cvbgdata != NULL )
    {
        icvReleaseBackgroundData( &cvbgdata );
        cvbgdata = NULL;
    }
}


/*
 * icvGetAuxImages
 *
 * Get sum, tilted, sqsum images and calculate normalization factor
 * All images must be allocated.
 */
static
void icvGetAuxImages( CvMat* img, CvMat* sum, CvMat* tilted,
                      CvMat* sqsum, float* normfactor )
{
    CvRect normrect;
    int p0, p1, p2, p3;
    sum_type   valsum   = 0;
    sqsum_type valsqsum = 0;
    double area = 0.0;

    cvIntegral( img, sum, sqsum, tilted );
    normrect = cvRect( 1, 1, img->cols - 2, img->rows - 2 );
    CV_SUM_OFFSETS( p0, p1, p2, p3, normrect, img->cols + 1 )

    area = normrect.width * normrect.height;
    valsum = ((sum_type*) (sum->data.ptr))[p0] - ((sum_type*) (sum->data.ptr))[p1]
           - ((sum_type*) (sum->data.ptr))[p2] + ((sum_type*) (sum->data.ptr))[p3];
    valsqsum = ((sqsum_type*) (sqsum->data.ptr))[p0]
             - ((sqsum_type*) (sqsum->data.ptr))[p1]
             - ((sqsum_type*) (sqsum->data.ptr))[p2]
             + ((sqsum_type*) (sqsum->data.ptr))[p3];

    /* sqrt( valsqsum / area - ( valsum / are )^2 ) * area */
    (*normfactor) = (float) sqrt( (double) (area * valsqsum - (double)valsum * valsum) );
}


/* consumed counter */
typedef uint64 ccounter_t;

#define CCOUNTER_MAX CV_BIG_UINT(0xffffffffffffffff)
#define CCOUNTER_SET_ZERO(cc) ((cc) = 0)
#define CCOUNTER_INC(cc) ( (CCOUNTER_MAX > (cc) ) ? (++(cc)) : (CCOUNTER_MAX) )
#define CCOUNTER_ADD(cc0, cc1) ( ((CCOUNTER_MAX-(cc1)) > (cc0) ) ? ((cc0) += (cc1)) : ((cc0) = CCOUNTER_MAX) )
#define CCOUNTER_DIV(cc0, cc1) ( ((cc1) == 0) ? 0 : ( ((double)(cc0))/(double)(int64)(cc1) ) )



/*
 * icvGetHaarTrainingData
 *
 * Unified method that can now be used for vec file, bg file and bg vec file
 *
 * Fill <data> with samples, passed <cascade>
 */
static
int icvGetHaarTrainingData( CvHaarTrainingData* data, int first, int count,
                            CvIntHaarClassifier* cascade,
                            CvGetHaarTrainingDataCallback callback, void* userdata,
                            int* consumed, double* acceptance_ratio )
{
    int i = 0;
    ccounter_t getcount = 0;
    ccounter_t thread_getcount = 0;
    ccounter_t consumed_count;
    ccounter_t thread_consumed_count;

    /* private variables */
    CvMat img;
    CvMat sum;
    CvMat tilted;
    CvMat sqsum;

    sum_type* sumdata;
    sum_type* tilteddata;
    float*    normfactor;

    /* end private variables */

    assert( data != NULL );
    assert( first + count <= data->maxnum );
    assert( cascade != NULL );
    assert( callback != NULL );

    // if( !cvbgdata ) return 0; this check needs to be done in the callback for BG

    CCOUNTER_SET_ZERO(getcount);
    CCOUNTER_SET_ZERO(thread_getcount);
    CCOUNTER_SET_ZERO(consumed_count);
    CCOUNTER_SET_ZERO(thread_consumed_count);

    #ifdef CV_OPENMP
    #pragma omp parallel private(img, sum, tilted, sqsum, sumdata, tilteddata, \
                                 normfactor, thread_consumed_count, thread_getcount)
    #endif /* CV_OPENMP */
    {
        sumdata    = NULL;
        tilteddata = NULL;
        normfactor = NULL;

        CCOUNTER_SET_ZERO(thread_getcount);
        CCOUNTER_SET_ZERO(thread_consumed_count);
        int ok = 1;

        img = cvMat( data->winsize.height, data->winsize.width, CV_8UC1,
            cvAlloc( sizeof( uchar ) * data->winsize.height * data->winsize.width ) );
        sum = cvMat( data->winsize.height + 1, data->winsize.width + 1,
                     CV_SUM_MAT_TYPE, NULL );
        tilted = cvMat( data->winsize.height + 1, data->winsize.width + 1,
                        CV_SUM_MAT_TYPE, NULL );
        sqsum = cvMat( data->winsize.height + 1, data->winsize.width + 1, CV_SQSUM_MAT_TYPE,
                       cvAlloc( sizeof( sqsum_type ) * (data->winsize.height + 1)
                                                     * (data->winsize.width + 1) ) );

        #ifdef CV_OPENMP
        #pragma omp for schedule(static, 1)
        #endif /* CV_OPENMP */
        for( i = first; (i < first + count); i++ )
        {
            if( !ok )
                continue;
            for( ; ; )
            {
                ok = callback( &img, userdata );
                if( !ok )
                    break;

                CCOUNTER_INC(thread_consumed_count);

                sumdata = (sum_type*) (data->sum.data.ptr + i * data->sum.step);
                tilteddata = (sum_type*) (data->tilted.data.ptr + i * data->tilted.step);
                normfactor = data->normfactor.data.fl + i;
                sum.data.ptr = (uchar*) sumdata;
                tilted.data.ptr = (uchar*) tilteddata;
                icvGetAuxImages( &img, &sum, &tilted, &sqsum, normfactor );
                if( cascade->eval( cascade, sumdata, tilteddata, *normfactor ) != 0.0F )
                {
                    CCOUNTER_INC(thread_getcount);
                    break;
                }
            }

#ifdef CV_VERBOSE
            if( (i - first) % 500 == 0 )
            {
                fprintf( stderr, "%3d%%\r", (int) ( 100.0 * (i - first) / count ) );
                fflush( stderr );
            }
#endif /* CV_VERBOSE */
        }

        cvFree( &(img.data.ptr) );
        cvFree( &(sqsum.data.ptr) );

        #ifdef CV_OPENMP
        #pragma omp critical (c_consumed_count)
        #endif /* CV_OPENMP */
        {
            /* consumed_count += thread_consumed_count; */
            CCOUNTER_ADD(getcount, thread_getcount);
            CCOUNTER_ADD(consumed_count, thread_consumed_count);
        }
    } /* omp parallel */

    if( consumed != NULL )
    {
        *consumed = (int)consumed_count;
    }

    if( acceptance_ratio != NULL )
    {
        /* *acceptance_ratio = ((double) count) / consumed_count; */
        *acceptance_ratio = CCOUNTER_DIV(count, consumed_count);
    }

    return static_cast<int>(getcount);
}

/*
 * icvGetHaarTrainingDataFromBG
 *
 * Fill <data> with background samples, passed <cascade>
 * Background reading process must be initialized before call.
 */
//static
//int icvGetHaarTrainingDataFromBG( CvHaarTrainingData* data, int first, int count,
//                                  CvIntHaarClassifier* cascade, double* acceptance_ratio )
//{
//    int i = 0;
//    ccounter_t consumed_count;
//    ccounter_t thread_consumed_count;
//
//    /* private variables */
//    CvMat img;
//    CvMat sum;
//    CvMat tilted;
//    CvMat sqsum;
//
//    sum_type* sumdata;
//    sum_type* tilteddata;
//    float*    normfactor;
//
//    /* end private variables */
//
//    assert( data != NULL );
//    assert( first + count <= data->maxnum );
//    assert( cascade != NULL );
//
//    if( !cvbgdata ) return 0;
//
//    CCOUNTER_SET_ZERO(consumed_count);
//    CCOUNTER_SET_ZERO(thread_consumed_count);
//
//    #ifdef CV_OPENMP
//    #pragma omp parallel private(img, sum, tilted, sqsum, sumdata, tilteddata,
//                                 normfactor, thread_consumed_count)
//    #endif /* CV_OPENMP */
//    {
//        sumdata    = NULL;
//        tilteddata = NULL;
//        normfactor = NULL;
//
//        CCOUNTER_SET_ZERO(thread_consumed_count);
//
//        img = cvMat( data->winsize.height, data->winsize.width, CV_8UC1,
//            cvAlloc( sizeof( uchar ) * data->winsize.height * data->winsize.width ) );
//        sum = cvMat( data->winsize.height + 1, data->winsize.width + 1,
//                     CV_SUM_MAT_TYPE, NULL );
//        tilted = cvMat( data->winsize.height + 1, data->winsize.width + 1,
//                        CV_SUM_MAT_TYPE, NULL );
//        sqsum = cvMat( data->winsize.height + 1, data->winsize.width + 1,
//                       CV_SQSUM_MAT_TYPE,
//                       cvAlloc( sizeof( sqsum_type ) * (data->winsize.height + 1)
//                                                     * (data->winsize.width + 1) ) );
//
//        #ifdef CV_OPENMP
//        #pragma omp for schedule(static, 1)
//        #endif /* CV_OPENMP */
//        for( i = first; i < first + count; i++ )
//        {
//            for( ; ; )
//            {
//                icvGetBackgroundImage( cvbgdata, cvbgreader, &img );
//
//                CCOUNTER_INC(thread_consumed_count);
//
//                sumdata = (sum_type*) (data->sum.data.ptr + i * data->sum.step);
//                tilteddata = (sum_type*) (data->tilted.data.ptr + i * data->tilted.step);
//                normfactor = data->normfactor.data.fl + i;
//                sum.data.ptr = (uchar*) sumdata;
//                tilted.data.ptr = (uchar*) tilteddata;
//                icvGetAuxImages( &img, &sum, &tilted, &sqsum, normfactor );
//                if( cascade->eval( cascade, sumdata, tilteddata, *normfactor ) != 0.0F )
//                {
//                    break;
//                }
//            }
//
//#ifdef CV_VERBOSE
//            if( (i - first) % 500 == 0 )
//            {
//                fprintf( stderr, "%3d%%\r", (int) ( 100.0 * (i - first) / count ) );
//                fflush( stderr );
//            }
//#endif /* CV_VERBOSE */
//
//        }
//
//        cvFree( &(img.data.ptr) );
//        cvFree( &(sqsum.data.ptr) );
//
//        #ifdef CV_OPENMP
//        #pragma omp critical (c_consumed_count)
//        #endif /* CV_OPENMP */
//        {
//            /* consumed_count += thread_consumed_count; */
//            CCOUNTER_ADD(consumed_count, thread_consumed_count);
//        }
//    } /* omp parallel */
//
//    if( acceptance_ratio != NULL )
//    {
//        /* *acceptance_ratio = ((double) count) / consumed_count; */
//        *acceptance_ratio = CCOUNTER_DIV(count, consumed_count);
//    }
//
//    return count;
//}

int icvGetHaarTraininDataFromVecCallback( CvMat* img, void* userdata )
{
    uchar tmp = 0;
    int r = 0;
    int c = 0;

    assert( img->rows * img->cols == ((CvVecFile*) userdata)->vecsize );

    size_t elements_read = fread( &tmp, sizeof( tmp ), 1, ((CvVecFile*) userdata)->input );
    CV_Assert(elements_read == 1);
    elements_read = fread( ((CvVecFile*) userdata)->vector, sizeof( short ),
           ((CvVecFile*) userdata)->vecsize, ((CvVecFile*) userdata)->input );
    CV_Assert(elements_read == (size_t)((CvVecFile*) userdata)->vecsize);

    if( feof( ((CvVecFile*) userdata)->input ) ||
        (((CvVecFile*) userdata)->last)++ >= ((CvVecFile*) userdata)->count )
    {
        return 0;
    }

    for( r = 0; r < img->rows; r++ )
    {
        for( c = 0; c < img->cols; c++ )
        {
            CV_MAT_ELEM( *img, uchar, r, c ) =
                (uchar) ( ((CvVecFile*) userdata)->vector[r * img->cols + c] );
        }
    }

    return 1;
}

static int icvGetHaarTrainingDataFromBGCallback ( CvMat* img, void* /*userdata*/ )
{
    if (! cvbgdata)
      return 0;

    if (! cvbgreader)
      return 0;

    // just in case icvGetBackgroundImage is not thread-safe ...
    #ifdef CV_OPENMP
    #pragma omp critical (get_background_image_callback)
    #endif /* CV_OPENMP */
    {
      icvGetBackgroundImage( cvbgdata, cvbgreader, img );
    }

    return 1;
}

/*
 * icvGetHaarTrainingDataFromVec
 * Get training data from .vec file
 */
static
int icvGetHaarTrainingDataFromVec( CvHaarTrainingData* data, int first, int count,
                                   CvIntHaarClassifier* cascade,
                                   const char* filename,
                                   int* consumed )
{
    int getcount = 0;

    CV_FUNCNAME( "icvGetHaarTrainingDataFromVec" );

    __BEGIN__;

    CvVecFile file;
    short tmp = 0;

    file.input = NULL;
    if( filename ) file.input = fopen( filename, "rb" );

    if( file.input != NULL )
    {
        size_t elements_read1 = fread( &file.count, sizeof( file.count ), 1, file.input );
        size_t elements_read2 = fread( &file.vecsize, sizeof( file.vecsize ), 1, file.input );
        size_t elements_read3 = fread( &tmp, sizeof( tmp ), 1, file.input );
        size_t elements_read4 = fread( &tmp, sizeof( tmp ), 1, file.input );
        CV_Assert(elements_read1 == 1 && elements_read2 == 1 && elements_read3 == 1 && elements_read4 == 1);

        if( !feof( file.input ) )
        {
            if( file.vecsize != data->winsize.width * data->winsize.height )
            {
                fclose( file.input );
                CV_ERROR( CV_StsError, "Vec file sample size mismatch" );
            }

            file.last = 0;
            file.vector = (short*) cvAlloc( sizeof( *file.vector ) * file.vecsize );
            getcount = icvGetHaarTrainingData( data, first, count, cascade,
                icvGetHaarTraininDataFromVecCallback, &file, consumed, NULL);
            cvFree( &file.vector );
        }
        fclose( file.input );
    }

    __END__;

    return getcount;
}

/*
 * icvGetHaarTrainingDataFromBG
 *
 * Fill <data> with background samples, passed <cascade>
 * Background reading process must be initialized before call, alternaly, a file
 * name to a vec file may be passed, a NULL filename indicates old behaviour
 */
static
int icvGetHaarTrainingDataFromBG( CvHaarTrainingData* data, int first, int count,
                                  CvIntHaarClassifier* cascade, double* acceptance_ratio, const char * filename = NULL )
{
    CV_FUNCNAME( "icvGetHaarTrainingDataFromBG" );

    __BEGIN__;

    if (filename)
    {
        CvVecFile file;
        short tmp = 0;

        file.input = NULL;
        if( filename ) file.input = fopen( filename, "rb" );

        if( file.input != NULL )
        {
            size_t elements_read1 = fread( &file.count, sizeof( file.count ), 1, file.input );
            size_t elements_read2 = fread( &file.vecsize, sizeof( file.vecsize ), 1, file.input );
            size_t elements_read3 = fread( &tmp, sizeof( tmp ), 1, file.input );
            size_t elements_read4 = fread( &tmp, sizeof( tmp ), 1, file.input );
            CV_Assert(elements_read1 == 1 && elements_read2 == 1 && elements_read3 == 1 && elements_read4 == 1);
            if( !feof( file.input ) )
            {
                if( file.vecsize != data->winsize.width * data->winsize.height )
                {
                    fclose( file.input );
                    CV_ERROR( CV_StsError, "Vec file sample size mismatch" );
                }

                file.last = 0;
                file.vector = (short*) cvAlloc( sizeof( *file.vector ) * file.vecsize );
                icvGetHaarTrainingData( data, first, count, cascade,
                    icvGetHaarTraininDataFromVecCallback, &file, NULL, acceptance_ratio);
                cvFree( &file.vector );
            }
            fclose( file.input );
        }
    }
    else
    {
        icvGetHaarTrainingData( data, first, count, cascade,
            icvGetHaarTrainingDataFromBGCallback, NULL, NULL, acceptance_ratio);
    }

    __END__;

    return count;
}

void cvCreateCascadeClassifier( const char* dirname,
                                const char* vecfilename,
                                const char* bgfilename,
                                int npos, int nneg, int nstages,
                                int numprecalculated,
                                int numsplits,
                                float minhitrate, float maxfalsealarm,
                                float weightfraction,
                                int mode, int symmetric,
                                int equalweights,
                                int winwidth, int winheight,
                                int boosttype, int stumperror )
{
    CvCascadeHaarClassifier* cascade = NULL;
    CvHaarTrainingData* data = NULL;
    CvIntHaarFeatures* haar_features;
    CvSize winsize;
    int i = 0;
    int j = 0;
    int poscount = 0;
    int negcount = 0;
    int consumed = 0;
    double false_alarm = 0;
    char stagename[PATH_MAX];
    float posweight = 1.0F;
    float negweight = 1.0F;
    FILE* file;

#ifdef CV_VERBOSE
    double proctime = 0.0F;
#endif /* CV_VERBOSE */

    assert( dirname != NULL );
    assert( bgfilename != NULL );
    assert( vecfilename != NULL );
    assert( nstages > 0 );

    winsize = cvSize( winwidth, winheight );

    cascade = (CvCascadeHaarClassifier*) icvCreateCascadeHaarClassifier( nstages );
    cascade->count = 0;

    if( icvInitBackgroundReaders( bgfilename, winsize ) )
    {
        data = icvCreateHaarTrainingData( winsize, npos + nneg );
        haar_features = icvCreateIntHaarFeatures( winsize, mode, symmetric );

#ifdef CV_VERBOSE
        printf("Number of features used : %d\n", haar_features->count);
#endif /* CV_VERBOSE */

        for( i = 0; i < nstages; i++, cascade->count++ )
        {
            sprintf( stagename, "%s%d/%s", dirname, i, CV_STAGE_CART_FILE_NAME );
            cascade->classifier[i] =
                icvLoadCARTStageHaarClassifier( stagename, winsize.width + 1 );

            if( !icvMkDir( stagename ) )
            {

#ifdef CV_VERBOSE
                printf( "UNABLE TO CREATE DIRECTORY: %s\n", stagename );
#endif /* CV_VERBOSE */

                break;
            }
            if( cascade->classifier[i] != NULL )
            {

#ifdef CV_VERBOSE
                printf( "STAGE: %d LOADED.\n", i );
#endif /* CV_VERBOSE */

                continue;
            }

#ifdef CV_VERBOSE
            printf( "STAGE: %d\n", i );
#endif /* CV_VERBOSE */

            poscount = icvGetHaarTrainingDataFromVec( data, 0, npos,
                (CvIntHaarClassifier*) cascade, vecfilename, &consumed );
#ifdef CV_VERBOSE
            printf( "POS: %d %d %f\n", poscount, consumed,
                    ((float) poscount) / consumed );
#endif /* CV_VERBOSE */

            if( poscount <= 0 )
            {

#ifdef CV_VERBOSE
            printf( "UNABLE TO OBTAIN POS SAMPLES\n" );
#endif /* CV_VERBOSE */

                break;
            }

#ifdef CV_VERBOSE
            proctime = -TIME( 0 );
#endif /* CV_VERBOSE */

            negcount = icvGetHaarTrainingDataFromBG( data, poscount, nneg,
                (CvIntHaarClassifier*) cascade, &false_alarm );
#ifdef CV_VERBOSE
            printf( "NEG: %d %g\n", negcount, false_alarm );
            printf( "BACKGROUND PROCESSING TIME: %.2f\n",
                (proctime + TIME( 0 )) );
#endif /* CV_VERBOSE */

            if( negcount <= 0 )
            {

#ifdef CV_VERBOSE
            printf( "UNABLE TO OBTAIN NEG SAMPLES\n" );
#endif /* CV_VERBOSE */

                break;
            }

            data->sum.rows = data->tilted.rows = poscount + negcount;
            data->normfactor.cols = data->weights.cols = data->cls.cols =
                    poscount + negcount;

            posweight = (equalweights) ? 1.0F / (poscount + negcount) : (0.5F / poscount);
            negweight = (equalweights) ? 1.0F / (poscount + negcount) : (0.5F / negcount);
            for( j = 0; j < poscount; j++ )
            {
                data->weights.data.fl[j] = posweight;
                data->cls.data.fl[j] = 1.0F;

            }
            for( j = poscount; j < poscount + negcount; j++ )
            {
                data->weights.data.fl[j] = negweight;
                data->cls.data.fl[j] = 0.0F;
            }

#ifdef CV_VERBOSE
            proctime = -TIME( 0 );
#endif /* CV_VERBOSE */

            icvPrecalculate( data, haar_features, numprecalculated );

#ifdef CV_VERBOSE
            printf( "PRECALCULATION TIME: %.2f\n", (proctime + TIME( 0 )) );
#endif /* CV_VERBOSE */

#ifdef CV_VERBOSE
            proctime = -TIME( 0 );
#endif /* CV_VERBOSE */

            cascade->classifier[i] = icvCreateCARTStageClassifier(  data, NULL,
                haar_features, minhitrate, maxfalsealarm, symmetric, weightfraction,
                numsplits, (CvBoostType) boosttype, (CvStumpError) stumperror, 0 );

#ifdef CV_VERBOSE
            printf( "STAGE TRAINING TIME: %.2f\n", (proctime + TIME( 0 )) );
#endif /* CV_VERBOSE */

            file = fopen( stagename, "w" );
            if( file != NULL )
            {
                cascade->classifier[i]->save(
                    (CvIntHaarClassifier*) cascade->classifier[i], file );
                fclose( file );
            }
            else
            {

#ifdef CV_VERBOSE
                printf( "FAILED TO SAVE STAGE CLASSIFIER IN FILE %s\n", stagename );
#endif /* CV_VERBOSE */

            }

        }
        icvReleaseIntHaarFeatures( &haar_features );
        icvReleaseHaarTrainingData( &data );

        if( i == nstages )
        {
            char xml_path[1024];
            int len = (int)strlen(dirname);
            CvHaarClassifierCascade* cascade1 = 0;
            strcpy( xml_path, dirname );
            if( xml_path[len-1] == '\\' || xml_path[len-1] == '/' )
                len--;
            strcpy( xml_path + len, ".xml" );
            cascade1 = cvLoadHaarClassifierCascade( dirname, cvSize(winwidth,winheight) );
            if( cascade1 )
                cvSave( xml_path, cascade1 );
            cvReleaseHaarClassifierCascade( &cascade1 );
        }
    }
    else
    {
#ifdef CV_VERBOSE
        printf( "FAILED TO INITIALIZE BACKGROUND READERS\n" );
#endif /* CV_VERBOSE */
    }

    /* CLEAN UP */
    icvDestroyBackgroundReaders();
    cascade->release( (CvIntHaarClassifier**) &cascade );
}

/* tree cascade classifier */

static int icvNumSplits( CvStageHaarClassifier* stage )
{
    int i;
    int num;

    num = 0;
    for( i = 0; i < stage->count; i++ )
    {
        num += ((CvCARTHaarClassifier*) stage->classifier[i])->count;
    }

    return num;
}

static void icvSetNumSamples( CvHaarTrainingData* training_data, int num )
{
    assert( num <= training_data->maxnum );

    training_data->sum.rows = training_data->tilted.rows = num;
    training_data->normfactor.cols = num;
    training_data->cls.cols = training_data->weights.cols = num;
}

static void icvSetWeightsAndClasses( CvHaarTrainingData* training_data,
                              int num1, float weight1, float cls1,
                              int num2, float weight2, float cls2 )
{
    int j;

    assert( num1 + num2 <= training_data->maxnum );

    for( j = 0; j < num1; j++ )
    {
        training_data->weights.data.fl[j] = weight1;
        training_data->cls.data.fl[j] = cls1;
    }
    for( j = num1; j < num1 + num2; j++ )
    {
        training_data->weights.data.fl[j] = weight2;
        training_data->cls.data.fl[j] = cls2;
    }
}

static CvMat* icvGetUsedValues( CvHaarTrainingData* training_data,
                         int start, int num,
                         CvIntHaarFeatures* haar_features,
                         CvStageHaarClassifier* stage )
{
    CvMat* ptr = NULL;
    CvMat* feature_idx = NULL;

    CV_FUNCNAME( "icvGetUsedValues" );

    __BEGIN__;

    int num_splits;
    int i, j;
    int r;
    int total, last;

    num_splits = icvNumSplits( stage );

    CV_CALL( feature_idx = cvCreateMat( 1, num_splits, CV_32SC1 ) );

    total = 0;
    for( i = 0; i < stage->count; i++ )
    {
        CvCARTHaarClassifier* cart;

        cart = (CvCARTHaarClassifier*) stage->classifier[i];
        for( j = 0; j < cart->count; j++ )
        {
            feature_idx->data.i[total++] = cart->compidx[j];
        }
    }
    icvSort_32s( feature_idx->data.i, total, 0 );

    last = 0;
    for( i = 1; i < total; i++ )
    {
        if( feature_idx->data.i[i] != feature_idx->data.i[last] )
        {
            feature_idx->data.i[++last] = feature_idx->data.i[i];
        }
    }
    total = last + 1;
    CV_CALL( ptr = cvCreateMat( num, total, CV_32FC1 ) );


    #ifdef CV_OPENMP
    #pragma omp parallel for
    #endif
    for( r = start; r < start + num; r++ )
    {
        int c;

        for( c = 0; c < total; c++ )
        {
            float val, normfactor;
            int fnum;

            fnum = feature_idx->data.i[c];

            val = cvEvalFastHaarFeature( haar_features->fastfeature + fnum,
                (sum_type*) (training_data->sum.data.ptr
                        + r * training_data->sum.step),
                (sum_type*) (training_data->tilted.data.ptr
                        + r * training_data->tilted.step) );
            normfactor = training_data->normfactor.data.fl[r];
            val = ( normfactor == 0.0F ) ? 0.0F : (val / normfactor);
            CV_MAT_ELEM( *ptr, float, r - start, c ) = val;
        }
    }

    __END__;

    cvReleaseMat( &feature_idx );

    return ptr;
}

/* possible split in the tree */
typedef struct CvSplit
{
    CvTreeCascadeNode* parent;
    CvTreeCascadeNode* single_cluster;
    CvTreeCascadeNode* multiple_clusters;
    int num_clusters;
    float single_multiple_ratio;

    struct CvSplit* next;
} CvSplit;


void cvCreateTreeCascadeClassifier( const char* dirname,
                                    const char* vecfilename,
                                    const char* bgfilename,
                                    int npos, int nneg, int nstages,
                                    int numprecalculated,
                                    int numsplits,
                                    float minhitrate, float maxfalsealarm,
                                    float weightfraction,
                                    int mode, int symmetric,
                                    int equalweights,
                                    int winwidth, int winheight,
                                    int boosttype, int stumperror,
                                    int maxtreesplits, int minpos, bool bg_vecfile )
{
    CvTreeCascadeClassifier* tcc = NULL;
    CvIntHaarFeatures* haar_features = NULL;
    CvHaarTrainingData* training_data = NULL;
    CvMat* vals = NULL;
    CvMat* cluster_idx = NULL;
    CvMat* idx = NULL;
    CvMat* features_idx = NULL;

    CV_FUNCNAME( "cvCreateTreeCascadeClassifier" );

    __BEGIN__;

    int i, k;
    CvTreeCascadeNode* leaves;
    int best_num, cur_num;
    CvSize winsize;
    char stage_name[PATH_MAX];
    char buf[PATH_MAX];
    char* suffix;
    int total_splits;

    int poscount;
    int negcount;
    int consumed;
    double false_alarm;
    double proctime;

    int nleaves;
    double required_leaf_fa_rate;
    float neg_ratio;

    int max_clusters;

    max_clusters = CV_MAX_CLUSTERS;
    neg_ratio = (float) nneg / npos;

    nleaves = 1 + MAX( 0, maxtreesplits );
    required_leaf_fa_rate = pow( (double) maxfalsealarm, (double) nstages ) / nleaves;

    printf( "Required leaf false alarm rate: %g\n", required_leaf_fa_rate );

    total_splits = 0;

    winsize = cvSize( winwidth, winheight );

    CV_CALL( cluster_idx = cvCreateMat( 1, npos + nneg, CV_32SC1 ) );
    CV_CALL( idx = cvCreateMat( 1, npos + nneg, CV_32SC1 ) );

    CV_CALL( tcc = (CvTreeCascadeClassifier*)
        icvLoadTreeCascadeClassifier( dirname, winwidth + 1, &total_splits ) );
    CV_CALL( leaves = icvFindDeepestLeaves( tcc ) );

    CV_CALL( icvPrintTreeCascade( tcc->root ) );

    haar_features = icvCreateIntHaarFeatures( winsize, mode, symmetric );

    printf( "Number of features used : %d\n", haar_features->count );

    training_data = icvCreateHaarTrainingData( winsize, npos + nneg );

    sprintf( stage_name, "%s/", dirname );
    suffix = stage_name + strlen( stage_name );

    if (! bg_vecfile)
      if( !icvInitBackgroundReaders( bgfilename, winsize ) && nstages > 0 )
          CV_ERROR( CV_StsError, "Unable to read negative images" );

    if( nstages > 0 )
    {
        /* width-first search in the tree */
        do
        {
            CvSplit* first_split;
            CvSplit* last_split;
            CvSplit* cur_split;

            CvTreeCascadeNode* parent;
            CvTreeCascadeNode* cur_node;
            CvTreeCascadeNode* last_node;

            first_split = last_split = cur_split = NULL;
            parent = leaves;
            leaves = NULL;
            do
            {
                int best_clusters; /* best selected number of clusters */
                float posweight, negweight;
                double leaf_fa_rate;

                if( parent ) sprintf( buf, "%d", parent->idx );
                else sprintf( buf, "NULL" );
                printf( "\nParent node: %s\n\n", buf );

                printf( "*** 1 cluster ***\n" );

                tcc->eval = icvEvalTreeCascadeClassifierFilter;
                /* find path from the root to the node <parent> */
                icvSetLeafNode( tcc, parent );

                /* load samples */
                consumed = 0;
                poscount = icvGetHaarTrainingDataFromVec( training_data, 0, npos,
                    (CvIntHaarClassifier*) tcc, vecfilename, &consumed );

                printf( "POS: %d %d %f\n", poscount, consumed, ((double) poscount)/consumed );

                if( poscount <= 0 )
                    CV_ERROR( CV_StsError, "Unable to obtain positive samples" );

                fflush( stdout );

                proctime = -TIME( 0 );

                nneg = (int) (neg_ratio * poscount);
                negcount = icvGetHaarTrainingDataFromBG( training_data, poscount, nneg,
                    (CvIntHaarClassifier*) tcc, &false_alarm, bg_vecfile ? bgfilename : NULL );
                printf( "NEG: %d %g\n", negcount, false_alarm );

                printf( "BACKGROUND PROCESSING TIME: %.2f\n", (proctime + TIME( 0 )) );

                if( negcount <= 0 )
                    CV_ERROR( CV_StsError, "Unable to obtain negative samples" );

                leaf_fa_rate = false_alarm;
                if( leaf_fa_rate <= required_leaf_fa_rate )
                {
                    printf( "Required leaf false alarm rate achieved. "
                            "Branch training terminated.\n" );
                }
                else if( nleaves == 1 && tcc->next_idx == nstages )
                {
                    printf( "Required number of stages achieved. "
                            "Branch training terminated.\n" );
                }
                else
                {
                    CvTreeCascadeNode* single_cluster;
                    CvTreeCascadeNode* multiple_clusters;
                    int single_num;

                    icvSetNumSamples( training_data, poscount + negcount );
                    posweight = (equalweights) ? 1.0F / (poscount + negcount) : (0.5F/poscount);
                    negweight = (equalweights) ? 1.0F / (poscount + negcount) : (0.5F/negcount);
                    icvSetWeightsAndClasses( training_data,
                        poscount, posweight, 1.0F, negcount, negweight, 0.0F );

                    fflush( stdout );

                    /* precalculate feature values */
                    proctime = -TIME( 0 );
                    icvPrecalculate( training_data, haar_features, numprecalculated );
                    printf( "Precalculation time: %.2f\n", (proctime + TIME( 0 )) );

                    /* train stage classifier using all positive samples */
                    CV_CALL( single_cluster = icvCreateTreeCascadeNode() );
                    fflush( stdout );

                    proctime = -TIME( 0 );
                    single_cluster->stage =
                        (CvStageHaarClassifier*) icvCreateCARTStageClassifier(
                            training_data, NULL, haar_features,
                            minhitrate, maxfalsealarm, symmetric,
                            weightfraction, numsplits, (CvBoostType) boosttype,
                            (CvStumpError) stumperror, 0 );
                    printf( "Stage training time: %.2f\n", (proctime + TIME( 0 )) );

                    single_num = icvNumSplits( single_cluster->stage );
                    best_num = single_num;
                    best_clusters = 1;
                    multiple_clusters = NULL;

                    printf( "Number of used features: %d\n", single_num );

                    if( maxtreesplits >= 0 )
                    {
                        max_clusters = MIN( max_clusters, maxtreesplits - total_splits + 1 );
                    }

                    /* try clustering */
                    vals = NULL;
                    for( k = 2; k <= max_clusters; k++ )
                    {
                        int cluster;
                        int stop_clustering;

                        printf( "*** %d clusters ***\n", k );

                        /* check whether clusters are big enough */
                        stop_clustering = ( k * minpos > poscount );
                        if( !stop_clustering )
                        {
                            int num[CV_MAX_CLUSTERS];

                            if( k == 2 )
                            {
                                proctime = -TIME( 0 );
                                CV_CALL( vals = icvGetUsedValues( training_data, 0, poscount,
                                    haar_features, single_cluster->stage ) );
                                printf( "Getting values for clustering time: %.2f\n", (proctime + TIME(0)) );
                                printf( "Value matirx size: %d x %d\n", vals->rows, vals->cols );
                                fflush( stdout );

                                cluster_idx->cols = vals->rows;
                                for( i = 0; i < negcount; i++ ) idx->data.i[i] = poscount + i;
                            }

                            proctime = -TIME( 0 );

                            CV_CALL( cvKMeans2( vals, k, cluster_idx, CV_TERM_CRITERIA() ) );

                            printf( "Clustering time: %.2f\n", (proctime + TIME( 0 )) );

                            for( cluster = 0; cluster < k; cluster++ ) num[cluster] = 0;
                            for( i = 0; i < cluster_idx->cols; i++ )
                                num[cluster_idx->data.i[i]]++;
                            for( cluster = 0; cluster < k; cluster++ )
                            {
                                if( num[cluster] < minpos )
                                {
                                    stop_clustering = 1;
                                    break;
                                }
                            }
                        }

                        if( stop_clustering )
                        {
                            printf( "Clusters are too small. Clustering aborted.\n" );
                            break;
                        }

                        cur_num = 0;
                        cur_node = last_node = NULL;
                        for( cluster = 0; (cluster < k) && (cur_num < best_num); cluster++ )
                        {
                            CvTreeCascadeNode* new_node;

                            int num_splits;
                            int last_pos;
                            int total_pos;

                            printf( "Cluster: %d\n", cluster );

                            last_pos = negcount;
                            for( i = 0; i < cluster_idx->cols; i++ )
                            {
                                if( cluster_idx->data.i[i] == cluster )
                                {
                                    idx->data.i[last_pos++] = i;
                                }
                            }
                            idx->cols = last_pos;

                            total_pos = idx->cols - negcount;
                            printf( "# pos: %d of %d. (%d%%)\n", total_pos, poscount,
                                100 * total_pos / poscount );

                            CV_CALL( new_node = icvCreateTreeCascadeNode() );
                            if( last_node ) last_node->next = new_node;
                            else cur_node = new_node;
                            last_node = new_node;

                            posweight = (equalweights)
                                ? 1.0F / (total_pos + negcount) : (0.5F / total_pos);
                            negweight = (equalweights)
                                ? 1.0F / (total_pos + negcount) : (0.5F / negcount);

                            icvSetWeightsAndClasses( training_data,
                                poscount, posweight, 1.0F, negcount, negweight, 0.0F );

                            /* CV_DEBUG_SAVE( idx ); */

                            fflush( stdout );

                            proctime = -TIME( 0 );
                            new_node->stage = (CvStageHaarClassifier*)
                                icvCreateCARTStageClassifier( training_data, idx, haar_features,
                                    minhitrate, maxfalsealarm, symmetric,
                                    weightfraction, numsplits, (CvBoostType) boosttype,
                                    (CvStumpError) stumperror, best_num - cur_num );
                            printf( "Stage training time: %.2f\n", (proctime + TIME( 0 )) );

                            if( !(new_node->stage) )
                            {
                                printf( "Stage training aborted.\n" );
                                cur_num = best_num + 1;
                            }
                            else
                            {
                                num_splits = icvNumSplits( new_node->stage );
                                cur_num += num_splits;

                                printf( "Number of used features: %d\n", num_splits );
                            }
                        } /* for each cluster */

                        if( cur_num < best_num )
                        {
                            icvReleaseTreeCascadeNodes( &multiple_clusters );
                            best_num = cur_num;
                            best_clusters = k;
                            multiple_clusters = cur_node;
                        }
                        else
                        {
                            icvReleaseTreeCascadeNodes( &cur_node );
                        }
                    } /* try different number of clusters */
                    cvReleaseMat( &vals );

                    CvSplit* curSplit;
                    CV_CALL( curSplit = (CvSplit*) cvAlloc( sizeof( *curSplit ) ) );
                    CV_ZERO_OBJ( curSplit );

                    if( last_split ) last_split->next = curSplit;
                    else first_split = curSplit;
                    last_split = curSplit;

                    curSplit->single_cluster = single_cluster;
                    curSplit->multiple_clusters = multiple_clusters;
                    curSplit->num_clusters = best_clusters;
                    curSplit->parent = parent;
                    curSplit->single_multiple_ratio = (float) single_num / best_num;
                }

                if( parent ) parent = parent->next_same_level;
            } while( parent );

            /* choose which nodes should be splitted */
            do
            {
                float max_single_multiple_ratio;

                cur_split = NULL;
                max_single_multiple_ratio = 0.0F;
                last_split = first_split;
                while( last_split )
                {
                    if( last_split->single_cluster && last_split->multiple_clusters &&
                        last_split->single_multiple_ratio > max_single_multiple_ratio )
                    {
                        max_single_multiple_ratio = last_split->single_multiple_ratio;
                        cur_split = last_split;
                    }
                    last_split = last_split->next;
                }
                if( cur_split )
                {
                    if( maxtreesplits < 0 ||
                        cur_split->num_clusters <= maxtreesplits - total_splits + 1 )
                    {
                        cur_split->single_cluster = NULL;
                        total_splits += cur_split->num_clusters - 1;
                    }
                    else
                    {
                        icvReleaseTreeCascadeNodes( &(cur_split->multiple_clusters) );
                        cur_split->multiple_clusters = NULL;
                    }
                }
            } while( cur_split );

            /* attach new nodes to the tree */
            leaves = last_node = NULL;
            last_split = first_split;
            while( last_split )
            {
                cur_node = (last_split->multiple_clusters)
                    ? last_split->multiple_clusters : last_split->single_cluster;
                parent = last_split->parent;
                if( parent ) parent->child = cur_node;

                /* connect leaves via next_same_level and save them */
                for( ; cur_node; cur_node = cur_node->next )
                {
                    FILE* file;

                    if( last_node ) last_node->next_same_level = cur_node;
                    else leaves = cur_node;
                    last_node = cur_node;
                    cur_node->parent = parent;

                    cur_node->idx = tcc->next_idx;
                    tcc->next_idx++;
                    sprintf( suffix, "%d/%s", cur_node->idx, CV_STAGE_CART_FILE_NAME );
                    file = NULL;
                    if( icvMkDir( stage_name ) && (file = fopen( stage_name, "w" )) != 0 )
                    {
                        cur_node->stage->save( (CvIntHaarClassifier*) cur_node->stage, file );
                        fprintf( file, "\n%d\n%d\n",
                            ((parent) ? parent->idx : -1),
                            ((cur_node->next) ? tcc->next_idx : -1) );
                    }
                    else
                    {
                        printf( "Failed to save classifier into %s\n", stage_name );
                    }
                    if( file ) fclose( file );
                }

                if( parent ) sprintf( buf, "%d", parent->idx );
                else sprintf( buf, "NULL" );
                printf( "\nParent node: %s\n", buf );
                printf( "Chosen number of splits: %d\n\n", (last_split->multiple_clusters)
                    ? (last_split->num_clusters - 1) : 0 );

                cur_split = last_split;
                last_split = last_split->next;
                cvFree( &cur_split );
            } /* for each split point */

            printf( "Total number of splits: %d\n", total_splits );

            if( !(tcc->root) ) tcc->root = leaves;
            CV_CALL( icvPrintTreeCascade( tcc->root ) );

        } while( leaves );

        /* save the cascade to xml file */
        {
            char xml_path[1024];
            int len = (int)strlen(dirname);
            CvHaarClassifierCascade* cascade = 0;
            strcpy( xml_path, dirname );
            if( xml_path[len-1] == '\\' || xml_path[len-1] == '/' )
                len--;
            strcpy( xml_path + len, ".xml" );
            cascade = cvLoadHaarClassifierCascade( dirname, cvSize(winwidth,winheight) );
            if( cascade )
                cvSave( xml_path, cascade );
            cvReleaseHaarClassifierCascade( &cascade );
        }

    } /* if( nstages > 0 ) */

    /* check cascade performance */
    printf( "\nCascade performance\n" );

    tcc->eval = icvEvalTreeCascadeClassifier;

    /* load samples */
    consumed = 0;
    poscount = icvGetHaarTrainingDataFromVec( training_data, 0, npos,
        (CvIntHaarClassifier*) tcc, vecfilename, &consumed );

    printf( "POS: %d %d %f\n", poscount, consumed,
        (consumed > 0) ? (((float) poscount)/consumed) : 0 );

    if( poscount <= 0 )
        fprintf( stderr, "Warning: unable to obtain positive samples\n" );

    proctime = -TIME( 0 );

    negcount = icvGetHaarTrainingDataFromBG( training_data, poscount, nneg,
        (CvIntHaarClassifier*) tcc, &false_alarm, bg_vecfile ? bgfilename : NULL );

    printf( "NEG: %d %g\n", negcount, false_alarm );

    printf( "BACKGROUND PROCESSING TIME: %.2f\n", (proctime + TIME( 0 )) );

    if( negcount <= 0 )
        fprintf( stderr, "Warning: unable to obtain negative samples\n" );

    __END__;

    if (! bg_vecfile)
      icvDestroyBackgroundReaders();

    if( tcc ) tcc->release( (CvIntHaarClassifier**) &tcc );
    icvReleaseIntHaarFeatures( &haar_features );
    icvReleaseHaarTrainingData( &training_data );
    cvReleaseMat( &cluster_idx );
    cvReleaseMat( &idx );
    cvReleaseMat( &vals );
    cvReleaseMat( &features_idx );
}



void cvCreateTrainingSamples( const char* filename,
                              const char* imgfilename, int bgcolor, int bgthreshold,
                              const char* bgfilename, int count,
                              int invert, int maxintensitydev,
                              double maxxangle, double maxyangle, double maxzangle,
                              int showsamples,
                              int winwidth, int winheight )
{
    CvSampleDistortionData data;

    assert( filename != NULL );
    assert( imgfilename != NULL );

    if( !icvMkDir( filename ) )
    {
        fprintf( stderr, "Unable to create output file: %s\n", filename );
        return;
    }
    if( icvStartSampleDistortion( imgfilename, bgcolor, bgthreshold, &data ) )
    {
        FILE* output = NULL;

        output = fopen( filename, "wb" );
        if( output != NULL )
        {
            int hasbg;
            int i;
            CvMat sample;
            int inverse;

            hasbg = 0;
            hasbg = (bgfilename != NULL && icvInitBackgroundReaders( bgfilename,
                     cvSize( winwidth,winheight ) ) );

            sample = cvMat( winheight, winwidth, CV_8UC1, cvAlloc( sizeof( uchar ) *
                            winheight * winwidth ) );

            icvWriteVecHeader( output, count, sample.cols, sample.rows );

            if( showsamples )
            {
                cvNamedWindow( "Sample", CV_WINDOW_AUTOSIZE );
            }

            inverse = invert;
            for( i = 0; i < count; i++ )
            {
                if( hasbg )
                {
                    icvGetBackgroundImage( cvbgdata, cvbgreader, &sample );
                }
                else
                {
                    cvSet( &sample, cvScalar( bgcolor ) );
                }

                if( invert == CV_RANDOM_INVERT )
                {
                    inverse = (rand() > (RAND_MAX/2));
                }
                icvPlaceDistortedSample( &sample, inverse, maxintensitydev,
                    maxxangle, maxyangle, maxzangle,
                    0   /* nonzero means placing image without cut offs */,
                    0.0 /* nozero adds random shifting                  */,
                    0.0 /* nozero adds random scaling                   */,
                    &data );

                if( showsamples )
                {
                    cvShowImage( "Sample", &sample );
                    if( cvWaitKey( 0 ) == 27 )
                    {
                        showsamples = 0;
                    }
                }

                icvWriteVecSample( output, &sample );

#ifdef CV_VERBOSE
                if( i % 500 == 0 )
                {
                    printf( "\r%3d%%", 100 * i / count );
                }
#endif /* CV_VERBOSE */
            }
            icvDestroyBackgroundReaders();
            cvFree( &(sample.data.ptr) );
            fclose( output );
        } /* if( output != NULL ) */

        icvEndSampleDistortion( &data );
    }

#ifdef CV_VERBOSE
    printf( "\r      \r" );
#endif /* CV_VERBOSE */

}

#define CV_INFO_FILENAME "info.dat"


void cvCreateTestSamples( const char* infoname,
                          const char* imgfilename, int bgcolor, int bgthreshold,
                          const char* bgfilename, int count,
                          int invert, int maxintensitydev,
                          double maxxangle, double maxyangle, double maxzangle,
                          int showsamples,
                          int winwidth, int winheight )
{
    CvSampleDistortionData data;

    assert( infoname != NULL );
    assert( imgfilename != NULL );
    assert( bgfilename != NULL );

    if( !icvMkDir( infoname ) )
    {

#if CV_VERBOSE
        fprintf( stderr, "Unable to create directory hierarchy: %s\n", infoname );
#endif /* CV_VERBOSE */

        return;
    }
    if( icvStartSampleDistortion( imgfilename, bgcolor, bgthreshold, &data ) )
    {
        char fullname[PATH_MAX];
        char* filename;
        CvMat win;
        FILE* info;

        if( icvInitBackgroundReaders( bgfilename, cvSize( 10, 10 ) ) )
        {
            int i;
            int x, y, width, height;
            float scale;
            float maxscale;
            int inverse;

            if( showsamples )
            {
                cvNamedWindow( "Image", CV_WINDOW_AUTOSIZE );
            }

            info = fopen( infoname, "w" );
            strcpy( fullname, infoname );
            filename = strrchr( fullname, '\\' );
            if( filename == NULL )
            {
                filename = strrchr( fullname, '/' );
            }
            if( filename == NULL )
            {
                filename = fullname;
            }
            else
            {
                filename++;
            }

            count = MIN( count, cvbgdata->count );
            inverse = invert;
            for( i = 0; i < count; i++ )
            {
                icvGetNextFromBackgroundData( cvbgdata, cvbgreader );

                maxscale = MIN( 0.7F * cvbgreader->src.cols / winwidth,
                                   0.7F * cvbgreader->src.rows / winheight );
                if( maxscale < 1.0F ) continue;

                scale = (maxscale - 1.0F) * rand() / RAND_MAX + 1.0F;
                width = (int) (scale * winwidth);
                height = (int) (scale * winheight);
                x = (int) ((0.1+0.8 * rand()/RAND_MAX) * (cvbgreader->src.cols - width));
                y = (int) ((0.1+0.8 * rand()/RAND_MAX) * (cvbgreader->src.rows - height));

                cvGetSubArr( &cvbgreader->src, &win, cvRect( x, y ,width, height ) );
                if( invert == CV_RANDOM_INVERT )
                {
                    inverse = (rand() > (RAND_MAX/2));
                }
                icvPlaceDistortedSample( &win, inverse, maxintensitydev,
                                         maxxangle, maxyangle, maxzangle,
                                         1, 0.0, 0.0, &data );


                sprintf( filename, "%04d_%04d_%04d_%04d_%04d.jpg",
                         (i + 1), x, y, width, height );

                if( info )
                {
                    fprintf( info, "%s %d %d %d %d %d\n",
                        filename, 1, x, y, width, height );
                }

                cvSaveImage( fullname, &cvbgreader->src );
                if( showsamples )
                {
                    cvShowImage( "Image", &cvbgreader->src );
                    if( cvWaitKey( 0 ) == 27 )
                    {
                        showsamples = 0;
                    }
                }
            }
            if( info ) fclose( info );
            icvDestroyBackgroundReaders();
        }
        icvEndSampleDistortion( &data );
    }
}


/* End of file. */
