/*#******************************************************************************
** 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.
**
**
** bioinspired : interfaces allowing OpenCV users to integrate Human Vision System models. Presented models originate from Jeanny Herault's original research and have been reused and adapted by the author&collaborators for computed vision applications since his thesis with Alice Caplier at Gipsa-Lab.
** Use: extract still images & image sequences features, from contours details to motion spatio-temporal features, etc. for high level visual scene analysis. Also contribute to image enhancement/compression such as tone mapping.
**
** Maintainers : Listic lab (code author current affiliation & applications) and Gipsa Lab (original research origins & applications)
**
**  Creation - enhancement process 2007-2011
**      Author: Alexandre Benoit (benoit.alexandre.vision@gmail.com), LISTIC lab, Annecy le vieux, France
**
** Theses algorithm have been developped by Alexandre BENOIT since his thesis with Alice Caplier at Gipsa-Lab (www.gipsa-lab.inpg.fr) and the research he pursues at LISTIC Lab (www.listic.univ-savoie.fr).
** Refer to the following research paper for more information:
** Benoit A., Caplier A., Durette B., Herault, J., "USING HUMAN VISUAL SYSTEM MODELING FOR BIO-INSPIRED LOW LEVEL IMAGE PROCESSING", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773, DOI: http://dx.doi.org/10.1016/j.cviu.2010.01.011
** This work have been carried out thanks to Jeanny Herault who's research and great discussions are the basis of all this work, please take a look at his book:
** Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891.
**
** The retina filter includes the research contributions of phd/research collegues from which code has been redrawn by the author :
** _take a look at the retinacolor.hpp module to discover Brice Chaix de Lavarene color mosaicing/demosaicing and the reference paper:
** ====> B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007
** _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions.
** ====> more informations in the above cited Jeanny Heraults's book.
**
**                          License Agreement
**               For Open Source Computer Vision Library
**
** Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
** Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved.
**
**               For Human Visual System tools (bioinspired)
** Copyright (C) 2007-2011, LISTIC Lab, Annecy le Vieux and GIPSA Lab, Grenoble, France, 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:
**
** * Redistributions of source code must retain the above copyright notice,
**    this list of conditions and the following disclaimer.
**
** * Redistributions in binary form must reproduce the above copyright notice,
**    this list of conditions and the following disclaimer in the documentation
**    and/or other materials provided with the distribution.
**
** * The name of the copyright holders may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** This software is provided by the copyright holders and contributors "as is" and
** any express or implied warranties, including, but not limited to, the implied
** warranties of merchantability and fitness for a particular purpose are disclaimed.
** In no event shall the Intel Corporation or contributors be liable for any direct,
** indirect, incidental, special, exemplary, or consequential damages
** (including, but not limited to, procurement of substitute goods or services;
** loss of use, data, or profits; or business interruption) however caused
** and on any theory of liability, whether in contract, strict liability,
** or tort (including negligence or otherwise) arising in any way out of
** the use of this software, even if advised of the possibility of such damage.
*******************************************************************************/

#ifndef __TEMPLATEBUFFER_HPP__
#define __TEMPLATEBUFFER_HPP__

#include <valarray>
#include <cstdlib>
#include <iostream>
#include <cmath>


//#define __TEMPLATEBUFFERDEBUG //define TEMPLATEBUFFERDEBUG in order to display debug information

namespace cv
{
namespace bioinspired
{
//// If a parallelization method is available then, you should define MAKE_PARALLEL, in the other case, the classical serial code will be used
#define MAKE_PARALLEL
// ==> then include required includes
#ifdef MAKE_PARALLEL

// ==> declare usefull generic tools
template <class type>
class Parallel_clipBufferValues: public cv::ParallelLoopBody
{
private:
    type *bufferToClip;
    type minValue, maxValue;

public:
    Parallel_clipBufferValues(type* bufferToProcess, const type min, const type max)
        : bufferToClip(bufferToProcess), minValue(min), maxValue(max) { }

    virtual void operator()( const cv::Range &r ) const CV_OVERRIDE {
        type *inputOutputBufferPTR=bufferToClip+r.start;
        for (int jf = r.start; jf != r.end; ++jf, ++inputOutputBufferPTR)
        {
            if (*inputOutputBufferPTR>maxValue)
                *inputOutputBufferPTR=maxValue;
            else if (*inputOutputBufferPTR<minValue)
                *inputOutputBufferPTR=minValue;
        }
    }
};
#endif

    /**
    * @class TemplateBuffer
    * @brief this class is a simple template memory buffer which contains basic functions to get information on or normalize the buffer content
    * note that thanks to the parent STL template class "valarray", it is possible to perform easily operations on the full array such as addition, product etc.
    * @author Alexandre BENOIT (benoit.alexandre.vision@gmail.com), helped by Gelu IONESCU (gelu.ionescu@lis.inpg.fr)
    * creation date: september 2007
    */
    template <class type> class TemplateBuffer : public std::valarray<type>
    {
    public:

        /**
        * constructor for monodimensional array
        * @param dim: the size of the vector
        */
        TemplateBuffer(const size_t dim=0)
            : std::valarray<type>((type)0, dim)
        {
            _NBrows=1;
            _NBcolumns=dim;
            _NBdepths=1;
            _NBpixels=dim;
            _doubleNBpixels=2*dim;
        }

        /**
        * constructor by copy for monodimensional array
        * @param pVal: the pointer to a buffer to copy
        * @param dim: the size of the vector
        */
        TemplateBuffer(const type* pVal, const size_t dim)
            : std::valarray<type>(pVal, dim)
        {
            _NBrows=1;
            _NBcolumns=dim;
            _NBdepths=1;
            _NBpixels=dim;
            _doubleNBpixels=2*dim;
        }

        /**
        * constructor for bidimensional array
        * @param dimRows: the size of the vector
        * @param dimColumns: the size of the vector
        * @param depth: the number of layers of the buffer in its third dimension (3 of color images, 1 for gray images.
        */
        TemplateBuffer(const size_t dimRows, const size_t dimColumns, const size_t depth=1)
            : std::valarray<type>((type)0, dimRows*dimColumns*depth)
        {
#ifdef TEMPLATEBUFFERDEBUG
            std::cout<<"TemplateBuffer::TemplateBuffer: new buffer, size="<<dimRows<<", "<<dimColumns<<", "<<depth<<"valarraySize="<<this->size()<<std::endl;
#endif
            _NBrows=dimRows;
            _NBcolumns=dimColumns;
            _NBdepths=depth;
            _NBpixels=dimRows*dimColumns;
            _doubleNBpixels=2*dimRows*dimColumns;
            //_createTableIndex();
#ifdef TEMPLATEBUFFERDEBUG
            std::cout<<"TemplateBuffer::TemplateBuffer: construction successful"<<std::endl;
#endif

        }

        /**
        * copy constructor
        * @param toCopy
        * @return thenconstructed instance
        *emplateBuffer(const TemplateBuffer &toCopy)
        :_NBrows(toCopy.getNBrows()),_NBcolumns(toCopy.getNBcolumns()),_NBdepths(toCopy.getNBdephs()), _NBpixels(toCopy.getNBpixels()), _doubleNBpixels(toCopy.getNBpixels()*2)
        //std::valarray<type>(toCopy)
        {
        memcpy(Buffer(), toCopy.Buffer(), this->size());
        }*/
        /**
        * destructor
        */
        virtual ~TemplateBuffer()
        {
#ifdef TEMPLATEBUFFERDEBUG
            std::cout<<"~TemplateBuffer"<<std::endl;
#endif
        }

        /**
        * delete the buffer content (set zeros)
        */
        inline void setZero() { std::valarray<type>::operator=(0); } //memset(Buffer(), 0, sizeof(type)*_NBpixels); }

        /**
        * @return the numbers of rows (height) of the images used by the object
        */
        inline unsigned int getNBrows() { return (unsigned int)_NBrows; }

        /**
        * @return the numbers of columns (width) of the images used by the object
        */
        inline unsigned int getNBcolumns() { return (unsigned int)_NBcolumns; }

        /**
        * @return the numbers of pixels (width*height) of the images used by the object
        */
        inline unsigned int getNBpixels() { return (unsigned int)_NBpixels; }

        /**
        * @return the numbers of pixels (width*height) of the images used by the object
        */
        inline unsigned int getDoubleNBpixels() { return (unsigned int)_doubleNBpixels; }

        /**
        * @return the numbers of depths (3rd dimension: 1 for gray images, 3 for rgb images) of the images used by the object
        */
        inline unsigned int getDepthSize() { return (unsigned int)_NBdepths; }

        /**
        * resize the buffer and recompute table index etc.
        */
        void resizeBuffer(const size_t dimRows, const size_t dimColumns, const size_t depth=1)
        {
            this->resize(dimRows*dimColumns*depth);
            _NBrows=dimRows;
            _NBcolumns=dimColumns;
            _NBdepths=depth;
            _NBpixels=dimRows*dimColumns;
            _doubleNBpixels=2*dimRows*dimColumns;
        }

        inline TemplateBuffer<type> & operator=(const std::valarray<type> &b)
        {
            //std::cout<<"TemplateBuffer<type> & operator= affect vector: "<<std::endl;
            std::valarray<type>::operator=(b);
            return *this;
        }

        inline TemplateBuffer<type> & operator=(const type &b)
        {
            //std::cout<<"TemplateBuffer<type> & operator= affect value: "<<b<<std::endl;
            std::valarray<type>::operator=(b);
            return *this;
        }

        /*  inline const type  &operator[](const unsigned int &b)
        {
        return (*this)[b];
        }
        */
        /**
        * @return the buffer adress in non const mode
        */
        inline type*    Buffer()            {    return &(*this)[0];    }

        ///////////////////////////////////////////////////////
        // Standard Image manipulation functions

        /**
        * standard 0 to 255 image normalization function
        * @param inputOutputBuffer: the image to be normalized (rewrites the input), if no parameter, then, the built in buffer reachable by getOutput() function is normalized
        * @param nbPixels: specifies the number of pixel on which the normalization should be performed, if 0, then all pixels specified in the constructor are processed
        * @param maxOutputValue: the maximum output value
        */
        static void normalizeGrayOutput_0_maxOutputValue(type *inputOutputBuffer, const size_t nbPixels, const type maxOutputValue=(type)255.0);

        /**
        * standard 0 to 255 image normalization function
        * @param inputOutputBuffer: the image to be normalized (rewrites the input), if no parameter, then, the built in buffer reachable by getOutput() function is normalized
        * @param nbPixels: specifies the number of pixel on which the normalization should be performed, if 0, then all pixels specified in the constructor are processed
        * @param maxOutputValue: the maximum output value
        */
        void normalizeGrayOutput_0_maxOutputValue(const type maxOutputValue=(type)255.0) { normalizeGrayOutput_0_maxOutputValue(this->Buffer(), this->size(), maxOutputValue); }

        /**
        * sigmoide image normalization function (saturates min and max values)
        * @param meanValue: specifies the mean value of th pixels to be processed
        * @param sensitivity: strenght of the sigmoide
        * @param inputPicture: the image to be normalized if no parameter, then, the built in buffer reachable by getOutput() function is normalized
        * @param outputBuffer: the ouput buffer on which the result is writed, if no parameter, then, the built in buffer reachable by getOutput() function is normalized
        * @param maxOutputValue: the maximum output value
        */
        static void normalizeGrayOutputCentredSigmoide(const type meanValue, const type sensitivity, const type maxOutputValue, type *inputPicture, type *outputBuffer, const unsigned int nbPixels);

        /**
        * sigmoide image normalization function on the current buffer (saturates min and max values)
        * @param meanValue: specifies the mean value of th pixels to be processed
        * @param sensitivity: strenght of the sigmoide
        * @param maxOutputValue: the maximum output value
        */
        inline void normalizeGrayOutputCentredSigmoide(const type meanValue=(type)0.0, const type sensitivity=(type)2.0, const type maxOutputValue=(type)255.0) {  CV_UNUSED(maxOutputValue); normalizeGrayOutputCentredSigmoide(meanValue, sensitivity, 255.0, this->Buffer(), this->Buffer(), this->getNBpixels()); }

        /**
        * sigmoide image normalization function (saturates min and max values), in this function, the sigmoide is centered on low values (high saturation of the medium and high values
        * @param inputPicture: the image to be normalized if no parameter, then, the built in buffer reachable by getOutput() function is normalized
        * @param outputBuffer: the ouput buffer on which the result is writed, if no parameter, then, the built in buffer reachable by getOutput() function is normalized
        * @param sensitivity: strenght of the sigmoide
        * @param maxOutputValue: the maximum output value
        */
        void normalizeGrayOutputNearZeroCentreredSigmoide(type *inputPicture=(type*)NULL, type *outputBuffer=(type*)NULL, const type sensitivity=(type)40, const type maxOutputValue=(type)255.0);

        /**
        * center and reduct the image (image-mean)/std
        * @param inputOutputBuffer: the image to be normalized if no parameter, the result is rewrited on it
        */
        void centerReductImageLuminance(type *inputOutputBuffer=(type*)NULL);

        /**
        * @return standard deviation of the buffer
        */
        double getStandardDeviation()
        {
            double standardDeviation=0;
            double meanValue=getMean();

            type *bufferPTR=Buffer();
            for (unsigned int i=0;i<this->size();++i)
            {
                double diff=(*(bufferPTR++)-meanValue);
                standardDeviation+=diff*diff;
            }
            return std::sqrt(standardDeviation/this->size());
        }

        /**
        * Clip buffer histogram
        * @param minRatio: the minimum ratio of the lower pixel values, range=[0,1] and lower than maxRatio
        * @param maxRatio: the aximum ratio of the higher pixel values, range=[0,1] and higher than minRatio
        */
        void clipHistogram(double minRatio, double maxRatio, double maxOutputValue)
        {

            if (minRatio>=maxRatio)
            {
                std::cerr<<"TemplateBuffer::clipHistogram: minRatio must be inferior to maxRatio, buffer unchanged"<<std::endl;
                return;
            }

            /*    minRatio=min(max(minRatio, 1.0),0.0);
            maxRatio=max(max(maxRatio, 0.0),1.0);
            */

            // find the pixel value just above the threshold
            const double maxThreshold=this->max()*maxRatio;
            const double minThreshold=(this->max()-this->min())*minRatio+this->min();

            type *bufferPTR=this->Buffer();

            double deltaH=maxThreshold;
            double deltaL=maxThreshold;

            double updatedHighValue=maxThreshold;
            double updatedLowValue=maxThreshold;

            for (unsigned int i=0;i<this->size();++i)
            {
                double curentValue=(double)*(bufferPTR++);

                // updating "closest to the high threshold" pixel value
                double highValueTest=maxThreshold-curentValue;
                if (highValueTest>0)
                {
                    if (deltaH>highValueTest)
                    {
                        deltaH=highValueTest;
                        updatedHighValue=curentValue;
                    }
                }

                // updating "closest to the low threshold" pixel value
                double lowValueTest=curentValue-minThreshold;
                if (lowValueTest>0)
                {
                    if (deltaL>lowValueTest)
                    {
                        deltaL=lowValueTest;
                        updatedLowValue=curentValue;
                    }
                }
            }

            std::cout<<"Tdebug"<<std::endl;
            std::cout<<"deltaL="<<deltaL<<", deltaH="<<deltaH<<std::endl;
            std::cout<<"this->max()"<<this->max()<<"maxThreshold="<<maxThreshold<<"updatedHighValue="<<updatedHighValue<<std::endl;
            std::cout<<"this->min()"<<this->min()<<"minThreshold="<<minThreshold<<"updatedLowValue="<<updatedLowValue<<std::endl;
            // clipping values outside than the updated thresholds
            bufferPTR=this->Buffer();
#ifdef MAKE_PARALLEL // call the TemplateBuffer multitreaded clipping method
            parallel_for_(cv::Range(0,this->size()), Parallel_clipBufferValues<type>(bufferPTR, updatedLowValue, updatedHighValue));
#else

            for (unsigned int i=0;i<this->size();++i, ++bufferPTR)
            {
                if (*bufferPTR<updatedLowValue)
                    *bufferPTR=updatedLowValue;
                else if (*bufferPTR>updatedHighValue)
                    *bufferPTR=updatedHighValue;
            }
#endif
            normalizeGrayOutput_0_maxOutputValue(this->Buffer(), this->size(), maxOutputValue);

        }

        /**
        * @return the mean value of the vector
        */
        inline double getMean() { return this->sum()/this->size(); }

    protected:
        size_t _NBrows;
        size_t _NBcolumns;
        size_t _NBdepths;
        size_t _NBpixels;
        size_t _doubleNBpixels;
        // utilities
        static type _abs(const type x);

    };

    ///////////////////////////////////////////////////////////////////////
    /// normalize output between 0 and 255, can be applied on images of different size that the declared size if nbPixels parameters is setted up;
    template <class type>
    void TemplateBuffer<type>::normalizeGrayOutput_0_maxOutputValue(type *inputOutputBuffer, const size_t processedPixels, const type maxOutputValue)
    {
        type maxValue=inputOutputBuffer[0], minValue=inputOutputBuffer[0];

        // get the min and max value
        type *inputOutputBufferPTR=inputOutputBuffer;
        for (size_t j = 0; j<processedPixels; ++j)
        {
            type pixValue = *(inputOutputBufferPTR++);
            if (maxValue < pixValue)
                maxValue = pixValue;
            else if (minValue > pixValue)
                minValue = pixValue;
        }
        // change the range of the data to 0->255

        type factor = maxOutputValue/(maxValue-minValue);
        type offset = (type)(-minValue*factor);

        inputOutputBufferPTR=inputOutputBuffer;
        for (size_t j = 0; j < processedPixels; ++j, ++inputOutputBufferPTR)
            *inputOutputBufferPTR=*(inputOutputBufferPTR)*factor+offset;

    }
    // normalize data with a sigmoide close to 0 (saturates values for those superior to 0)
    template <class type>
    void TemplateBuffer<type>::normalizeGrayOutputNearZeroCentreredSigmoide(type *inputBuffer, type *outputBuffer, const type sensitivity, const type maxOutputValue)
    {
        if (inputBuffer==NULL)
            inputBuffer=Buffer();
        if (outputBuffer==NULL)
            outputBuffer=Buffer();

        type X0cube=sensitivity*sensitivity*sensitivity;

        type *inputBufferPTR=inputBuffer;
        type *outputBufferPTR=outputBuffer;

        for (size_t j = 0; j < _NBpixels; ++j, ++inputBufferPTR)
        {

            type currentCubeLuminance=*inputBufferPTR**inputBufferPTR**inputBufferPTR;
            *(outputBufferPTR++)=maxOutputValue*currentCubeLuminance/(currentCubeLuminance+X0cube);
        }
    }

    // normalize and adjust luminance with a centered to 128 sigmode
    template <class type>
    void TemplateBuffer<type>::normalizeGrayOutputCentredSigmoide(const type meanValue, const type sensitivity, const type maxOutputValue, type *inputBuffer, type *outputBuffer, const unsigned int nbPixels)
    {

        if (sensitivity==1.0)
        {
            std::cerr<<"TemplateBuffer::TemplateBuffer<type>::normalizeGrayOutputCentredSigmoide error: 2nd parameter (sensitivity) must not equal 0, copying original data..."<<std::endl;
            memcpy(outputBuffer, inputBuffer, sizeof(type)*nbPixels);
            return;
        }

        type X0=maxOutputValue/(sensitivity-(type)1.0);

        type *inputBufferPTR=inputBuffer;
        type *outputBufferPTR=outputBuffer;

        for (size_t j = 0; j < nbPixels; ++j, ++inputBufferPTR)
            *(outputBufferPTR++)=(meanValue+(meanValue+X0)*(*(inputBufferPTR)-meanValue)/(_abs(*(inputBufferPTR)-meanValue)+X0));

    }

    // center and reduct the image (image-mean)/std
    template <class type>
    void TemplateBuffer<type>::centerReductImageLuminance(type *inputOutputBuffer)
    {
        // if outputBuffer unsassigned, the rewrite the buffer
        if (inputOutputBuffer==NULL)
            inputOutputBuffer=Buffer();
        type meanValue=0, stdValue=0;

        // compute mean value
        for (size_t j = 0; j < _NBpixels; ++j)
            meanValue+=inputOutputBuffer[j];
        meanValue/=((type)_NBpixels);

        // compute std value
        type *inputOutputBufferPTR=inputOutputBuffer;
        for (size_t index=0;index<_NBpixels;++index)
        {
            type inputMinusMean=*(inputOutputBufferPTR++)-meanValue;
            stdValue+=inputMinusMean*inputMinusMean;
        }

        stdValue=std::sqrt(stdValue/((type)_NBpixels));
        // adjust luminance in regard of mean and std value;
        inputOutputBufferPTR=inputOutputBuffer;
        for (size_t index=0;index<_NBpixels;++index, ++inputOutputBufferPTR)
            *inputOutputBufferPTR=(*(inputOutputBufferPTR)-meanValue)/stdValue;
    }


    template <class type>
    type TemplateBuffer<type>::_abs(const type x)
    {

        if (x>0)
            return x;
        else
            return -x;
    }

    template < >
    inline int TemplateBuffer<int>::_abs(const int x)
    {
        return std::abs(x);
    }
    template < >
    inline double TemplateBuffer<double>::_abs(const double x)
    {
        return std::fabs(x);
    }

    template < >
    inline float TemplateBuffer<float>::_abs(const float x)
    {
        return std::fabs(x);
    }

}// end of namespace bioinspired
}// end of namespace cv
#endif
