pub mod mnn; pub mod ort; use crate::errors::*; use error_stack::ResultExt; use ndarray::{Array1, Array2, ArrayView3, ArrayView4}; use ndarray_math::{CosineSimilarity, EuclideanDistance}; /// Configuration for face embedding processing #[derive(Debug, Clone, PartialEq)] pub struct FaceEmbeddingConfig { /// Input image width expected by the model pub input_width: usize, /// Input image height expected by the model pub input_height: usize, /// Whether to normalize embeddings to unit vectors pub normalize: bool, } impl FaceEmbeddingConfig { pub fn with_input_size(mut self, width: usize, height: usize) -> Self { self.input_width = width; self.input_height = height; self } pub fn with_normalization(mut self, normalize: bool) -> Self { self.normalize = normalize; self } } impl Default for FaceEmbeddingConfig { fn default() -> Self { Self { input_width: 320, input_height: 320, normalize: false, } } } /// Represents a face embedding vector #[derive(Debug, Clone, PartialEq)] pub struct FaceEmbedding { /// The embedding vector pub vector: Array1, /// Optional confidence score for the embedding quality pub confidence: Option, } impl FaceEmbedding { pub fn new(vector: Array1) -> Self { Self { vector, confidence: None, } } pub fn with_confidence(mut self, confidence: f32) -> Self { self.confidence = Some(confidence); self } /// Calculate cosine similarity with another embedding pub fn cosine_similarity(&self, other: &FaceEmbedding) -> f32 { self.vector.cosine_similarity(&other.vector).unwrap_or(0.0) } /// Calculate Euclidean distance with another embedding pub fn euclidean_distance(&self, other: &FaceEmbedding) -> f32 { self.vector .euclidean_distance(other.vector.view()) .unwrap_or(f32::INFINITY) } /// Normalize the embedding vector to unit length pub fn normalize(&mut self) { let norm = self.vector.mapv(|x| x * x).sum().sqrt(); if norm > 0.0 { self.vector.mapv_inplace(|x| x / norm); } } /// Get the dimensionality of the embedding pub fn dimension(&self) -> usize { self.vector.len() } } /// Raw model outputs that can be converted to embeddings pub trait IntoEmbeddings { fn into_embeddings(self, config: &FaceEmbeddingConfig) -> Result>; } impl IntoEmbeddings for Array2 { fn into_embeddings(self, config: &FaceEmbeddingConfig) -> Result> { let mut embeddings = Vec::new(); for row in self.rows() { let mut vector = row.to_owned(); if config.normalize { let norm = vector.mapv(|x| x * x).sum().sqrt(); if norm > 0.0 { vector.mapv_inplace(|x| x / norm); } } embeddings.push(FaceEmbedding::new(vector)); } Ok(embeddings) } } /// Common trait for face embedding backends pub trait FaceNetEmbedder { /// Generate embeddings for a batch of face images fn run_model(&mut self, faces: ArrayView4) -> Result>; /// Generate embeddings with full pipeline including postprocessing fn generate_embeddings( &mut self, faces: ArrayView4, config: FaceEmbeddingConfig, ) -> Result> { let raw_output = self .run_model(faces) .change_context(Error) .attach_printable("Failed to generate embeddings")?; raw_output .into_embeddings(&config) .attach_printable("Failed to process embeddings") } /// Generate a single embedding from a single face image fn generate_embedding( &mut self, face: ArrayView3, config: FaceEmbeddingConfig, ) -> Result { // Add batch dimension let face_batch = face.insert_axis(ndarray::Axis(0)); let embeddings = self.generate_embeddings(face_batch.view(), config)?; embeddings .into_iter() .next() .ok_or(Error) .attach_printable("No embedding generated for input face") } } /// Utility functions for embedding processing pub mod utils { use super::*; /// Compute pairwise cosine similarities between two sets of embeddings pub fn pairwise_cosine_similarities( embeddings1: &[FaceEmbedding], embeddings2: &[FaceEmbedding], ) -> Array2 { let n1 = embeddings1.len(); let n2 = embeddings2.len(); let mut similarities = Array2::zeros((n1, n2)); for (i, emb1) in embeddings1.iter().enumerate() { for (j, emb2) in embeddings2.iter().enumerate() { similarities[(i, j)] = emb1.cosine_similarity(emb2); } } similarities } /// Find the best matching embedding from a gallery for each query pub fn find_best_matches( queries: &[FaceEmbedding], gallery: &[FaceEmbedding], ) -> Vec<(usize, f32)> { let similarities = pairwise_cosine_similarities(queries, gallery); let mut best_matches = Vec::new(); for i in 0..queries.len() { let row = similarities.row(i); let (best_idx, best_score) = row .iter() .enumerate() .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap()) .unwrap(); best_matches.push((best_idx, *best_score)); } best_matches } /// Filter embeddings by minimum quality threshold pub fn filter_by_confidence( embeddings: Vec, min_confidence: f32, ) -> Vec { embeddings .into_iter() .filter(|emb| emb.confidence.map_or(true, |conf| conf >= min_confidence)) .collect() } }