/*
************************** SLEDOVANIE OBJEKTU VO VIDEU *************************
********************************* Peter Betko **********************************

autor: Peter Betko
datum: 11.10.2011

Program na sledovanie objektu vo videu, s vyuzitim algoritmov zalozenych na baze
Casticoveho filtra (Particle filter).

subor cvparticle.h - systemovy model, praca s casticami
*/
/* The MIT License
 * 
 * Copyright (c) 2008, Naotoshi Seo <sonots(at)sonots.com>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 */
#ifndef CV_PARTICLE_INCLUDED
#define CV_PARTICLE_INCLUDED

#include "cv.h"
#include "cvaux.h"
#include "cxcore.h"

#include <time.h>
#include "cvsetrow.h"
#include "cvsetcol.h"
#include "cvlogsum.h"
#include "cvanglemean.h"
#include "cvrandgauss.h"

/******************************* Structures **********************************/
/**
 * Particle Filter structure
 */
typedef struct CvParticle {
    // config
    int num_states;    /**< Number of tracking states, e.g.,
                          4 if x, y, width, height */
    int num_particles; /**< Number of particles */
    bool logweight;    /**< log weights are stored in "weights". */
    // transition
    CvMat* dynamics;   /**< num_states x num_states. Dynamics model. */
    CvRNG  rng;        /**< Random seed */
    CvMat* std;        /**< num_states x 1. Standard deviation for gaussian noise
                          Set standard deviation == 0 for no noise */
    CvMat* stds;       /**< num_states x num_particles. 
                          Std for each particle so that you could be varying 
                          noise variance for each particle.
                          "std" is used if "stds" is not set. */
    CvMat* bound;      /**< num_states x 3 (lowerbound, upperbound, 
                          wrap_around (like angle) flag 0 or 1)
                          Set lowerbound == upperbound to express no bound */
    // particle states
    CvMat* particles;  /**< num_states x num_particles. The particles. 
                          The transition states values of all particles. */
    CvMat* weights;    /**< 1 x num_particles. The weights of 
                          each particle respect to the particle id in "particles". 
                          "weights" are used to approximated the posterior pdf. */
} CvParticle;

/**************************** Function Prototypes ****************************/

#ifndef NO_DOXYGEN
CVAPI(CvParticle*) cvCreateParticle( int num_states, int num_particles, bool logweight );
CVAPI(void) cvReleaseParticle( CvParticle** p );

CVAPI(void) cvParticleSetDynamics( CvParticle* p, const CvMat* dynamics );
CVAPI(void) cvParticleSetNoise( CvParticle* p, CvRNG rng, const CvMat* std );
CVAPI(void) cvParticleSetBound( CvParticle* p, const CvMat* bound );

CVAPI(int)  cvParticleGetMax( const CvParticle* p );
CVAPI(void) cvParticleGetMean( const CvParticle* p, CvMat* meanp );
CVAPI(void) cvParticlePrint( const CvParticle* p, int p_id );

CVAPI(void) cvParticleBound( CvParticle* p );
CVAPI(void) cvParticleNormalize( CvParticle* p );

CVAPI(void) cvParticleInit( CvParticle* p, const CvParticle* init );
CVAPI(void) cvParticleTransition( CvParticle* p );
CVAPI(void) cvParticleResample( CvParticle* p );
#endif

/*************************** Constructor / Destructor *************************/

/**
 * Allocate Particle filter structure
 *
 * @param num_states    Number of tracking states, e.g., 4 if x, y, width, height
 * @param num_particles Number of particles
 * @param logweight     The weights parameter is log  or not
 * @return CvParticle*
 */
CVAPI(CvParticle*) cvCreateParticle( int num_states, 
                                     int num_particles, 
                                     bool logweight CV_DEFAULT(false) )
{
    CvParticle *p = NULL;
    CV_FUNCNAME( "cvCreateParticle" );
    __CV_BEGIN__;
    CV_ASSERT( num_states > 0 );
    CV_ASSERT( num_particles > 0 );
    p = (CvParticle *) cvAlloc( sizeof( CvParticle ) );
    p->num_particles = num_particles;
    p->num_states    = num_states;
    p->dynamics      = cvCreateMat( num_states, num_states, CV_32FC1 );
    p->rng           = 1;
    p->std           = cvCreateMat( num_states, 1, CV_32FC1 );
    p->bound         = cvCreateMat( num_states, 3, CV_32FC1 );
    p->particles     = cvCreateMat( num_states, num_particles, CV_32FC1 );
    p->weights       = cvCreateMat( 1, num_particles, CV_64FC1 );
    p->logweight     = logweight;
    p->stds          = NULL;

    // Default dynamics: next state = curr state + noise
    cvSetIdentity( p->dynamics, cvScalar(1.0) );
    cvSet( p->std, cvScalar(1.0) );

    cvZero( p->bound );

    __CV_END__;
    return p;
}

/**
 * Release Particle filter structure
 *
 * @param particle
 */
CVAPI(void) cvReleaseParticle( CvParticle** particle )
{
    CvParticle *p = NULL;
    CV_FUNCNAME( "cvReleaseParticle" );
    __CV_BEGIN__;
    p = *particle;
    if( !p ) __CV_EXIT__;
    
    CV_CALL( cvReleaseMat( &p->dynamics ) );
    CV_CALL( cvReleaseMat( &p->std ) );
    CV_CALL( cvReleaseMat( &p->bound ) );
    CV_CALL( cvReleaseMat( &p->particles ) );
    CV_CALL( cvReleaseMat( &p->weights ) );
    if( p->stds != NULL )
        CV_CALL( cvReleaseMat( &p->stds ) );

    CV_CALL( cvFree( &p ) );
    __CV_END__;
}

/***************************** Setter ***************************************/

/**
 * Set dynamics model
 *
 * @param particle
 * @param dynamics (num_states) x (num_states). dynamics model
 *    new_state = dynamics * curr_state + noise
 */
CVAPI(void) cvParticleSetDynamics( CvParticle* p, const CvMat* dynamics )
{
    CV_FUNCNAME( "cvParticleSetDynamics" );
    __CV_BEGIN__;
    CV_ASSERT( p->num_states == dynamics->rows );
    CV_ASSERT( p->num_states == dynamics->cols );
    //cvCopy( dynamics, p->dynamics );
    cvConvert( dynamics, p->dynamics );
    __CV_END__;
}

/**
 * Set noise model
 *
 * @param particle
 * @param rng      random seed. refer cvRNG(time(NULL))
 * @param std      num_states x 1. standard deviation for gaussian noise
 *                 Set standard deviation == 0 for no noise
 */
CVAPI(void) cvParticleSetNoise( CvParticle* p, CvRNG rng, const CvMat* std )
{
    CV_FUNCNAME( "cvParticleSetNoise" );
    __CV_BEGIN__;
    CV_ASSERT( p->num_states == std->rows );
    p->rng = rng;
    //cvCopy( std, p->std );
    cvConvert( std, p->std );
    __CV_END__;
}

/**
 * Set lowerbound and upperbound used for bounding tracking state transition
 *
 * @param particle
 * @param bound    num_states x 3 (lowerbound, upperbound, circular flag 0 or 1)
 *                 Set lowerbound == upperbound to express no bound
 */
CVAPI(void) cvParticleSetBound( CvParticle* p, const CvMat* bound )
{
    CV_FUNCNAME( "cvParticleSetBound" );
    __CV_BEGIN__;
    CV_ASSERT( p->num_states == bound->rows );
    CV_ASSERT( 3 == bound->cols );
    //cvCopy( bound, p->bound );
    cvConvert( bound, p->bound );
    __CV_END__;
}

/************************ Utility ******************************************/

/**
 * Get id of the most probable particle
 *
 * @param particle
 * @return int
 */
CVAPI(int) cvParticleGetMax( const CvParticle* p )
{
    double minval, maxval;
    CvPoint min_loc, max_loc;
    cvMinMaxLoc( p->weights, &minval, &maxval, &min_loc, &max_loc );
    return max_loc.x;
}

/**
 * Get evaluation of the most probable particle
 *
 * @param particle
 * @return double
 */
CVAPI(double) cvParticleGetMaxVal( const CvParticle* p )
{
    double minval, maxval;
    CvPoint min_loc, max_loc;
    cvMinMaxLoc( p->weights, &minval, &maxval, &min_loc, &max_loc );
    return maxval;
}

/**
 * Get the mean state (particle)
 *
 * @param particle
 * @param meanp     num_states x 1, CV_32FC1 or CV_64FC1
 * @return CVAPI(void)
 */
CVAPI(void) cvParticleGetMean( const CvParticle* p, CvMat* meanp )
{
    CvMat* weights = NULL;
    CvMat* particles_i, hdr;
    int i, j;
    CV_FUNCNAME( "cvParticleGetMean" );
    __CV_BEGIN__;
    CV_ASSERT( meanp->rows == p->num_states && meanp->cols == 1 );
    if( !p->logweight )
    {
        weights = p->weights;
    }
    else
    {
        weights = cvCreateMat( 1, p->num_particles, p->weights->type );
        cvExp( p->weights, weights );
    }

    for( i = 0; i < p->num_states; i++ )
    {
        int circular = (int) cvmGet( p->bound, i, 2 );
        if( !circular ) // usual mean
        {
            particles_i = cvGetRow( p->particles, &hdr, i );
            double mean = 0;
            for( j = 0; j < p->num_particles; j++ )
            {
                mean += cvmGet( particles_i, 0, j ) * cvmGet( weights, 0, j );
            }
            cvmSet( meanp, i, 0, mean );
        }
        else // wrapped mean (angle)
        {
            double wrap = cvmGet( p->bound, i, 1 ) - cvmGet( p->bound, i, 0 );
            particles_i = cvGetRow( p->particles, &hdr, i );
            CvScalar mean = cvAngleMean( particles_i, weights, wrap );
            cvmSet( meanp, i, 0, mean.val[0] );
        }
    }

    if( weights != p->weights )
        cvReleaseMat( &weights );
    __CV_END__;
}


/**
 * Print states of a particle
 *
 * @param particle
 * @param p_id      particle id
 */
CVAPI(void) cvParticlePrint( const CvParticle*p, int p_id CV_DEFAULT(-1) )
{
    if( p_id == -1 ) // print all
    {
        int n;
        for( n = 0; n < p->num_particles; n++ )
        {
            cvParticlePrint( p, n );
        }
    }
    else {
        int i;
        for( i = 0; i < p->num_states; i++ )
        {
            printf( "%6.1f ", cvmGet( p->particles, i, p_id ) );
        }
        printf( "\n" );
        fflush( stdout );
    }
}

/****************************** Helper functions ******************************/
/*
 * Do normalization of weights
 *
 * @param particle
 * @see cvParticleResample
 */
CVAPI(void) cvParticleNormalize( CvParticle* p )
{
    if( !p->logweight )
    {
        CvScalar normterm = cvSum( p->weights );
        cvScale( p->weights, p->weights, 1.0 / normterm.val[0] );
    }
    else // log version
    {
        CvScalar normterm = cvLogSum( p->weights );
		//printf("%.3f %.3f %.3f %.3f\n", normterm.val[0],normterm.val[1],normterm.val[2],normterm.val[3]);
        cvSubS( p->weights, normterm, p->weights );
    }
}

/**
 * Apply lower bound and upper bound for particle states.
 *
 * @param particle
 * @note Used by See also functions
 * @see cvParticleTransition
 */
CVAPI(void) cvParticleBound( CvParticle* p )
{
    int row, col;
    double lower, upper;
    int circular;
    CvMat* stateparticles, hdr;
    float state;
    // @todo:     np.width   = (double)MAX( 2.0, MIN( maxX - 1 - x, width ) );
    for( row = 0; row < p->num_states; row++ )
    {
        lower = cvmGet( p->bound, row, 0 );
        upper = cvmGet( p->bound, row, 1 );
        circular = (int) cvmGet( p->bound, row, 2 );
        if( lower == upper ) continue; // no bound flag
        if( circular ) {
            for( col = 0; col < p->num_particles; col++ ) {
                state = cvmGet( p->particles, row, col );
                state = state < lower ? state + upper : ( state >= upper ? state - upper : state );
                cvmSet( p->particles, row, col, state );
            }
        } else {
            stateparticles = cvGetRow( p->particles, &hdr, row );
            cvMinS( stateparticles, upper, stateparticles );
            cvMaxS( stateparticles, lower, stateparticles );
        }
    }
}

/******************* Main (Related to Algorithm) *****************************/

/**
 * Initialize states
 *
 * If initial states are given, these states are uniformly copied.
 * If not given, states are uniform randomly sampled within lowerbound 
 * and upperbound regions.
 *
 * @param particle
 * @param init       initial states.
 */
CVAPI(void) cvParticleInit( CvParticle* p, const CvParticle* init = NULL )
{
    int i, c, n, s;
    if( init ) // copy
    {
        int *num_copy;
        CvMat init_particle;

        int divide = p->num_particles / init->num_particles;
        int remain = p->num_particles - divide * init->num_particles;
        num_copy = (int*) malloc( init->num_particles * sizeof(int) );
        for( i = 0; i < init->num_particles; i++ )
        {
            num_copy[i] = divide + ( i < remain ? 1 : 0 );
        }
        
        n = 0; // copy all states once
        for( i = 0; i < init->num_particles; i++ )
        {
            cvGetCol( init->particles, &init_particle, i );
            for( c = 0; c < num_copy[i]; c++ )
            {
                cvSetCol( &init_particle, p->particles, n++ );
            }
        }
        // randomize partial states if necessary
        for( s = 0; s < init->num_states; s++ )
        {
            n = 0;
            for( i = 0; i < init->num_particles; i++ )
            {
                if( FLT_MAX - cvmGet( init->particles, s, i ) < FLT_EPSILON ) // randomize flag
                {
                    CvScalar lower, upper;
                    CvMat* statenoiseT = cvCreateMat( num_copy[i], 1, p->particles->type );
                    lower = cvScalar( cvmGet( p->bound, s, 0 ) );
                    upper = cvScalar( cvmGet( p->bound, s, 1 ) );
                    cvRandArr( &p->rng, statenoiseT, CV_RAND_UNI, lower, upper );
                    for( c = 0; c < num_copy[i]; c++ )
                    {
                        cvmSet( p->particles, s, n++, cvmGet( statenoiseT, c, 0 ) );
                    }
                    cvReleaseMat( &statenoiseT );
                }
            }
        }

        free( num_copy );
    } 
    else // randomize all states
    {
        CvScalar lower, upper;
        CvMat* statenoiseT = cvCreateMat( p->num_particles, 1, p->particles->type );
        CvMat* statenoise  = cvCreateMat( 1, p->num_particles, p->particles->type );
        for( s = 0; s < p->num_states; s++ )
        {
            lower = cvScalar( cvmGet( p->bound, s, 0 ) );
            upper = cvScalar( cvmGet( p->bound, s, 1 ) );
            cvRandArr( &p->rng, statenoiseT, CV_RAND_UNI, lower, upper );
            cvT( statenoiseT, statenoise );
            cvSetRow( statenoise, p->particles, s );
        }
        cvReleaseMat( &statenoise );
        cvReleaseMat( &statenoiseT );
    }
}

/**
 * Samples new particles from given particles
 *
 * Currently suppports only linear combination of states transition model. 
 * Write up a function by yourself to supports nonlinear dynamics
 * such as Taylor series model and call your function instead of this function. 
 * Other functions should not necessary be modified.
 *
 * @param particle
 * @note Uses See also functions inside.
 * @see cvParticleBound
 */
CVAPI(void) cvParticleTransition( CvParticle* p )
{
    int i, j;
    CvMat* transits = cvCreateMat( p->num_states, p->num_particles, p->particles->type );
    CvMat* noises   = cvCreateMat( p->num_states, p->num_particles, p->particles->type );
    CvMat* noise, noisehdr;
    double std;
    
    // dynamics
    cvMatMul( p->dynamics, p->particles, transits );
    
    // noise generation
    if( p->stds == NULL ) //sum je pre vsetky particles rovnaky
    {
        for( i = 0; i < p->num_states; i++ ) //pre kazdy state (x,y,w,h,r)
        {
            std = cvmGet( p->std, i, 0 );
            noise = cvGetRow( noises, &noisehdr, i );
            if( std == 0.0 )
                cvZero( noise );
            else
                cvRandArr( &p->rng, noise, CV_RAND_NORMAL, cvScalar(0), cvScalar( std ) );
        }
    }
    else
    {
        for( i = 0; i < p->num_states; i++ )
        {
            for( j = 0; j < p->num_particles; j++ )
            {
                std = cvmGet( p->stds, i, j );
                if( std == 0.0 )
                    cvmSet( noises, i, j, 0.0 );
                else
                    cvmSet( noises, i, j, cvRandGauss( &p->rng, std ) );
            }
        }
    }

    // dynamics + noise
    cvAdd( transits, noises, p->particles );

    cvReleaseMat( &transits );
    cvReleaseMat( &noises );

    cvParticleBound( p );
}

/**
 * Re-samples a set of particles according to their weights to produce a
 * new set of unweighted particles
 *
 * Simply copy, not uniform randomly selects
 *
 * @param particle
 * @note Uses See also functions inside.
 */
CVAPI(void) cvParticleResample( CvParticle* p )
{
    int i, j, np, k = 0;
    CvMat* particle, hdr;
    CvMat* new_particles = cvCreateMat( p->num_states, p->num_particles, p->particles->type );
    double weight;
    int max_loc;

    k = 0;
    for( i = 0; i < p->num_particles; i++ ) //weights su uz normalizovane (ich suma je 1)
    {
        particle = cvGetCol( p->particles, &hdr, i ); //ulozi sa x,y,w,h,r o particle
        weight = cvmGet( p->weights, 0, i ); //ulozi sa weight particlu
        weight = p->logweight ? exp( weight ) : weight; //ak su vahy ulozene vo weights -> weight = e^weight
        np = cvRound( weight * p->num_particles ); //particle s vyssim ohodnotenim sa skopiruje do viacerych novych particles
        for( j = 0; j < np; j++ )
        {
            cvSetCol( particle, new_particles, k++ );
            if( k == p->num_particles )
                goto exit;
        }
    }

	//pokial sme este neskopirovali vsekty particles, zvysne skopirujeme podla najlepsie ohodnotenej particle
    max_loc = cvParticleGetMax( p );
    particle = cvGetCol( p->particles, &hdr, max_loc );
    while( k < p->num_particles )  //kym sa nenastavia vsetky particles
        cvSetCol( particle, new_particles, k++ ); //nastav zostavajucim particles maximalne ohodnotenie?

exit:
    cvReleaseMat( &p->particles );
    p->particles = new_particles;
}

#endif
