use std::collections::{HashMap, HashSet};
use rayon::prelude::*;
use crate::params::Params;
use super::COCOeval;
use super::types::{AccumulatedEval, EvalImg, EvalShape};
pub(super) fn precision_recall_curve(
tp_cum: &[f64],
fp_cum: &[f64],
num_gt: usize,
rec_thrs: &[f64],
) -> (f64, Vec<(usize, f64, usize)>) {
let nd = tp_cum.len();
if nd == 0 || num_gt == 0 {
return (0.0, vec![]);
}
let num_gt_f = num_gt as f64;
let mut rc = vec![0.0f64; nd];
let mut pr = vec![0.0f64; nd];
for d in 0..nd {
rc[d] = tp_cum[d] / num_gt_f;
let total = tp_cum[d] + fp_cum[d];
pr[d] = if total > 0.0 { tp_cum[d] / total } else { 0.0 };
}
let final_recall = rc[nd - 1];
for d in (0..nd.saturating_sub(1)).rev() {
pr[d] = pr[d].max(pr[d + 1]);
}
let mut result = Vec::with_capacity(rec_thrs.len());
let mut rc_ptr = 0;
for (r_idx, &rec_thr) in rec_thrs.iter().enumerate() {
while rc_ptr < nd && rc[rc_ptr] < rec_thr {
rc_ptr += 1;
}
if rc_ptr < nd {
result.push((r_idx, pr[rc_ptr], rc_ptr));
}
}
(final_recall, result)
}
pub(super) fn accumulate_impl(
eval_imgs: &[Option<EvalImg>],
params: &Params,
img_filter: Option<&HashSet<u64>>,
) -> AccumulatedEval {
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 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>> = vec![Vec::new(); k * a];
for eval in eval_imgs.iter().flatten() {
if let Some(filter) = img_filter {
if !filter.contains(&eval.image_id) {
continue;
}
}
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, };
grouped[k_idx * a + a_idx].push(eval);
}
}
let work_items: Vec<(usize, usize, usize)> = (0..k)
.flat_map(|k_idx| {
(0..a).flat_map(move |a_idx| (0..m).map(move |m_idx| (k_idx, a_idx, m_idx)))
})
.collect();
struct AccResult {
precision_writes: Vec<(usize, f64)>,
recall_writes: Vec<(usize, 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, m_idx)| {
let max_det = params.max_dets[m_idx];
let evals = &grouped[k_idx * a + a_idx];
let total_dts: usize = evals.iter().map(|e| e.dt_scores.len().min(max_det)).sum();
let mut all_dt_scores: Vec<f64> = Vec::with_capacity(total_dts);
let mut all_dt_matched: Vec<Vec<bool>> =
(0..t).map(|_| Vec::with_capacity(total_dts)).collect();
let mut all_dt_ignore: Vec<Vec<bool>> =
(0..t).map(|_| Vec::with_capacity(total_dts)).collect();
let mut num_gt = 0usize;
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[t_idx][..nd]);
all_dt_ignore[t_idx].extend_from_slice(&eval_img.dt_ignore[t_idx][..nd]);
}
num_gt += eval_img.gt_ignore.iter().filter(|&&x| !x).count();
}
let mut precision_writes = Vec::new();
let mut recall_writes = Vec::new();
let mut scores_writes = Vec::new();
if num_gt == 0 {
return AccResult {
precision_writes,
recall_writes,
scores_writes,
};
}
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_writes.push((p_idx, 0.0));
scores_writes.push((p_idx, 0.0));
}
}
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);
recall_writes.push((recall_idx, 0.0));
}
return AccResult {
precision_writes,
recall_writes,
scores_writes,
};
}
let sorted_scores: Vec<f64> = inds.iter().map(|&i| all_dt_scores[i]).collect();
let mut tp = vec![0.0f64; nd];
let mut fp = vec![0.0f64; nd];
for t_idx in 0..t {
for (out_idx, &src_idx) in inds.iter().enumerate() {
if all_dt_ignore[t_idx][src_idx] {
tp[out_idx] = 0.0;
fp[out_idx] = 0.0;
} else if all_dt_matched[t_idx][src_idx] {
tp[out_idx] = 1.0;
fp[out_idx] = 0.0;
} else {
tp[out_idx] = 0.0;
fp[out_idx] = 1.0;
}
}
for d in 1..nd {
tp[d] += tp[d - 1];
fp[d] += fp[d - 1];
}
let (final_recall, curve) =
precision_recall_curve(&tp, &fp, num_gt, ¶ms.rec_thrs);
let recall_idx = shape.recall_idx(t_idx, k_idx, a_idx, m_idx);
recall_writes.push((recall_idx, final_recall));
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);
precision_writes.push((p_idx, pr_val));
scores_writes.push((p_idx, sorted_scores[rc_ptr]));
}
}
AccResult {
precision_writes,
recall_writes,
scores_writes,
}
})
.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];
for result in results {
for (idx, val) in result.precision_writes {
precision[idx] = val;
}
for (idx, val) in result.recall_writes {
recall[idx] = val;
}
for (idx, val) in result.scores_writes {
scores[idx] = val;
}
}
AccumulatedEval {
precision,
recall,
scores,
shape,
}
}
impl COCOeval {
pub fn accumulate(&mut self) {
self.eval = Some(accumulate_impl(&self.eval_imgs, &self.params, None));
}
}