// Tencent is pleased to support the open source community by making TNN available.
//
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
// https://opensource.org/licenses/BSD-3-Clause
//
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

#include "tnn/utils/bbox_util.h"

#include <algorithm>
#include <cassert>
#include <cfloat>
#include <cmath>
#include <string>

namespace TNN_NS {

bool SortBBoxAscend(const NormalizedBBox& bbox1, const NormalizedBBox& bbox2) {
    return bbox1.score() < bbox2.score();
}

bool SortBBoxDescend(const NormalizedBBox& bbox1, const NormalizedBBox& bbox2) {
    return bbox1.score() > bbox2.score();
}

template <typename T>
bool SortScorePairAscend(const pair<float, T>& pair1, const pair<float, T>& pair2) {
    return pair1.first < pair2.first;
}

// Explicit initialization.
template bool SortScorePairAscend(const pair<float, int>& pair1, const pair<float, int>& pair2);
template bool SortScorePairAscend(const pair<float, pair<int, int>>& pair1, const pair<float, pair<int, int>>& pair2);

template <typename T>
bool SortScorePairDescend(const pair<float, T>& pair1, const pair<float, T>& pair2) {
    return pair1.first > pair2.first;
}

// Explicit initialization.
template bool SortScorePairDescend(const pair<float, int>& pair1, const pair<float, int>& pair2);
template bool SortScorePairDescend(const pair<float, pair<int, int>>& pair1, const pair<float, pair<int, int>>& pair2);

NormalizedBBox UnitBBox() {
    NormalizedBBox unit_bbox;
    unit_bbox.set_xmin(0.);
    unit_bbox.set_ymin(0.);
    unit_bbox.set_xmax(1.);
    unit_bbox.set_ymax(1.);
    return unit_bbox;
}

void IntersectBBox(const NormalizedBBox& bbox1, const NormalizedBBox& bbox2, NormalizedBBox* intersect_bbox) {
    if (bbox2.xmin() > bbox1.xmax() || bbox2.xmax() < bbox1.xmin() || bbox2.ymin() > bbox1.ymax() ||
        bbox2.ymax() < bbox1.ymin()) {
        // Return [0, 0, 0, 0] if there is no intersection.
        intersect_bbox->set_xmin(0);
        intersect_bbox->set_ymin(0);
        intersect_bbox->set_xmax(0);
        intersect_bbox->set_ymax(0);
    } else {
        intersect_bbox->set_xmin(std::max(bbox1.xmin(), bbox2.xmin()));
        intersect_bbox->set_ymin(std::max(bbox1.ymin(), bbox2.ymin()));
        intersect_bbox->set_xmax(std::min(bbox1.xmax(), bbox2.xmax()));
        intersect_bbox->set_ymax(std::min(bbox1.ymax(), bbox2.ymax()));
    }
}

float BBoxSize(const NormalizedBBox& bbox, const bool normalized) {
    if (bbox.xmax() < bbox.xmin() || bbox.ymax() < bbox.ymin()) {
        // If bbox is invalid (e.g. xmax < xmin or ymax < ymin), return 0.
        return 0;
    } else {
        if (bbox.has_size()) {
            return bbox.size();
        } else {
            float width  = bbox.xmax() - bbox.xmin();
            float height = bbox.ymax() - bbox.ymin();
            if (normalized) {
                return width * height;
            } else {
                // If bbox is not within range [0, 1].
                return (width + 1) * (height + 1);
            }
        }
    }
}

void ClipBBox(const NormalizedBBox& bbox, NormalizedBBox* clip_bbox) {
    clip_bbox->set_xmin(std::max(std::min(bbox.xmin(), 1.f), 0.f));
    clip_bbox->set_ymin(std::max(std::min(bbox.ymin(), 1.f), 0.f));
    clip_bbox->set_xmax(std::max(std::min(bbox.xmax(), 1.f), 0.f));
    clip_bbox->set_ymax(std::max(std::min(bbox.ymax(), 1.f), 0.f));
    clip_bbox->clear_size();
    clip_bbox->set_size(BBoxSize(*clip_bbox));
    clip_bbox->set_difficult(bbox.difficult());
}

void ScaleBBox(const NormalizedBBox& bbox, const int height, const int width, NormalizedBBox* scale_bbox) {
    scale_bbox->set_xmin(bbox.xmin() * width);
    scale_bbox->set_ymin(bbox.ymin() * height);
    scale_bbox->set_xmax(bbox.xmax() * width);
    scale_bbox->set_ymax(bbox.ymax() * height);
    scale_bbox->clear_size();
    bool normalized = !(width > 1 || height > 1);
    scale_bbox->set_size(BBoxSize(*scale_bbox, normalized));
    scale_bbox->set_difficult(bbox.difficult());
}

void LocateBBox(const NormalizedBBox& src_bbox, const NormalizedBBox& bbox, NormalizedBBox* loc_bbox) {
    float src_width  = src_bbox.xmax() - src_bbox.xmin();
    float src_height = src_bbox.ymax() - src_bbox.ymin();
    loc_bbox->set_xmin(src_bbox.xmin() + bbox.xmin() * src_width);
    loc_bbox->set_ymin(src_bbox.ymin() + bbox.ymin() * src_height);
    loc_bbox->set_xmax(src_bbox.xmin() + bbox.xmax() * src_width);
    loc_bbox->set_ymax(src_bbox.ymin() + bbox.ymax() * src_height);
    loc_bbox->set_difficult(bbox.difficult());
}

bool ProjectBBox(const NormalizedBBox& src_bbox, const NormalizedBBox& bbox, NormalizedBBox* proj_bbox) {
    if (bbox.xmin() >= src_bbox.xmax() || bbox.xmax() <= src_bbox.xmin() || bbox.ymin() >= src_bbox.ymax() ||
        bbox.ymax() <= src_bbox.ymin()) {
        return false;
    }
    float src_width  = src_bbox.xmax() - src_bbox.xmin();
    float src_height = src_bbox.ymax() - src_bbox.ymin();
    proj_bbox->set_xmin((bbox.xmin() - src_bbox.xmin()) / src_width);
    proj_bbox->set_ymin((bbox.ymin() - src_bbox.ymin()) / src_height);
    proj_bbox->set_xmax((bbox.xmax() - src_bbox.xmin()) / src_width);
    proj_bbox->set_ymax((bbox.ymax() - src_bbox.ymin()) / src_height);
    proj_bbox->set_difficult(bbox.difficult());
    ClipBBox(*proj_bbox, proj_bbox);
    if (BBoxSize(*proj_bbox) > 0) {
        return true;
    } else {
        return false;
    }
}

float JaccardOverlap(const NormalizedBBox& bbox1, const NormalizedBBox& bbox2, const bool normalized) {
    NormalizedBBox intersect_bbox;
    IntersectBBox(bbox1, bbox2, &intersect_bbox);
    float intersect_width, intersect_height;
    if (normalized) {
        intersect_width  = intersect_bbox.xmax() - intersect_bbox.xmin();
        intersect_height = intersect_bbox.ymax() - intersect_bbox.ymin();
    } else {
        intersect_width  = intersect_bbox.xmax() - intersect_bbox.xmin() + 1;
        intersect_height = intersect_bbox.ymax() - intersect_bbox.ymin() + 1;
    }
    if (intersect_width > 0 && intersect_height > 0) {
        float intersect_size = intersect_width * intersect_height;
        float bbox1_size     = BBoxSize(bbox1);
        float bbox2_size     = BBoxSize(bbox2);
        return intersect_size / (bbox1_size + bbox2_size - intersect_size);
    } else {
        return 0.;
    }
}

float BBoxCoverage(const NormalizedBBox& bbox1, const NormalizedBBox& bbox2) {
    NormalizedBBox intersect_bbox;
    IntersectBBox(bbox1, bbox2, &intersect_bbox);
    float intersect_size = BBoxSize(intersect_bbox);
    if (intersect_size > 0) {
        float bbox1_size = BBoxSize(bbox1);
        return intersect_size / bbox1_size;
    } else {
        return 0.;
    }
}

/*
 * bool MeetEmitConstraint(const NormalizedBBox& src_bbox,
                        const NormalizedBBox& bbox,
                        const EmitConstraint& emit_constraint) {
  EmitType emit_type = emit_constraint.emit_type();
  if (emit_type == EmitConstraint_EmitType_CENTER) {
    float x_center = (bbox.xmin() + bbox.xmax()) / 2;
    float y_center = (bbox.ymin() + bbox.ymax()) / 2;
    if (x_center >= src_bbox.xmin() && x_center <= src_bbox.xmax() &&
        y_center >= src_bbox.ymin() && y_center <= src_bbox.ymax()) {
      return true;
    } else {
      return false;
    }
  } else if (emit_type == EmitConstraint_EmitType_MIN_OVERLAP) {
    float bbox_coverage = BBoxCoverage(bbox, src_bbox);
    return bbox_coverage > emit_constraint.emit_overlap();
  } else {
    LOG(FATAL) << "Unknown emit type.";
    return false;
  }
}*/

void EncodeBBox(const NormalizedBBox& prior_bbox, const vector<float>& prior_variance, const CodeType code_type,
                const bool encode_variance_in_target, const NormalizedBBox& bbox, NormalizedBBox* encode_bbox) {
    if (code_type == PriorBoxParameter_CodeType_CORNER) {
        if (encode_variance_in_target) {
            encode_bbox->set_xmin(bbox.xmin() - prior_bbox.xmin());
            encode_bbox->set_ymin(bbox.ymin() - prior_bbox.ymin());
            encode_bbox->set_xmax(bbox.xmax() - prior_bbox.xmax());
            encode_bbox->set_ymax(bbox.ymax() - prior_bbox.ymax());
        } else {
            // Encode variance in bbox.
            assert(prior_variance.size() == 4);
            for (size_t i = 0; i < prior_variance.size(); ++i) {
                assert(prior_variance[i] > 0);
            }
            encode_bbox->set_xmin((bbox.xmin() - prior_bbox.xmin()) / prior_variance[0]);
            encode_bbox->set_ymin((bbox.ymin() - prior_bbox.ymin()) / prior_variance[1]);
            encode_bbox->set_xmax((bbox.xmax() - prior_bbox.xmax()) / prior_variance[2]);
            encode_bbox->set_ymax((bbox.ymax() - prior_bbox.ymax()) / prior_variance[3]);
        }
    } else if (code_type == PriorBoxParameter_CodeType_CENTER_SIZE) {
        float prior_width = prior_bbox.xmax() - prior_bbox.xmin();
        assert(prior_width > 0);
        float prior_height = prior_bbox.ymax() - prior_bbox.ymin();
        assert(prior_height > 0);
        float prior_center_x = (prior_bbox.xmin() + prior_bbox.xmax()) / 2.f;
        float prior_center_y = (prior_bbox.ymin() + prior_bbox.ymax()) / 2.f;

        float bbox_width = bbox.xmax() - bbox.xmin();
        assert(bbox_width > 0);
        float bbox_height = bbox.ymax() - bbox.ymin();
        assert(bbox_height > 0);
        float bbox_center_x = (bbox.xmin() + bbox.xmax()) / 2.f;
        float bbox_center_y = (bbox.ymin() + bbox.ymax()) / 2.f;

        if (encode_variance_in_target) {
            encode_bbox->set_xmin((bbox_center_x - prior_center_x) / prior_width);
            encode_bbox->set_ymin((bbox_center_y - prior_center_y) / prior_height);
            encode_bbox->set_xmax(log(bbox_width / prior_width));
            encode_bbox->set_ymax(log(bbox_height / prior_height));
        } else {
            // Encode variance in bbox.
            encode_bbox->set_xmin((bbox_center_x - prior_center_x) / prior_width / prior_variance[0]);
            encode_bbox->set_ymin((bbox_center_y - prior_center_y) / prior_height / prior_variance[1]);
            encode_bbox->set_xmax(log(bbox_width / prior_width) / prior_variance[2]);
            encode_bbox->set_ymax(log(bbox_height / prior_height) / prior_variance[3]);
        }
    } else {
        // LOG(FATAL) << "Unknown LocLossType.";
        assert(false);
    }
}

void DecodeBBox(const NormalizedBBox& prior_bbox, const vector<float>& prior_variance, const CodeType code_type,
                const bool variance_encoded_in_target, const bool clip_bbox, const NormalizedBBox& bbox,
                NormalizedBBox* decode_bbox) {
    if (code_type == PriorBoxParameter_CodeType_CORNER) {
        if (variance_encoded_in_target) {
            // variance is encoded in target, we simply need to add the offset
            // predictions.
            decode_bbox->set_xmin(prior_bbox.xmin() + bbox.xmin());
            decode_bbox->set_ymin(prior_bbox.ymin() + bbox.ymin());
            decode_bbox->set_xmax(prior_bbox.xmax() + bbox.xmax());
            decode_bbox->set_ymax(prior_bbox.ymax() + bbox.ymax());
        } else {
            // variance is encoded in bbox, we need to scale the offset
            // accordingly.
            decode_bbox->set_xmin(prior_bbox.xmin() + prior_variance[0] * bbox.xmin());
            decode_bbox->set_ymin(prior_bbox.ymin() + prior_variance[1] * bbox.ymin());
            decode_bbox->set_xmax(prior_bbox.xmax() + prior_variance[2] * bbox.xmax());
            decode_bbox->set_ymax(prior_bbox.ymax() + prior_variance[3] * bbox.ymax());
        }
    } else if (code_type == PriorBoxParameter_CodeType_CENTER_SIZE) {
        float prior_width = prior_bbox.xmax() - prior_bbox.xmin();
        assert(prior_width > 0);
        float prior_height = prior_bbox.ymax() - prior_bbox.ymin();
        assert(prior_height > 0);
        float prior_center_x = (prior_bbox.xmin() + prior_bbox.xmax()) / 2.f;
        float prior_center_y = (prior_bbox.ymin() + prior_bbox.ymax()) / 2.f;

        float decode_bbox_center_x, decode_bbox_center_y;
        float decode_bbox_width, decode_bbox_height;
        if (variance_encoded_in_target) {
            // variance is encoded in target, we simply need to retore the
            // offset predictions.
            decode_bbox_center_x = bbox.xmin() * prior_width + prior_center_x;
            decode_bbox_center_y = bbox.ymin() * prior_height + prior_center_y;
            decode_bbox_width    = exp(bbox.xmax()) * prior_width;
            decode_bbox_height   = exp(bbox.ymax()) * prior_height;
        } else {
            // variance is encoded in bbox, we need to scale the offset
            // accordingly.
            decode_bbox_center_x = prior_variance[0] * bbox.xmin() * prior_width + prior_center_x;
            decode_bbox_center_y = prior_variance[1] * bbox.ymin() * prior_height + prior_center_y;
            decode_bbox_width    = exp(prior_variance[2] * bbox.xmax()) * prior_width;
            decode_bbox_height   = exp(prior_variance[3] * bbox.ymax()) * prior_height;
        }

        decode_bbox->set_xmin(decode_bbox_center_x - decode_bbox_width / 2.f);
        decode_bbox->set_ymin(decode_bbox_center_y - decode_bbox_height / 2.f);
        decode_bbox->set_xmax(decode_bbox_center_x + decode_bbox_width / 2.f);
        decode_bbox->set_ymax(decode_bbox_center_y + decode_bbox_height / 2.f);
    } else if (code_type == PriorBoxParameter_CodeType_CORNER_SIZE) {
        float prior_width = prior_bbox.xmax() - prior_bbox.xmin();
        assert(prior_width > 0);
        float prior_height = prior_bbox.ymax() - prior_bbox.ymin();
        assert(prior_height > 0);
        if (variance_encoded_in_target) {
            // variance is encoded in target, we simply need to add the offset
            // predictions.
            decode_bbox->set_xmin(prior_bbox.xmin() + bbox.xmin() * prior_width);
            decode_bbox->set_ymin(prior_bbox.ymin() + bbox.ymin() * prior_height);
            decode_bbox->set_xmax(prior_bbox.xmax() + bbox.xmax() * prior_width);
            decode_bbox->set_ymax(prior_bbox.ymax() + bbox.ymax() * prior_height);
        } else {
            // variance is encoded in bbox, we need to scale the offset
            // accordingly.
            decode_bbox->set_xmin(prior_bbox.xmin() + prior_variance[0] * bbox.xmin() * prior_width);
            decode_bbox->set_ymin(prior_bbox.ymin() + prior_variance[1] * bbox.ymin() * prior_height);
            decode_bbox->set_xmax(prior_bbox.xmax() + prior_variance[2] * bbox.xmax() * prior_width);
            decode_bbox->set_ymax(prior_bbox.ymax() + prior_variance[3] * bbox.ymax() * prior_height);
        }
    } else {
        // LOG(FATAL) << "Unknown LocLossType.";
        assert(false);  // Unknown LocLossType.
    }
    float bbox_size = BBoxSize(*decode_bbox);
    decode_bbox->set_size(bbox_size);
    if (clip_bbox) {
        ClipBBox(*decode_bbox, decode_bbox);
    }
}

void DecodeBBoxes(const vector<NormalizedBBox>& prior_bboxes, const vector<vector<float>>& prior_variances,
                  const CodeType code_type, const bool variance_encoded_in_target, const bool clip_bbox,
                  const vector<NormalizedBBox>& bboxes, vector<NormalizedBBox>* decode_bboxes) {
    CHECK_EQ(prior_bboxes.size(), prior_variances.size());
    CHECK_EQ(prior_bboxes.size(), bboxes.size());
    int num_bboxes = static_cast<int>(prior_bboxes.size());
    if (num_bboxes >= 1) {
        CHECK_EQ(prior_variances[0].size(), 4);
    }
    decode_bboxes->clear();
    for (int i = 0; i < num_bboxes; ++i) {
        NormalizedBBox decode_bbox;
        DecodeBBox(prior_bboxes[i], prior_variances[i], code_type, variance_encoded_in_target, clip_bbox, bboxes[i],
                   &decode_bbox);
        decode_bboxes->push_back(decode_bbox);
    }
}

void DecodeBBoxesAll(const vector<LabelBBox>& all_loc_preds, const vector<NormalizedBBox>& prior_bboxes,
                     const vector<vector<float>>& prior_variances, const int num, const bool share_location,
                     const int num_loc_classes, const int background_label_id, const CodeType code_type,
                     const bool variance_encoded_in_target, const bool clip, vector<LabelBBox>* all_decode_bboxes) {
    assert(all_loc_preds.size() == num);
    all_decode_bboxes->clear();
    all_decode_bboxes->resize(num);
    for (int i = 0; i < num; ++i) {
        // Decode predictions into bboxes.
        LabelBBox& decode_bboxes = (*all_decode_bboxes)[i];
        for (int c = 0; c < num_loc_classes; ++c) {
            int label = share_location ? -1 : c;
            if (label == background_label_id) {
                // Ignore background class.
                continue;
            }
            if (all_loc_preds[i].find(label) == all_loc_preds[i].end()) {
                // Something bad happened if there are no predictions for
                // current label.
                // LOG(FATAL) << "Could not find location predictions for label
                // " << label;
                assert(false);  // Could not find location predictions for label
            }
            const vector<NormalizedBBox>& label_loc_preds = all_loc_preds[i].find(label)->second;
            DecodeBBoxes(prior_bboxes, prior_variances, code_type, variance_encoded_in_target, clip, label_loc_preds,
                         &(decode_bboxes[label]));
        }
    }
}

void CasRegDecodeBBoxesAll(const vector<LabelBBox>& all_loc_preds, const vector<NormalizedBBox>& prior_bboxes,
                           const vector<vector<float>>& prior_variances, const int num, const bool share_location,
                           const int num_loc_classes, const int background_label_id, const CodeType code_type,
                           const bool variance_encoded_in_target, const bool clip, vector<LabelBBox>* all_decode_bboxes,
                           const vector<LabelBBox>& all_arm_loc_preds) {
    CHECK_EQ(all_loc_preds.size(), num);
    all_decode_bboxes->clear();
    all_decode_bboxes->resize(num);
    for (int i = 0; i < num; ++i) {
        // apply arm_loc_preds to prior_box
        const vector<NormalizedBBox>& arm_loc_preds = all_arm_loc_preds[i].find(-1)->second;
        vector<NormalizedBBox> decode_prior_bboxes;
        bool clip_bbox = false;
        DecodeBBoxes(prior_bboxes, prior_variances, code_type, variance_encoded_in_target, clip_bbox, arm_loc_preds,
                     &decode_prior_bboxes);
        // Decode predictions into bboxes.
        LabelBBox& decode_bboxes = (*all_decode_bboxes)[i];
        for (int c = 0; c < num_loc_classes; ++c) {
            int label = share_location ? -1 : c;
            if (label == background_label_id) {
                // Ignore background class.
                continue;
            }
            if (all_loc_preds[i].find(label) == all_loc_preds[i].end()) {
                // Something bad happened if there are no predictions for
                // current label. LOG(FATAL) << "Could not find location
                // predictions for label " << label;
            }
            const vector<NormalizedBBox>& label_loc_preds = all_loc_preds[i].find(label)->second;
            DecodeBBoxes(decode_prior_bboxes, prior_variances, code_type, variance_encoded_in_target, clip,
                         label_loc_preds, &(decode_bboxes[label]));
        }
    }
}

void MatchBBoxPostProcess(map<int, map<int, float>>& overlaps, vector<int>* match_indices,
                          const MatchType match_type, vector<float>* match_overlaps,
                          vector<int>& gt_indices, int num_gt, const float overlap_threshold) {
    switch (match_type) {
        case MultiBoxLossParameter_MatchType_BIPARTITE:
            // Already done.
            break;
        case MultiBoxLossParameter_MatchType_PER_PREDICTION:
            // Get most overlaped for the rest prediction bboxes.
            for (map<int, map<int, float>>::iterator it = overlaps.begin(); it != overlaps.end(); ++it) {
                int i = it->first;
                if ((*match_indices)[i] != -1) {
                    // The prediction already have matched ground truth.
                    continue;
                }
                int max_gt_idx    = -1;
                float max_overlap = -1;
                for (int j = 0; j < num_gt; ++j) {
                    if (it->second.find(j) == it->second.end()) {
                        // No overlap between the i-th prediction and j-th
                        // ground truth.
                        continue;
                    }
                    // Find the maximum overlapped pair.
                    float overlap = it->second[j];
                    if (overlap >= overlap_threshold && overlap > max_overlap) {
                        // If the prediction has not been matched to any ground
                        // truth, and the overlap is larger than maximum
                        // overlap, update.
                        max_gt_idx  = j;
                        max_overlap = overlap;
                    }
                }
                if (max_gt_idx != -1) {
                    // Found a matched ground truth.
                    CHECK_EQ((*match_indices)[i], -1);
                    (*match_indices)[i]  = gt_indices[max_gt_idx];
                    (*match_overlaps)[i] = max_overlap;
                }
            }
            break;
        default:
            // LOG(FATAL) << "Unknown matching type.";
            assert(false);  // Unknown matching type.
            break;
    }
}

void MatchBBox(const vector<NormalizedBBox>& gt_bboxes, const vector<NormalizedBBox>& pred_bboxes, const int label,
               const MatchType match_type, const float overlap_threshold, vector<int>* match_indices,
               vector<float>* match_overlaps) {
    int num_pred = static_cast<int>(pred_bboxes.size());
    match_indices->clear();
    match_indices->resize(num_pred, -1);
    match_overlaps->clear();
    match_overlaps->resize(num_pred, 0.);

    int num_gt = 0;
    vector<int> gt_indices;
    if (label == -1) {
        // label -1 means comparing against all ground truth.
        num_gt = static_cast<int>(gt_bboxes.size());
        for (int i = 0; i < num_gt; ++i) {
            gt_indices.push_back(i);
        }
    } else {
        // Count number of ground truth boxes which has the desired label.
        for (size_t i = 0; i < gt_bboxes.size(); ++i) {
            if (gt_bboxes[i].label() == label) {
                num_gt++;
                gt_indices.push_back(static_cast<int>(i));
            }
        }
    }
    if (num_gt == 0) {
        return;
    }

    // Store the positive overlap between predictions and ground truth.
    map<int, map<int, float>> overlaps;
    for (int i = 0; i < num_pred; ++i) {
        for (int j = 0; j < num_gt; ++j) {
            float overlap = JaccardOverlap(pred_bboxes[i], gt_bboxes[gt_indices[j]]);
            if (overlap > 1e-6) {
                (*match_overlaps)[i] = std::max((*match_overlaps)[i], overlap);
                overlaps[i][j]       = overlap;
            }
        }
    }

    // Bipartite matching.
    vector<int> gt_pool;
    for (int i = 0; i < num_gt; ++i) {
        gt_pool.push_back(i);
    }
    while (gt_pool.size() > 0) {
        // Find the most overlapped gt and cooresponding predictions.
        int max_idx       = -1;
        int max_gt_idx    = -1;
        float max_overlap = -1;
        for (map<int, map<int, float>>::iterator it = overlaps.begin(); it != overlaps.end(); ++it) {
            int i = it->first;
            if ((*match_indices)[i] != -1) {
                // The prediction already have matched ground truth.
                continue;
            }
            for (size_t p = 0; p < gt_pool.size(); ++p) {
                int j = gt_pool[p];
                if (it->second.find(j) == it->second.end()) {
                    // No overlap between the i-th prediction and j-th ground
                    // truth.
                    continue;
                }
                // Find the maximum overlapped pair.
                if (it->second[j] > max_overlap) {
                    // If the prediction has not been matched to any ground
                    // truth, and the overlap is larger than maximum overlap,
                    // update.
                    max_idx     = i;
                    max_gt_idx  = j;
                    max_overlap = it->second[j];
                }
            }
        }
        if (max_idx == -1) {
            // Cannot find good match.
            break;
        } else {
            CHECK_EQ((*match_indices)[max_idx], -1);
            (*match_indices)[max_idx]  = gt_indices[max_gt_idx];
            (*match_overlaps)[max_idx] = max_overlap;
            // Erase the ground truth.
            gt_pool.erase(std::find(gt_pool.begin(), gt_pool.end(), max_gt_idx));
        }
    }

    MatchBBoxPostProcess(overlaps, match_indices, match_type, match_overlaps,
                         gt_indices, num_gt, overlap_threshold);

    return;
}

template <typename Dtype>
void GetGroundTruth(const Dtype* gt_data, const int num_gt, const int background_label_id, const bool use_difficult_gt,
                    map<int, vector<NormalizedBBox>>* all_gt_bboxes) {
    all_gt_bboxes->clear();
    for (int i = 0; i < num_gt; ++i) {
        int start_idx = i * 8;
        int item_id   = static_cast<int>(gt_data[start_idx]);
        if (item_id == -1) {
            continue;
        }
        int label = static_cast<int>(gt_data[start_idx + 1]);
        assert(background_label_id != label);
        bool difficult = static_cast<bool>(gt_data[start_idx + 7]);
        if (!use_difficult_gt && difficult) {
            // Skip reading difficult ground truth.
            continue;
        }
        NormalizedBBox bbox;
        bbox.set_label(label);
        bbox.set_xmin(static_cast<float>(gt_data[start_idx + 3]));
        bbox.set_ymin(static_cast<float>(gt_data[start_idx + 4]));
        bbox.set_xmax(static_cast<float>(gt_data[start_idx + 5]));
        bbox.set_ymax(static_cast<float>(gt_data[start_idx + 6]));
        bbox.set_difficult(difficult);
        float bbox_size = BBoxSize(bbox);
        bbox.set_size(bbox_size);
        (*all_gt_bboxes)[item_id].push_back(bbox);
    }
}

// Explicit initialization.
template void GetGroundTruth(const float* gt_data, const int num_gt, const int background_label_id,
                             const bool use_difficult_gt, map<int, vector<NormalizedBBox>>* all_gt_bboxes);
template void GetGroundTruth(const double* gt_data, const int num_gt, const int background_label_id,
                             const bool use_difficult_gt, map<int, vector<NormalizedBBox>>* all_gt_bboxes);

template <typename Dtype>
void GetGroundTruth(const Dtype* gt_data, const int num_gt, const int background_label_id, const bool use_difficult_gt,
                    map<int, LabelBBox>* all_gt_bboxes) {
    all_gt_bboxes->clear();
    for (int i = 0; i < num_gt; ++i) {
        int start_idx = i * 8;
        int item_id   = static_cast<int>(gt_data[start_idx]);
        if (item_id == -1) {
            break;
        }
        NormalizedBBox bbox;
        int label = static_cast<int>(gt_data[start_idx + 1]);
        assert(background_label_id != label);
        bool difficult = static_cast<bool>(gt_data[start_idx + 7]);
        if (!use_difficult_gt && difficult) {
            // Skip reading difficult ground truth.
            continue;
        }
        bbox.set_xmin(static_cast<float>(gt_data[start_idx + 3]));
        bbox.set_ymin(static_cast<float>(gt_data[start_idx + 4]));
        bbox.set_xmax(static_cast<float>(gt_data[start_idx + 5]));
        bbox.set_ymax(static_cast<float>(gt_data[start_idx + 6]));
        bbox.set_difficult(difficult);
        float bbox_size = BBoxSize(bbox);
        bbox.set_size(bbox_size);
        (*all_gt_bboxes)[item_id][label].push_back(bbox);
    }
}

// Explicit initialization.
template void GetGroundTruth(const float* gt_data, const int num_gt, const int background_label_id,
                             const bool use_difficult_gt, map<int, LabelBBox>* all_gt_bboxes);
template void GetGroundTruth(const double* gt_data, const int num_gt, const int background_label_id,
                             const bool use_difficult_gt, map<int, LabelBBox>* all_gt_bboxes);

template <typename Dtype>
void GetLocPredictions(const Dtype* loc_data, const int num, const int num_preds_per_class, const int num_loc_classes,
                       const bool share_location, vector<LabelBBox>* loc_preds) {
    loc_preds->clear();
    if (share_location) {
        CHECK_EQ(num_loc_classes, 1);
    }
    loc_preds->resize(num);
    for (int i = 0; i < num; ++i) {
        LabelBBox& label_bbox = (*loc_preds)[i];
        for (int p = 0; p < num_preds_per_class; ++p) {
            int start_idx = p * num_loc_classes * 4;
            for (int c = 0; c < num_loc_classes; ++c) {
                int label = share_location ? -1 : c;
                if (label_bbox.find(label) == label_bbox.end()) {
                    label_bbox[label].resize(num_preds_per_class);
                }
                label_bbox[label][p].set_xmin(static_cast<float>(loc_data[start_idx + c * 4]));
                label_bbox[label][p].set_ymin(static_cast<float>(loc_data[start_idx + c * 4 + 1]));
                label_bbox[label][p].set_xmax(static_cast<float>(loc_data[start_idx + c * 4 + 2]));
                label_bbox[label][p].set_ymax(static_cast<float>(loc_data[start_idx + c * 4 + 3]));
            }
        }
        loc_data += num_preds_per_class * num_loc_classes * 4;
    }
}

// Explicit initialization.
template void GetLocPredictions(const float* loc_data, const int num, const int num_preds_per_class,
                                const int num_loc_classes, const bool share_location, vector<LabelBBox>* loc_preds);
template void GetLocPredictions(const double* loc_data, const int num, const int num_preds_per_class,
                                const int num_loc_classes, const bool share_location, vector<LabelBBox>* loc_preds);

template <typename Dtype>
void GetConfidenceScores(const Dtype* conf_data, const int num, const int num_preds_per_class, const int num_classes,
                         vector<map<int, vector<float>>>* conf_preds) {
    conf_preds->clear();
    conf_preds->resize(num);
    for (int i = 0; i < num; ++i) {
        map<int, vector<float>>& label_scores = (*conf_preds)[i];
        for (int p = 0; p < num_preds_per_class; ++p) {
            int start_idx = p * num_classes;
            for (int c = 0; c < num_classes; ++c) {
                label_scores[c].push_back(static_cast<float>(conf_data[start_idx + c]));
            }
        }
        conf_data += num_preds_per_class * num_classes;
    }
}

// Explicit initialization.
template void GetConfidenceScores(const float* conf_data, const int num, const int num_preds_per_class,
                                  const int num_classes, vector<map<int, vector<float>>>* conf_preds);
template void GetConfidenceScores(const double* conf_data, const int num, const int num_preds_per_class,
                                  const int num_classes, vector<map<int, vector<float>>>* conf_preds);

template <typename Dtype>
void OSGetConfidenceScores(const Dtype* conf_data, const Dtype* arm_conf_data, const int num,
                           const int num_preds_per_class, const int num_classes,
                           vector<map<int, vector<float>>>* conf_preds, float objectness_score) {
    conf_preds->clear();
    conf_preds->resize(num);
    for (int i = 0; i < num; ++i) {
        map<int, vector<float>>& label_scores = (*conf_preds)[i];
        for (int p = 0; p < num_preds_per_class; ++p) {
            int start_idx = p * num_classes;
            if (arm_conf_data[p * 2 + 1] < objectness_score) {
                for (int c = 0; c < num_classes; ++c) {
                    if (c == 0) {
                        label_scores[c].push_back(1.0);
                    } else {
                        label_scores[c].push_back(0.0);
                    }
                }
            } else {
                for (int c = 0; c < num_classes; ++c) {
                    label_scores[c].push_back(static_cast<float>(conf_data[start_idx + c]));
                }
            }
        }
        conf_data += num_preds_per_class * num_classes;
        arm_conf_data += num_preds_per_class * 2;
    }
}

// Explicit initialization.
template void OSGetConfidenceScores(const float* conf_data, const float* arm_conf_data, const int num,
                                    const int num_preds_per_class, const int num_classes,
                                    vector<map<int, vector<float>>>* conf_preds, float objectness_score);
template void OSGetConfidenceScores(const double* conf_data, const double* arm_conf_data, const int num,
                                    const int num_preds_per_class, const int num_classes,
                                    vector<map<int, vector<float>>>* conf_preds, float objectness_score);

template <typename Dtype>
void GetConfidenceScores(const Dtype* conf_data, const int num, const int num_preds_per_class, const int num_classes,
                         const bool class_major, vector<map<int, vector<float>>>* conf_preds) {
    conf_preds->clear();
    conf_preds->resize(num);
    for (int i = 0; i < num; ++i) {
        map<int, vector<float>>& label_scores = (*conf_preds)[i];
        if (class_major) {
            for (int c = 0; c < num_classes; ++c) {
                label_scores[c].assign(conf_data, conf_data + num_preds_per_class);
                conf_data += num_preds_per_class;
            }
        } else {
            for (int p = 0; p < num_preds_per_class; ++p) {
                int start_idx = p * num_classes;
                for (int c = 0; c < num_classes; ++c) {
                    label_scores[c].push_back(static_cast<float>(conf_data[start_idx + c]));
                }
            }
            conf_data += num_preds_per_class * num_classes;
        }
    }
}

// Explicit initialization.
template void GetConfidenceScores(const float* conf_data, const int num, const int num_preds_per_class,
                                  const int num_classes, const bool class_major,
                                  vector<map<int, vector<float>>>* conf_preds);
template void GetConfidenceScores(const double* conf_data, const int num, const int num_preds_per_class,
                                  const int num_classes, const bool class_major,
                                  vector<map<int, vector<float>>>* conf_preds);

template <typename Dtype>
void GetMaxConfidenceScores(const Dtype* conf_data, const int num, const int num_preds_per_class, const int num_classes,
                            const int background_label_id, const ConfLossType loss_type,
                            vector<vector<float>>* all_max_scores) {
    all_max_scores->clear();
    for (int i = 0; i < num; ++i) {
        vector<float> max_scores;
        for (int p = 0; p < num_preds_per_class; ++p) {
            int start_idx    = p * num_classes;
            Dtype maxval     = -FLT_MAX;
            Dtype maxval_pos = -FLT_MAX;
            for (int c = 0; c < num_classes; ++c) {
                maxval = std::max<Dtype>(conf_data[start_idx + c], maxval);
                if (c != background_label_id) {
                    // Find maximum scores for positive classes.
                    maxval_pos = std::max<Dtype>(conf_data[start_idx + c], maxval_pos);
                }
            }
            if (loss_type == MultiBoxLossParameter_ConfLossType_SOFTMAX) {
                // Compute softmax probability.
                Dtype sum = 0.;
                for (int c = 0; c < num_classes; ++c) {
                    sum += std::exp(conf_data[start_idx + c] - maxval);
                }
                maxval_pos = std::exp(maxval_pos - maxval) / sum;
            } else if (loss_type == MultiBoxLossParameter_ConfLossType_LOGISTIC) {
                maxval_pos = static_cast<Dtype>(1.f / (1.f + exp(-maxval_pos)));
            } else {
                // LOG(FATAL) << "Unknown conf loss type.";
                assert(false);
            }
            max_scores.push_back(static_cast<float>(maxval_pos));
        }
        conf_data += num_preds_per_class * num_classes;
        all_max_scores->push_back(max_scores);
    }
}

// Explicit initialization.
template void GetMaxConfidenceScores(const float* conf_data, const int num, const int num_preds_per_class,
                                     const int num_classes, const int background_label_id, const ConfLossType loss_type,
                                     vector<vector<float>>* all_max_scores);
template void GetMaxConfidenceScores(const double* conf_data, const int num, const int num_preds_per_class,
                                     const int num_classes, const int background_label_id, const ConfLossType loss_type,
                                     vector<vector<float>>* all_max_scores);

template <typename Dtype>
void GetPriorBBoxes(const Dtype* prior_data, const int num_priors, vector<NormalizedBBox>* prior_bboxes,
                    vector<vector<float>>* prior_variances) {
    prior_bboxes->clear();
    prior_variances->clear();
    for (int i = 0; i < num_priors; ++i) {
        int start_idx = i * 4;
        NormalizedBBox bbox;
        bbox.set_xmin(static_cast<const float>(prior_data[start_idx]));
        bbox.set_ymin(static_cast<const float>(prior_data[start_idx + 1]));
        bbox.set_xmax(static_cast<const float>(prior_data[start_idx + 2]));
        bbox.set_ymax(static_cast<const float>(prior_data[start_idx + 3]));
        float bbox_size = BBoxSize(bbox);
        bbox.set_size(bbox_size);
        prior_bboxes->push_back(bbox);
    }

    for (int i = 0; i < num_priors; ++i) {
        int start_idx = (num_priors + i) * 4;
        vector<float> var;
        for (int j = 0; j < 4; ++j) {
            var.push_back(static_cast<float>(prior_data[start_idx + j]));
        }
        prior_variances->push_back(var);
    }
}

// Explicit initialization.
template void GetPriorBBoxes(const float* prior_data, const int num_priors, vector<NormalizedBBox>* prior_bboxes,
                             vector<vector<float>>* prior_variances);
template void GetPriorBBoxes(const double* prior_data, const int num_priors, vector<NormalizedBBox>* prior_bboxes,
                             vector<vector<float>>* prior_variances);

template <typename Dtype>
void GetDetectionResults(const Dtype* det_data, const int num_det, const int background_label_id,
                         map<int, map<int, vector<NormalizedBBox>>>* all_detections) {
    all_detections->clear();
    for (int i = 0; i < num_det; ++i) {
        int start_idx = i * 7;
        int item_id   = static_cast<int>(det_data[start_idx]);
        if (item_id == -1) {
            continue;
        }
        int label = static_cast<int>(det_data[start_idx + 1]);
        CHECK_NE(background_label_id, label);
        NormalizedBBox bbox;
        bbox.set_score(static_cast<float>(det_data[start_idx + 2]));
        bbox.set_xmin(static_cast<float>(det_data[start_idx + 3]));
        bbox.set_ymin(static_cast<float>(det_data[start_idx + 4]));
        bbox.set_xmax(static_cast<float>(det_data[start_idx + 5]));
        bbox.set_ymax(static_cast<float>(det_data[start_idx + 6]));
        float bbox_size = BBoxSize(bbox);
        bbox.set_size(bbox_size);
        (*all_detections)[item_id][label].push_back(bbox);
    }
}

// Explicit initialization.
template void GetDetectionResults(const float* det_data, const int num_det, const int background_label_id,
                                  map<int, map<int, vector<NormalizedBBox>>>* all_detections);
template void GetDetectionResults(const double* det_data, const int num_det, const int background_label_id,
                                  map<int, map<int, vector<NormalizedBBox>>>* all_detections);

void GetTopKScoreIndex(const vector<float>& scores, const vector<int>& indices, const int top_k,
                       vector<pair<float, int>>* score_index_vec) {
    CHECK_EQ(scores.size(), indices.size());

    // Generate index score pairs.
    for (size_t i = 0; i < scores.size(); ++i) {
        score_index_vec->push_back(std::make_pair(scores[i], indices[i]));
    }

    // Sort the score pair according to the scores in descending order
    std::stable_sort(score_index_vec->begin(), score_index_vec->end(), SortScorePairDescend<int>);

    // Keep top_k scores if needed.
    if (top_k > -1 && top_k < score_index_vec->size()) {
        score_index_vec->resize(top_k);
    }
}

void ApplyNMS(const vector<NormalizedBBox>& bboxes, const vector<float>& scores, const float threshold, const int top_k,
              const bool reuse_overlaps, map<int, map<int, float>>* overlaps, vector<int>* indices) {
    // Sanity check.
    assert(bboxes.size() == scores.size());

    // Get top_k scores (with corresponding indices).
    /*vector<int> idx(boost::counting_iterator<int>(0),
                    boost::counting_iterator<int>(scores.size()));*/
    vector<int> idx;
    for (size_t i = 0; i < scores.size(); ++i) {
        idx.push_back(static_cast<int>(i));
    }

    vector<pair<float, int>> score_index_vec;
    GetTopKScoreIndex(scores, idx, top_k, &score_index_vec);

    // Do nms.
    indices->clear();
    while (score_index_vec.size() != 0) {
        // Get the current highest score box.
        int best_idx                    = score_index_vec.front().second;
        const NormalizedBBox& best_bbox = bboxes[best_idx];
        if (BBoxSize(best_bbox) < 1e-5) {
            // Erase small box.
            score_index_vec.erase(score_index_vec.begin());
            continue;
        }
        indices->push_back(best_idx);
        // Erase the best box.
        score_index_vec.erase(score_index_vec.begin());

        if (top_k > -1 && indices->size() >= top_k) {
            // Stop if finding enough bboxes for nms.
            break;
        }

        // Compute overlap between best_bbox and other remaining bboxes.
        // Remove a bbox if the overlap with best_bbox is larger than
        // nms_threshold.
        for (vector<pair<float, int>>::iterator it = score_index_vec.begin(); it != score_index_vec.end();) {
            int cur_idx                    = it->second;
            const NormalizedBBox& cur_bbox = bboxes[cur_idx];
            if (BBoxSize(cur_bbox) < 1e-5) {
                // Erase small box.
                it = score_index_vec.erase(it);
                continue;
            }
            float cur_overlap = 0.;
            if (reuse_overlaps) {
                if (overlaps->find(best_idx) != overlaps->end() &&
                    overlaps->find(best_idx)->second.find(cur_idx) != overlaps->find(best_idx)->second.end()) {
                    // Use the computed overlap.
                    cur_overlap = (*overlaps)[best_idx][cur_idx];
                } else if (overlaps->find(cur_idx) != overlaps->end() &&
                           overlaps->find(cur_idx)->second.find(best_idx) != overlaps->find(cur_idx)->second.end()) {
                    // Use the computed overlap.
                    cur_overlap = (*overlaps)[cur_idx][best_idx];
                } else {
                    cur_overlap = JaccardOverlap(best_bbox, cur_bbox);
                    // Store the overlap for future use.
                    (*overlaps)[best_idx][cur_idx] = cur_overlap;
                }
            } else {
                cur_overlap = JaccardOverlap(best_bbox, cur_bbox);
            }

            // Remove it if necessary
            if (cur_overlap > threshold) {
                it = score_index_vec.erase(it);
            } else {
                ++it;
            }
        }
    }
}

void ApplyNMS(const bool* overlapped, const int num, vector<int>* indices) {
    /*vector<int> index_vec(boost::counting_iterator<int>(0),
                          boost::counting_iterator<int>(num));*/

    vector<int> index_vec;
    for (int i = 0; i < num; ++i) {
        index_vec.push_back(i);
    }
    // Do nms.
    indices->clear();
    while (index_vec.size() != 0) {
        // Get the current highest score box.
        int best_idx = index_vec.front();
        indices->push_back(best_idx);
        // Erase the best box.
        index_vec.erase(index_vec.begin());

        for (vector<int>::iterator it = index_vec.begin(); it != index_vec.end();) {
            int cur_idx = *it;

            // Remove it if necessary
            if (overlapped[best_idx * num + cur_idx]) {
                it = index_vec.erase(it);
            } else {
                ++it;
            }
        }
    }
}

void GetMaxScoreIndex(const vector<float>& scores, const float threshold, const int top_k,
                      vector<pair<float, int>>* score_index_vec) {
    // Generate index score pairs.
    for (size_t i = 0; i < scores.size(); ++i) {
        if (scores[i] > threshold) {
            score_index_vec->push_back(std::make_pair(scores[i], i));
        }
    }

    // Sort the score pair according to the scores in descending order
    std::stable_sort(score_index_vec->begin(), score_index_vec->end(), SortScorePairDescend<int>);

    // Keep top_k scores if needed.
    if (top_k > -1 && top_k < score_index_vec->size()) {
        score_index_vec->resize(top_k);
    }
}

void ApplyNMSFast(const vector<NormalizedBBox>& bboxes, const vector<float>& scores, const float score_threshold,
                  const float nms_threshold, const float eta, const int top_k, vector<int>* indices) {
    // Sanity check.
    assert(bboxes.size() == scores.size());  //"bboxes and scores have different size."

    // Get top_k scores (with corresponding indices).
    vector<pair<float, int>> score_index_vec;
    GetMaxScoreIndex(scores, score_threshold, top_k, &score_index_vec);

    // Do nms.
    float adaptive_threshold = nms_threshold;
    indices->clear();
    while (score_index_vec.size() != 0) {
        const int idx = score_index_vec.front().second;
        bool keep     = true;
        for (int k = 0; k < indices->size(); ++k) {
            if (keep) {
                const int kept_idx = (*indices)[k];
                float overlap      = JaccardOverlap(bboxes[idx], bboxes[kept_idx]);
                keep               = overlap <= adaptive_threshold;
            } else {
                break;
            }
        }
        if (keep) {
            indices->push_back(idx);
        }
        score_index_vec.erase(score_index_vec.begin());
        if (keep && eta < 1 && adaptive_threshold > 0.5) {
            adaptive_threshold *= eta;
        }
    }
}

void CumSum(const vector<pair<float, int>>& pairs, vector<int>* cumsum) {
    // Sort the pairs based on first item of the pair.
    vector<pair<float, int>> sort_pairs = pairs;
    std::stable_sort(sort_pairs.begin(), sort_pairs.end(), SortScorePairDescend<int>);

    cumsum->clear();
    for (size_t i = 0; i < sort_pairs.size(); ++i) {
        if (i == 0) {
            cumsum->push_back(sort_pairs[i].second);
        } else {
            cumsum->push_back(cumsum->back() + sort_pairs[i].second);
        }
    }
}

void ComputeAP(const vector<pair<float, int>>& tp, const int num_pos, const vector<pair<float, int>>& fp,
               const string ap_version, vector<float>* prec, vector<float>* rec, float* ap) {
    const float eps = 1e-6f;
    CHECK_EQ(tp.size(), fp.size());
    const int num = static_cast<int>(tp.size());
    // Make sure that tp and fp have complement value.
    for (int i = 0; i < num; ++i) {
        CHECK_LE(fabs(tp[i].first - fp[i].first), eps);
        CHECK_EQ(tp[i].second, 1 - fp[i].second);
    }
    prec->clear();
    rec->clear();
    *ap = 0;
    if (tp.size() == 0 || num_pos == 0) {
        return;
    }

    // Compute cumsum of tp.
    vector<int> tp_cumsum;
    CumSum(tp, &tp_cumsum);
    CHECK_EQ(tp_cumsum.size(), num);

    // Compute cumsum of fp.
    vector<int> fp_cumsum;
    CumSum(fp, &fp_cumsum);
    CHECK_EQ(fp_cumsum.size(), num);

    // Compute precision.
    for (int i = 0; i < num; ++i) {
        prec->push_back(static_cast<float>(tp_cumsum[i]) / (tp_cumsum[i] + fp_cumsum[i]));
    }

    // Compute recall.
    for (int i = 0; i < num; ++i) {
        CHECK_LE(tp_cumsum[i], num_pos);
        rec->push_back(static_cast<float>(tp_cumsum[i]) / num_pos);
    }

    if (ap_version == "11point") {
        // VOC2007 style for computing AP.
        vector<float> max_precs(11, 0.);
        int start_idx = num - 1;
        for (int j = 10; j >= 0; --j) {
            for (int i = start_idx; i >= 0; --i) {
                if ((*rec)[i] < j / 10.) {
                    start_idx = i;
                    if (j > 0) {
                        max_precs[j - 1] = max_precs[j];
                    }
                    break;
                } else {
                    if (max_precs[j] < (*prec)[i]) {
                        max_precs[j] = (*prec)[i];
                    }
                }
            }
        }
        for (int j = 10; j >= 0; --j) {
            *ap += max_precs[j] / 11;
        }
    } else if (ap_version == "MaxIntegral") {
        // VOC2012 or ILSVRC style for computing AP.
        float cur_rec  = rec->back();
        float cur_prec = prec->back();
        for (int i = num - 2; i >= 0; --i) {
            cur_prec = std::max<float>((*prec)[i], cur_prec);
            if (fabs(cur_rec - (*rec)[i]) > eps) {
                *ap += cur_prec * fabs(cur_rec - (*rec)[i]);
            }
            cur_rec = (*rec)[i];
        }
        *ap += cur_rec * cur_prec;
    } else if (ap_version == "Integral") {
        // Natural integral.
        float prev_rec = 0.;
        for (int i = 0; i < num; ++i) {
            if (fabs((*rec)[i] - prev_rec) > eps) {
                *ap += (*prec)[i] * fabs((*rec)[i] - prev_rec);
            }
            prev_rec = (*rec)[i];
        }
    } else {
        // LOG(FATAL) << "Unknown ap_version: " << ap_version;
        assert(false);
    }
}

#ifdef USE_OPENCV
cv::Scalar HSV2RGB(const float h, const float s, const float v) {
    const int h_i = static_cast<int>(h * 6);
    const float f = h * 6 - h_i;
    const float p = v * (1 - s);
    const float q = v * (1 - f * s);
    const float t = v * (1 - (1 - f) * s);
    float r, g, b;
    switch (h_i) {
        case 0:
            r = v;
            g = t;
            b = p;
            break;
        case 1:
            r = q;
            g = v;
            b = p;
            break;
        case 2:
            r = p;
            g = v;
            b = t;
            break;
        case 3:
            r = p;
            g = q;
            b = v;
            break;
        case 4:
            r = t;
            g = p;
            b = v;
            break;
        case 5:
            r = v;
            g = p;
            b = q;
            break;
        default:
            r = 1;
            g = 1;
            b = 1;
            break;
    }
    return cv::Scalar(r * 255, g * 255, b * 255);
}

// http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically
vector<cv::Scalar> GetColors(const int n) {
    vector<cv::Scalar> colors;
    cv::RNG rng(12345);
    const float golden_ratio_conjugate = 0.618033988749895;
    const float s                      = 0.3;
    const float v                      = 0.99;
    for (int i = 0; i < n; ++i) {
        const float h = std::fmod(rng.uniform(0.f, 1.f) + golden_ratio_conjugate, 1.f);
        colors.push_back(HSV2RGB(h, s, v));
    }
    return colors;
}

static clock_t start_clock = clock();

template <typename Dtype>
void VisualizeBBox(const vector<cv::Mat>& images, const Blob<Dtype>* detections, const float threshold,
                   const vector<cv::Scalar>& colors, const map<int, string>& label_to_display_name) {
    // Retrieve detections.
    CHECK_EQ(detections->width(), 7);
    const int num_det = detections->height();
    const int num_img = images.size();
    if (num_det == 0 || num_img == 0) {
        return;
    }
    // Comute FPS.
    float fps = num_img / (static_cast<double>(clock() - start_clock) / CLOCKS_PER_SEC);

    const Dtype* detections_data = detections->cpu_data();
    const int width              = images[0].cols;
    const int height             = images[0].rows;
    vector<LabelBBox> all_detections(num_img);
    for (int i = 0; i < num_det; ++i) {
        const int img_idx = detections_data[i * 7];
        CHECK_LT(img_idx, num_img);
        const int label   = detections_data[i * 7 + 1];
        const float score = detections_data[i * 7 + 2];
        if (score < threshold) {
            continue;
        }
        NormalizedBBox bbox;
        bbox.set_xmin(detections_data[i * 7 + 3] * width);
        bbox.set_ymin(detections_data[i * 7 + 4] * height);
        bbox.set_xmax(detections_data[i * 7 + 5] * width);
        bbox.set_ymax(detections_data[i * 7 + 6] * height);
        bbox.set_score(score);
        all_detections[img_idx][label].push_back(bbox);
    }

    int fontface  = cv::FONT_HERSHEY_SIMPLEX;
    double scale  = 1;
    int thickness = 2;
    int baseline  = 0;
    char buffer[50];
    for (int i = 0; i < num_img; ++i) {
        cv::Mat image = images[i];
        // Show FPS.
        snprintf(buffer, sizeof(buffer), "FPS: %.2f", fps);
        cv::Size text = cv::getTextSize(buffer, fontface, scale, thickness, &baseline);
        cv::rectangle(image, cv::Point(0, 0), cv::Point(text.width, text.height + baseline), CV_RGB(255, 255, 255),
                      CV_FILLED);
        cv::putText(image, buffer, cv::Point(0, text.height + baseline / 2.), fontface, scale, CV_RGB(0, 0, 0),
                    thickness, 8);
        // Draw bboxes.
        for (map<int, vector<NormalizedBBox>>::iterator it = all_detections[i].begin(); it != all_detections[i].end();
             ++it) {
            int label         = it->first;
            string label_name = "Unknown";
            if (label_to_display_name.find(label) != label_to_display_name.end()) {
                label_name = label_to_display_name.find(label)->second;
            }
            CHECK_LT(label, colors.size());
            const cv::Scalar& color              = colors[label];
            const vector<NormalizedBBox>& bboxes = it->second;
            for (int j = 0; j < bboxes.size(); ++j) {
                cv::Point top_left_pt(bboxes[j].xmin(), bboxes[j].ymin());
                cv::Point bottom_right_pt(bboxes[j].xmax(), bboxes[j].ymax());
                cv::rectangle(image, top_left_pt, bottom_right_pt, color, 4);
                cv::Point bottom_left_pt(bboxes[j].xmin(), bboxes[j].ymax());
                snprintf(buffer, sizeof(buffer), "%s: %.2f", label_name.c_str(), bboxes[j].score());
                cv::Size text = cv::getTextSize(buffer, fontface, scale, thickness, &baseline);
                cv::rectangle(image, bottom_left_pt + cv::Point(0, 0),
                              bottom_left_pt + cv::Point(text.width, -text.height - baseline), color, CV_FILLED);
                cv::putText(image, buffer, bottom_left_pt - cv::Point(0, baseline), fontface, scale, CV_RGB(0, 0, 0),
                            thickness, 8);
            }
        }
        cv::imshow("detections", image);
        if (cv::waitKey(1) == 27) {
            raise(SIGINT);
        }
    }
    start_clock = clock();
}

template void VisualizeBBox(const vector<cv::Mat>& images, const Blob<float>* detections, const float threshold,
                            const vector<cv::Scalar>& colors, const map<int, string>& label_to_display_name);
template void VisualizeBBox(const vector<cv::Mat>& images, const Blob<double>* detections, const float threshold,
                            const vector<cv::Scalar>& colors, const map<int, string>& label_to_display_name);

#endif  // USE_OPENCV

}  // namespace TNN_NS
