use super::frame::{
DerResult, Region, build_collar_mask, build_speaker_frames, der_core, optimal_speaker_mapping,
};
use crate::types::{SpeakerTurn, TimeRange};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpeakerRecall {
pub speaker: u32,
pub ref_frames: u64,
pub recalled_frames: u64,
pub recall: f64,
}
#[derive(Debug, Clone)]
pub struct DerDecomposition {
pub total: DerResult,
pub single_speaker: DerResult,
pub overlap: DerResult,
pub per_speaker_recall: Vec<SpeakerRecall>,
}
pub fn compute_der_decomposition(
reference: &[SpeakerTurn],
hypothesis: &[SpeakerTurn],
collar: f64,
) -> DerDecomposition {
DerDecomposition {
total: der_core(reference, hypothesis, collar, Region::All, None),
single_speaker: der_core(reference, hypothesis, collar, Region::SingleSpeaker, None),
overlap: der_core(reference, hypothesis, collar, Region::Overlap, None),
per_speaker_recall: compute_per_speaker_recall(reference, hypothesis, collar),
}
}
fn compute_per_speaker_recall(
reference: &[SpeakerTurn],
hypothesis: &[SpeakerTurn],
collar: f64,
) -> Vec<SpeakerRecall> {
if reference.is_empty() || !collar.is_finite() || collar < 0.0 {
return Vec::new();
}
let max_time = reference
.iter()
.chain(hypothesis.iter())
.map(|t| t.time.end)
.fold(0.0f64, f64::max);
if !max_time.is_finite() || max_time < 0.0 {
return Vec::new();
}
let n_frames = TimeRange::grid_frame_count(max_time);
let collar_mask = build_collar_mask(reference, collar, n_frames);
let ref_frames = build_speaker_frames(reference, n_frames);
let hyp_frames = build_speaker_frames(hypothesis, n_frames);
let mapping = optimal_speaker_mapping(&ref_frames, &hyp_frames, &collar_mask);
let mut ref_to_hyp: HashMap<u32, u32> = HashMap::new();
for (&h, &r) in &mapping {
ref_to_hyp.insert(r, h);
}
let mut ref_count: HashMap<u32, u64> = HashMap::new();
let mut recalled: HashMap<u32, u64> = HashMap::new();
for i in 0..n_frames {
if collar_mask[i] {
continue;
}
for &r in &ref_frames[i] {
*ref_count.entry(r).or_insert(0) += 1;
if let Some(&h) = ref_to_hyp.get(&r)
&& hyp_frames[i].contains(&h)
{
*recalled.entry(r).or_insert(0) += 1;
}
}
}
let mut out: Vec<SpeakerRecall> = ref_count
.into_iter()
.map(|(speaker, ref_frames)| {
let recalled_frames = recalled.get(&speaker).copied().unwrap_or(0);
SpeakerRecall {
speaker,
ref_frames,
recalled_frames,
recall: recalled_frames as f64 / ref_frames as f64,
}
})
.collect();
out.sort_by_key(|s| s.speaker);
out
}