// Copyright (C) 2018-2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include <chrono>  // NOLINT
#include <algorithm>
#include <deque>
#include <map>
#include <memory>
#include <limits>
#include <set>
#include <string>
#include <vector>
#include <utility>
#include "openvino/openvino.hpp"
#include "cnn.hpp"
#include "actions.hpp"
#include "action_detector.hpp"
#include "detector.hpp"
#include "face_reid.hpp"
#include "tracker.hpp"
#include "logger.hpp"
#include "gflags/gflags.h"
#include "utils/default_flags.hpp"
#include "monitors/presenter.h"
#include "utils/args_helper.hpp"
#include "utils/images_capture.h"
#include "utils/ocv_common.hpp"
#include "utils/slog.hpp"

#include <models/detection_model.h>
#include <models/detection_model_centernet.h>
#include <models/detection_model_faceboxes.h>
#include <models/detection_model_retinaface.h>
#include <models/detection_model_retinaface_pt.h>
#include <models/detection_model_ssd.h>
#include <models/detection_model_yolo.h>
#include <models/detection_model_yolov3_onnx.h>
#include <models/detection_model_yolox.h>
#include <models/input_data.h>
#include <models/model_base.h>
#include <models/results.h>
#include <pipelines/async_pipeline.h>
#include <pipelines/metadata.h>
#include <utils/common.hpp>
#include <utils/config_factory.h>
#include <utils/performance_metrics.hpp>
#include <random>
#include <models/hpe_model_associative_embedding.h>
#include <models/hpe_model_openpose.h>
#include "PeopleTracking.h"
using namespace ANSPPTrack;

DEFINE_INPUT_FLAGS
DEFINE_OUTPUT_FLAGS

static const char help_message[] = "Print a usage message.";
static const char at_message[] ="Required. Architecture type: centernet, faceboxes, retinaface, retinaface-pytorch, ssd, yolo, yolov3-onnx or yolox";
static const char model_message[] = "Required. Path to an .xml file with a trained model.";
static const char target_device_message[] =
"Optional. Specify the target device to infer on (the list of available devices is shown below). "
"Default value is CPU. Use \"-d HETERO:<comma-separated_devices_list>\" format to specify HETERO plugin. "
"The demo will look for a suitable plugin for a specified device.";
static const char labels_message[] = "Optional. Path to a file with labels mapping.";
static const char layout_message[] = "Optional. Specify inputs layouts."
" Ex. NCHW or input0:NCHW,input1:NC in case of more than one input.";
static const char thresh_output_message[] = "Optional. Probability threshold for detections.";
static const char raw_output_message[] = "Optional. Inference results as raw values.";
static const char input_resizable_message[] =
"Optional. Enables resizable input with support of ROI crop & auto resize.";
static const char nireq_message[] = "Optional. Number of infer requests. If this option is omitted, number of infer "
"requests is determined automatically.";
static const char num_threads_message[] = "Optional. Number of threads.";
static const char num_streams_message[] = "Optional. Number of streams to use for inference on the CPU or/and GPU in "
"throughput mode (for HETERO and MULTI device cases use format "
"<device1>:<nstreams1>,<device2>:<nstreams2> or just <nstreams>)";
static const char no_show_message[] = "Optional. Don't show output.";
static const char utilization_monitors_message[] = "Optional. List of monitors to show initially.";
static const char iou_thresh_output_message[] =
"Optional. Filtering intersection over union threshold for overlapping boxes.";
static const char yolo_af_message[] = "Optional. Use advanced postprocessing/filtering algorithm for YOLO.";
static const char output_resolution_message[] =
"Optional. Specify the maximum output window resolution "
"in (width x height) format. Example: 1280x720. Input frame size used by default.";
static const char anchors_message[] = "Optional. A comma separated list of anchors. "
"By default used default anchors for model. Only for YOLOV4 architecture type.";
static const char masks_message[] = "Optional. A comma separated list of mask for anchors. "
"By default used default masks for model. Only for YOLOV4 architecture type.";
static const char reverse_input_channels_message[] = "Optional. Switch the input channels order from BGR to RGB.";
static const char mean_values_message[] =
"Optional. Normalize input by subtracting the mean values per channel. Example: \"255.0 255.0 255.0\"";
static const char scale_values_message[] = "Optional. Divide input by scale values per channel. Division is applied "
"after mean values subtraction. Example: \"255.0 255.0 255.0\"";



static const char read_limit_message[] = "Optional. Read length limit before stopping or restarting reading the input.";
static const char person_action_detection_model_message[] = "Required. Path to the Person/Action Detection Retail model (.xml) file.";
static const char face_detection_model_message[] = "Required. Path to the Face Detection model (.xml) file.";
static const char facial_landmarks_model_message[] = "Optional. Path to the Facial Landmarks Regression Retail model (.xml) file.";
static const char face_reid_model_message[] = "Optional. Path to the Face Reidentification Retail model (.xml) file.";
static const char target_device_message_action_detection[] = "Optional. Specify the target device for Person/Action Detection Retail "
"(the list of available devices is shown below). Default value is CPU. "
"Use \"-d HETERO:<comma-separated_devices_list>\" format to specify HETERO plugin. "
"The application looks for a suitable plugin for the specified device.";
static const char target_device_message_face_detection[] = "Optional. Specify the target device for Face Detection Retail "
"(the list of available devices is shown below). Default value is CPU. "
"Use \"-d HETERO:<comma-separated_devices_list>\" format to specify HETERO plugin. "
"The application looks for a suitable plugin for the specified device.";
static const char target_device_message_landmarks_regression[] = "Optional. Specify the target device for Landmarks Regression Retail "
"(the list of available devices is shown below). Default value is CPU. "
"Use \"-d HETERO:<comma-separated_devices_list>\" format to specify HETERO plugin. "
"The application looks for a suitable plugin for the specified device.";
static const char target_device_message_face_reid[] = "Optional. Specify the target device for Face Reidentification Retail "
"(the list of available devices is shown below). Default value is CPU. "
"Use \"-d HETERO:<comma-separated_devices_list>\" format to specify HETERO plugin. "
"The application looks for a suitable plugin for the specified device.";
static const char greedy_reid_matching_message[] = "Optional. Use faster greedy matching algorithm in face reid.";
static const char face_threshold_output_message[] = "Optional. Probability threshold for face detections.";
static const char person_threshold_output_message[] = "Optional. Probability threshold for person/action detection.";
static const char action_threshold_output_message[] = "Optional. Probability threshold for action recognition.";
static const char threshold_output_message_face_reid[] = "Optional. Cosine distance threshold between two vectors for face reidentification.";
static const char reid_gallery_path_message[] = "Optional. Path to a faces gallery in .json format.";
static const char act_stat_output_message[] = "Optional. Output file name to save per-person action statistics in. Requires -teacher_id and -a_top to be unset and -fg to be set";
static const char input_image_height_output_message[] = "Optional. Input image height for face detector.";
static const char input_image_width_output_message[] = "Optional. Input image width for face detector.";
static const char expand_ratio_output_message[] = "Optional. Expand ratio for bbox before face recognition.";
static const char teacher_id_message[] = "Optional. ID of a teacher. You must also set a faces gallery parameter (-fg) to use it.";
static const char min_action_duration_message[] = "Optional. Minimum action duration in seconds.";
static const char same_action_time_delta_message[] = "Optional. Maximum time difference between actions in seconds.";
static const char student_actions_message[] = "Optional. List of student actions separated by a comma.";
static const char top_actions_message[] = "Optional. List of student actions (for top-k mode) separated by a comma.";
static const char teacher_actions_message[] = "Optional. List of teacher actions separated by a comma.";
static const char target_action_name_message[] = "Optional. Target action name.";
static const char target_actions_num_message[] = "Optional. Number of first K students. If this parameter is positive, "
"the demo detects first K persons with the action, pointed by the parameter 'top_id'";
static const char crop_gallery_message[] = "Optional. Crop images during faces gallery creation.";
static const char face_threshold_registration_output_message[] = "Optional. Probability threshold for face detections during database registration.";
static const char min_size_fr_reg_output_message[] = "Optional. Minimum input size for faces during database registration.";
static const char act_det_output_message[] = "Optional. Output file name to save per-person action detections in.";
static const char tracker_smooth_size_message[] = "Optional. Number of frames to smooth actions.";

DEFINE_bool(h, false, help_message);
DEFINE_uint32(read_limit, gflags::uint32(std::numeric_limits<size_t>::max()), read_limit_message);
DEFINE_string(m_act, "C:\\Programs\\ANSFR\\Models\\person-detection-action-recognition-0005\\FP32\\person-detection-action-recognition-0005.xml", person_action_detection_model_message);
DEFINE_string(m_fd, "C:\\Programs\\ANSFR\\Models\\FaceDetection\\face-detection-adas-0001\\FP32\\face-detection-adas-0001.xml", face_detection_model_message);
//DEFINE_string(m_fd, "C:\\Programs\\ANSFR\\Models\\FaceDetection\\retinaface-resnet50-pytorch\\FP32\\retinaface-resnet50-pytorch.xml", face_detection_model_message);

DEFINE_string(m_lm, "C:\\Programs\\ANSFR\\Models\\FaceDetection\\landmarks-regression-retail-0009\\FP32\\landmarks-regression-retail-0009.xml", facial_landmarks_model_message);
//DEFINE_string(m_reid, "C:\\Programs\\ANSFR\\Models\\FaceRecognition\\face-reidentification-retail-0095\\FP16\\face-reidentification-retail-0095.xml", face_reid_model_message);
//DEFINE_string(m_reid, "C:\\Programs\\ANSFR\\Models\\FaceRecognition\\face-recognition-resnet100-arcface-onnx\\FP32\\face-recognition-resnet100-arcface-onnx.xml", face_reid_model_message);
DEFINE_string(m_reid, "C:\\Programs\\ANSFR\\Models\\FaceRecognition\\facenet-20180408-102900\\FP32\\facenet-20180408-102900.xml", face_reid_model_message);
//DEFINE_string(m_reid, "C:\\Programs\\ANSFR\\Models\\FaceRecognition\\facenet-20180408-102900\\FP16\\facenet-20180408-102900.xml", face_reid_model_message);

//DEFINE_string(m_reid, "C:\\Programs\\ANSFR\\Models\\FaceRecognition\\face-recognition-resnet100-arcface-onnx\\FP16\\face-recognition-resnet100-arcface-onnx.xml", face_reid_model_message);


DEFINE_string(d_act, "CPU", target_device_message_action_detection);
DEFINE_string(d_fd, "CPU", target_device_message_face_detection);
DEFINE_string(d_lm, "CPU", target_device_message_landmarks_regression);
DEFINE_string(d_reid, "CPU", target_device_message_face_reid);
DEFINE_bool(greedy_reid_matching, false, greedy_reid_matching_message);
DEFINE_string(ad, "", act_stat_output_message);
DEFINE_bool(r, false, raw_output_message);
DEFINE_double(t_ad, 0.3, person_threshold_output_message);
DEFINE_double(t_ar, 0.3, action_threshold_output_message);
DEFINE_double(t_fd, 0.2, face_threshold_output_message);
DEFINE_double(t_reid, 0.5, threshold_output_message_face_reid);
DEFINE_string(fg, "C:\\Programs\\ANSFR\\faces_gallery\\faces_gallery.json", reid_gallery_path_message);
DEFINE_bool(no_show, false, no_show_message);
DEFINE_int32(inh_fd, 600, input_image_height_output_message);
DEFINE_int32(inw_fd, 600, input_image_width_output_message);
DEFINE_double(exp_r_fd, 1.15, face_threshold_output_message);
DEFINE_string(teacher_id, "", teacher_id_message);
DEFINE_double(min_ad, 1.0, min_action_duration_message);
DEFINE_double(d_ad, 1.0, same_action_time_delta_message);
DEFINE_string(student_ac, "sitting,standing,raising_hand", student_actions_message);
DEFINE_string(top_ac, "sitting,raising_hand", top_actions_message);
DEFINE_string(teacher_ac, "standing,writing,demonstrating", teacher_actions_message);
DEFINE_string(top_id, "raising_hand", target_action_name_message);
DEFINE_int32(a_top, -1, target_actions_num_message);
DEFINE_bool(crop_gallery, false, crop_gallery_message);
DEFINE_double(t_reg_fd, 0.9, face_threshold_registration_output_message);
DEFINE_int32(min_size_fr, 128, min_size_fr_reg_output_message);
DEFINE_string(al, "", act_det_output_message);
DEFINE_int32(ss_t, -1, tracker_smooth_size_message);
DEFINE_string(u, "", utilization_monitors_message);

DEFINE_string(at, "", at_message);
DEFINE_string(m, "", model_message);
DEFINE_string(d, "GPU", target_device_message);
DEFINE_string(labels, "", labels_message);
DEFINE_string(layout, "", layout_message);
DEFINE_double(t, 0.5, thresh_output_message);
DEFINE_double(iou_t, 0.5, iou_thresh_output_message);
DEFINE_bool(auto_resize, false, input_resizable_message);
DEFINE_uint32(nireq, 0, nireq_message);
DEFINE_uint32(nthreads, 0, num_threads_message);
DEFINE_string(nstreams, "", num_streams_message);
DEFINE_bool(yolo_af, true, yolo_af_message);
DEFINE_string(output_resolution, "", output_resolution_message);
DEFINE_string(anchors, "", anchors_message);
DEFINE_string(masks, "", masks_message);
DEFINE_bool(reverse_input_channels, false, reverse_input_channels_message);
DEFINE_string(mean_values, "", mean_values_message);
DEFINE_string(scale_values, "", scale_values_message);


/**
* @brief This function show a help message
*/
static void showUsage() {
    std::cout << std::endl;
    std::cout << "smart_classroom_demo [OPTION]" << std::endl;
    std::cout << "Options:" << std::endl;
    std::cout << std::endl;
    std::cout << "    -h                             " << help_message << std::endl;
    std::cout << "    -i                             " << input_message << std::endl;
    std::cout << "    -loop                          " << loop_message << std::endl;
    std::cout << "    -read_limit                    " << read_limit_message << std::endl;
    std::cout << "    -o \"<path>\"                    " << output_message << std::endl;
    std::cout << "    -limit \"<num>\"                 " << limit_message << std::endl;
    std::cout << "    -m_act '<path>'                " << person_action_detection_model_message << std::endl;
    std::cout << "    -m_fd '<path>'                 " << face_detection_model_message << std::endl;
    std::cout << "    -m_lm '<path>'                 " << facial_landmarks_model_message << std::endl;
    std::cout << "    -m_reid '<path>'               " << face_reid_model_message << std::endl;
    std::cout << "    -d_act '<device>'              " << target_device_message_action_detection << std::endl;
    std::cout << "    -d_fd '<device>'               " << target_device_message_face_detection << std::endl;
    std::cout << "    -d_lm '<device>'               " << target_device_message_landmarks_regression << std::endl;
    std::cout << "    -d_reid '<device>'             " << target_device_message_face_reid << std::endl;
    std::cout << "    -greedy_reid_matching          " << greedy_reid_matching_message << std::endl;
    std::cout << "    -r                             " << raw_output_message << std::endl;
    std::cout << "    -ad                            " << act_stat_output_message << std::endl;
    std::cout << "    -t_ad                          " << person_threshold_output_message << std::endl;
    std::cout << "    -t_ar                          " << action_threshold_output_message << std::endl;
    std::cout << "    -t_fd                          " << face_threshold_output_message << std::endl;
    std::cout << "    -inh_fd                        " << input_image_height_output_message << std::endl;
    std::cout << "    -inw_fd                        " << input_image_width_output_message << std::endl;
    std::cout << "    -exp_r_fd                      " << expand_ratio_output_message << std::endl;
    std::cout << "    -t_reid                        " << threshold_output_message_face_reid << std::endl;
    std::cout << "    -fg                            " << reid_gallery_path_message << std::endl;
    std::cout << "    -teacher_id                    " << teacher_id_message << std::endl;
    std::cout << "    -no_show                       " << no_show_message << std::endl;
    std::cout << "    -min_ad                        " << min_action_duration_message << std::endl;
    std::cout << "    -d_ad                          " << same_action_time_delta_message << std::endl;
    std::cout << "    -student_ac                    " << student_actions_message << std::endl;
    std::cout << "    -top_ac                        " << top_actions_message << std::endl;
    std::cout << "    -teacher_ac                    " << teacher_actions_message << std::endl;
    std::cout << "    -top_id                        " << target_action_name_message << std::endl;
    std::cout << "    -a_top                         " << target_actions_num_message << std::endl;
    std::cout << "    -crop_gallery                  " << crop_gallery_message << std::endl;
    std::cout << "    -t_reg_fd                      " << face_threshold_registration_output_message << std::endl;
    std::cout << "    -min_size_fr                   " << min_size_fr_reg_output_message << std::endl;
    std::cout << "    -al                            " << act_det_output_message << std::endl;
    std::cout << "    -ss_t                          " << tracker_smooth_size_message << std::endl;
    std::cout << "    -u                             " << utilization_monitors_message << std::endl;
}

using ImageWithFrameIndex = std::pair<cv::Mat, int>;
std::unique_ptr<PedestrianTracker> CreatePedestrianTracker(const std::string& reid_model,
    const ov::Core& core,
    const std::string& deviceName,
    bool should_keep_tracking_info) {
    ppTrackerParams params;

    if (should_keep_tracking_info) {
        params.drop_forgotten_tracks = false;
        params.max_num_objects_in_track = -1;
    }

    std::unique_ptr<PedestrianTracker> tracker(new PedestrianTracker(params));

    // Load reid-model.
    std::shared_ptr<ppIImageDescriptor> descriptor_fast =
        std::make_shared<ppResizedImageDescriptor>(cv::Size(16, 32), cv::InterpolationFlags::INTER_LINEAR);
    std::shared_ptr<ppIDescriptorDistance> distance_fast = std::make_shared<ppMatchTemplateDistance>();

    tracker->set_descriptor_fast(descriptor_fast);
    tracker->set_distance_fast(distance_fast);

    if (!reid_model.empty()) {
        ppModelConfigTracker reid_config(reid_model);
        reid_config.max_batch_size = 16;  // defaulting to 16
        std::shared_ptr<ppIImageDescriptor> descriptor_strong =
            std::make_shared<ppDescriptor>(reid_config, core, deviceName);

        if (descriptor_strong == nullptr) {
            throw std::runtime_error("[SAMPLES] internal error - invalid descriptor");
        }
        std::shared_ptr<ppIDescriptorDistance> distance_strong = std::make_shared<ppCosDistance>(descriptor_strong->size());

        tracker->set_descriptor_strong(descriptor_strong);
        tracker->set_distance_strong(distance_strong);
    }
    else {
        slog::warn << "Reid model "
            << "was not specified. "
            << "Only fast reidentification approach will be used." << slog::endl;
    }

    return tracker;
}



cv::Mat renderHumanPose(HumanPoseResult& result, OutputTransform& outputTransform) {
    if (!result.metaData) {
        throw std::invalid_argument("Renderer: metadata is null");
    }

    auto outputImg = result.metaData->asRef<ImageMetaData>().img;

    if (outputImg.empty()) {
        throw std::invalid_argument("Renderer: image provided in metadata is empty");
    }
    outputTransform.resize(outputImg);
    static const cv::Scalar colors[HPEOpenPose::keypointsNumber] = { cv::Scalar(255, 0, 0),
                                                                    cv::Scalar(255, 85, 0),
                                                                    cv::Scalar(255, 170, 0),
                                                                    cv::Scalar(255, 255, 0),
                                                                    cv::Scalar(170, 255, 0),
                                                                    cv::Scalar(85, 255, 0),
                                                                    cv::Scalar(0, 255, 0),
                                                                    cv::Scalar(0, 255, 85),
                                                                    cv::Scalar(0, 255, 170),
                                                                    cv::Scalar(0, 255, 255),
                                                                    cv::Scalar(0, 170, 255),
                                                                    cv::Scalar(0, 85, 255),
                                                                    cv::Scalar(0, 0, 255),
                                                                    cv::Scalar(85, 0, 255),
                                                                    cv::Scalar(170, 0, 255),
                                                                    cv::Scalar(255, 0, 255),
                                                                    cv::Scalar(255, 0, 170),
                                                                    cv::Scalar(255, 0, 85) };
    static const std::pair<int, int> keypointsOP[] = { {1, 2},
                                                      {1, 5},
                                                      {2, 3},
                                                      {3, 4},
                                                      {5, 6},
                                                      {6, 7},
                                                      {1, 8},
                                                      {8, 9},
                                                      {9, 10},
                                                      {1, 11},
                                                      {11, 12},
                                                      {12, 13},
                                                      {1, 0},
                                                      {0, 14},
                                                      {14, 16},
                                                      {0, 15},
                                                      {15, 17} };

    static const std::pair<int, int> keypointsAE[] = { {15, 13},
                                                      {13, 11},
                                                      {16, 14},
                                                      {14, 12},
                                                      {11, 12},
                                                      {5, 11},
                                                      {6, 12},
                                                      {5, 6},
                                                      {5, 7},
                                                      {6, 8},
                                                      {7, 9},
                                                      {8, 10},
                                                      {1, 2},
                                                      {0, 1},
                                                      {0, 2},
                                                      {1, 3},
                                                      {2, 4},
                                                      {3, 5},
                                                      {4, 6} };
    const int stickWidth = 4;
    const cv::Point2f absentKeypoint(-1.0f, -1.0f);
    for (auto& pose : result.poses) {
        for (size_t keypointIdx = 0; keypointIdx < pose.keypoints.size(); keypointIdx++) {
            if (pose.keypoints[keypointIdx] != absentKeypoint) {
                outputTransform.scaleCoord(pose.keypoints[keypointIdx]);
                cv::circle(outputImg, pose.keypoints[keypointIdx], 4, colors[keypointIdx], -1);
            }
        }
    }
    std::vector<std::pair<int, int>> limbKeypointsIds;
    if (!result.poses.empty()) {
        if (result.poses[0].keypoints.size() == HPEOpenPose::keypointsNumber) {
            limbKeypointsIds.insert(limbKeypointsIds.begin(), std::begin(keypointsOP), std::end(keypointsOP));
        }
        else {
            limbKeypointsIds.insert(limbKeypointsIds.begin(), std::begin(keypointsAE), std::end(keypointsAE));
        }
    }
    cv::Mat pane = outputImg.clone();
    for (auto pose : result.poses) {
        for (const auto& limbKeypointsId : limbKeypointsIds) {
            std::pair<cv::Point2f, cv::Point2f> limbKeypoints(pose.keypoints[limbKeypointsId.first],
                pose.keypoints[limbKeypointsId.second]);
            if (limbKeypoints.first == absentKeypoint || limbKeypoints.second == absentKeypoint) {
                continue;
            }

            float meanX = (limbKeypoints.first.x + limbKeypoints.second.x) / 2;
            float meanY = (limbKeypoints.first.y + limbKeypoints.second.y) / 2;
            cv::Point difference = limbKeypoints.first - limbKeypoints.second;
            double length = std::sqrt(difference.x * difference.x + difference.y * difference.y);
            int angle = static_cast<int>(std::atan2(difference.y, difference.x) * 180 / CV_PI);
            std::vector<cv::Point> polygon;
            cv::ellipse2Poly(cv::Point2d(meanX, meanY), cv::Size2d(length / 2, stickWidth), angle, 0, 360, 1, polygon);
            cv::fillConvexPoly(pane, polygon, colors[limbKeypointsId.second]);
        }
    }
    cv::addWeighted(outputImg, 0.4, pane, 0.6, 0, outputImg);
    return outputImg;
}

namespace ANSTEST {

    class Visualizer {
    private:
        cv::Mat m_frame;
        cv::Mat m_top_persons;
        const bool m_enabled;
        const int m_num_top_persons;
        LazyVideoWriter m_writer;
        float m_rect_scale_x;
        float m_rect_scale_y;
        static int const m_max_input_width = 1920;
        std::string const m_main_window_name = "Smart classroom demo";
        std::string const m_top_window_name = "Top-k students";
        static int const m_crop_width = 128;
        static int const m_crop_height = 320;
        static int const m_header_size = 80;
        static int const m_margin_size = 5;

    public:
        Visualizer(bool enabled, const LazyVideoWriter& writer, int num_top_persons) :
            m_enabled(enabled), m_num_top_persons(num_top_persons), m_writer(writer), m_rect_scale_x(0), m_rect_scale_y(0) {
            if (!m_enabled) {
                return;
            }

            cv::namedWindow(m_main_window_name);

            if (m_num_top_persons > 0) {
                cv::namedWindow(m_top_window_name);

                CreateTopWindow();
                ClearTopWindow();
            }
        }

        static cv::Size GetOutputSize(const cv::Size& input_size) {
            if (input_size.width > m_max_input_width) {
                float ratio = static_cast<float>(input_size.height) / input_size.width;
                return cv::Size(m_max_input_width, cvRound(ratio * m_max_input_width));
            }
            return input_size;
        }

        void SetFrame(const cv::Mat& frame) {
            if (!m_enabled) {
                return;
            }

            m_frame = frame.clone();
            m_rect_scale_x = 1;
            m_rect_scale_y = 1;
            cv::Size new_size = GetOutputSize(m_frame.size());
            if (new_size != m_frame.size()) {
                m_rect_scale_x = static_cast<float>(new_size.height) / m_frame.size().height;
                m_rect_scale_y = static_cast<float>(new_size.width) / m_frame.size().width;
                cv::resize(m_frame, m_frame, new_size);
            }
        }

        void Show() {
            if (m_enabled) {
                cv::imshow(m_main_window_name, m_frame);
            }

            m_writer.write(m_frame);
        }

        void DrawCrop(cv::Rect roi, int id, const cv::Scalar& color) const {
            if (!m_enabled || m_num_top_persons <= 0) {
                return;
            }

            if (id < 0 || id >= m_num_top_persons) {
                return;
            }

            if (m_rect_scale_x != 1 || m_rect_scale_y != 1) {
                roi.x = cvRound(roi.x * m_rect_scale_x);
                roi.y = cvRound(roi.y * m_rect_scale_y);

                roi.height = cvRound(roi.height * m_rect_scale_y);
                roi.width = cvRound(roi.width * m_rect_scale_x);
            }

            roi.x = std::max(0, roi.x);
            roi.y = std::max(0, roi.y);
            roi.width = std::min(roi.width, m_frame.cols - roi.x);
            roi.height = std::min(roi.height, m_frame.rows - roi.y);

            const auto crop_label = std::to_string(id + 1);

            auto frame_crop = m_frame(roi).clone();
            cv::resize(frame_crop, frame_crop, cv::Size(m_crop_width, m_crop_height));

            const int shift = (id + 1) * m_margin_size + id * m_crop_width;
            frame_crop.copyTo(m_top_persons(cv::Rect(shift, m_header_size, m_crop_width, m_crop_height)));

            cv::imshow(m_top_window_name, m_top_persons);
        }

        void DrawObject(cv::Rect rect, const std::string& label_to_draw,
            const cv::Scalar& text_color, const cv::Scalar& bbox_color, bool plot_bg) {
            if (!m_enabled) {
                return;
            }

            if (m_rect_scale_x != 1 || m_rect_scale_y != 1) {
                rect.x = cvRound(rect.x * m_rect_scale_x);
                rect.y = cvRound(rect.y * m_rect_scale_y);

                rect.height = cvRound(rect.height * m_rect_scale_y);
                rect.width = cvRound(rect.width * m_rect_scale_x);
            }
            cv::rectangle(m_frame, rect, bbox_color);

            if (plot_bg && !label_to_draw.empty()) {
                int baseLine = 0;
                const cv::Size label_size = cv::getTextSize(label_to_draw, cv::FONT_HERSHEY_PLAIN, 1, 1, &baseLine);
                cv::rectangle(
                    m_frame,
                    cv::Point(rect.x, rect.y - label_size.height),
                    cv::Point(rect.x + label_size.width, rect.y + baseLine),
                    bbox_color, cv::FILLED);
            }
            if (!label_to_draw.empty()) {
                cv::putText(m_frame, label_to_draw, cv::Point(rect.x, rect.y), cv::FONT_HERSHEY_PLAIN, 1,
                    text_color, 1, cv::LINE_AA);
            }
        }

        void CreateTopWindow() {
            if (!m_enabled || m_num_top_persons <= 0) {
                return;
            }

            const int width = m_margin_size * (m_num_top_persons + 1) + m_crop_width * m_num_top_persons;
            const int height = m_header_size + m_crop_height + m_margin_size;

            m_top_persons.create(height, width, CV_8UC3);
        }

        void ClearTopWindow() {
            if (!m_enabled || m_num_top_persons <= 0) {
                return;
            }

            m_top_persons.setTo(cv::Scalar(255, 255, 255));

            for (int i = 0; i < m_num_top_persons; ++i) {
                const int shift = (i + 1) * m_margin_size + i * m_crop_width;

                cv::rectangle(m_top_persons, cv::Point(shift, m_header_size),
                    cv::Point(shift + m_crop_width, m_header_size + m_crop_height),
                    cv::Scalar(128, 128, 128), cv::FILLED);

                const auto label_to_draw = "#" + std::to_string(i + 1);
                int baseLine = 0;
                const auto label_size = cv::getTextSize(label_to_draw, cv::FONT_HERSHEY_SIMPLEX, 2, 2, &baseLine);
                const int text_shift = (m_crop_width - label_size.width) / 2;
                cv::putText(m_top_persons, label_to_draw,
                    cv::Point(shift + text_shift, label_size.height + baseLine / 2),
                    cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 0), 2, cv::LINE_AA);
            }

            cv::imshow(m_top_window_name, m_top_persons);
        }

        void Finalize() const {
            if (m_enabled) {
                cv::destroyWindow(m_main_window_name);

                if (m_num_top_persons > 0) {
                    cv::destroyWindow(m_top_window_name);
                }
            }
        }
    };

    const int default_action_index = -1;  // Unknown action class

    void ConvertActionMapsToFrameEventTracks(const std::vector<std::map<int, int>>& obj_id_to_action_maps,
        int default_action,
        std::map<int, FrameEventsTrack>* obj_id_to_actions_track) {
        for (size_t frame_id = 0; frame_id < obj_id_to_action_maps.size(); ++frame_id) {
            for (const auto& tup : obj_id_to_action_maps[frame_id]) {
                if (tup.second != default_action) {
                    (*obj_id_to_actions_track)[tup.first].emplace_back(frame_id, tup.second);
                }
            }
        }
    }

    void SmoothTracks(const std::map<int, FrameEventsTrack>& obj_id_to_actions_track,
        int start_frame, int end_frame, int window_size, int min_length, int default_action,
        std::map<int, RangeEventsTrack>* obj_id_to_events) {
        // Iterate over face tracks
        for (const auto& tup : obj_id_to_actions_track) {
            const auto& frame_events = tup.second;
            if (frame_events.empty()) {
                continue;
            }

            RangeEventsTrack range_events;


            // Merge neighbouring events and filter short ones
            range_events.emplace_back(frame_events.front().frame_id,
                frame_events.front().frame_id + 1,
                frame_events.front().action);

            for (size_t frame_id = 1; frame_id < frame_events.size(); ++frame_id) {
                const auto& last_range_event = range_events.back();
                const auto& cur_frame_event = frame_events[frame_id];

                if (last_range_event.end_frame_id + window_size - 1 >= cur_frame_event.frame_id &&
                    last_range_event.action == cur_frame_event.action) {
                    range_events.back().end_frame_id = cur_frame_event.frame_id + 1;
                }
                else {
                    if (range_events.back().end_frame_id - range_events.back().begin_frame_id < min_length) {
                        range_events.pop_back();
                    }

                    range_events.emplace_back(cur_frame_event.frame_id,
                        cur_frame_event.frame_id + 1,
                        cur_frame_event.action);
                }
            }
            if (range_events.back().end_frame_id - range_events.back().begin_frame_id < min_length) {
                range_events.pop_back();
            }

            // Extrapolate track
            if (range_events.empty()) {
                range_events.emplace_back(start_frame, end_frame, default_action);
            }
            else {
                range_events.front().begin_frame_id = start_frame;
                range_events.back().end_frame_id = end_frame;
            }

            // Interpolate track
            for (size_t event_id = 1; event_id < range_events.size(); ++event_id) {
                auto& last_event = range_events[event_id - 1];
                auto& cur_event = range_events[event_id];

                int middle_point = static_cast<int>(0.5f * (cur_event.begin_frame_id + last_event.end_frame_id));

                cur_event.begin_frame_id = middle_point;
                last_event.end_frame_id = middle_point;
            }

            // Merge consecutive events
            auto& final_events = (*obj_id_to_events)[tup.first];
            final_events.push_back(range_events.front());
            for (size_t event_id = 1; event_id < range_events.size(); ++event_id) {
                const auto& cur_event = range_events[event_id];

                if (final_events.back().action == cur_event.action) {
                    final_events.back().end_frame_id = cur_event.end_frame_id;
                }
                else {
                    final_events.push_back(cur_event);
                }
            }
        }
    }

    void ConvertRangeEventsTracksToActionMaps(int num_frames,
        const std::map<int, RangeEventsTrack>& obj_id_to_events,
        std::vector<std::map<int, int>>* obj_id_to_action_maps) {
        obj_id_to_action_maps->resize(num_frames);

        for (const auto& tup : obj_id_to_events) {
            const int obj_id = tup.first;
            const auto& events = tup.second;

            for (const auto& event : events) {
                for (int frame_id = event.begin_frame_id; frame_id < event.end_frame_id; ++frame_id) {
                    (*obj_id_to_action_maps)[frame_id].emplace(obj_id, event.action);
                }
            }
        }
    }

    std::string GetActionTextLabel(const unsigned label, const std::vector<std::string>& actions_map) {
        if (label < actions_map.size()) {
            return actions_map[label];
        }
        return "__undefined__";
    }

    cv::Scalar GetActionTextColor(const unsigned label) {
        static const cv::Scalar label_colors[] = {
            cv::Scalar(0, 255, 0), cv::Scalar(255, 0, 0), cv::Scalar(0, 0, 255), cv::Scalar(0, 255, 255) };
        if (label < arraySize(label_colors)) {
            return label_colors[label];
        }
        return cv::Scalar(0, 0, 0);
    }

    float CalculateIoM(const cv::Rect& rect1, const cv::Rect& rect2) {
        int area1 = rect1.area();
        int area2 = rect2.area();

        float area_min = static_cast<float>(std::min(area1, area2));
        float area_intersect = static_cast<float>((rect1 & rect2).area());

        return area_intersect / area_min;
    }

    cv::Rect DecreaseRectByRelBorders(const cv::Rect& r) {
        float w = static_cast<float>(r.width);
        float h = static_cast<float>(r.height);

        float left = std::ceil(w * 0.0f);
        float top = std::ceil(h * 0.0f);
        float right = std::ceil(w * 0.0f);
        float bottom = std::ceil(h * .7f);

        cv::Rect res;
        res.x = r.x + static_cast<int>(left);
        res.y = r.y + static_cast<int>(top);
        res.width = static_cast<int>(r.width - left - right);
        res.height = static_cast<int>(r.height - top - bottom);
        return res;
    }

    int GetIndexOfTheNearestPerson(const TrackedObject& face, const std::vector<TrackedObject>& tracked_persons) {
        int argmax = -1;
        float max_iom = std::numeric_limits<float>::lowest();
        for (size_t i = 0; i < tracked_persons.size(); i++) {
            float iom = CalculateIoM(face.rect, DecreaseRectByRelBorders(tracked_persons[i].rect));
            if ((iom > 0) && (iom > max_iom)) {
                max_iom = iom;
                argmax = i;
            }
        }
        return argmax;
    }

    std::map<int, int> GetMapFaceTrackIdToLabel(const std::vector<Track>& face_tracks) {
        std::map<int, int> face_track_id_to_label;
        for (const auto& track : face_tracks) {
            const auto& first_obj = track.first_object;
            // check consistency
            // to receive this consistency for labels
            // use the function UpdateTrackLabelsToBestAndFilterOutUnknowns
            for (const auto& obj : track.objects) {
                SCR_CHECK_EQ(obj.label, first_obj.label);
                SCR_CHECK_EQ(obj.object_id, first_obj.object_id);
            }

            auto cur_obj_id = first_obj.object_id;
            auto cur_label = first_obj.label;
            if (face_track_id_to_label.count(cur_obj_id) == 1) {
                throw std::runtime_error("Repeating face tracks");
            }
            face_track_id_to_label[cur_obj_id] = cur_label;
        }
        return face_track_id_to_label;
    }

    class FaceRecognizer {
    public:
        virtual ~FaceRecognizer() = default;

        virtual bool LabelExists(const std::string& label) const = 0;
        virtual std::string GetLabelByID(int id) const = 0;
        virtual std::vector<std::string> GetIDToLabelMap() const = 0;

        virtual std::vector<int> Recognize(const cv::Mat& frame, const detection::DetectedObjects& faces) = 0;
    };

    class FaceRecognizerNull : public FaceRecognizer {
    public:
        bool LabelExists(const std::string&) const override { return false; }

        std::string GetLabelByID(int) const override {
            return EmbeddingsGallery::unknown_label;
        }

        std::vector<std::string> GetIDToLabelMap() const override { return {}; }

        std::vector<int> Recognize(const cv::Mat&, const detection::DetectedObjects& faces) override {
            return std::vector<int>(faces.size(), EmbeddingsGallery::unknown_id);
        }
    };

    class FaceRecognizerDefault : public FaceRecognizer {
    public:
        FaceRecognizerDefault(
            const CnnConfig& landmarks_detector_config,
            const CnnConfig& reid_config,
            const detection::DetectorConfig& face_registration_det_config,
            const std::string& face_gallery_path,
            double reid_threshold,
            int min_size_fr,
            bool crop_gallery,
            bool greedy_reid_matching) :
            landmarks_detector(landmarks_detector_config),
            face_reid(reid_config),
            face_gallery(face_gallery_path, reid_threshold, min_size_fr, crop_gallery,
                face_registration_det_config, landmarks_detector, face_reid,
                greedy_reid_matching)
        {
            if (face_gallery.size() == 0) {
                slog::warn << "Face reid gallery is empty!" << slog::endl;
            }
            else {
                slog::info << "Face reid gallery size: " << face_gallery.size() << slog::endl;
            }
        }

        bool LabelExists(const std::string& label) const override {
            return face_gallery.LabelExists(label);
        }

        std::string GetLabelByID(int id) const override {
            return face_gallery.GetLabelByID(id);
        }

        std::vector<std::string> GetIDToLabelMap() const override {
            return face_gallery.GetIDToLabelMap();
        }

        std::vector<int> Recognize(const cv::Mat& frame, const detection::DetectedObjects& faces) override {
            const int maxLandmarksBatch = landmarks_detector.maxBatchSize();
            int numFaces = (int)faces.size();

            std::vector<cv::Mat> landmarks;
            std::vector<cv::Mat> embeddings;
            std::vector<cv::Mat> face_rois;

            auto face_roi = [&](const detection::DetectedObject& face) {
                return frame(face.rect);
                };
            if (numFaces < maxLandmarksBatch) {
                std::vector<float*> embeddingResult;
                std::vector<float> embeddingRs;
                float* embData;

                std::transform(faces.begin(), faces.end(), std::back_inserter(face_rois), face_roi);
                landmarks_detector.Compute(face_rois, &landmarks, cv::Size(2, 5));
                AlignFaces(&face_rois, &landmarks);
                face_reid.Compute(face_rois, &embeddings);
                embeddingRs.assign(embeddings[0].begin<float>(), embeddings[0].end<float>());
                embData = embeddingRs.data();
                for (int i = 0; i < 10; i++) {
                    std::cout << "Data[" << i << "]=" << embData[i] << std::endl;
                }
                embeddingResult.push_back(embData);
            }
            else {
                auto embedding = [&](cv::Mat& emb) { return emb; };
                for (int n = numFaces; n > 0; n -= maxLandmarksBatch) {
                    landmarks.clear();
                    face_rois.clear();
                    size_t start_idx = size_t(numFaces) - n;
                    size_t end_idx = start_idx + std::min(numFaces, maxLandmarksBatch);
                    std::transform(faces.begin() + start_idx, faces.begin() + end_idx, std::back_inserter(face_rois), face_roi);

                    landmarks_detector.Compute(face_rois, &landmarks, cv::Size(2, 5));

                    AlignFaces(&face_rois, &landmarks);

                    std::vector<cv::Mat> batch_embeddings;
                    face_reid.Compute(face_rois, &batch_embeddings);
                    std::transform(batch_embeddings.begin(), batch_embeddings.end(), std::back_inserter(embeddings), embedding);
                }
            }

            return face_gallery.GetIDsByEmbeddings(embeddings);
        }

    private:
        VectorCNN landmarks_detector;
        VectorCNN face_reid;
        EmbeddingsGallery face_gallery;
    };

    bool ParseAndCheckCommandLine(int argc, char* argv[]) {
        // Parsing and validation of input args

        gflags::ParseCommandLineNonHelpFlags(&argc, &argv, true);
        if (FLAGS_h) {
            showUsage();
            showAvailableDevices();
            return false;
        }

        if (FLAGS_i.empty()) {
            throw std::logic_error("Parameter -i is not set");
        }
        if (FLAGS_m_act.empty() && FLAGS_m_fd.empty()) {
            throw std::logic_error("At least one parameter -m_act or -m_fd must be set");
        }

        return true;
    }



    class ColorPalette {
    private:
        std::vector<cv::Scalar> palette;

        static double getRandom(double a = 0.0, double b = 1.0) {
            static std::default_random_engine e;
            std::uniform_real_distribution<> dis(a, std::nextafter(b, std::numeric_limits<double>::max()));
            return dis(e);
        }

        static double distance(const cv::Scalar& c1, const cv::Scalar& c2) {
            auto dh = std::fmin(std::fabs(c1[0] - c2[0]), 1 - fabs(c1[0] - c2[0])) * 2;
            auto ds = std::fabs(c1[1] - c2[1]);
            auto dv = std::fabs(c1[2] - c2[2]);

            return dh * dh + ds * ds + dv * dv;
        }

        static cv::Scalar maxMinDistance(const std::vector<cv::Scalar>& colorSet,
            const std::vector<cv::Scalar>& colorCandidates) {
            std::vector<double> distances;
            distances.reserve(colorCandidates.size());
            for (auto& c1 : colorCandidates) {
                auto min =
                    *std::min_element(colorSet.begin(), colorSet.end(), [&c1](const cv::Scalar& a, const cv::Scalar& b) {
                    return distance(c1, a) < distance(c1, b);
                        });
                distances.push_back(distance(c1, min));
            }
            auto max = std::max_element(distances.begin(), distances.end());
            return colorCandidates[std::distance(distances.begin(), max)];
        }

        static cv::Scalar hsv2rgb(const cv::Scalar& hsvColor) {
            cv::Mat rgb;
            cv::Mat hsv(1, 1, CV_8UC3, hsvColor);
            cv::cvtColor(hsv, rgb, cv::COLOR_HSV2RGB);
            return cv::Scalar(rgb.data[0], rgb.data[1], rgb.data[2]);
        }

    public:
        explicit ColorPalette(size_t n) {
            palette.reserve(n);
            std::vector<cv::Scalar> hsvColors(1, { 1., 1., 1. });
            std::vector<cv::Scalar> colorCandidates;
            size_t numCandidates = 100;

            hsvColors.reserve(n);
            colorCandidates.resize(numCandidates);
            for (size_t i = 1; i < n; ++i) {
                std::generate(colorCandidates.begin(), colorCandidates.end(), []() {
                    return cv::Scalar{ getRandom(), getRandom(0.8, 1.0), getRandom(0.5, 1.0) };
                    });
                hsvColors.push_back(maxMinDistance(hsvColors, colorCandidates));
            }

            for (auto& hsv : hsvColors) {
                // Convert to OpenCV HSV format
                hsv[0] *= 179;
                hsv[1] *= 255;
                hsv[2] *= 255;

                palette.push_back(hsv2rgb(hsv));
            }
        }

        const cv::Scalar& operator[](size_t index) const {
            return palette[index % palette.size()];
        }
    };

    cv::Mat renderDetectionData(DetectionResult& result, const ColorPalette& palette, OutputTransform& outputTransform) {
        if (!result.metaData) {
            throw std::invalid_argument("Renderer: metadata is null");
        }

        auto outputImg = result.metaData->asRef<ImageMetaData>().img;

        if (outputImg.empty()) {
            throw std::invalid_argument("Renderer: image provided in metadata is empty");
        }
        //outputTransform.resize(outputImg);
        // Visualizing result data over source image
        if (FLAGS_r) {
            slog::debug << " -------------------- Frame # " << result.frameId << "--------------------" << slog::endl;
            slog::debug << " Class ID  | Confidence | XMIN | YMIN | XMAX | YMAX " << slog::endl;
        }

        for (auto& obj : result.objects) {
            if (FLAGS_r) {
                slog::debug << " " << std::left << std::setw(9) << obj.label << " | " << std::setw(10) << obj.confidence
                    << " | " << std::setw(4) << int(obj.x) << " | " << std::setw(4) << int(obj.y) << " | "
                    << std::setw(4) << int(obj.x + obj.width) << " | " << std::setw(4) << int(obj.y + obj.height)
                    << slog::endl;
            }
            //outputTransform.scaleRect(obj);
            std::ostringstream conf;
            conf << ":" << std::fixed << std::setprecision(1) << obj.confidence * 100 << '%';
            const auto& color = palette[obj.labelID];
            putHighlightedText(outputImg,
                obj.label + conf.str(),
                cv::Point2f(obj.x, obj.y - 5),
                cv::FONT_HERSHEY_COMPLEX_SMALL,
                1,
                color,
                2);
            cv::rectangle(outputImg, obj, color, 2);
        }

        try {
            for (auto& lmark : result.asRef<RetinaFaceDetectionResult>().landmarks) {
                outputTransform.scaleCoord(lmark);
                cv::circle(outputImg, lmark, 2, cv::Scalar(0, 255, 255), -1);
            }
        }
        catch (const std::bad_cast&) {}

        return outputImg;
    }
} // namespace

int SmartClassApp() {
    try {
        PerformanceMetrics metrics;
        FLAGS_i = "C:\\Programs\\ANSFR\\classroom.mp4";
        const auto ad_model_path = FLAGS_m_act;
        const auto fd_model_path = FLAGS_m_fd;
        const auto fr_model_path = FLAGS_m_reid;
        const auto lm_model_path = FLAGS_m_lm;
        const auto teacher_id = FLAGS_teacher_id;

        if (!FLAGS_teacher_id.empty() && !FLAGS_top_id.empty()) {
            slog::err << "Cannot run simultaneously teacher action and top-k students recognition."
                << slog::endl;
            return 1;
        }

        const auto actions_type = FLAGS_teacher_id.empty() ?
            (FLAGS_a_top > 0 ? TOP_K : STUDENT) :
            TEACHER;
        if (!FLAGS_ad.empty()) {
            if (actions_type != STUDENT) {
                slog::err << "-ad requires -teacher_id and -a_top to be unset" << slog::endl;
                return 1;
            }
            if (FLAGS_fg.empty()) {
                slog::err << "-ad requires -fg to be set" << slog::endl;
                return 1;
            }
        }
        const auto actions_map = actions_type == STUDENT ?
            split(FLAGS_student_ac, ',') : actions_type == TOP_K ?
            split(FLAGS_top_ac, ',') :
            split(FLAGS_teacher_ac, ',');
        const auto num_top_persons = actions_type == TOP_K ? FLAGS_a_top : -1;
        const auto top_action_id = actions_type == TOP_K ?
            std::distance(actions_map.begin(), find(actions_map.begin(), actions_map.end(), FLAGS_top_id)) : -1;
        if (actions_type == TOP_K && (top_action_id < 0 || top_action_id >= static_cast<int>(actions_map.size()))) {
            slog::err << "Cannot find target action: " << FLAGS_top_id << slog::endl;
            return 1;
        }

        slog::info << ov::get_openvino_version() << slog::endl;
        ov::Core core;

        std::unique_ptr<AsyncDetection<DetectedAction>> action_detector;
        if (!ad_model_path.empty()) {
            // Load action detector
            ActionDetectorConfig action_config(ad_model_path, "Person/Action Detection");
            action_config.m_deviceName = FLAGS_d_act;
            action_config.m_core = core;
            action_config.is_async = true;
            action_config.detection_confidence_threshold = static_cast<float>(FLAGS_t_ad);
            action_config.action_confidence_threshold = static_cast<float>(FLAGS_t_ar);
            action_config.num_action_classes = actions_map.size();
            action_detector.reset(new ActionDetection(action_config));
        }
        else {
            action_detector.reset(new NullDetection<DetectedAction>);
            slog::info << "Person/Action Detection DISABLED" << slog::endl;
        }

        std::unique_ptr<AsyncDetection<detection::DetectedObject>> face_detector;
        if (!fd_model_path.empty()) {
            // Load face detector
            detection::DetectorConfig face_config(fd_model_path);
            face_config.m_deviceName = FLAGS_d_fd;
            face_config.m_core = core;
            face_config.is_async = true;
            face_config.confidence_threshold = static_cast<float>(FLAGS_t_fd);
            face_config.input_h = FLAGS_inh_fd;
            face_config.input_w = FLAGS_inw_fd;
            face_config.increase_scale_x = static_cast<float>(FLAGS_exp_r_fd);
            face_config.increase_scale_y = static_cast<float>(FLAGS_exp_r_fd);
            face_detector.reset(new detection::FaceDetection(face_config));
        }
        else {
            face_detector.reset(new NullDetection<detection::DetectedObject>);
        }

        std::unique_ptr<ANSTEST::FaceRecognizer> face_recognizer;

        if (!fd_model_path.empty() && !fr_model_path.empty() && !lm_model_path.empty()) {
            // Create face recognizer
            detection::DetectorConfig face_registration_det_config(fd_model_path);
            face_registration_det_config.m_deviceName = FLAGS_d_fd;
            face_registration_det_config.m_core = core;
            face_registration_det_config.is_async = false;
            face_registration_det_config.confidence_threshold = static_cast<float>(FLAGS_t_reg_fd);
            face_registration_det_config.increase_scale_x = static_cast<float>(FLAGS_exp_r_fd);
            face_registration_det_config.increase_scale_y = static_cast<float>(FLAGS_exp_r_fd);

            CnnConfig reid_config(fr_model_path, "Face Re-Identification");
            reid_config.m_deviceName = FLAGS_d_reid;
            reid_config.m_max_batch_size = 1;
            reid_config.m_core = core;

            CnnConfig landmarks_config(lm_model_path, "Facial Landmarks Regression");
            landmarks_config.m_deviceName = FLAGS_d_lm;
            landmarks_config.m_max_batch_size = 1;
            landmarks_config.m_core = core;

            face_recognizer.reset(new ANSTEST::FaceRecognizerDefault(
                landmarks_config, reid_config,
                face_registration_det_config,
                FLAGS_fg, FLAGS_t_reid, FLAGS_min_size_fr, FLAGS_crop_gallery, FLAGS_greedy_reid_matching));

            if (actions_type == TEACHER && !face_recognizer->LabelExists(teacher_id)) {
                slog::err << "Teacher id does not exist in the gallery!" << slog::endl;
                return 1;
            }
        }
        else {
            slog::warn << "Face Recognition models are disabled!" << slog::endl;
            if (actions_type == TEACHER) {
                slog::err << "Face recognition must be enabled to recognize teacher actions." << slog::endl;
                return 1;
            }

            face_recognizer.reset(new ANSTEST::FaceRecognizerNull);
        }

        // Create tracker for reid
        TrackerParams tracker_reid_params;
        tracker_reid_params.min_track_duration = 1;
        tracker_reid_params.forget_delay = 150;
        tracker_reid_params.affinity_thr = 0.8f;
        tracker_reid_params.averaging_window_size_for_rects = 1;
        tracker_reid_params.averaging_window_size_for_labels = std::numeric_limits<int>::max();
        tracker_reid_params.bbox_heights_range = cv::Vec2f(10, 1080);
        tracker_reid_params.drop_forgotten_tracks = false;
        tracker_reid_params.max_num_objects_in_track = std::numeric_limits<int>::max();
        tracker_reid_params.objects_type = "face";

        Tracker tracker_reid(tracker_reid_params);

        // Create Tracker for action recognition
        TrackerParams tracker_action_params;
        tracker_action_params.min_track_duration = 8;
        tracker_action_params.forget_delay = 150;
        tracker_action_params.affinity_thr = 0.9f;
        tracker_action_params.averaging_window_size_for_rects = 5;
        tracker_action_params.averaging_window_size_for_labels = FLAGS_ss_t > 0 ?
            FLAGS_ss_t : actions_type == TOP_K ? 5 : 1;
        tracker_action_params.bbox_heights_range = cv::Vec2f(10, 2160);
        tracker_action_params.drop_forgotten_tracks = false;
        tracker_action_params.max_num_objects_in_track = std::numeric_limits<int>::max();
        tracker_action_params.objects_type = "action";

        Tracker tracker_action(tracker_action_params);

        size_t work_num_frames = 0;
        size_t wait_num_frames = 0;
        size_t total_num_frames = 0;
        const char ESC_KEY = 27;
        const char SPACE_KEY = 32;
        const cv::Scalar green_color(0, 255, 0);
        const cv::Scalar red_color(0, 0, 255);
        const cv::Scalar white_color(255, 255, 255);
        std::vector<std::map<int, int>> face_obj_id_to_action_maps;
        std::map<int, int> top_k_obj_ids;

        int teacher_track_id = -1;

        std::unique_ptr<ImagesCapture> cap = openImagesCapture(FLAGS_i, FLAGS_loop, read_type::safe, 0, FLAGS_read_limit);
        cv::Mat frame = cap->read();

        cv::Size graphSize{ static_cast<int>(frame.cols / 4), 60 };
        Presenter presenter(FLAGS_u, frame.rows - graphSize.height - 10, graphSize);

        LazyVideoWriter videoWriter{ FLAGS_o, cap->fps(), FLAGS_limit };
        ANSTEST::Visualizer sc_visualizer(!FLAGS_no_show, videoWriter, num_top_persons);
        DetectionsLogger logger(slog::debug, FLAGS_r, FLAGS_ad, FLAGS_al);
        if (actions_type != TOP_K) {
            action_detector->enqueue(frame);
            action_detector->submitRequest();
            face_detector->enqueue(frame);
            face_detector->submitRequest();
        }

        bool is_monitoring_enabled = false;

        bool is_last_frame = false;
        while (!is_last_frame) {
            auto startTime = std::chrono::steady_clock::now();
            cv::Mat prev_frame = std::move(frame);
            frame = cap->read();
            if (frame.data && frame.size() != prev_frame.size()) {
                throw std::runtime_error("Can't track objects on images of different size");
            }
            is_last_frame = !frame.data;

            logger.CreateNextFrameRecord(FLAGS_i, work_num_frames, prev_frame.cols, prev_frame.rows);
            char key = cv::waitKey(1);
            if (key == ESC_KEY) {
                break;
            }
            presenter.handleKey(key);

            presenter.drawGraphs(prev_frame);

            sc_visualizer.SetFrame(prev_frame);
            if (actions_type == TOP_K) {
                if ((is_monitoring_enabled && key == SPACE_KEY) || (!is_monitoring_enabled && key != SPACE_KEY)) {
                    if (key == SPACE_KEY) {
                        action_detector->wait();
                        action_detector->fetchResults();

                        tracker_action.Reset();
                        top_k_obj_ids.clear();

                        is_monitoring_enabled = false;

                        sc_visualizer.ClearTopWindow();
                    }

                    ++wait_num_frames;
                    metrics.update(startTime, frame, { 10, 22 }, cv::FONT_HERSHEY_COMPLEX, 0.65);
                }
                else {
                    if (key == SPACE_KEY) {
                        is_monitoring_enabled = true;

                        action_detector->enqueue(prev_frame);
                        action_detector->submitRequest();
                    }

                    action_detector->wait();
                    DetectedActions actions = action_detector->fetchResults();

                    if (!is_last_frame) {
                        action_detector->enqueue(frame);
                        action_detector->submitRequest();
                    }

                    TrackedObjects tracked_action_objects;
                    for (const auto& action : actions) {
                        tracked_action_objects.emplace_back(action.rect, action.detection_conf, action.label);
                    }

                    tracker_action.Process(prev_frame, tracked_action_objects, total_num_frames);
                    const auto tracked_actions = tracker_action.TrackedDetectionsWithLabels();

                    if (static_cast<int>(top_k_obj_ids.size()) < FLAGS_a_top) {
                        for (const auto& action : tracked_actions) {
                            if (action.label == top_action_id && top_k_obj_ids.count(action.object_id) == 0) {
                                const int action_id_in_top = top_k_obj_ids.size();
                                top_k_obj_ids.emplace(action.object_id, action_id_in_top);

                                sc_visualizer.DrawCrop(action.rect, action_id_in_top, red_color);

                                if (static_cast<int>(top_k_obj_ids.size()) >= FLAGS_a_top) {
                                    break;
                                }
                            }
                        }
                    }

                    for (const auto& action : tracked_actions) {
                        auto box_color = white_color;
                        std::string box_caption = "";

                        if (top_k_obj_ids.count(action.object_id) > 0) {
                            box_color = red_color;
                            box_caption = std::to_string(top_k_obj_ids[action.object_id] + 1);
                        }

                        sc_visualizer.DrawObject(action.rect, box_caption, white_color, box_color, true);
                    }
                    ++work_num_frames;
                    metrics.update(startTime, frame, { 10, 22 }, cv::FONT_HERSHEY_COMPLEX, 0.65);
                }
            }
            else {
                face_detector->wait();
                detection::DetectedObjects faces = face_detector->fetchResults();
                action_detector->wait();
                DetectedActions actions = action_detector->fetchResults();
                if (!is_last_frame) {
                    face_detector->enqueue(frame);
                    face_detector->submitRequest();
                    action_detector->enqueue(frame);
                    action_detector->submitRequest();
                }
                auto ids = face_recognizer->Recognize(prev_frame, faces);
                TrackedObjects tracked_face_objects;

                for (size_t i = 0; i < faces.size(); i++) {
                    tracked_face_objects.emplace_back(faces[i].rect, faces[i].confidence, ids[i]);
                }
                tracker_reid.Process(prev_frame, tracked_face_objects, work_num_frames);
                const auto tracked_faces = tracker_reid.TrackedDetectionsWithLabels();

                TrackedObjects tracked_action_objects;
                for (const auto& action : actions) {
                    tracked_action_objects.emplace_back(action.rect, action.detection_conf, action.label);
                }
                tracker_action.Process(prev_frame, tracked_action_objects, work_num_frames);
                const auto tracked_actions = tracker_action.TrackedDetectionsWithLabels();

                std::map<int, int> frame_face_obj_id_to_action;
                for (size_t j = 0; j < tracked_faces.size(); j++) {
                    const auto& face = tracked_faces[j];
                    std::string face_label = face_recognizer->GetLabelByID(face.label);

                    std::string label_to_draw;
                    if (face.label != EmbeddingsGallery::unknown_id)
                        label_to_draw += face_label;

                    int person_ind = ANSTEST::GetIndexOfTheNearestPerson(face, tracked_actions);
                    int action_ind = ANSTEST::default_action_index;
                    if (person_ind >= 0) {
                        action_ind = tracked_actions[person_ind].label;
                    }

                    if (actions_type == STUDENT) {
                        if (action_ind != ANSTEST::default_action_index) {
                            label_to_draw += "[" + ANSTEST::GetActionTextLabel(action_ind, actions_map) + "]";
                        }
                        frame_face_obj_id_to_action[face.object_id] = action_ind;
                        sc_visualizer.DrawObject(face.rect, label_to_draw, red_color, white_color, true);
                        logger.AddFaceToFrame(face.rect, face_label, "");
                    }

                    if ((actions_type == TEACHER) && (person_ind >= 0)) {
                        if (face_label == teacher_id) {
                            teacher_track_id = tracked_actions[person_ind].object_id;
                        }
                        else if (teacher_track_id == tracked_actions[person_ind].object_id) {
                            teacher_track_id = -1;
                        }
                    }
                }

                if (actions_type == STUDENT) {
                    for (const auto& action : tracked_actions) {
                        const auto& action_label = ANSTEST::GetActionTextLabel(action.label, actions_map);
                        const auto& action_color = ANSTEST::GetActionTextColor(action.label);
                        const auto& text_label = fd_model_path.empty() ? action_label : "";
                        sc_visualizer.DrawObject(action.rect, text_label, action_color, white_color, true);
                        logger.AddPersonToFrame(action.rect, action_label, "");
                        logger.AddDetectionToFrame(action, work_num_frames);
                    }
                    face_obj_id_to_action_maps.push_back(frame_face_obj_id_to_action);
                }
                else if (teacher_track_id >= 0) {
                    auto res_find = std::find_if(tracked_actions.begin(), tracked_actions.end(),
                        [teacher_track_id](const TrackedObject& o) { return o.object_id == teacher_track_id; });
                    if (res_find != tracked_actions.end()) {
                        const auto& track_action = *res_find;
                        const auto& action_label = ANSTEST::GetActionTextLabel(track_action.label, actions_map);
                        sc_visualizer.DrawObject(track_action.rect, action_label, red_color, white_color, true);
                        logger.AddPersonToFrame(track_action.rect, action_label, teacher_id);
                    }
                }
                metrics.update(startTime, frame, { 10, 22 }, cv::FONT_HERSHEY_COMPLEX, 0.65);
                ++work_num_frames;
            }

            ++total_num_frames;

            sc_visualizer.Show();

            logger.FinalizeFrameRecord();
        }
        sc_visualizer.Finalize();

        if (actions_type == STUDENT) {
            auto face_tracks = tracker_reid.vector_tracks();

            // correct labels for track
            std::vector<Track> new_face_tracks = UpdateTrackLabelsToBestAndFilterOutUnknowns(face_tracks);
            std::map<int, int> face_track_id_to_label = ANSTEST::GetMapFaceTrackIdToLabel(new_face_tracks);

            std::vector<std::string> face_id_to_label_map = face_recognizer->GetIDToLabelMap();

            if (!face_id_to_label_map.empty()) {
                std::map<int, FrameEventsTrack> face_obj_id_to_actions_track;
                ANSTEST::ConvertActionMapsToFrameEventTracks(face_obj_id_to_action_maps, ANSTEST::default_action_index,
                    &face_obj_id_to_actions_track);

                const int start_frame = 0;
                const int end_frame = face_obj_id_to_action_maps.size();
                const int smooth_window_size = static_cast<int>(cap->fps() * FLAGS_d_ad);
                const int smooth_min_length = static_cast<int>(cap->fps() * FLAGS_min_ad);
                std::map<int, RangeEventsTrack> face_obj_id_to_events;
                ANSTEST::SmoothTracks(face_obj_id_to_actions_track, start_frame, end_frame,
                    smooth_window_size, smooth_min_length, ANSTEST::default_action_index,
                    &face_obj_id_to_events);

                slog::debug << "Final ID->events mapping" << slog::endl;
                logger.DumpTracks(face_obj_id_to_events,
                    actions_map, face_track_id_to_label,
                    face_id_to_label_map);

                std::vector<std::map<int, int>> face_obj_id_to_smoothed_action_maps;
                ANSTEST::ConvertRangeEventsTracksToActionMaps(end_frame, face_obj_id_to_events,
                    &face_obj_id_to_smoothed_action_maps);

                slog::debug << "Final per-frame ID->action mapping" << slog::endl;
                logger.DumpDetections(FLAGS_i, frame.size(), work_num_frames,
                    new_face_tracks,
                    face_track_id_to_label,
                    actions_map, face_id_to_label_map,
                    face_obj_id_to_smoothed_action_maps);
            }
        }

        slog::info << "Metrics report:" << slog::endl;
        metrics.logTotal();
        slog::info << presenter.reportMeans() << slog::endl;
    }
    catch (const std::exception& error) {
        slog::err << error.what() << slog::endl;
        return 1;
    }
    catch (...) {
        slog::err << "Unknown/internal exception happened." << slog::endl;
        return 1;
    }

    return 0;
}


int ObjectDetection() {
    try {
        PerformanceMetrics metrics;

        const auto& strAnchors = split(FLAGS_anchors, ',');
        const auto& strMasks = split(FLAGS_masks, ',');
        FLAGS_i = "C:\\Programs\\ANSFR\\classroom2.mp4";
        FLAGS_loop = false;
        FLAGS_at = "ssd";
        FLAGS_m = "C:\\Programs\\ANSFR\\Models\\FaceDetection\\face-detection-0204\\FP32\\face-detection-0204.xml";
        
        //FLAGS_at = "retinaface-pytorch";
        //FLAGS_m = "C:\\Programs\\ANSFR\\Models\\FaceDetection\\retinaface-resnet50-pytorch\\FP16\\retinaface-resnet50-pytorch.xml";

        std::vector<float> anchors;
        std::vector<int64_t> masks;
        try {
            for (auto& str : strAnchors) {
                anchors.push_back(std::stof(str));
            }
        }
        catch (...) { throw std::runtime_error("Invalid anchors list is provided."); }

        try {
            for (auto& str : strMasks) {
                masks.push_back(std::stoll(str));
            }
        }
        catch (...) { throw std::runtime_error("Invalid masks list is provided."); }

        //------------------------------- Preparing Input ------------------------------------------------------
        auto cap = openImagesCapture(FLAGS_i, FLAGS_loop, FLAGS_nireq == 1 ? read_type::efficient : read_type::safe);
        cv::Mat curr_frame;

        //------------------------------ Running Detection routines ----------------------------------------------
        std::vector<std::string> labels;
        if (!FLAGS_labels.empty())
            labels = DetectionModel::loadLabels(FLAGS_labels);
        ANSTEST::ColorPalette palette(labels.size() > 0 ? labels.size() : 100);

        std::unique_ptr<ModelBase> model;
        if (FLAGS_at == "centernet") {
            model.reset(new ModelCenterNet(FLAGS_m, static_cast<float>(FLAGS_t), labels, FLAGS_layout));
        }
        else if (FLAGS_at == "faceboxes") {
            model.reset(new ModelFaceBoxes(FLAGS_m,
                static_cast<float>(FLAGS_t),
                FLAGS_auto_resize,
                static_cast<float>(FLAGS_iou_t),
                FLAGS_layout));
        }
        else if (FLAGS_at == "retinaface") {
            model.reset(new ModelRetinaFace(FLAGS_m,
                static_cast<float>(FLAGS_t),
                FLAGS_auto_resize,
                static_cast<float>(FLAGS_iou_t),
                FLAGS_layout));
        }
        else if (FLAGS_at == "retinaface-pytorch") {
            model.reset(new ModelRetinaFacePT(FLAGS_m,
                static_cast<float>(FLAGS_t),
                FLAGS_auto_resize,
                static_cast<float>(FLAGS_iou_t),
                FLAGS_layout));
        }
        else if (FLAGS_at == "ssd") {
            model.reset(new ModelSSD(FLAGS_m, static_cast<float>(FLAGS_t), FLAGS_auto_resize, labels, FLAGS_layout));
        }
        else if (FLAGS_at == "yolo") {
            model.reset(new ModelYolo(FLAGS_m,
                static_cast<float>(FLAGS_t),
                FLAGS_auto_resize,
                FLAGS_yolo_af,
                static_cast<float>(FLAGS_iou_t),
                labels,
                anchors,
                masks,
                FLAGS_layout));
        }
        else if (FLAGS_at == "yolov3-onnx") {
            model.reset(new ModelYoloV3ONNX(FLAGS_m,
                static_cast<float>(FLAGS_t),
                labels,
                FLAGS_layout));
        }
        else if (FLAGS_at == "yolox") {
            model.reset(new ModelYoloX(FLAGS_m,
                static_cast<float>(FLAGS_t),
                static_cast<float>(FLAGS_iou_t),
                labels,
                FLAGS_layout));
        }
        else {
            slog::err << "No model type or invalid model type (-at) provided: " + FLAGS_at << slog::endl;
            return -1;
        }
        model->setInputsPreprocessing(FLAGS_reverse_input_channels, FLAGS_mean_values, FLAGS_scale_values);
        slog::info << ov::get_openvino_version() << slog::endl;

        ov::Core core;

        AsyncPipeline pipeline(std::move(model),
            ConfigFactory::getUserConfig(FLAGS_d, FLAGS_nireq, FLAGS_nstreams, FLAGS_nthreads),
            core);
        Presenter presenter(FLAGS_u);

        bool keepRunning = true;
        int64_t frameNum = -1;
        std::unique_ptr<ResultBase> result;
        uint32_t framesProcessed = 0;

        LazyVideoWriter videoWriter{ FLAGS_o, cap->fps(), FLAGS_limit };

        PerformanceMetrics renderMetrics;

        cv::Size outputResolution;
        OutputTransform outputTransform = OutputTransform();
        size_t found = FLAGS_output_resolution.find("x");

        while (keepRunning) {
            if (pipeline.isReadyToProcess()) {
                auto startTime = std::chrono::steady_clock::now();

                //--- Capturing frame
                curr_frame = cap->read();

                if (curr_frame.empty()) {
                    // Input stream is over
                    break;
                }

                frameNum = pipeline.submitData(ImageInputData(curr_frame),
                    std::make_shared<ImageMetaData>(curr_frame, startTime));
            }

            if (frameNum == 0) {
                if (found == std::string::npos) {
                    outputResolution = curr_frame.size();
                }
                else {
                    outputResolution = cv::Size{
                        std::stoi(FLAGS_output_resolution.substr(0, found)),
                        std::stoi(FLAGS_output_resolution.substr(found + 1, FLAGS_output_resolution.length())) };
                    outputTransform = OutputTransform(curr_frame.size(), outputResolution);
                    outputResolution = outputTransform.computeResolution();
                }
            }

            //--- Waiting for free input slot or output data available. Function will return immediately if any of them
            // are available.
            pipeline.waitForData();

            //--- Checking for results and rendering data if it's ready
            //--- If you need just plain data without rendering - cast result's underlying pointer to DetectionResult*
            //    and use your own processing instead of calling renderDetectionData().
            while (keepRunning && (result = pipeline.getResult())) {
                auto renderingStart = std::chrono::steady_clock::now();
                cv::Mat outFrame = ANSTEST::renderDetectionData(result->asRef<DetectionResult>(), palette, outputTransform);

                //--- Showing results and device information
                presenter.drawGraphs(outFrame);
                renderMetrics.update(renderingStart);
                metrics.update(result->metaData->asRef<ImageMetaData>().timeStamp,
                    outFrame,
                    { 10, 22 },
                    cv::FONT_HERSHEY_COMPLEX,
                    0.65);

                videoWriter.write(outFrame);
                framesProcessed++;

                if (!FLAGS_no_show) {
                    cv::imshow("Detection Results", outFrame);
                    //--- Processing keyboard events
                    int key = cv::waitKey(1);
                    if (27 == key || 'q' == key || 'Q' == key) {  // Esc
                        keepRunning = false;
                    }
                    else {
                        presenter.handleKey(key);
                    }
                }
            }
        }  // while(keepRunning)

        // ------------ Waiting for completion of data processing and rendering the rest of results ---------
        pipeline.waitForTotalCompletion();

        for (; framesProcessed <= frameNum; framesProcessed++) {
            result = pipeline.getResult();
            if (result != nullptr) {
                auto renderingStart = std::chrono::steady_clock::now();
                cv::Mat outFrame = ANSTEST::renderDetectionData(result->asRef<DetectionResult>(), palette, outputTransform);
                //--- Showing results and device information
                presenter.drawGraphs(outFrame);
                renderMetrics.update(renderingStart);
                metrics.update(result->metaData->asRef<ImageMetaData>().timeStamp,
                    outFrame,
                    { 10, 22 },
                    cv::FONT_HERSHEY_COMPLEX,
                    0.65);
                videoWriter.write(outFrame);
                if (!FLAGS_no_show) {
                    cv::imshow("Detection Results", outFrame);
                    //--- Updating output window
                    cv::waitKey(1);
                }
            }
        }

        slog::info << "Metrics report:" << slog::endl;
        metrics.logTotal();
        logLatencyPerStage(cap->getMetrics().getTotal().latency,
            pipeline.getPreprocessMetrics().getTotal().latency,
            pipeline.getInferenceMetircs().getTotal().latency,
            pipeline.getPostprocessMetrics().getTotal().latency,
            renderMetrics.getTotal().latency);
        slog::info << presenter.reportMeans() << slog::endl;
    }
    catch (const std::exception& error) {
        slog::err << error.what() << slog::endl;
        return 1;
    }
    catch (...) {
        slog::err << "Unknown/internal exception happened." << slog::endl;
        return 1;
    }

    return 0;
}


int PeopleTrackingExample() {
        PerformanceMetrics metrics;
        // Reading command line parameters.
        std::string det_model = "C:\\Programs\\PeopleTracking\\FP32\\person-detection-retail-0013.xml";
        std::string reid_model = "C:\\Programs\\PeopleTracking\\FP32\\person-reidentification-retail-0277.xml";
        std::string videoPath = "C:\\Programs\\Documentation\\Video\\Abandoned_Luggage_Easy3.mp4";
        std::string detlog_out = "";
        std::string FLAGS_at = "ssd";
        auto detector_mode = "CPU";
        auto reid_mode = "CPU";

        bool should_print_out = true;

        bool should_show = !FLAGS_no_show;
        int delay = 3;
        if (!should_show)
            delay = -1;
        should_show = (delay >= 0);

        bool should_save_det_log = !detlog_out.empty();

        std::vector<std::string> labels;
        if (!FLAGS_labels.empty())
            labels = DetectionModel::loadLabels(FLAGS_labels);

        std::unique_ptr<ModelBase> detectionModel;
        if (FLAGS_at == "centernet") {
            detectionModel.reset(new ModelCenterNet(det_model, static_cast<float>(FLAGS_t), labels, ""));
        }
        else if (FLAGS_at == "ssd") {
            detectionModel.reset(
                new ModelSSD(det_model, static_cast<float>(FLAGS_t), FLAGS_auto_resize, labels, ""));
        }
        else if (FLAGS_at == "yolo") {
            detectionModel.reset(new ModelYolo(det_model,
                static_cast<float>(FLAGS_t),
                FLAGS_auto_resize,
                FLAGS_yolo_af,
                static_cast<float>(FLAGS_iou_t),
                labels,
                {},
                {},
                ""));
        }
        else {
            slog::err << "No model type or invalid model type (-at) provided: " << slog::endl;
            return -1;
        }

        std::vector<std::string> devices{ detector_mode, reid_mode };

        slog::info << ov::get_openvino_version() << slog::endl;
        ov::Core core;

        auto model = detectionModel->compileModel(
            ConfigFactory::getUserConfig("CPU", FLAGS_nireq, FLAGS_nstreams, FLAGS_nthreads),
            core);
        auto req = model.create_infer_request();
        bool should_keep_tracking_info = should_save_det_log || should_print_out;
        std::unique_ptr<PedestrianTracker> tracker =
            CreatePedestrianTracker(reid_model, core, reid_mode, should_keep_tracking_info);

        std::unique_ptr<ImagesCapture> cap =
            openImagesCapture(videoPath,
                FLAGS_loop,
                FLAGS_nireq == 1 ? read_type::efficient : read_type::safe,
                0,
                FLAGS_read_limit);


        double video_fps = cap->fps();
        if (0.0 == video_fps) {
            video_fps = 60.0;
        }

        auto startTime = std::chrono::steady_clock::now();
        cv::Mat frame = cap->read();
        cv::Size firstFrameSize = frame.size();

        LazyVideoWriter videoWriter{ FLAGS_o, cap->fps(), FLAGS_limit };
        cv::Size graphSize{ static_cast<int>(frame.cols / 4), 60 };
        Presenter presenter(FLAGS_u, 10, graphSize);

        for (unsigned frameIdx = 0;; ++frameIdx) {
            detectionModel->preprocess(ImageInputData(frame), req);

            req.infer();

            InferenceResult res;

            res.internalModelData = std::make_shared<InternalImageModelData>(frame.cols, frame.rows);

            res.metaData = std::make_shared<ImageMetaData>(frame, std::chrono::steady_clock::now());

            for (const auto& outName : detectionModel->getOutputsNames()) {
                const auto& outTensor = req.get_tensor(outName);

                if (ov::element::i32 == outTensor.get_element_type()) {
                    res.outputsData.emplace(outName, outTensor);
                }
                else {
                    res.outputsData.emplace(outName, outTensor);
                }
            }

            auto result = (detectionModel->postprocess(res))->asRef<DetectionResult>();

            ANSPPTrack::ppTrackedObjects detections;

            for (size_t i = 0; i < result.objects.size(); i++) {
                ANSPPTrack::ppTrackedObject object;
                object.confidence = result.objects[i].confidence;

                const float frame_width_ = static_cast<float>(frame.cols);
                const float frame_height_ = static_cast<float>(frame.rows);
                object.frame_idx = result.frameId;

                const float x0 = std::min(std::max(0.0f, result.objects[i].x / frame_width_), 1.0f) * frame_width_;
                const float y0 = std::min(std::max(0.0f, result.objects[i].y / frame_height_), 1.0f) * frame_height_;
                const float x1 =
                    std::min(std::max(0.0f, (result.objects[i].x + result.objects[i].width) / frame_width_), 1.0f) *
                    frame_width_;
                const float y1 =
                    std::min(std::max(0.0f, (result.objects[i].y + result.objects[i].height) / frame_height_), 1.0f) *
                    frame_height_;

                object.rect = cv::Rect2f(cv::Point(static_cast<int>(round(static_cast<double>(x0))),
                    static_cast<int>(round(static_cast<double>(y0)))),
                    cv::Point(static_cast<int>(round(static_cast<double>(x1))),
                        static_cast<int>(round(static_cast<double>(y1)))));
                int FLAGS_person_label = -1;
                if (object.rect.area() > 0 &&
                    (static_cast<int>(result.objects[i].labelID) == FLAGS_person_label || FLAGS_person_label == -1)) {
                    detections.emplace_back(object);
                }
            }

            // timestamp in milliseconds
            uint64_t cur_timestamp = static_cast<uint64_t>(1000.0 / video_fps * frameIdx);
            tracker->Process(frame, detections, cur_timestamp);

            // Drawing colored "worms" (tracks).
            frame = tracker->DrawActiveTracks(frame);

            // Drawing all detected objects on a frame by BLUE COLOR
            for (const auto& detection : detections) {
                cv::rectangle(frame, detection.rect, cv::Scalar(255, 0, 0), 3);
            }

            // Drawing tracked detections only by RED color and print ID and detection
            // confidence level.
            for (const auto& detection : tracker->TrackedDetections()) {
                cv::rectangle(frame, detection.rect, cv::Scalar(0, 0, 255), 3);
                std::string text =
                    std::to_string(detection.object_id) + " conf: " + std::to_string(detection.confidence);
                putHighlightedText(frame,
                    text,
                    detection.rect.tl() - cv::Point{ 10, 10 },
                    cv::FONT_HERSHEY_COMPLEX,
                    0.65,
                    cv::Scalar(0, 0, 255),
                    2);
            }
            presenter.drawGraphs(frame);
            metrics.update(startTime, frame, { 10, 22 }, cv::FONT_HERSHEY_COMPLEX, 0.65);

            videoWriter.write(frame);
            if (should_show) {
                cv::imshow("dbg", frame);
                char k = cv::waitKey(delay);
                if (k == 27)
                    break;
                presenter.handleKey(k);
            }

            if (should_save_det_log && (frameIdx % 100 == 0)) {
                ANSPPTrack::ppDetectionLog log = tracker->GetDetectionLog(true);
                //SaveDetectionLogToTrajFile(detlog_out, log);
            }
            startTime = std::chrono::steady_clock::now();
            frame = cap->read();
            if (!frame.data)
                break;
            if (frame.size() != firstFrameSize)
                throw std::runtime_error("Can't track objects on images of different size");
        }

        if (should_keep_tracking_info) {
            ANSPPTrack::ppDetectionLog log = tracker->GetDetectionLog(true);
            if (should_print_out)
                PrintDetectionLog(log);
        }

        slog::info << "Metrics report:" << slog::endl;
        metrics.logTotal();
        slog::info << presenter.reportMeans() << slog::endl;

    return 0;
}


int HumanKeyPoint() {
    try {
        PerformanceMetrics metrics, renderMetrics;

        std::string videoPath = "C:\\Programs\\Documentation\\Video\\Abandoned_Luggage_Easy3.mp4";
        FLAGS_i = videoPath;
        std::string FLAGS_at = "openpose";
        int FLAGS_tsize = 0;
        std::string FLAGS_m = "C:\\Programs\\PeopleTracking\\FP32\\human_pose_estimation.xml";
        //------------------------------- Preparing Input ------------------------------------------------------
        auto cap = openImagesCapture(FLAGS_i, FLAGS_loop, FLAGS_nireq == 1 ? read_type::efficient : read_type::safe);
        auto startTime = std::chrono::steady_clock::now();
        cv::Mat curr_frame = cap->read();

        LazyVideoWriter videoWriter{ FLAGS_o, cap->fps(), FLAGS_limit };

        OutputTransform outputTransform = OutputTransform();
        cv::Size outputResolution = curr_frame.size();
        size_t found = FLAGS_output_resolution.find("x");
        if (found != std::string::npos) {
            outputResolution =
                cv::Size{ std::stoi(FLAGS_output_resolution.substr(0, found)),
                         std::stoi(FLAGS_output_resolution.substr(found + 1, FLAGS_output_resolution.length())) };
            outputTransform = OutputTransform(curr_frame.size(), outputResolution);
            outputResolution = outputTransform.computeResolution();
        }

        //------------------------------ Running Human Pose Estimation routines
        //----------------------------------------------

        double aspectRatio = 1;// curr_frame.cols / static_cast<double>(curr_frame.rows);
        std::unique_ptr<ModelBase> model;
        if (FLAGS_at == "openpose") {
            model.reset(new HPEOpenPose(FLAGS_m, aspectRatio, FLAGS_tsize, static_cast<float>(FLAGS_t), FLAGS_layout));
        }
        else if (FLAGS_at == "ae") {
            model.reset(new HpeAssociativeEmbedding(FLAGS_m,
                aspectRatio,
                FLAGS_tsize,
                static_cast<float>(FLAGS_t),
                FLAGS_layout));
        }
        else if (FLAGS_at == "higherhrnet") {
            float delta = 0.5f;
            model.reset(new HpeAssociativeEmbedding(FLAGS_m,
                aspectRatio,
                FLAGS_tsize,
                static_cast<float>(FLAGS_t),
                FLAGS_layout,
                delta,
                RESIZE_KEEP_ASPECT_LETTERBOX));
        }
        else {
            slog::err << "No model type or invalid model type (-at) provided: " + FLAGS_at << slog::endl;
            return -1;
        }

        slog::info << ov::get_openvino_version() << slog::endl;
        ov::Core core;

        AsyncPipeline pipeline(std::move(model),
            ConfigFactory::getUserConfig(FLAGS_d, FLAGS_nireq, FLAGS_nstreams, FLAGS_nthreads),
            core);
        Presenter presenter(FLAGS_u);

        int64_t frameNum =
            pipeline.submitData(ImageInputData(curr_frame), std::make_shared<ImageMetaData>(curr_frame, startTime));

        uint32_t framesProcessed = 0;
        bool keepRunning = true;
        std::unique_ptr<ResultBase> result;

        while (keepRunning) {
            if (pipeline.isReadyToProcess()) {
                //--- Capturing frame
                startTime = std::chrono::steady_clock::now();
                curr_frame = cap->read();
                if (curr_frame.empty()) {
                    // Input stream is over
                    break;
                }
                frameNum = pipeline.submitData(ImageInputData(curr_frame),
                    std::make_shared<ImageMetaData>(curr_frame, startTime));
            }

            //--- Waiting for free input slot or output data available. Function will return immediately if any of them
            // are available.
            pipeline.waitForData();

            //--- Checking for results and rendering data if it's ready
            //--- If you need just plain data without rendering - cast result's underlying pointer to HumanPoseResult*
            //    and use your own processing instead of calling renderHumanPose().
            while (keepRunning && (result = pipeline.getResult())) {
                auto renderingStart = std::chrono::steady_clock::now();
                cv::Mat outFrame = renderHumanPose(result->asRef<HumanPoseResult>(), outputTransform);
                
                //--- Showing results and device information
                presenter.drawGraphs(outFrame);
                renderMetrics.update(renderingStart);
                metrics.update(result->metaData->asRef<ImageMetaData>().timeStamp,
                    outFrame,
                    { 10, 22 },
                    cv::FONT_HERSHEY_COMPLEX,
                    0.65);
                videoWriter.write(outFrame);
                framesProcessed++;
                if (!FLAGS_no_show) {
                    cv::imshow("Human Pose Estimation Results", outFrame);
                    //--- Processing keyboard events
                    int key = cv::waitKey(1);
                    if (27 == key || 'q' == key || 'Q' == key) {  // Esc
                        keepRunning = false;
                    }
                    else {
                        presenter.handleKey(key);
                    }
                }
            }
        }

        // ------------ Waiting for completion of data processing and rendering the rest of results ---------
        pipeline.waitForTotalCompletion();
        for (; framesProcessed <= frameNum; framesProcessed++) {
            while (!(result = pipeline.getResult())) {}
            auto renderingStart = std::chrono::steady_clock::now();
            cv::Mat outFrame = renderHumanPose(result->asRef<HumanPoseResult>(), outputTransform);
            //--- Showing results and device information
            presenter.drawGraphs(outFrame);
            renderMetrics.update(renderingStart);
            metrics.update(result->metaData->asRef<ImageMetaData>().timeStamp,
                outFrame,
                { 10, 22 },
                cv::FONT_HERSHEY_COMPLEX,
                0.65);
            videoWriter.write(outFrame);
            if (!FLAGS_no_show) {
                cv::imshow("Human Pose Estimation Results", outFrame);
                //--- Updating output window
                cv::waitKey(1);
            }
        }

        slog::info << "Metrics report:" << slog::endl;
        metrics.logTotal();
        logLatencyPerStage(cap->getMetrics().getTotal().latency,
            pipeline.getPreprocessMetrics().getTotal().latency,
            pipeline.getInferenceMetircs().getTotal().latency,
            pipeline.getPostprocessMetrics().getTotal().latency,
            renderMetrics.getTotal().latency);

        slog::info << presenter.reportMeans() << slog::endl;
    }
    catch (const std::exception& error) {
        slog::err << error.what() << slog::endl;
        return 1;
    }
    catch (...) {
        slog::err << "Unknown/internal exception happened." << slog::endl;
        return 1;
    }

    return 0;
}
int main(int argc, char* argv[]) {
 // return ObjectDetection();
 //  return SmartClassApp();
 //   return PeopleTrackingExample();
    return HumanKeyPoint();
}
