use crabcamera::commands::quality::{
analyze_frame_blur, analyze_frame_exposure, analyze_quality_trends, auto_capture_with_quality,
capture_best_quality_frame, get_quality_config, update_quality_config, validate_frame_quality,
validate_provided_frame, ValidationConfigDto,
};
use crabcamera::quality::{
BlurDetector, BlurLevel, ExposureAnalyzer, ExposureLevel, QualityValidator, ValidationConfig,
};
use crabcamera::types::{CameraFormat, CameraFrame};
use std::time::Instant;
use tokio;
const TEST_DEVICE_ID: &str = "test_camera_quality";
fn create_test_frame_with_pattern(width: u32, height: u32, pattern: &str) -> CameraFrame {
let size = (width * height * 3) as usize;
let mut data = vec![0u8; size];
match pattern {
"solid_gray" => {
for i in (0..size).step_by(3) {
data[i] = 128; data[i + 1] = 128; data[i + 2] = 128; }
}
"checkboard" => {
let check_size = 8;
for y in 0..height {
for x in 0..width {
let is_white = ((x / check_size) + (y / check_size)) % 2 == 0;
let color = if is_white { 255 } else { 0 };
let idx = ((y * width + x) * 3) as usize;
if idx + 2 < size {
data[idx] = color;
data[idx + 1] = color;
data[idx + 2] = color;
}
}
}
}
"gradient" => {
for y in 0..height {
for x in 0..width {
let intensity = (x * 255 / width) as u8;
let idx = ((y * width + x) * 3) as usize;
if idx + 2 < size {
data[idx] = intensity;
data[idx + 1] = intensity;
data[idx + 2] = intensity;
}
}
}
}
"dark" => {
for i in (0..size).step_by(3) {
data[i] = 20;
data[i + 1] = 20;
data[i + 2] = 20;
}
}
"bright" => {
for i in (0..size).step_by(3) {
data[i] = 240;
data[i + 1] = 240;
data[i + 2] = 240;
}
}
"noisy" => {
for i in (0..size).step_by(3) {
let noise = (i % 50) as u8;
data[i] = 128 + noise;
data[i + 1] = 128 + noise;
data[i + 2] = 128 + noise;
}
}
_ => {
for i in (0..size).step_by(3) {
data[i] = 128;
data[i + 1] = 128;
data[i + 2] = 128;
}
}
}
CameraFrame::new(data, width, height, "test".to_string())
}
#[test]
fn test_blur_detector_patterns() {
let detector = BlurDetector::default();
let sharp_frame = create_test_frame_with_pattern(100, 100, "checkboard");
let sharp_metrics = detector.analyze_frame(&sharp_frame);
println!("Sharp checkboard metrics:");
println!(" Variance: {:.2}", sharp_metrics.variance);
println!(" Gradient: {:.2}", sharp_metrics.gradient_magnitude);
println!(" Edge density: {:.4}", sharp_metrics.edge_density);
println!(" Quality score: {:.3}", sharp_metrics.quality_score);
assert!(sharp_metrics.variance > 100.0);
assert!(matches!(
sharp_metrics.blur_level,
BlurLevel::Sharp | BlurLevel::Good
));
let blurry_frame = create_test_frame_with_pattern(100, 100, "solid_gray");
let blurry_metrics = detector.analyze_frame(&blurry_frame);
println!("Solid gray metrics:");
println!(" Variance: {:.2}", blurry_metrics.variance);
println!(" Quality score: {:.3}", blurry_metrics.quality_score);
assert!(blurry_metrics.variance < 50.0);
assert!(matches!(
blurry_metrics.blur_level,
BlurLevel::Blurry | BlurLevel::VeryBlurry
));
}
#[test]
fn test_exposure_analyzer_patterns() {
let analyzer = ExposureAnalyzer::default();
let gradient_frame = create_test_frame_with_pattern(100, 100, "gradient");
let gradient_metrics = analyzer.analyze_frame(&gradient_frame);
println!("Gradient exposure metrics:");
println!(" Mean brightness: {:.3}", gradient_metrics.mean_brightness);
println!(" Std dev: {:.3}", gradient_metrics.brightness_std);
println!(" Dynamic range: {:.3}", gradient_metrics.dynamic_range);
println!(" Quality score: {:.3}", gradient_metrics.quality_score);
assert!(gradient_metrics.dynamic_range > 0.8);
assert!(gradient_metrics.brightness_std > 0.2);
let dark_frame = create_test_frame_with_pattern(100, 100, "dark");
let dark_metrics = analyzer.analyze_frame(&dark_frame);
assert!(dark_metrics.mean_brightness < 0.2);
assert_eq!(dark_metrics.exposure_level, ExposureLevel::Underexposed);
assert!(dark_metrics.dark_pixel_ratio > 0.8);
let bright_frame = create_test_frame_with_pattern(100, 100, "bright");
let bright_metrics = analyzer.analyze_frame(&bright_frame);
assert!(bright_metrics.mean_brightness > 0.8);
assert_eq!(bright_metrics.exposure_level, ExposureLevel::Overexposed);
assert!(bright_metrics.bright_pixel_ratio > 0.8);
}
#[test]
fn test_quality_validator() {
let validator = QualityValidator::default();
let high_quality = create_test_frame_with_pattern(640, 480, "checkboard");
let hq_report = validator.validate_frame(&high_quality);
println!("High quality frame report:");
println!(" Overall score: {:.3}", hq_report.score.overall);
println!(" Blur score: {:.3}", hq_report.score.blur);
println!(" Exposure score: {:.3}", hq_report.score.exposure);
println!(" Is acceptable: {}", hq_report.is_acceptable);
assert!(hq_report.score.overall > 0.5);
assert!(hq_report.score.blur > 0.5);
let low_quality = create_test_frame_with_pattern(320, 240, "solid_gray");
let lq_report = validator.validate_frame(&low_quality);
println!("Low quality frame report:");
println!(" Overall score: {:.3}", lq_report.score.overall);
println!(" Technical score: {:.3}", lq_report.score.technical);
assert!(lq_report.score.overall < 0.7);
assert!(lq_report.score.blur < 0.5);
}
#[test]
fn test_blur_level_boundaries() {
assert_eq!(BlurLevel::from_variance(1500.0), BlurLevel::Sharp);
assert_eq!(BlurLevel::from_variance(800.0), BlurLevel::Good);
assert_eq!(BlurLevel::from_variance(300.0), BlurLevel::Moderate);
assert_eq!(BlurLevel::from_variance(100.0), BlurLevel::Blurry);
assert_eq!(BlurLevel::from_variance(10.0), BlurLevel::VeryBlurry);
assert_eq!(BlurLevel::Sharp.quality_score(), 1.0);
assert_eq!(BlurLevel::Good.quality_score(), 0.8);
assert_eq!(BlurLevel::Moderate.quality_score(), 0.6);
assert_eq!(BlurLevel::Blurry.quality_score(), 0.3);
assert_eq!(BlurLevel::VeryBlurry.quality_score(), 0.1);
}
#[test]
fn test_exposure_level_boundaries() {
assert_eq!(
ExposureLevel::from_brightness(0.1),
ExposureLevel::Underexposed
);
assert_eq!(
ExposureLevel::from_brightness(0.3),
ExposureLevel::SlightlyDark
);
assert_eq!(
ExposureLevel::from_brightness(0.5),
ExposureLevel::WellExposed
);
assert_eq!(
ExposureLevel::from_brightness(0.7),
ExposureLevel::SlightlyBright
);
assert_eq!(
ExposureLevel::from_brightness(0.9),
ExposureLevel::Overexposed
);
assert_eq!(ExposureLevel::WellExposed.quality_score(), 1.0);
assert_eq!(ExposureLevel::SlightlyDark.quality_score(), 0.8);
assert_eq!(ExposureLevel::SlightlyBright.quality_score(), 0.8);
assert_eq!(ExposureLevel::Underexposed.quality_score(), 0.3);
assert_eq!(ExposureLevel::Overexposed.quality_score(), 0.3);
}
#[tokio::test]
async fn test_validate_provided_frame_command() {
let hq_frame = create_test_frame_with_pattern(1280, 720, "checkboard");
let result = validate_provided_frame(hq_frame).await;
assert!(result.is_ok());
let report = result.unwrap();
assert!(report.score.overall >= 0.0 && report.score.overall <= 1.0);
assert!(report.score.blur >= 0.0 && report.score.blur <= 1.0);
assert!(report.score.exposure >= 0.0 && report.score.exposure <= 1.0);
let lq_frame = create_test_frame_with_pattern(160, 120, "solid_gray");
let result = validate_provided_frame(lq_frame).await;
assert!(result.is_ok());
let report = result.unwrap();
assert!(report.score.overall < 0.8); }
#[tokio::test]
async fn test_quality_config_management() {
let default_config = get_quality_config().await;
assert!(default_config.is_ok());
let config = default_config.unwrap();
println!("Default quality config:");
println!(" Blur threshold: {}", config.blur_threshold);
println!(" Exposure threshold: {}", config.exposure_threshold);
println!(" Overall threshold: {}", config.overall_threshold);
assert!(config.blur_threshold > 0.0 && config.blur_threshold <= 1.0);
assert!(config.exposure_threshold > 0.0 && config.exposure_threshold <= 1.0);
assert!(config.overall_threshold > 0.0 && config.overall_threshold <= 1.0);
let new_config = ValidationConfigDto {
blur_threshold: 0.8,
exposure_threshold: 0.9,
overall_threshold: 0.85,
min_width: 1920,
min_height: 1080,
max_noise_level: 0.1,
};
let update_result = update_quality_config(new_config.clone()).await;
assert!(update_result.is_ok());
}
#[tokio::test]
async fn test_validate_frame_quality_command() {
let device_id = Some(TEST_DEVICE_ID.to_string());
let format = Some(CameraFormat::standard());
let result = validate_frame_quality(device_id, format).await;
match result {
Ok(report) => {
println!("Captured frame quality:");
println!(" Overall score: {:.3}", report.score.overall);
println!(" Blur score: {:.3}", report.score.blur);
println!(" Exposure score: {:.3}", report.score.exposure);
println!(" Technical score: {:.3}", report.score.technical);
assert!(report.score.overall >= 0.0 && report.score.overall <= 1.0);
assert!(report.blur_metrics.unwrap().quality_score >= 0.0);
assert!(report.exposure_metrics.unwrap().quality_score >= 0.0);
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!(
"Warning: Quality validation test skipped (no camera): {}",
e
);
}
Err(e) => {
println!("Unexpected quality validation error: {}", e);
}
}
}
#[tokio::test]
async fn test_analyze_frame_blur_command() {
let device_id = Some(TEST_DEVICE_ID.to_string());
let format = Some(CameraFormat::standard());
let result = analyze_frame_blur(device_id, format).await;
match result {
Ok(metrics) => {
println!("Blur analysis metrics:");
println!(" Variance: {:.2}", metrics.variance);
println!(" Gradient magnitude: {:.2}", metrics.gradient_magnitude);
println!(" Edge density: {:.4}", metrics.edge_density);
println!(" Blur level: {:?}", metrics.blur_level);
assert!(metrics.variance >= 0.0);
assert!(metrics.gradient_magnitude >= 0.0);
assert!(metrics.edge_density >= 0.0 && metrics.edge_density <= 1.0);
assert!(metrics.quality_score >= 0.0 && metrics.quality_score <= 1.0);
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Blur analysis test skipped (no camera): {}", e);
}
Err(e) => {
println!("Unexpected blur analysis error: {}", e);
}
}
}
#[tokio::test]
async fn test_analyze_frame_exposure_command() {
let device_id = Some(TEST_DEVICE_ID.to_string());
let format = Some(CameraFormat::standard());
let result = analyze_frame_exposure(device_id, format).await;
match result {
Ok(metrics) => {
println!("Exposure analysis metrics:");
println!(" Mean brightness: {:.3}", metrics.mean_brightness);
println!(" Brightness std: {:.3}", metrics.brightness_std);
println!(" Dark pixel ratio: {:.3}", metrics.dark_pixel_ratio);
println!(" Bright pixel ratio: {:.3}", metrics.bright_pixel_ratio);
println!(" Dynamic range: {:.3}", metrics.dynamic_range);
println!(" Exposure level: {:?}", metrics.exposure_level);
assert!(metrics.mean_brightness >= 0.0 && metrics.mean_brightness <= 1.0);
assert!(metrics.brightness_std >= 0.0);
assert!(metrics.dark_pixel_ratio >= 0.0 && metrics.dark_pixel_ratio <= 1.0);
assert!(metrics.bright_pixel_ratio >= 0.0 && metrics.bright_pixel_ratio <= 1.0);
assert!(metrics.dynamic_range >= 0.0 && metrics.dynamic_range <= 1.0);
assert!(metrics.histogram.len() == 256);
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Exposure analysis test skipped (no camera): {}", e);
}
Err(e) => {
println!("Unexpected exposure analysis error: {}", e);
}
}
}
#[tokio::test]
async fn test_capture_best_quality_frame() {
let device_id = Some(TEST_DEVICE_ID.to_string());
let format = Some(CameraFormat::standard());
let result = capture_best_quality_frame(device_id, format, Some(3)).await;
match result {
Ok(capture_result) => {
println!("Best quality capture result:");
println!(" Attempts used: {}", capture_result.attempts_used);
println!(
" Quality score: {:.3}",
capture_result.quality_report.score.overall
);
assert!(capture_result.attempts_used <= 3);
assert!(capture_result.frame.is_valid());
assert!(capture_result.quality_report.score.overall >= 0.0);
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Best quality test skipped (no camera): {}", e);
}
Err(e) => {
println!("Unexpected best quality error: {}", e);
}
}
}
#[tokio::test]
async fn test_auto_capture_with_quality() {
let device_id = Some(TEST_DEVICE_ID.to_string());
let format = Some(CameraFormat::standard());
let result = auto_capture_with_quality(
device_id,
format,
Some(0.5), Some(5), Some(10), )
.await;
match result {
Ok(capture_result) => {
println!("Auto capture result:");
println!(" Attempts used: {}", capture_result.attempts_used);
println!(
" Quality achieved: {:.3}",
capture_result.quality_report.score.overall
);
assert!(capture_result.attempts_used <= 5);
assert!(capture_result.quality_report.score.overall >= 0.5);
assert!(capture_result.frame.is_valid());
}
Err(e) if e.contains("mutex") || e.contains("camera") || e.contains("timeout") => {
println!("Warning: Auto capture test result: {}", e);
}
Err(e) => {
println!("Unexpected auto capture error: {}", e);
}
}
}
#[tokio::test]
async fn test_analyze_quality_trends() {
let device_id = Some(TEST_DEVICE_ID.to_string());
let format = Some(CameraFormat::standard());
let result = analyze_quality_trends(device_id, format, Some(5)).await;
match result {
Ok(analysis) => {
println!("Quality trend analysis:");
println!(" Samples analyzed: {}", analysis.samples_analyzed);
println!(" Average quality: {:.3}", analysis.average_quality);
println!(" Quality variance: {:.6}", analysis.quality_variance);
println!(" Stability score: {:.3}", analysis.stability_score);
println!(" Best score: {:.3}", analysis.best_score);
println!(" Worst score: {:.3}", analysis.worst_score);
println!(" Acceptable ratio: {:.3}", analysis.acceptable_ratio);
assert!(analysis.samples_analyzed <= 5);
assert!(analysis.average_quality >= 0.0 && analysis.average_quality <= 1.0);
assert!(analysis.stability_score >= 0.0 && analysis.stability_score <= 1.0);
assert!(analysis.best_score >= analysis.worst_score);
assert!(analysis.acceptable_ratio >= 0.0 && analysis.acceptable_ratio <= 1.0);
}
Err(e) if e.contains("mutex") || e.contains("camera") => {
println!("Warning: Quality trends test skipped (no camera): {}", e);
}
Err(e) => {
println!("Unexpected quality trends error: {}", e);
}
}
}
#[test]
fn test_quality_analysis_performance() {
let detector = BlurDetector::default();
let analyzer = ExposureAnalyzer::default();
let validator = QualityValidator::default();
let sizes = [(320, 240), (640, 480), (1280, 720), (1920, 1080)];
for (width, height) in sizes.iter() {
let frame = create_test_frame_with_pattern(*width, *height, "checkboard");
let start = Instant::now();
let blur_metrics = detector.analyze_frame(&frame);
let blur_time = start.elapsed();
let start = Instant::now();
let exposure_metrics = analyzer.analyze_frame(&frame);
let exposure_time = start.elapsed();
let start = Instant::now();
let quality_report = validator.validate_frame(&frame);
let validation_time = start.elapsed();
println!("Performance for {}x{} frame:", width, height);
println!(" Blur detection: {:?}", blur_time);
println!(" Exposure analysis: {:?}", exposure_time);
println!(" Full validation: {:?}", validation_time);
println!(" Quality score: {:.3}", quality_report.score.overall);
assert!(validation_time.as_millis() < 2000);
assert!(blur_metrics.quality_score >= 0.0);
assert!(exposure_metrics.quality_score >= 0.0);
assert!(quality_report.score.overall >= 0.0);
}
}
#[test]
fn test_algorithm_mathematical_correctness() {
let detector = BlurDetector::default();
let analyzer = ExposureAnalyzer::default();
let sharp_frame = create_test_frame_with_pattern(100, 100, "checkboard");
let sharp_metrics = detector.analyze_frame(&sharp_frame);
assert!(sharp_metrics.variance > 1000.0);
assert!(sharp_metrics.gradient_magnitude > 50.0);
assert!(sharp_metrics.edge_density > 0.3);
let gradient_frame = create_test_frame_with_pattern(256, 1, "gradient");
let gradient_metrics = analyzer.analyze_frame(&gradient_frame);
assert!(gradient_metrics.mean_brightness > 0.45);
assert!(gradient_metrics.mean_brightness < 0.55);
assert!(gradient_metrics.dynamic_range > 0.95);
assert!(gradient_metrics.brightness_std > 0.25);
}
#[test]
fn test_quality_analysis_edge_cases() {
let detector = BlurDetector::default();
let analyzer = ExposureAnalyzer::default();
let tiny_frame = create_test_frame_with_pattern(1, 1, "solid_gray");
let tiny_blur = detector.analyze_frame(&tiny_frame);
let tiny_exposure = analyzer.analyze_frame(&tiny_frame);
assert!(tiny_blur.quality_score >= 0.0);
assert!(tiny_exposure.quality_score >= 0.0);
let large_frame = create_test_frame_with_pattern(2048, 2048, "noisy");
let large_blur = detector.analyze_frame(&large_frame);
assert!(large_blur.variance >= 0.0);
let mut extreme_data = vec![0u8; 300]; for i in (0..300).step_by(3) {
extreme_data[i] = if i % 6 == 0 { 255 } else { 0 };
extreme_data[i + 1] = if i % 6 == 3 { 255 } else { 0 };
extreme_data[i + 2] = 0;
}
let extreme_frame = CameraFrame::new(extreme_data, 10, 10, "test".to_string());
let extreme_metrics = analyzer.analyze_frame(&extreme_frame);
assert!(extreme_metrics.dynamic_range > 0.4);
}
#[test]
fn test_custom_configurations() {
let strict_detector = BlurDetector::new(500.0, 100.0); let lenient_detector = BlurDetector::new(50.0, 10.0);
let test_frame = create_test_frame_with_pattern(200, 200, "noisy");
let strict_metrics = strict_detector.analyze_frame(&test_frame);
let lenient_metrics = lenient_detector.analyze_frame(&test_frame);
assert!(
strict_detector.is_acceptable_quality(&strict_metrics)
<= lenient_detector.is_acceptable_quality(&lenient_metrics)
);
let custom_analyzer = ExposureAnalyzer::new(40, 200); let metrics = custom_analyzer.analyze_frame(&test_frame);
assert!(metrics.quality_score >= 0.0 && metrics.quality_score <= 1.0);
}
#[test]
fn test_custom_quality_validator() {
let custom_config = ValidationConfig {
blur_threshold: 0.8,
exposure_threshold: 0.9,
overall_threshold: 0.85,
min_resolution: (1920, 1080),
max_noise_level: 0.1,
};
let validator = QualityValidator::new(custom_config);
let hd_frame = create_test_frame_with_pattern(1920, 1080, "checkboard");
let hd_report = validator.validate_frame(&hd_frame);
println!("HD frame with strict validation:");
println!(" Overall score: {:.3}", hd_report.score.overall);
println!(" Technical score: {:.3}", hd_report.score.technical);
println!(" Is acceptable: {}", hd_report.is_acceptable);
assert!(hd_report.score.technical >= 0.5);
let ld_frame = create_test_frame_with_pattern(640, 480, "checkboard");
let ld_report = validator.validate_frame(&ld_frame);
assert!(ld_report.score.technical <= hd_report.score.technical);
}