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

    #include "../lsh/projection_hash.h"
    #include "hashed_feature_image_abstract.h"
    #include <vector>
    #include "../algs.h"
    #include "../matrix.h"
    #include "../statistics.h"

    namespace dlib
    {

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

        template <
            typename feature_extractor,
            typename hash_function_type_ = projection_hash
            >
        class hashed_feature_image : noncopyable
        {

        public:
            typedef feature_extractor feature_extractor_type;
            typedef hash_function_type_ hash_function_type;

            typedef std::vector<std::pair<unsigned int,double> > descriptor_type;

            hashed_feature_image (
            );

            void clear (
            );

            void set_hash (
                const hash_function_type& hash_
            );

            const hash_function_type& get_hash (
            ) const;

            void copy_configuration (
                const feature_extractor& item
            );

            void copy_configuration (
                const hashed_feature_image& item
            );

            template <
                typename image_type
                >
            inline void load (
                const image_type& img
            );

            inline size_t size (
            ) const;

            inline long nr (
            ) const;

            inline long nc (
            ) const;

            inline long get_num_dimensions (
            ) const;

            void use_relative_feature_weights (
            );

            void use_uniform_feature_weights (
            );

            bool uses_uniform_feature_weights (
            ) const;

            inline const descriptor_type& operator() (
                long row,
                long col
            ) const;

            inline const rectangle get_block_rect (
                long row,
                long col
            ) const;

            inline const point image_to_feat_space (
                const point& p
            ) const;

            inline const rectangle image_to_feat_space (
                const rectangle& rect
            ) const;

            inline const point feat_to_image_space (
                const point& p
            ) const;

            inline const rectangle feat_to_image_space (
                const rectangle& rect
            ) const;

            template <typename T>
            friend void serialize (
                const hashed_feature_image<T>& item,
                std::ostream& out
            );

            template <typename T>
            friend void deserialize (
                hashed_feature_image<T>& item,
                std::istream& in
            );

        private:

            array2d<unsigned long> feats;
            feature_extractor fe;
            hash_function_type phash;
            std::vector<float> feat_counts;
            bool uniform_feature_weights;

            // This is a transient variable.  It is just here so it doesn't have to be
            // reallocated over and over inside operator()
            mutable descriptor_type hash_feats;

        };

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

        template <typename T>
        void serialize (
            const hashed_feature_image<T>& item,
            std::ostream& out
        )
        {
            int version = 1;
            serialize(version, out);
            serialize(item.feats, out);
            serialize(item.fe, out);
            serialize(item.phash, out);
            serialize(item.feat_counts, out);
            serialize(item.uniform_feature_weights, out);
        }

        template <typename T>
        void deserialize (
            hashed_feature_image<T>& item,
            std::istream& in
        )
        {
            int version = 0;
            deserialize(version, in);
            if (version != 1)
                throw serialization_error("Unexpected version found while deserializing a dlib::hashed_feature_image object.");

            deserialize(item.feats, in);
            deserialize(item.fe, in);
            deserialize(item.phash, in);
            deserialize(item.feat_counts, in);
            deserialize(item.uniform_feature_weights, in);
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    //                        hashed_feature_image member functions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        hashed_feature_image<feature_extractor,hash_function_type>::
        hashed_feature_image (
        )
        {
            clear();
            hash_feats.resize(1);
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        void hashed_feature_image<feature_extractor,hash_function_type>::
        clear (
        )
        {
            fe.clear();
            phash = hash_function_type();
            feats.clear();
            feat_counts.clear();
            uniform_feature_weights = false;
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        void hashed_feature_image<feature_extractor,hash_function_type>::
        set_hash (
            const hash_function_type& hash_
        )
        {
            phash = hash_;
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        const hash_function_type& hashed_feature_image<feature_extractor,hash_function_type>::
        get_hash (
        ) const
        {
            return phash;
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        void hashed_feature_image<feature_extractor,hash_function_type>::
        copy_configuration (
            const feature_extractor& item
        )
        {
            fe.copy_configuration(item);
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        void hashed_feature_image<feature_extractor,hash_function_type>::
        copy_configuration (
            const hashed_feature_image& item
        )
        {
            fe.copy_configuration(item.fe);
            phash = item.phash;
            uniform_feature_weights = item.uniform_feature_weights;
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        template <
            typename image_type
            >
        void hashed_feature_image<feature_extractor,hash_function_type>::
        load (
            const image_type& img
        )
        {
            fe.load(img);

            if (fe.size() != 0)
            {
                feats.set_size(fe.nr(), fe.nc());
                feat_counts.assign(phash.num_hash_bins(),1);
                if (uniform_feature_weights)
                {
                    for (long r = 0; r < feats.nr(); ++r)
                    {
                        for (long c = 0; c < feats.nc(); ++c)
                        {
                            feats[r][c] = phash(fe(r,c));
                        }
                    }
                }
                else
                {
                    for (long r = 0; r < feats.nr(); ++r)
                    {
                        for (long c = 0; c < feats.nc(); ++c)
                        {
                            feats[r][c] = phash(fe(r,c));
                            feat_counts[feats[r][c]]++;
                        }
                    }
                }
            }
            else
            {
                feats.set_size(0,0);
            }

            if (!uniform_feature_weights)
            {
                // use the inverse frequency as the scale for each feature.  We also scale
                // these counts so that they are invariant to the size of the image (we scale
                // them so they all look like they come from a 500x400 images).
                const double scale = image_size(img)/(500.0*400.0);
                for (unsigned long i = 0; i < feat_counts.size(); ++i)
                {
                    feat_counts[i] = scale/feat_counts[i];
                }
            }

            fe.unload();
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        size_t hashed_feature_image<feature_extractor,hash_function_type>::
        size (
        ) const
        {
            return feats.size();
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        long hashed_feature_image<feature_extractor,hash_function_type>::
        nr (
        ) const
        {
            return feats.nr();
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        long hashed_feature_image<feature_extractor,hash_function_type>::
        nc (
        ) const
        {
            return feats.nc();
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        long hashed_feature_image<feature_extractor,hash_function_type>::
        get_num_dimensions (
        ) const
        {
            return phash.num_hash_bins();
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        void hashed_feature_image<feature_extractor,hash_function_type>::
        use_relative_feature_weights (
        )
        {
            uniform_feature_weights = false;
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        void hashed_feature_image<feature_extractor,hash_function_type>::
        use_uniform_feature_weights (
        )
        {
            uniform_feature_weights = true;
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        bool hashed_feature_image<feature_extractor,hash_function_type>::
        uses_uniform_feature_weights (
        ) const
        {
            return uniform_feature_weights;
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        const std::vector<std::pair<unsigned int,double> >& hashed_feature_image<feature_extractor,hash_function_type>::
        operator() (
            long row,
            long col
        ) const
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(0 <= row && row < nr() &&
                        0 <= col && col < nc(),
                "\t descriptor_type hashed_feature_image::operator(row,col)"
                << "\n\t Invalid inputs were given to this function"
                << "\n\t row:  " << row
                << "\n\t col:  " << col
                << "\n\t nr(): " << nr()
                << "\n\t nc(): " << nc()
                << "\n\t this: " << this
                );

            hash_feats[0] = std::make_pair(feats[row][col],feat_counts[feats[row][col]]);
            return hash_feats;
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        const rectangle hashed_feature_image<feature_extractor,hash_function_type>::
        get_block_rect (
            long row,
            long col
        ) const
        {
            return fe.get_block_rect(row,col);
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        const point hashed_feature_image<feature_extractor,hash_function_type>::
        image_to_feat_space (
            const point& p
        ) const
        {
            return fe.image_to_feat_space(p);
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        const rectangle hashed_feature_image<feature_extractor,hash_function_type>::
        image_to_feat_space (
            const rectangle& rect
        ) const
        {
            return fe.image_to_feat_space(rect);
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        const point hashed_feature_image<feature_extractor,hash_function_type>::
        feat_to_image_space (
            const point& p
        ) const
        {
            return fe.feat_to_image_space(p);
        }

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

        template <
            typename feature_extractor,
            typename hash_function_type
            >
        const rectangle hashed_feature_image<feature_extractor,hash_function_type>::
        feat_to_image_space (
            const rectangle& rect
        ) const
        {
            return fe.feat_to_image_space(rect);
        }

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

    }

    #endif // DLIB_HASHED_IMAGE_FEATUrES_Hh_