pub mod align;
pub mod capture;
pub mod merge;
use crate::types::CameraFrame;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FocusStackConfig {
pub num_steps: u32,
pub step_delay_ms: u32,
pub focus_start: f32,
pub focus_end: f32,
pub enable_alignment: bool,
pub sharpness_threshold: f32,
pub blend_levels: u32,
}
impl Default for FocusStackConfig {
fn default() -> Self {
Self {
num_steps: 10,
step_delay_ms: 200,
focus_start: 0.0,
focus_end: 1.0,
enable_alignment: true,
sharpness_threshold: 0.5,
blend_levels: 5,
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FocusStackResult {
pub merged_frame: CameraFrame,
pub num_sources: usize,
pub alignment_error: f32,
pub processing_time_ms: u64,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum FocusStackError {
InsufficientImages {
required: usize,
provided: usize,
},
DimensionMismatch {
expected: (u32, u32),
got: (u32, u32),
},
DataCorruption {
frame_size: usize,
expected_size: usize,
},
AlignmentFailed(String),
MergeFailed(String),
InvalidConfig(String),
}
impl std::fmt::Display for FocusStackError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InsufficientImages { required, provided } => {
write!(f, "Insufficient images: need {required}, got {provided}")
}
Self::DimensionMismatch { expected, got } => {
write!(
f,
"Image dimension mismatch: expected {}x{}, got {}x{}",
expected.0, expected.1, got.0, got.1
)
}
Self::DataCorruption {
frame_size,
expected_size,
} => {
write!(
f,
"Frame data corruption: got {frame_size} bytes, expected {expected_size}"
)
}
Self::AlignmentFailed(msg) => write!(f, "Alignment failed: {msg}"),
Self::MergeFailed(msg) => write!(f, "Merge failed: {msg}"),
Self::InvalidConfig(msg) => write!(f, "Invalid config: {msg}"),
}
}
}
impl std::error::Error for FocusStackError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = FocusStackConfig::default();
assert_eq!(config.num_steps, 10);
assert_eq!(config.step_delay_ms, 200);
assert!((config.focus_start - 0.0).abs() < 1e-6);
assert!((config.focus_end - 1.0).abs() < 1e-6);
assert!(config.enable_alignment);
assert!((config.sharpness_threshold - 0.5).abs() < 1e-6);
assert_eq!(config.blend_levels, 5);
}
#[test]
fn test_config_validation() {
let config = FocusStackConfig {
num_steps: 2,
..Default::default()
};
assert!(config.num_steps >= 2);
assert!(config.focus_start >= 0.0 && config.focus_start <= 1.0);
assert!(config.focus_end >= 0.0 && config.focus_end <= 1.0);
assert!(config.sharpness_threshold >= 0.0 && config.sharpness_threshold <= 1.0);
assert!(config.blend_levels >= 3 && config.blend_levels <= 10);
}
#[test]
fn test_error_display() {
let err = FocusStackError::InsufficientImages {
required: 5,
provided: 2,
};
assert!(err.to_string().contains("Insufficient images"));
let err = FocusStackError::DimensionMismatch {
expected: (1920, 1080),
got: (1280, 720),
};
assert!(err.to_string().contains("dimension mismatch"));
}
}