// Copyright (C) 2014  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_LBP_Hh_
#define DLIB_LBP_Hh_

#include "lbp_abstract.h"
#include "../image_processing/generic_image.h"
#include "assign_image.h"
#include "../pixel.h"

namespace dlib
{

// ----------------------------------------------------------------------------------------

    template <
        typename image_type,
        typename image_type2
        >
    void make_uniform_lbp_image (
        const image_type& img_,
        image_type2& lbp_
    )
    {
        const static unsigned char uniform_lbps[] = {
            0, 1, 2, 3, 4, 58, 5, 6, 7, 58, 58, 58, 8, 58, 9, 10, 11, 58, 58, 58, 58, 58,
            58, 58, 12, 58, 58, 58, 13, 58, 14, 15, 16, 58, 58, 58, 58, 58, 58, 58, 58, 58,
            58, 58, 58, 58, 58, 58, 17, 58, 58, 58, 58, 58, 58, 58, 18, 58, 58, 58, 19, 58,
            20, 21, 22, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58,
            58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 23, 58, 58, 58, 58, 58,
            58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 24, 58, 58, 58, 58, 58, 58, 58, 25, 58,
            58, 58, 26, 58, 27, 28, 29, 30, 58, 31, 58, 58, 58, 32, 58, 58, 58, 58, 58, 58,
            58, 33, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 34, 58, 58,
            58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58,
            58, 58, 58, 58, 58, 58, 58, 58, 58, 35, 36, 37, 58, 38, 58, 58, 58, 39, 58, 58,
            58, 58, 58, 58, 58, 40, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58,
            58, 41, 42, 43, 58, 44, 58, 58, 58, 45, 58, 58, 58, 58, 58, 58, 58, 46, 47, 48,
            58, 49, 58, 58, 58, 50, 51, 52, 58, 53, 54, 55, 56, 57
        };

        COMPILE_TIME_ASSERT(sizeof(uniform_lbps) == 256);

        const_image_view<image_type> img(img_);
        image_view<image_type2> lbp(lbp_);

        lbp.set_size(img.nr(), img.nc());

        // set all the border pixels to the "non-uniform LBP value".
        assign_border_pixels(lbp, 1, 1, 58);

        typedef typename image_traits<image_type>::pixel_type pixel_type;
        typedef typename pixel_traits<pixel_type>::basic_pixel_type basic_pixel_type;

        for (long r = 1; r+1 < img.nr(); ++r)
        {
            for (long c = 1; c+1 < img.nc(); ++c)
            {
                const basic_pixel_type pix = get_pixel_intensity(img[r][c]);
                unsigned char b1 = 0;
                unsigned char b2 = 0;
                unsigned char b3 = 0;
                unsigned char b4 = 0;
                unsigned char b5 = 0;
                unsigned char b6 = 0;
                unsigned char b7 = 0;
                unsigned char b8 = 0;

                unsigned char x = 0;
                if (get_pixel_intensity(img[r-1][c-1]) > pix) b1 = 0x80; 
                if (get_pixel_intensity(img[r-1][c  ]) > pix) b2 = 0x40;
                if (get_pixel_intensity(img[r-1][c+1]) > pix) b3 = 0x20;
                x |= b1;
                if (get_pixel_intensity(img[r  ][c-1]) > pix) b4 = 0x10;
                x |= b2;
                if (get_pixel_intensity(img[r  ][c+1]) > pix) b5 = 0x08;
                x |= b3;
                if (get_pixel_intensity(img[r+1][c-1]) > pix) b6 = 0x04;
                x |= b4;
                if (get_pixel_intensity(img[r+1][c  ]) > pix) b7 = 0x02;
                x |= b5;
                if (get_pixel_intensity(img[r+1][c+1]) > pix) b8 = 0x01;

                x |= b6;
                x |= b7;
                x |= b8;

                lbp[r][c] = uniform_lbps[x];
            }
        }
    }

// ----------------------------------------------------------------------------------------

    template <
        typename image_type,
        typename T
        >
    void extract_histogram_descriptors (
        const image_type& img_,
        const point& loc,
        std::vector<T>& histograms,
        const unsigned int cell_size = 10,
        const unsigned int block_size = 4,
        const unsigned int max_val = 58
    )
    {
        // make sure requires clause is not broken
        DLIB_ASSERT(cell_size >= 1 && block_size >= 1 && max_val < 256 && 
                    (unsigned int)max(mat(img_)) <= max_val,
            "\t void extract_histogram_descriptors()"
            << "\n\t Invalid inputs were given to this function."
            << "\n\t cell_size:      " << cell_size
            << "\n\t block_size:     " << block_size
            << "\n\t max_val:        " << max_val
            << "\n\t max(mat(img_)): " << max(mat(img_))
            );

        typedef typename image_traits<image_type>::pixel_type pixel_type;
        COMPILE_TIME_ASSERT((is_same_type<pixel_type, unsigned char>::value));

        const_image_view<image_type> img(img_);

        const rectangle area = get_rect(img);
        const rectangle window = centered_rect(loc, block_size*cell_size, block_size*cell_size);
        unsigned int cell_top = window.top();
        for (unsigned int br = 0; br < block_size; ++br)
        {
            unsigned int cell_left = window.left();
            for (unsigned int bc = 0; bc < block_size; ++bc)
            {
                // figure out the cell boundaries
                rectangle cell(cell_left, cell_top, cell_left+cell_size-1, cell_top+cell_size-1);
                cell = cell.intersect(area);

                // make the actual histogram for this cell
                unsigned int hist[256] = {0};
                for (long r = cell.top(); r <= cell.bottom(); ++r)
                {
                    for (long c = cell.left(); c <= cell.right(); ++c)
                    {
                        hist[img[r][c]]++;
                    }
                }

                // copy histogram into the output.
                histograms.insert(histograms.end(), hist, hist + max_val+1);

                cell_left += cell_size;
            }
            cell_top += cell_size;
        }
    }

// ----------------------------------------------------------------------------------------

    template <
        typename image_type,
        typename T
        >
    void extract_uniform_lbp_descriptors (
        const image_type& img,
        std::vector<T>& feats,
        const unsigned int cell_size = 10
    )
    {
        // make sure requires clause is not broken
        DLIB_ASSERT(cell_size >= 1,
            "\t void extract_uniform_lbp_descriptors()"
            << "\n\t Invalid inputs were given to this function."
            << "\n\t cell_size:      " << cell_size
            );

        feats.clear();
        array2d<unsigned char> lbp;
        make_uniform_lbp_image(img, lbp);
        for (long r = 0; r < lbp.nr(); r+=cell_size)
        {
            for (long c = 0; c < lbp.nc(); c+=cell_size)
            {
                const rectangle cell = rectangle(c,r,c+cell_size-1,r+cell_size-1).intersect(get_rect(lbp));
                // make the actual histogram for this cell
                unsigned int hist[59] = {0};
                for (long r = cell.top(); r <= cell.bottom(); ++r)
                {
                    for (long c = cell.left(); c <= cell.right(); ++c)
                    {
                        hist[lbp[r][c]]++;
                    }
                }

                // copy histogram into the output.
                feats.insert(feats.end(), hist, hist + 59);
            }
        }

        for (unsigned long i = 0; i < feats.size(); ++i)
            feats[i] = std::sqrt(feats[i]);
    }

// ----------------------------------------------------------------------------------------

    template <
        typename image_type,
        typename T
        >
    void extract_highdim_face_lbp_descriptors (
        const image_type& img,
        const full_object_detection& det,
        std::vector<T>& feats
    )
    {
        // make sure requires clause is not broken
        DLIB_CASSERT(det.num_parts() == 68,
            "\t void extract_highdim_face_lbp_descriptors()"
            << "\n\t Invalid inputs were given to this function."
            << "\n\t det.num_parts(): " << det.num_parts()
            );

        const unsigned long num_scales = 5; 
        feats.clear();
        dlib::vector<double,2> l, r;
        double cnt = 0;
        // Find the center of the left eye by averaging the points around 
        // the eye.
        for (unsigned long i = 36; i <= 41; ++i) 
        {
            l += det.part(i);
            ++cnt;
        }
        l /= cnt;

        // Find the center of the right eye by averaging the points around 
        // the eye.
        cnt = 0;
        for (unsigned long i = 42; i <= 47; ++i) 
        {
            r += det.part(i);
            ++cnt;
        }
        r /= cnt;

        // We only do feature extraction from these face parts.  These are things like the
        // corners of the eyes and mouth and stuff like that.
        std::vector<point> parts;
        parts.reserve(30);
        parts.push_back(l);
        parts.push_back(r);
        parts.push_back(det.part(17));
        parts.push_back(det.part(21));
        parts.push_back(det.part(22));
        parts.push_back(det.part(26));
        parts.push_back(det.part(36));
        parts.push_back(det.part(39));
        parts.push_back(det.part(42));
        parts.push_back(det.part(45));
        parts.push_back(det.part(27));
        parts.push_back(det.part(28));
        parts.push_back(det.part(29));
        parts.push_back(det.part(30));
        parts.push_back(det.part(31));
        parts.push_back(det.part(35));
        parts.push_back(det.part(33));
        parts.push_back(det.part(48));
        parts.push_back(det.part(54));
        parts.push_back(det.part(51));
        parts.push_back(det.part(57));

        array2d<unsigned char> lbp;
        make_uniform_lbp_image(img, lbp);
        for (unsigned long i = 0; i < parts.size(); ++i)
            extract_histogram_descriptors(lbp, parts[i], feats);

        if (num_scales > 1)
        {
            pyramid_down<4> pyr;
            image_type img_temp;
            pyr(img, img_temp);
            unsigned long num_pyr_calls = 1;

            // now pull the features out at coarser scales
            for (unsigned long iter = 1; iter < num_scales; ++iter)
            {
                // now do the feature extraction
                make_uniform_lbp_image(img_temp, lbp);
                for (unsigned long i = 0; i < parts.size(); ++i)
                    extract_histogram_descriptors(lbp, pyr.point_down(parts[i],num_pyr_calls), feats);

                if (iter+1 < num_scales)
                {
                    pyr(img_temp);
                    ++num_pyr_calls;
                }
            }
        }

        for (unsigned long i = 0; i < feats.size(); ++i)
            feats[i] = std::sqrt(feats[i]);

        DLIB_ASSERT(feats.size() == 99120, feats.size());
    }

// ----------------------------------------------------------------------------------------

}

#endif // DLIB_LBP_Hh_