use crate::postprocess::BoundingBox;
#[derive(Clone, Debug, PartialEq)]
pub struct Detection {
pub bbox: BoundingBox,
pub class_id: u32,
pub score: f32,
}
impl Detection {
#[must_use]
pub fn new(bbox: BoundingBox, class_id: u32, score: f32) -> Self {
Self {
bbox,
class_id,
score,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FaceEmbedding(Vec<f32>);
impl FaceEmbedding {
#[must_use]
pub fn from_unit(values: Vec<f32>) -> Self {
Self(values)
}
#[must_use]
pub fn from_raw(mut values: Vec<f32>) -> Self {
crate::postprocess::l2_normalize(&mut values);
Self(values)
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn as_slice(&self) -> &[f32] {
&self.0
}
#[must_use]
pub fn into_inner(self) -> Vec<f32> {
self.0
}
#[must_use]
pub fn cosine_similarity(&self, other: &Self) -> f32 {
crate::postprocess::cosine_similarity(self.as_slice(), other.as_slice())
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct AestheticScore {
score: f32,
distribution: [f32; 10],
}
impl AestheticScore {
#[must_use]
pub fn from_distribution(distribution: [f32; 10]) -> Self {
let score = weighted_mean_score(&distribution);
Self {
score,
distribution,
}
}
#[must_use]
pub fn score(&self) -> f32 {
self.score
}
#[must_use]
pub fn distribution(&self) -> &[f32; 10] {
&self.distribution
}
}
pub(crate) fn weighted_mean_score(distribution: &[f32; 10]) -> f32 {
let mut acc = 0.0_f32;
for (i, &p) in distribution.iter().enumerate() {
acc += ((i + 1) as f32) * p;
}
acc
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detection_constructor_populates_fields() {
let d = Detection::new(BoundingBox::new(0.0, 0.0, 1.0, 1.0), 3, 0.87);
assert_eq!(d.class_id, 3);
assert!((d.score - 0.87).abs() < 1e-6);
}
#[test]
fn face_embedding_from_raw_is_unit_norm() {
let emb = FaceEmbedding::from_raw(vec![3.0, 4.0]);
let norm: f32 = emb.as_slice().iter().map(|x| x * x).sum::<f32>().sqrt();
assert!((norm - 1.0).abs() < 1e-5);
}
#[test]
fn face_embedding_cosine_self_is_one() {
let emb = FaceEmbedding::from_raw(vec![0.1, 0.2, 0.3, 0.4]);
let sim = emb.cosine_similarity(&emb);
assert!((sim - 1.0).abs() < 1e-5);
}
#[test]
fn face_embedding_orthogonal_is_zero() {
let a = FaceEmbedding::from_raw(vec![1.0, 0.0, 0.0, 0.0]);
let b = FaceEmbedding::from_raw(vec![0.0, 1.0, 0.0, 0.0]);
assert!(a.cosine_similarity(&b).abs() < 1e-5);
}
#[test]
fn aesthetic_score_uniform_distribution_is_5_5() {
let dist = [0.1_f32; 10];
let s = AestheticScore::from_distribution(dist);
assert!((s.score() - 5.5).abs() < 1e-5);
}
#[test]
fn aesthetic_score_peaked_at_ten_is_ten() {
let mut dist = [0.0_f32; 10];
dist[9] = 1.0;
let s = AestheticScore::from_distribution(dist);
assert!((s.score() - 10.0).abs() < 1e-5);
}
#[test]
fn aesthetic_score_peaked_at_one_is_one() {
let mut dist = [0.0_f32; 10];
dist[0] = 1.0;
let s = AestheticScore::from_distribution(dist);
assert!((s.score() - 1.0).abs() < 1e-5);
}
}