botserver/src/botmodels/rekognition.rs

925 lines
27 KiB
Rust

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use uuid::Uuid;
#[derive(Debug, Clone)]
pub enum RekognitionError {
ConfigError(String),
AwsError(String),
InvalidImage(String),
FaceNotFound(String),
CollectionNotFound(String),
CollectionAlreadyExists(String),
QuotaExceeded,
ServiceUnavailable,
Unauthorized,
}
impl std::fmt::Display for RekognitionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ConfigError(s) => write!(f, "Config error: {s}"),
Self::AwsError(s) => write!(f, "AWS error: {s}"),
Self::InvalidImage(s) => write!(f, "Invalid image: {s}"),
Self::FaceNotFound(s) => write!(f, "Face not found: {s}"),
Self::CollectionNotFound(s) => write!(f, "Collection not found: {s}"),
Self::CollectionAlreadyExists(s) => write!(f, "Collection already exists: {s}"),
Self::QuotaExceeded => write!(f, "Quota exceeded"),
Self::ServiceUnavailable => write!(f, "Service unavailable"),
Self::Unauthorized => write!(f, "Unauthorized"),
}
}
}
impl std::error::Error for RekognitionError {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RekognitionConfig {
pub region: String,
pub access_key_id: Option<String>,
pub secret_access_key: Option<String>,
pub collection_id: Option<String>,
pub max_faces: u32,
pub quality_filter: QualityFilter,
pub face_match_threshold: f32,
pub use_face_liveness: bool,
pub enable_age_range: bool,
pub enable_emotions: bool,
pub enable_smile_detection: bool,
pub enable_eyeglasses_detection: bool,
pub enable_sunglasses_detection: bool,
pub enable_gender_detection: bool,
pub enable_beard_detection: bool,
pub enable_mustache_detection: bool,
pub enable_eyes_open_detection: bool,
pub enable_mouth_open_detection: bool,
pub enable_face_occluded_detection: bool,
}
impl Default for RekognitionConfig {
fn default() -> Self {
Self {
region: "us-east-1".to_string(),
access_key_id: None,
secret_access_key: None,
collection_id: None,
max_faces: 100,
quality_filter: QualityFilter::Auto,
face_match_threshold: 80.0,
use_face_liveness: false,
enable_age_range: true,
enable_emotions: true,
enable_smile_detection: true,
enable_eyeglasses_detection: true,
enable_sunglasses_detection: true,
enable_gender_detection: true,
enable_beard_detection: true,
enable_mustache_detection: true,
enable_eyes_open_detection: true,
enable_mouth_open_detection: true,
enable_face_occluded_detection: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum QualityFilter {
None,
Auto,
Low,
Medium,
High,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RekognitionFace {
pub face_id: Option<String>,
pub bounding_box: BoundingBox,
pub confidence: f32,
pub image_id: Option<String>,
pub external_image_id: Option<String>,
pub landmarks: Vec<Landmark>,
pub pose: Pose,
pub quality: FaceQuality,
pub age_range: Option<AgeRange>,
pub smile: Option<Smile>,
pub eyeglasses: Option<Eyeglasses>,
pub sunglasses: Option<Sunglasses>,
pub gender: Option<Gender>,
pub beard: Option<Beard>,
pub mustache: Option<Mustache>,
pub eyes_open: Option<EyesOpen>,
pub mouth_open: Option<MouthOpen>,
pub emotions: Vec<Emotion>,
pub face_occluded: Option<FaceOccluded>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BoundingBox {
pub width: f32,
pub height: f32,
pub left: f32,
pub top: f32,
}
impl BoundingBox {
pub fn to_absolute(&self, image_width: u32, image_height: u32) -> AbsoluteBoundingBox {
AbsoluteBoundingBox {
x: (self.left * image_width as f32) as u32,
y: (self.top * image_height as f32) as u32,
width: (self.width * image_width as f32) as u32,
height: (self.height * image_height as f32) as u32,
}
}
pub fn area(&self) -> f32 {
self.width * self.height
}
pub fn center(&self) -> (f32, f32) {
(self.left + self.width / 2.0, self.top + self.height / 2.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AbsoluteBoundingBox {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Landmark {
pub landmark_type: LandmarkType,
pub x: f32,
pub y: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum LandmarkType {
EyeLeft,
EyeRight,
MouthLeft,
MouthRight,
Nose,
LeftEyeBrowLeft,
LeftEyeBrowRight,
LeftEyeBrowUp,
RightEyeBrowLeft,
RightEyeBrowRight,
RightEyeBrowUp,
LeftEyeLeft,
LeftEyeRight,
LeftEyeUp,
LeftEyeDown,
RightEyeLeft,
RightEyeRight,
RightEyeUp,
RightEyeDown,
NoseLeft,
NoseRight,
MouthUp,
MouthDown,
LeftPupil,
RightPupil,
UpperJawlineLeft,
MidJawlineLeft,
ChinBottom,
MidJawlineRight,
UpperJawlineRight,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pose {
pub roll: f32,
pub yaw: f32,
pub pitch: f32,
}
impl Pose {
pub fn is_frontal(&self, threshold: f32) -> bool {
self.yaw.abs() < threshold && self.pitch.abs() < threshold && self.roll.abs() < threshold
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FaceQuality {
pub brightness: f32,
pub sharpness: f32,
}
impl FaceQuality {
pub fn is_acceptable(&self, min_brightness: f32, min_sharpness: f32) -> bool {
self.brightness >= min_brightness && self.sharpness >= min_sharpness
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgeRange {
pub low: u32,
pub high: u32,
}
impl AgeRange {
pub fn midpoint(&self) -> u32 {
(self.low + self.high) / 2
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Smile {
pub value: bool,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Eyeglasses {
pub value: bool,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Sunglasses {
pub value: bool,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Gender {
pub value: GenderValue,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum GenderValue {
Male,
Female,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Beard {
pub value: bool,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Mustache {
pub value: bool,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EyesOpen {
pub value: bool,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MouthOpen {
pub value: bool,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FaceOccluded {
pub value: bool,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Emotion {
pub emotion_type: EmotionType,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum EmotionType {
Happy,
Sad,
Angry,
Confused,
Disgusted,
Surprised,
Calm,
Fear,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FaceCollection {
pub collection_id: String,
pub collection_arn: Option<String>,
pub face_count: u64,
pub face_model_version: String,
pub creation_timestamp: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexedFace {
pub face_id: String,
pub bounding_box: BoundingBox,
pub image_id: String,
pub external_image_id: Option<String>,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FaceMatch {
pub similarity: f32,
pub face: RekognitionFace,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompareFacesMatch {
pub similarity: f32,
pub face: RekognitionFace,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetectFacesRequest {
pub image_bytes: Vec<u8>,
pub attributes: Vec<FaceAttribute>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum FaceAttribute {
Default,
All,
AgeRange,
Beard,
Emotions,
Eyeglasses,
EyesOpen,
Gender,
MouthOpen,
Mustache,
Smile,
Sunglasses,
FaceOccluded,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetectFacesResponse {
pub faces: Vec<RekognitionFace>,
pub orientation_correction: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexFacesRequest {
pub collection_id: String,
pub image_bytes: Vec<u8>,
pub external_image_id: Option<String>,
pub max_faces: Option<u32>,
pub quality_filter: Option<QualityFilter>,
pub detection_attributes: Vec<FaceAttribute>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexFacesResponse {
pub face_records: Vec<FaceRecord>,
pub orientation_correction: Option<String>,
pub face_model_version: String,
pub unindexed_faces: Vec<UnindexedFace>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FaceRecord {
pub face: IndexedFace,
pub face_detail: RekognitionFace,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnindexedFace {
pub reasons: Vec<UnindexedFaceReason>,
pub face_detail: RekognitionFace,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum UnindexedFaceReason {
ExceedMaxFaces,
ExtremePose,
LowBrightness,
LowSharpness,
LowConfidence,
SmallBoundingBox,
LowFaceQuality,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchFacesRequest {
pub collection_id: String,
pub face_id: String,
pub max_faces: Option<u32>,
pub face_match_threshold: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchFacesResponse {
pub searched_face_id: String,
pub face_matches: Vec<FaceMatch>,
pub face_model_version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchFacesByImageRequest {
pub collection_id: String,
pub image_bytes: Vec<u8>,
pub max_faces: Option<u32>,
pub face_match_threshold: Option<f32>,
pub quality_filter: Option<QualityFilter>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchFacesByImageResponse {
pub searched_face_bounding_box: BoundingBox,
pub searched_face_confidence: f32,
pub face_matches: Vec<FaceMatch>,
pub face_model_version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompareFacesRequest {
pub source_image_bytes: Vec<u8>,
pub target_image_bytes: Vec<u8>,
pub similarity_threshold: Option<f32>,
pub quality_filter: Option<QualityFilter>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompareFacesResponse {
pub source_image_face: Option<RekognitionFace>,
pub face_matches: Vec<CompareFacesMatch>,
pub unmatched_faces: Vec<RekognitionFace>,
pub source_image_orientation_correction: Option<String>,
pub target_image_orientation_correction: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteFacesRequest {
pub collection_id: String,
pub face_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteFacesResponse {
pub deleted_faces: Vec<String>,
pub undeleted_faces: Vec<UnsuccessfulFaceDeletion>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnsuccessfulFaceDeletion {
pub face_id: String,
pub reasons: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LivenessSessionRequest {
pub settings: Option<LivenessSettings>,
pub client_request_token: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LivenessSettings {
pub output_config: Option<LivenessOutputConfig>,
pub audit_images_limit: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LivenessOutputConfig {
pub s3_bucket: String,
pub s3_key_prefix: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LivenessSessionResponse {
pub session_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LivenessSession {
pub session_id: String,
pub status: LivenessSessionStatus,
pub settings: Option<LivenessSettings>,
pub created_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetFaceLivenessSessionResultsResponse {
pub session_id: String,
pub status: LivenessSessionStatus,
pub confidence: Option<f32>,
pub reference_image: Option<AuditImage>,
pub audit_images: Vec<AuditImage>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LivenessSessionStatus {
Created,
InProgress,
Succeeded,
Failed,
Expired,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditImage {
pub bytes: Option<Vec<u8>>,
pub s3_object: Option<S3Object>,
pub bounding_box: Option<BoundingBox>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct S3Object {
pub bucket: String,
pub name: String,
pub version: Option<String>,
}
pub struct RekognitionService {
config: Arc<RwLock<RekognitionConfig>>,
collections: Arc<RwLock<HashMap<String, FaceCollection>>>,
indexed_faces: Arc<RwLock<HashMap<String, Vec<IndexedFace>>>>,
face_details: Arc<RwLock<HashMap<String, RekognitionFace>>>,
}
impl Default for RekognitionService {
fn default() -> Self {
Self::new()
}
}
impl RekognitionService {
pub fn new() -> Self {
Self {
config: Arc::new(RwLock::new(RekognitionConfig::default())),
collections: Arc::new(RwLock::new(HashMap::new())),
indexed_faces: Arc::new(RwLock::new(HashMap::new())),
face_details: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn with_config(config: RekognitionConfig) -> Self {
Self {
config: Arc::new(RwLock::new(config)),
collections: Arc::new(RwLock::new(HashMap::new())),
indexed_faces: Arc::new(RwLock::new(HashMap::new())),
face_details: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn detect_faces(&self, request: DetectFacesRequest) -> Result<DetectFacesResponse, RekognitionError> {
if request.image_bytes.is_empty() {
return Err(RekognitionError::InvalidImage("Image bytes cannot be empty".to_string()));
}
let config = self.config.read().await;
let include_all = request.attributes.contains(&FaceAttribute::All);
let mut faces = Vec::new();
let simulated_face_count = 1;
for _ in 0..simulated_face_count {
let face = self.create_simulated_face(&config, include_all);
faces.push(face);
}
Ok(DetectFacesResponse {
faces,
orientation_correction: Some("ROTATE_0".to_string()),
})
}
fn create_simulated_face(&self, config: &RekognitionConfig, include_all: bool) -> RekognitionFace {
let landmarks = vec![
Landmark { landmark_type: LandmarkType::EyeLeft, x: 0.35, y: 0.35 },
Landmark { landmark_type: LandmarkType::EyeRight, x: 0.65, y: 0.35 },
Landmark { landmark_type: LandmarkType::Nose, x: 0.5, y: 0.55 },
Landmark { landmark_type: LandmarkType::MouthLeft, x: 0.38, y: 0.75 },
Landmark { landmark_type: LandmarkType::MouthRight, x: 0.62, y: 0.75 },
];
let age_range = if include_all || config.enable_age_range {
Some(AgeRange { low: 25, high: 35 })
} else {
None
};
let smile = if include_all || config.enable_smile_detection {
Some(Smile { value: true, confidence: 95.5 })
} else {
None
};
let eyeglasses = if include_all || config.enable_eyeglasses_detection {
Some(Eyeglasses { value: false, confidence: 98.2 })
} else {
None
};
let sunglasses = if include_all || config.enable_sunglasses_detection {
Some(Sunglasses { value: false, confidence: 99.1 })
} else {
None
};
let gender = if include_all || config.enable_gender_detection {
Some(Gender {
value: GenderValue::Male,
confidence: 97.8,
})
} else {
None
};
let beard = if include_all || config.enable_beard_detection {
Some(Beard { value: false, confidence: 96.3 })
} else {
None
};
let mustache = if include_all || config.enable_mustache_detection {
Some(Mustache { value: false, confidence: 97.1 })
} else {
None
};
let eyes_open = if include_all || config.enable_eyes_open_detection {
Some(EyesOpen { value: true, confidence: 99.5 })
} else {
None
};
let mouth_open = if include_all || config.enable_mouth_open_detection {
Some(MouthOpen { value: false, confidence: 98.7 })
} else {
None
};
let emotions = if include_all || config.enable_emotions {
vec![
Emotion { emotion_type: EmotionType::Happy, confidence: 85.2 },
Emotion { emotion_type: EmotionType::Calm, confidence: 12.5 },
Emotion { emotion_type: EmotionType::Surprised, confidence: 2.3 },
]
} else {
Vec::new()
};
let face_occluded = if include_all || config.enable_face_occluded_detection {
Some(FaceOccluded { value: false, confidence: 98.9 })
} else {
None
};
RekognitionFace {
face_id: Some(Uuid::new_v4().to_string()),
bounding_box: BoundingBox {
width: 0.25,
height: 0.35,
left: 0.35,
top: 0.15,
},
confidence: 99.8,
image_id: Some(Uuid::new_v4().to_string()),
external_image_id: None,
landmarks,
pose: Pose {
roll: 2.5,
yaw: -5.0,
pitch: 3.2,
},
quality: FaceQuality {
brightness: 85.5,
sharpness: 92.3,
},
age_range,
smile,
eyeglasses,
sunglasses,
gender,
beard,
mustache,
eyes_open,
mouth_open,
emotions,
face_occluded,
}
}
pub async fn create_collection(&self, collection_id: &str) -> Result<FaceCollection, RekognitionError> {
let mut collections = self.collections.write().await;
if collections.contains_key(collection_id) {
return Err(RekognitionError::CollectionAlreadyExists(collection_id.to_string()));
}
let collection = FaceCollection {
collection_id: collection_id.to_string(),
collection_arn: Some(format!("arn:aws:rekognition:us-east-1:123456789012:collection/{collection_id}")),
face_count: 0,
face_model_version: "6.0".to_string(),
creation_timestamp: Some(Utc::now()),
};
collections.insert(collection_id.to_string(), collection.clone());
let mut indexed_faces = self.indexed_faces.write().await;
indexed_faces.insert(collection_id.to_string(), Vec::new());
Ok(collection)
}
pub async fn delete_collection(&self, collection_id: &str) -> Result<(), RekognitionError> {
let mut collections = self.collections.write().await;
if collections.remove(collection_id).is_none() {
return Err(RekognitionError::CollectionNotFound(collection_id.to_string()));
}
let mut indexed_faces = self.indexed_faces.write().await;
indexed_faces.remove(collection_id);
Ok(())
}
pub async fn list_collections(&self) -> Vec<FaceCollection> {
let collections = self.collections.read().await;
collections.values().cloned().collect()
}
pub async fn describe_collection(&self, collection_id: &str) -> Result<FaceCollection, RekognitionError> {
let collections = self.collections.read().await;
collections
.get(collection_id)
.cloned()
.ok_or_else(|| RekognitionError::CollectionNotFound(collection_id.to_string()))
}
pub async fn index_faces(&self, request: IndexFacesRequest) -> Result<IndexFacesResponse, RekognitionError> {
if request.image_bytes.is_empty() {
return Err(RekognitionError::InvalidImage("Image bytes cannot be empty".to_string()));
}
let mut collections = self.collections.write().await;
let collection = collections
.get_mut(&request.collection_id)
.ok_or_else(|| RekognitionError::CollectionNotFound(request.collection_id.clone()))?;
let config = self.config.read().await;
let max_faces = request.max_faces.unwrap_or(config.max_faces);
let mut face_records = Vec::new();
let simulated_face_count = 1.min(max_faces as usize);
for _ in 0..simulated_face_count {
let face_id = Uuid::new_v4().to_string();
let image_id = Uuid::new_v4().to_string();
let indexed_face = IndexedFace {
face_id: face_id.clone(),
bounding_box: BoundingBox {
width: 0.25,
height: 0.35,
left: 0.35,
top: 0.15,
},
image_id: image_id.clone(),
external_image_id: request.external_image_id.clone(),
confidence: 99.8,
};
let face_detail = self.create_simulated_face(&config, true);
face_records.push(FaceRecord {
face: indexed_face.clone(),
face_detail: face_detail.clone(),
});
let mut indexed_faces = self.indexed_faces.write().await;
if let Some(faces) = indexed_faces.get_mut(&request.collection_id) {
faces.push(indexed_face);
}
let mut face_details = self.face_details.write().await;
face_details.insert(face_id, face_detail);
}
collection.face_count += face_records.len() as u64;
Ok(IndexFacesResponse {
face_records,
orientation_correction: Some("ROTATE_0".to_string()),
face_model_version: "6.0".to_string(),
unindexed_faces: Vec::new(),
})
}
pub async fn search_faces(&self, request: SearchFacesRequest) -> Result<SearchFacesResponse, RekognitionError> {
let indexed_faces = self.indexed_faces.read().await;
let collection_faces = indexed_faces
.get(&request.collection_id)
.ok_or_else(|| RekognitionError::CollectionNotFound(request.collection_id.clone()))?;
if !collection_faces.iter().any(|f| f.face_id == request.face_id) {
return Err(RekognitionError::FaceNotFound(request.face_id.clone()));
}
let config = self.config.read().await;
let threshold = request.face_match_threshold.unwrap_or(config.face_match_threshold);
let max_faces = request.max_faces.unwrap_or(10);
let mut face_matches = Vec::new();
for face in collection_faces.iter().filter(|f| f.face_id != request.face_id) {
let similarity = 85.0 + (rand_float() * 15.0);
if similarity >= threshold {
let face_details = self.face_details.read().await;
if let Some(detail) = face_details.get(&face.face_id) {
face_matches.push(FaceMatch {
similarity,
face: detail.clone(),
});
}
}
}
face_matches.sort_by(|a, b| b.similarity.partial_cmp(&a.similarity).unwrap_or(std::cmp::Ordering::Equal));
face_matches.truncate(max_faces as usize);
Ok(SearchFacesResponse {
searched_face_id: request.face_id,
face_matches,
face_model_version: "6.0".to_string(),
})
}
pub async fn search_faces_by_image(&self, request: SearchFacesByImageRequest) -> Result<SearchFacesByImageResponse, RekognitionError> {
if request.image_bytes.is_empty() {
return Err(RekognitionError::InvalidImage("Image bytes cannot be empty".to_string()));
}
let indexed_faces = self.indexed_faces.read().await;
let collection_faces = indexed_faces
.get(&request.collection_id)
.ok_or_else(|| RekognitionError::CollectionNotFound(request.collection_id.clone()))?;
let config = self.config.read().await;
let threshold = request.face_match_threshold.unwrap_or(config.face_match_threshold);
let max_faces = request.max_faces.unwrap_or(10);
let searched_face_bounding_box = BoundingBox {
width: 0.25,
height: 0.35,
left: 0.35,
top: 0.15,
};
let mut face_matches = Vec::new();
for face in collection_faces.iter() {
let similarity = 80.0 + (rand_float() * 20.0);
if similarity >= threshold {
let face_details = self.face_details.read().await;
if let Some(detail) = face_details.get(&face.face_id) {
face_matches.push(FaceMatch {
similarity,
face: detail.clone(),
});
}
}
}
face_matches.sort_by(|a, b| b.similarity.partial_cmp(&a.similarity).unwrap_or(std::cmp::Ordering::Equal));
face_matches.truncate(max_faces as usize);
Ok(SearchFacesByImageResponse {
searched_face_bounding_box,
searched_face_confidence: 99.5,
face_matches,
face_model_version: "6.0".to_string(),
})
}
}
fn rand_float() -> f32 {
use std::time::{SystemTime, UNIX_EPOCH};
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos();
(nanos % 1000) as f32 / 1000.0
}