use std::collections::{HashMap, HashSet};
use rayon::prelude::*;
use super::COCOeval;
use super::EvalMode;
use super::matching::EvalImg;
pub(super) struct EvalGrouping<'a> {
ev: &'a COCOeval,
grouped: Vec<Vec<(&'a EvalImg, u32)>>,
a: usize,
img_slots: HashMap<u64, u32>,
}
impl<'a> EvalGrouping<'a> {
pub(super) fn build(ev: &'a COCOeval) -> Self {
let params = &ev.params;
let k = if params.use_cats {
params.cat_ids.len()
} else {
1
};
let a = params.area_ranges.len();
let cat_id_to_k_idx: HashMap<u64, usize> = if params.use_cats {
params
.cat_ids
.iter()
.enumerate()
.map(|(i, &id)| (id, i))
.collect()
} else {
std::iter::once((u64::MAX, 0usize)).collect()
};
let area_rng_to_idx: HashMap<[u64; 2], usize> = params
.area_ranges
.iter()
.enumerate()
.map(|(i, ar)| ([ar.range[0].to_bits(), ar.range[1].to_bits()], i))
.collect();
let mut grouped: Vec<Vec<(&EvalImg, u32)>> = vec![Vec::new(); k * a];
let mut img_slots: HashMap<u64, u32> = HashMap::new();
for eval in ev.eval_imgs.iter().flatten() {
if let Some(&k_idx) = cat_id_to_k_idx.get(&eval.category_id) {
let a_key = [eval.area_rng[0].to_bits(), eval.area_rng[1].to_bits()];
let a_idx = match area_rng_to_idx.get(&a_key).copied() {
Some(idx) => idx,
None => continue, };
let next = img_slots.len() as u32;
let slot = *img_slots.entry(eval.image_id).or_insert(next);
grouped[k_idx * a + a_idx].push((eval, slot));
}
}
EvalGrouping {
ev,
grouped,
a,
img_slots,
}
}
pub(super) fn eval(&self) -> &'a COCOeval {
self.ev
}
pub(super) fn image_mask(&self, img_filter: Option<&HashSet<u64>>) -> Vec<bool> {
let n = self.img_slots.len();
let Some(filter) = img_filter else {
return vec![true; n];
};
let mut mask = vec![false; n];
for (img_id, &slot) in &self.img_slots {
if filter.contains(img_id) {
mask[slot as usize] = true;
}
}
mask
}
fn cell(&self, k_idx: usize, a_idx: usize) -> &[(&'a EvalImg, u32)] {
&self.grouped[k_idx * self.a + a_idx]
}
}
pub(super) fn accumulate_impl(
grouping: &EvalGrouping<'_>,
img_filter: Option<&HashSet<u64>>,
) -> AccumulatedEval {
let params = &grouping.eval().params;
let want_all_points = grouping.eval().eval_mode == EvalMode::OpenImages;
let t = params.iou_thrs.len();
let r = params.rec_thrs.len();
let k = if params.use_cats {
params.cat_ids.len()
} else {
1
};
let a = params.area_ranges.len();
let m = params.max_dets.len();
let img_mask = grouping.image_mask(img_filter);
let work_items: Vec<(usize, usize)> = (0..k)
.flat_map(|k_idx| (0..a).map(move |a_idx| (k_idx, a_idx)))
.collect();
#[derive(Default)]
struct AccResult {
filled: bool,
precision_writes: Vec<(usize, f64)>,
recall_writes: Vec<(usize, f64, f64)>,
scores_writes: Vec<(usize, f64)>,
}
let shape = EvalShape { t, r, k, a, m };
let results: Vec<AccResult> = work_items
.par_iter()
.map(|&(k_idx, a_idx)| {
let evals: Vec<&EvalImg> = grouping
.cell(k_idx, a_idx)
.iter()
.filter(|&&(_, slot)| img_mask[slot as usize])
.map(|&(e, _)| e)
.collect();
let num_gt: usize = evals.iter().map(|e| e.num_gt_in_denominator()).sum();
if num_gt == 0 {
return AccResult::default();
}
let mut out = AccResult {
filled: true,
precision_writes: Vec::with_capacity(m * t * r),
recall_writes: Vec::with_capacity(m * t),
scores_writes: Vec::with_capacity(m * t * r),
};
let mut all_dt_scores: Vec<f64> = Vec::new();
let mut all_dt_matched: Vec<Vec<bool>> = vec![Vec::new(); t];
let mut all_dt_ignore: Vec<Vec<bool>> = vec![Vec::new(); t];
let mut sorted_scores: Vec<f64> = Vec::new();
let (mut tp, mut fp) = (Vec::new(), Vec::new());
let mut pr_scratch = crate::metrics::counts::PrCurveScratch::default();
let mut curve: Vec<(usize, f64, usize)> = Vec::new();
for m_idx in 0..m {
let max_det = params.max_dets[m_idx];
all_dt_scores.clear();
for v in all_dt_matched.iter_mut().chain(all_dt_ignore.iter_mut()) {
v.clear();
}
for eval_img in &evals {
let nd = eval_img.dt_scores.len().min(max_det);
all_dt_scores.extend_from_slice(&eval_img.dt_scores[..nd]);
for t_idx in 0..t {
all_dt_matched[t_idx]
.extend_from_slice(&eval_img.dt_matched.row(t_idx)[..nd]);
all_dt_ignore[t_idx]
.extend_from_slice(&eval_img.dt_ignore.row(t_idx)[..nd]);
}
}
let mut inds: Vec<usize> = (0..all_dt_scores.len()).collect();
inds.sort_by(|&a, &b| {
all_dt_scores[b]
.partial_cmp(&all_dt_scores[a])
.unwrap_or(std::cmp::Ordering::Equal)
});
let nd = inds.len();
if nd == 0 {
for t_idx in 0..t {
let recall_idx = shape.recall_idx(t_idx, k_idx, a_idx, m_idx);
let ap = if want_all_points { 0.0 } else { -1.0 };
out.recall_writes.push((recall_idx, 0.0, ap));
}
continue;
}
sorted_scores.clear();
sorted_scores.extend(inds.iter().map(|&i| all_dt_scores[i]));
for t_idx in 0..t {
crate::metrics::counts::cumulative_tp_fp(
inds.iter().copied(),
&all_dt_matched[t_idx],
Some(&all_dt_ignore[t_idx]),
&mut tp,
&mut fp,
);
let final_recall = crate::metrics::counts::precision_recall_curve_into(
&tp,
&fp,
num_gt,
¶ms.rec_thrs,
&mut pr_scratch,
&mut curve,
);
let all_points_ap = if want_all_points {
crate::metrics::counts::average_precision_all_points(&tp, &fp, num_gt)
} else {
-1.0
};
let recall_idx = shape.recall_idx(t_idx, k_idx, a_idx, m_idx);
out.recall_writes
.push((recall_idx, final_recall, all_points_ap));
for &(r_idx, pr_val, rc_ptr) in &curve {
let p_idx = shape.precision_idx(t_idx, r_idx, k_idx, a_idx, m_idx);
out.precision_writes.push((p_idx, pr_val));
out.scores_writes.push((p_idx, sorted_scores[rc_ptr]));
}
}
}
out
})
.collect();
let total = t * r * k * a * m;
let mut precision = vec![-1.0f64; total];
let mut scores = vec![-1.0f64; total];
let total_recall = t * k * a * m;
let mut recall = vec![-1.0f64; total_recall];
let mut ap_all_points = vec![-1.0f64; total_recall];
for (&(k_idx, a_idx), result) in work_items.iter().zip(results) {
if result.filled {
for m_idx in 0..m {
for t_idx in 0..t {
for r_idx in 0..r {
let p_idx = shape.precision_idx(t_idx, r_idx, k_idx, a_idx, m_idx);
precision[p_idx] = 0.0;
scores[p_idx] = 0.0;
}
}
}
}
for (idx, val) in result.precision_writes {
precision[idx] = val;
}
for (idx, rec, ap) in result.recall_writes {
recall[idx] = rec;
ap_all_points[idx] = ap;
}
for (idx, val) in result.scores_writes {
scores[idx] = val;
}
}
AccumulatedEval {
precision,
recall,
ap_all_points,
scores,
shape,
}
}
impl COCOeval {
pub fn accumulate(&mut self) {
let eval = accumulate_impl(&EvalGrouping::build(self), None);
self.eval = Some(eval);
}
}
#[derive(Debug, Clone, Copy)]
pub struct EvalShape {
pub t: usize,
pub r: usize,
pub k: usize,
pub a: usize,
pub m: usize,
}
impl EvalShape {
pub fn precision_idx(&self, t: usize, r: usize, k: usize, a: usize, m: usize) -> usize {
((((t * self.r + r) * self.k + k) * self.a + a) * self.m) + m
}
pub fn recall_idx(&self, t: usize, k: usize, a: usize, m: usize) -> usize {
(((t * self.k + k) * self.a + a) * self.m) + m
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct AccumulatedEval {
pub precision: Vec<f64>,
pub recall: Vec<f64>,
pub ap_all_points: Vec<f64>,
pub scores: Vec<f64>,
pub shape: EvalShape,
}
impl AccumulatedEval {
pub fn precision_idx(&self, t: usize, r: usize, k: usize, a: usize, m: usize) -> usize {
self.shape.precision_idx(t, r, k, a, m)
}
pub fn recall_idx(&self, t: usize, k: usize, a: usize, m: usize) -> usize {
self.shape.recall_idx(t, k, a, m)
}
}