use crate::commands::capture::capture_single_photo;
use crate::quality::{BlurDetector, BlurMetrics, ExposureAnalyzer, ExposureMetrics};
use crate::quality::{QualityReport, QualityValidator, ValidationConfig};
use crate::types::CameraFrame;
use std::sync::Arc;
use tauri::command;
use tokio::sync::RwLock;
lazy_static::lazy_static! {
static ref QUALITY_VALIDATOR: Arc<RwLock<QualityValidator>> = Arc::new(RwLock::new(QualityValidator::default()));
}
#[command]
pub async fn validate_frame_quality(
device_id: Option<String>,
capture_format: Option<crate::types::CameraFormat>,
) -> Result<QualityReport, String> {
log::info!("Validating frame quality for device: {:?}", device_id);
let frame = capture_single_photo(device_id, capture_format).await?;
let validator = QUALITY_VALIDATOR.read().await;
let report = validator.validate_frame(&frame);
Ok(report)
}
#[command]
pub async fn validate_provided_frame(frame: CameraFrame) -> Result<QualityReport, String> {
log::info!(
"Validating provided frame: {}x{}",
frame.width,
frame.height
);
let validator = QUALITY_VALIDATOR.read().await;
let report = validator.validate_frame(&frame);
Ok(report)
}
#[command]
pub async fn analyze_frame_blur(
device_id: Option<String>,
capture_format: Option<crate::types::CameraFormat>,
) -> Result<BlurMetrics, String> {
log::info!("Analyzing frame blur for device: {:?}", device_id);
let frame = capture_single_photo(device_id, capture_format).await?;
let blur_detector = BlurDetector::default();
let metrics = blur_detector.analyze_frame(&frame);
Ok(metrics)
}
#[command]
pub async fn analyze_frame_exposure(
device_id: Option<String>,
capture_format: Option<crate::types::CameraFormat>,
) -> Result<ExposureMetrics, String> {
log::info!("Analyzing frame exposure for device: {:?}", device_id);
let frame = capture_single_photo(device_id, capture_format).await?;
let exposure_analyzer = ExposureAnalyzer::default();
let metrics = exposure_analyzer.analyze_frame(&frame);
Ok(metrics)
}
#[command]
pub async fn update_quality_config(config: ValidationConfigDto) -> Result<String, String> {
log::info!("Updating quality validation configuration");
let validation_config = ValidationConfig {
blur_threshold: config.blur_threshold,
exposure_threshold: config.exposure_threshold,
overall_threshold: config.overall_threshold,
min_resolution: (config.min_width, config.min_height),
max_noise_level: config.max_noise_level,
};
let validator = QualityValidator::new(validation_config);
let mut guard = QUALITY_VALIDATOR.write().await;
*guard = validator;
Ok("Quality validation configuration updated".to_string())
}
#[command]
pub async fn get_quality_config() -> Result<ValidationConfigDto, String> {
let validator = QUALITY_VALIDATOR.read().await;
let config = validator.config();
Ok(ValidationConfigDto {
blur_threshold: config.blur_threshold,
exposure_threshold: config.exposure_threshold,
overall_threshold: config.overall_threshold,
min_width: config.min_resolution.0,
min_height: config.min_resolution.1,
max_noise_level: config.max_noise_level,
})
}
#[command]
pub async fn capture_best_quality_frame(
device_id: Option<String>,
capture_format: Option<crate::types::CameraFormat>,
num_attempts: Option<u32>,
) -> Result<CaptureQualityResult, String> {
let attempts = num_attempts.unwrap_or(5).min(10); log::info!("Capturing best quality frame with {} attempts", attempts);
let validator = QUALITY_VALIDATOR.read().await;
let mut best_frame: Option<CameraFrame> = None;
let mut best_report: Option<QualityReport> = None;
let mut best_score = 0.0f32;
for attempt in 1..=attempts {
log::debug!("Quality capture attempt {} of {}", attempt, attempts);
match capture_single_photo(device_id.clone(), capture_format.clone()).await {
Ok(frame) => {
let report = validator.validate_frame(&frame);
if report.score.overall > best_score {
best_score = report.score.overall;
best_frame = Some(frame);
best_report = Some(report);
}
if best_score >= 0.9 {
log::info!("Excellent quality achieved on attempt {}", attempt);
break;
}
}
Err(e) => {
log::warn!("Frame capture failed on attempt {}: {}", attempt, e);
continue;
}
}
if attempt < attempts {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
}
match (best_frame, best_report) {
(Some(frame), Some(report)) => Ok(CaptureQualityResult {
frame,
quality_report: report,
attempts_used: attempts,
}),
_ => Err("Failed to capture any valid frames".to_string()),
}
}
#[command]
pub async fn auto_capture_with_quality(
device_id: Option<String>,
capture_format: Option<crate::types::CameraFormat>,
min_quality_threshold: Option<f32>,
max_attempts: Option<u32>,
timeout_seconds: Option<u32>,
) -> Result<CaptureQualityResult, String> {
let quality_threshold = min_quality_threshold.unwrap_or(0.7);
let max_tries = max_attempts.unwrap_or(20).min(50); let timeout = timeout_seconds.unwrap_or(30);
log::info!(
"Auto-capturing with quality threshold {} (max {} attempts, {}s timeout)",
quality_threshold,
max_tries,
timeout
);
let start_time = std::time::Instant::now();
let validator = QUALITY_VALIDATOR.read().await;
for attempt in 1..=max_tries {
if start_time.elapsed().as_secs() >= timeout as u64 {
return Err(format!("Auto-capture timeout after {} seconds", timeout));
}
log::debug!("Auto-capture attempt {} of {}", attempt, max_tries);
match capture_single_photo(device_id.clone(), capture_format.clone()).await {
Ok(frame) => {
let report = validator.validate_frame(&frame);
if report.score.overall >= quality_threshold {
log::info!(
"Quality threshold met on attempt {} (score: {:.3})",
attempt,
report.score.overall
);
return Ok(CaptureQualityResult {
frame,
quality_report: report,
attempts_used: attempt,
});
}
log::debug!(
"Quality not met (score: {:.3}), continuing...",
report.score.overall
);
}
Err(e) => {
log::warn!("Frame capture failed on attempt {}: {}", attempt, e);
}
}
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
}
Err(format!(
"Failed to capture frame meeting quality threshold {} after {} attempts",
quality_threshold, max_tries
))
}
#[command]
pub async fn analyze_quality_trends(
device_id: Option<String>,
capture_format: Option<crate::types::CameraFormat>,
num_samples: Option<u32>,
) -> Result<QualityTrendAnalysis, String> {
let samples = num_samples.unwrap_or(10).min(20); log::info!("Analyzing quality trends over {} samples", samples);
let validator = QUALITY_VALIDATOR.read().await;
let mut reports = Vec::new();
for i in 1..=samples {
log::debug!("Quality trend sample {} of {}", i, samples);
match capture_single_photo(device_id.clone(), capture_format.clone()).await {
Ok(frame) => {
let report = validator.validate_frame(&frame);
reports.push(report);
}
Err(e) => {
log::warn!("Failed to capture sample {}: {}", i, e);
continue;
}
}
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
}
if reports.is_empty() {
return Err("No valid samples captured for trend analysis".to_string());
}
let scores: Vec<f32> = reports.iter().map(|r| r.score.overall).collect();
let blur_scores: Vec<f32> = reports.iter().map(|r| r.score.blur).collect();
let exposure_scores: Vec<f32> = reports.iter().map(|r| r.score.exposure).collect();
let avg_quality = scores.iter().sum::<f32>() / scores.len() as f32;
let avg_blur = blur_scores.iter().sum::<f32>() / blur_scores.len() as f32;
let avg_exposure = exposure_scores.iter().sum::<f32>() / exposure_scores.len() as f32;
let quality_variance = scores
.iter()
.map(|&x| (x - avg_quality).powi(2))
.sum::<f32>()
/ scores.len() as f32;
let stability_score = (1.0 - quality_variance.sqrt()).clamp(0.0, 1.0);
Ok(QualityTrendAnalysis {
samples_analyzed: reports.len() as u32,
average_quality: avg_quality,
average_blur_score: avg_blur,
average_exposure_score: avg_exposure,
quality_variance,
stability_score,
best_score: scores.iter().fold(0.0f32, |a, &b| a.max(b)),
worst_score: scores.iter().fold(1.0f32, |a, &b| a.min(b)),
acceptable_ratio: reports.iter().filter(|r| r.is_acceptable).count() as f32
/ reports.len() as f32,
})
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ValidationConfigDto {
pub blur_threshold: f32,
pub exposure_threshold: f32,
pub overall_threshold: f32,
pub min_width: u32,
pub min_height: u32,
pub max_noise_level: f32,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CaptureQualityResult {
pub frame: CameraFrame,
pub quality_report: QualityReport,
pub attempts_used: u32,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct QualityTrendAnalysis {
pub samples_analyzed: u32,
pub average_quality: f32,
pub average_blur_score: f32,
pub average_exposure_score: f32,
pub quality_variance: f32,
pub stability_score: f32,
pub best_score: f32,
pub worst_score: f32,
pub acceptable_ratio: f32,
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_frame() -> CameraFrame {
let data = vec![128u8; 640 * 480 * 3];
CameraFrame::new(data, 640, 480, "test".to_string())
}
#[tokio::test]
async fn test_validate_provided_frame() {
let frame = create_test_frame();
let result = validate_provided_frame(frame).await;
assert!(result.is_ok());
let report = result.unwrap();
assert!(report.score.overall >= 0.0 && report.score.overall <= 1.0);
}
#[tokio::test]
async fn test_quality_config_update() {
let config = ValidationConfigDto {
blur_threshold: 0.8,
exposure_threshold: 0.8,
overall_threshold: 0.9,
min_width: 1920,
min_height: 1080,
max_noise_level: 0.2,
};
let result = update_quality_config(config.clone()).await;
assert!(result.is_ok());
let retrieved_config = get_quality_config().await.unwrap();
assert!((retrieved_config.blur_threshold - 0.8).abs() < 0.001);
assert!((retrieved_config.exposure_threshold - 0.8).abs() < 0.001);
assert!((retrieved_config.overall_threshold - 0.9).abs() < 0.001);
assert_eq!(retrieved_config.min_width, 1920);
assert_eq!(retrieved_config.min_height, 1080);
assert!((retrieved_config.max_noise_level - 0.2).abs() < 0.001);
}
#[tokio::test]
async fn test_quality_trend_analysis_empty() {
let result =
analyze_quality_trends(Some("invalid_camera".to_string()), None, Some(1)).await;
assert!(result.is_ok() || result.is_err());
}
}