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

    #include "../geometry.h"
    #include <vector>
    #include "box_overlap_testing_abstract.h"
    #include "full_object_detection_abstract.h"

    namespace dlib
    {

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

        struct rect_detection
        {
            double detection_confidence;
            unsigned long weight_index;
            rectangle rect;
        };

        struct full_detection
        {
            double detection_confidence;
            unsigned long weight_index;
            full_object_detection rect;
        };

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

        template <
            typename image_scanner_type_
            >
        class object_detector
        {
            /*!
                REQUIREMENTS ON image_scanner_type_
                    image_scanner_type_ must be an implementation of
                    dlib/image_processing/scan_image_pyramid_abstract.h or
                    dlib/image_processing/scan_fhog_pyramid.h or
                    dlib/image_processing/scan_image_custom.h or
                    dlib/image_processing/scan_image_boxes_abstract.h

                WHAT THIS OBJECT REPRESENTS
                    This object is a tool for detecting the positions of objects in an image.
                    In particular, it is a simple container to aggregate an instance of an image
                    scanner (i.e. scan_image_pyramid, scan_fhog_pyramid, scan_image_custom, or
                    scan_image_boxes), the weight vector needed by one of these image scanners,
                    and finally an instance of test_box_overlap.  The test_box_overlap object
                    is used to perform non-max suppression on the output of the image scanner
                    object.

                    Note further that this object can contain multiple weight vectors.  In this
                    case, it will run the image scanner multiple times, once with each of the
                    weight vectors.  Then it will aggregate the results from all runs, perform
                    non-max suppression and then return the results.  Therefore, the object_detector
                    can also be used as a container for a set of object detectors that all use
                    the same image scanner but different weight vectors.  This is useful since
                    the object detection procedure has two parts.  A loading step where the
                    image is loaded into the scanner, then a detect step which uses the weight
                    vector to locate objects in the image.  Since the loading step is independent
                    of the weight vector it is most efficient to run multiple detectors by
                    performing one load into a scanner followed by multiple detect steps.  This
                    avoids unnecessarily loading the same image into the scanner multiple times.
            !*/
        public:
            typedef image_scanner_type_ image_scanner_type;
            typedef typename image_scanner_type::feature_vector_type feature_vector_type;

            object_detector (
            );
            /*!
                ensures
                    - This detector won't generate any detections when
                      presented with an image.
                    - #num_detectors() == 0
            !*/

            object_detector (
                const object_detector& item
            );
            /*!
                ensures
                    - #*this is a copy of item
                    - #get_scanner() == item.get_scanner()
                      (note that only the "configuration" of item.get_scanner() is copied.
                      I.e. the copy is done using copy_configuration())
            !*/

            object_detector (
                const image_scanner_type& scanner,
                const test_box_overlap& overlap_tester,
                const feature_vector_type& w
            );
            /*!
                requires
                    - w.size() == scanner.get_num_dimensions() + 1
                    - scanner.get_num_detection_templates() > 0
                ensures
                    - When the operator() member function is called it will
                      invoke scanner.detect(w,dets,w(w.size()-1)), suppress
                      overlapping detections, and then report the results.
                    - when #*this is used to detect objects, the set of
                      output detections will never contain any overlaps
                      with respect to overlap_tester.  That is, for all
                      pairs of returned detections A and B, we will always
                      have: overlap_tester(A,B) == false
                    - #get_w() == w
                    - #get_overlap_tester() == overlap_tester
                    - #get_scanner() == scanner
                      (note that only the "configuration" of scanner is copied.
                      I.e. the copy is done using copy_configuration())
                    - #num_detectors() == 1
            !*/

            object_detector (
                const image_scanner_type& scanner,
                const test_box_overlap& overlap_tester,
                const std::vector<feature_vector_type>& w
            );
            /*!
                requires
                    - for all valid i:
                        - w[i].size() == scanner.get_num_dimensions() + 1
                    - scanner.get_num_detection_templates() > 0
                    - w.size() > 0
                ensures
                    - When the operator() member function is called it will invoke
                      get_scanner().detect(w[i],dets,w[i](w[i].size()-1)) for all valid i.  Then it
                      will take all the detections output by the calls to detect() and suppress
                      overlapping detections, and finally report the results.
                    - when #*this is used to detect objects, the set of output detections will
                      never contain any overlaps with respect to overlap_tester.  That is, for
                      all pairs of returned detections A and B, we will always have:
                        overlap_tester(A,B) == false
                    - for all valid i:
                        - #get_w(i) == w[i]
                    - #num_detectors() == w.size()
                    - #get_overlap_tester() == overlap_tester
                    - #get_scanner() == scanner
                      (note that only the "configuration" of scanner is copied.
                      I.e. the copy is done using copy_configuration())
            !*/

            explicit object_detector (
                const std::vector<object_detector>& detectors
            );
            /*!
                requires
                    - detectors.size() != 0
                    - All the detectors must use compatibly configured scanners.  That is, it
                      must make sense for the weight vector from one detector to be used with
                      the scanner from any other.
                    - for all valid i:
                        - detectors[i].get_scanner().get_num_dimensions() == detectors[0].get_scanner().get_num_dimensions()
                          (i.e. all the detectors use scanners that use the same kind of feature vectors.)
                ensures
                    - Very much like the above constructor, this constructor takes all the
                      given detectors and packs them into #*this.  That is, invoking operator()
                      on #*this will run all the detectors, perform non-max suppression, and
                      then report the results.
                    - When #*this is used to detect objects, the set of output detections will
                      never contain any overlaps with respect to overlap_tester.  That is, for
                      all pairs of returned detections A and B, we will always have:
                        overlap_tester(A,B) == false
                    - #num_detectors() == The sum of detectors[i].num_detectors() for all valid i.
                    - #get_overlap_tester() == detectors[0].get_overlap_tester()
                    - #get_scanner() == detectors[0].get_scanner()
                      (note that only the "configuration" of scanner is copied.  I.e. the copy
                      is done using copy_configuration())
            !*/

            unsigned long num_detectors (
            ) const;
            /*!
                ensures
                    - returns the number of weight vectors in this object.  Since each weight
                      vector logically represents an object detector, this returns the number
                      of object detectors contained in this object.
            !*/

            const feature_vector_type& get_w (
                unsigned long idx = 0
            ) const;
            /*!
                requires
                    - idx < num_detectors()
                ensures
                    - returns the idx-th weight vector loaded into this object.  All the weight vectors
                      have the same dimension and logically each represents a different detector.
            !*/

            const test_box_overlap& get_overlap_tester (
            ) const;
            /*!
                ensures
                    - returns the overlap tester used by this object
            !*/

            const image_scanner_type& get_scanner (
            ) const;
            /*!
                ensures
                    - returns the image scanner used by this object.
            !*/

            object_detector& operator= (
                const object_detector& item
            );
            /*!
                ensures
                    - #*this is a copy of item
                    - #get_scanner() == item.get_scanner()
                      (note that only the "configuration" of item.get_scanner() is
                      copied.  I.e. the copy is done using copy_configuration())
                    - returns #*this
            !*/

            template <
                typename image_type
                >
            void operator() (
                const image_type& img,
                std::vector<rect_detection>& dets,
                double adjust_threshold = 0
            );
            /*!
                requires
                    - img == an object which can be accepted by image_scanner_type::load()
                ensures
                    - Performs object detection on the given image and stores the detected
                      objects into #dets.  In particular, we will have that:
                        - #dets is sorted such that the highest confidence detections come
                          first.  E.g. element 0 is the best detection, element 1 the next
                          best, and so on.
                        - #dets.size() == the number of detected objects.
                        - #dets[i].detection_confidence == The strength of the i-th detection.
                          Larger values indicate that the detector is more confident that
                          #dets[i] is a correct detection rather than being a false alarm.
                          Moreover, the detection_confidence is equal to the detection value
                          output by the scanner minus the threshold value stored at the end of
                          the weight vector in get_w(#dets[i].weight_index).
                        - #dets[i].weight_index == the index for the weight vector that
                          generated this detection.
                        - #dets[i].rect == the bounding box for the i-th detection.
                    - #get_scanner() will have been loaded with img. Therefore, you can call
                      #get_scanner().get_feature_vector() to obtain the feature vectors or
                      #get_scanner().get_full_object_detection() to get the
                      full_object_detections for the resulting object detection boxes.
                    - The detection threshold is adjusted by having adjust_threshold added to
                      it.  Therefore, an adjust_threshold value > 0 makes detecting objects
                      harder while a negative value makes it easier.  Moreover, the following
                      will be true for all valid i:
                        - #dets[i].detection_confidence >= adjust_threshold
                      This means that, for example, you can obtain the maximum possible number
                      of detections by setting adjust_threshold equal to negative infinity.
            !*/

            template <
                typename image_type
                >
            void operator() (
                const image_type& img,
                std::vector<full_detection>& dets,
                double adjust_threshold = 0
            );
            /*!
                requires
                    - img == an object which can be accepted by image_scanner_type::load()
                ensures
                    - This function is identical to the above operator() routine, except that
                      it outputs full_object_detections instead of rectangles.  This means that
                      the output includes part locations.  In particular, calling this function
                      is the same as calling the above operator() routine and then using
                      get_scanner().get_full_object_detection() to resolve all the rectangles
                      into full_object_detections.  Therefore, this version of operator() is
                      simply a convenience function for performing this set of operations.
            !*/

            template <
                typename image_type
                >
            std::vector<rectangle> operator() (
                const image_type& img,
                double adjust_threshold = 0
            );
            /*!
                requires
                    - img == an object which can be accepted by image_scanner_type::load()
                ensures
                    - This function is identical to the above operator() routine, except that
                      it returns a std::vector<rectangle> which contains just the bounding
                      boxes of all the detections.
            !*/

            template <
                typename image_type
                >
            void operator() (
                const image_type& img,
                std::vector<std::pair<double, rectangle> >& dets,
                double adjust_threshold = 0
            );
            /*!
                requires
                    - img == an object which can be accepted by image_scanner_type::load()
                ensures
                    - performs object detection on the given image and stores the
                      detected objects into #dets.  In particular, we will have that:
                        - #dets is sorted such that the highest confidence detections
                          come first.  E.g. element 0 is the best detection, element 1
                          the next best, and so on.
                        - #dets.size() == the number of detected objects.
                        - #dets[i].first gives the "detection confidence", of the i-th
                          detection.  This is the detection value output by the scanner minus
                          the threshold value stored at the end of the weight vector in get_w().
                        - #dets[i].second == the bounding box for the i-th detection.
                    - #get_scanner() will have been loaded with img. Therefore, you can call
                      #get_scanner().get_feature_vector() to obtain the feature vectors or
                      #get_scanner().get_full_object_detection() to get the
                      full_object_detections for the resulting object detection boxes.
                    - The detection threshold is adjusted by having adjust_threshold added to
                      it.  Therefore, an adjust_threshold value > 0 makes detecting objects
                      harder while a negative value makes it easier.  Moreover, the following
                      will be true for all valid i:
                        - #dets[i].first >= adjust_threshold
                      This means that, for example, you can obtain the maximum possible number
                      of detections by setting adjust_threshold equal to negative infinity.
            !*/

            template <
                typename image_type
                >
            void operator() (
                const image_type& img,
                std::vector<std::pair<double, full_object_detection> >& dets,
                double adjust_threshold = 0
            );
            /*!
                requires
                    - img == an object which can be accepted by image_scanner_type::load()
                ensures
                    - This function is identical to the above operator() routine, except that
                      it outputs full_object_detections instead of rectangles.  This means that
                      the output includes part locations.  In particular, calling this function
                      is the same as calling the above operator() routine and then using
                      get_scanner().get_full_object_detection() to resolve all the rectangles
                      into full_object_detections.  Therefore, this version of operator() is
                      simply a convenience function for performing this set of operations.
            !*/

            template <
                typename image_type
                >
            void operator() (
                const image_type& img,
                std::vector<full_object_detection>& dets,
                double adjust_threshold = 0
            );
            /*!
                requires
                    - img == an object which can be accepted by image_scanner_type::load()
                ensures
                    - This function is identical to the above operator() routine, except that
                      it doesn't include a double valued score.  That is, it just outputs the
                      full_object_detections.
            !*/
        };

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

        template <typename T>
        void serialize (
            const object_detector<T>& item,
            std::ostream& out
        );
        /*!
            provides serialization support.  Note that this function only saves the
            configuration part of item.get_scanner().  That is, we use the scanner's
            copy_configuration() function to get a copy of the scanner that doesn't contain any
            loaded image data and we then save just the configuration part of the scanner.
            This means that any serialized object_detectors won't remember any images they have
            processed but will otherwise contain all their state and be able to detect objects
            in new images.
        !*/

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

        template <typename T>
        void deserialize (
            object_detector<T>& item,
            std::istream& in
        );
        /*!
            provides deserialization support
        !*/

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

    }

    #endif // DLIB_OBJECT_DeTECTOR_ABSTRACT_Hh_