Skip to main content

oximedia_align/
lib.rs

1//! Video alignment and registration tools for multi-camera synchronization in `OxiMedia`.
2//!
3//! This crate provides comprehensive tools for aligning and registering video from multiple cameras:
4//!
5//! # Temporal Alignment
6//!
7//! The [`temporal`] module provides time-based synchronization:
8//!
9//! - **Audio Cross-Correlation** - Sync cameras using audio tracks
10//! - **Timecode Synchronization** - LTC/VITC-based alignment
11//! - **Visual Markers** - Clapper detection and flash-based sync
12//! - **Sub-frame Accuracy** - Precise timing down to microseconds
13//!
14//! # Spatial Registration
15//!
16//! The [`spatial`] module provides geometric alignment:
17//!
18//! - **Homography Estimation** - Planar perspective transformation
19//! - **Perspective Correction** - Remove keystone distortion
20//! - **Feature Matching** - Correspond points between views
21//! - **RANSAC** - Robust outlier rejection
22//!
23//! # Feature Detection
24//!
25//! The [`features`] module provides patent-free feature detection and matching:
26//!
27//! - **FAST Corners** - High-speed corner detection
28//! - **BRIEF Descriptors** - Binary robust independent elementary features
29//! - **ORB Features** - Oriented FAST and Rotated BRIEF
30//! - **Brute-Force Matching** - Hamming distance matching
31//!
32//! # Lens Distortion
33//!
34//! The [`distortion`] module corrects lens aberrations:
35//!
36//! - **Brown-Conrady Model** - Radial and tangential distortion
37//! - **Fisheye Model** - Wide-angle lens correction
38//! - **Calibration** - Camera intrinsic parameter estimation
39//! - **Undistortion** - Real-time image correction
40//!
41//! # Color Matching
42//!
43//! The [`color`] module matches color across cameras:
44//!
45//! - **Color Transfer** - Match color distributions
46//! - **Histogram Matching** - Equalize color histograms
47//! - **White Balance** - Illuminant estimation
48//! - **Color Calibration** - ColorChecker-based calibration
49//!
50//! # Sync Markers
51//!
52//! The [`markers`] module detects synchronization markers:
53//!
54//! - **Clapper Detection** - Automatic slate detection
55//! - **Flash Detection** - Bright flash sync
56//! - **LED Markers** - Coded light patterns
57//! - **Audio Spike** - Sharp transient detection
58//!
59//! # Rolling Shutter
60//!
61//! The [`rolling_shutter`] module corrects rolling shutter artifacts:
62//!
63//! - **Motion Estimation** - Per-scanline motion vectors
64//! - **Correction** - Remove wobble and skew
65//! - **Global Shutter Simulation** - Temporal interpolation
66//!
67//! # Example: Audio-Based Sync
68//!
69//! ```
70//! use oximedia_align::temporal::{AudioSync, SyncConfig};
71//! use oximedia_align::AlignResult;
72//!
73//! # fn example() -> AlignResult<()> {
74//! // Configure audio synchronization
75//! let config = SyncConfig {
76//!     sample_rate: 48000,
77//!     window_size: 480000, // 10 seconds
78//!     max_offset: 240000,  // ±5 seconds
79//! };
80//!
81//! // Create audio sync analyzer
82//! let sync = AudioSync::new(config);
83//!
84//! // Find offset between two audio tracks
85//! // let offset = sync.find_offset(&audio1, &audio2)?;
86//! # Ok(())
87//! # }
88//! ```
89//!
90//! # Example: Homography Estimation
91//!
92//! ```
93//! use oximedia_align::spatial::{HomographyEstimator, RansacConfig};
94//! use oximedia_align::features::{FeatureMatcher, MatchPair};
95//!
96//! # fn example() -> oximedia_align::AlignResult<()> {
97//! // Configure RANSAC for robust estimation
98//! let config = RansacConfig {
99//!     threshold: 3.0,
100//!     max_iterations: 1000,
101//!     min_inliers: 8,
102//! };
103//!
104//! let estimator = HomographyEstimator::new(config);
105//!
106//! // Estimate homography from matched points
107//! // let (homography, inliers) = estimator.estimate(&matches)?;
108//! # Ok(())
109//! # }
110//! ```
111
112#![forbid(unsafe_code)]
113#![warn(missing_docs)]
114
115pub mod affine;
116pub mod align_report;
117pub mod audio_align;
118pub mod beat_align;
119pub mod bundle_adjust;
120pub mod color;
121pub mod confidence_map;
122pub mod distortion;
123pub mod drift_correct;
124pub mod drift_correction;
125pub mod elastic_align;
126pub mod farneback_flow;
127pub mod features;
128pub mod frame_matcher;
129pub mod frequency_align;
130pub mod gradient_flow;
131pub mod icp;
132pub mod illumination_invariant;
133pub mod image_stitch;
134pub mod integral_image;
135pub mod klt_tracker;
136pub mod lens_calibration;
137pub mod lip_sync;
138pub mod markers;
139pub mod motion_compensate;
140pub mod motion_interp;
141pub mod multi_stream;
142pub mod multicam_sync;
143pub mod multitrack_align;
144pub mod optical_flow;
145pub mod parallel_ransac;
146pub mod phase_correlate;
147pub mod projective_warp;
148pub mod prosac;
149pub mod rigid_transform;
150pub mod rolling_shutter;
151pub mod scene_align;
152pub mod simd_hamming;
153pub mod spatial;
154pub mod stabilize;
155pub mod stereo_depth;
156pub mod stereo_rectify;
157pub mod subframe_interp;
158pub mod sync_score;
159pub mod tempo_align;
160pub mod temporal;
161pub mod temporal_align;
162pub mod transform;
163pub mod warp;
164
165use thiserror::Error;
166
167/// Result type for alignment operations
168pub type AlignResult<T> = Result<T, AlignError>;
169
170/// Errors that can occur during alignment operations
171#[derive(Debug, Error)]
172pub enum AlignError {
173    /// Insufficient data for alignment
174    #[error("Insufficient data: {0}")]
175    InsufficientData(String),
176
177    /// Invalid configuration parameter
178    #[error("Invalid configuration: {0}")]
179    InvalidConfig(String),
180
181    /// No solution found
182    #[error("No solution found: {0}")]
183    NoSolution(String),
184
185    /// Numerical instability
186    #[error("Numerical instability: {0}")]
187    NumericalError(String),
188
189    /// Feature detection failed
190    #[error("Feature detection failed: {0}")]
191    FeatureError(String),
192
193    /// Matching failed
194    #[error("Matching failed: {0}")]
195    MatchingError(String),
196
197    /// Estimation failed
198    #[error("Estimation failed: {0}")]
199    EstimationError(String),
200
201    /// Synchronization failed
202    #[error("Synchronization failed: {0}")]
203    SyncError(String),
204
205    /// Color correction failed
206    #[error("Color correction failed: {0}")]
207    ColorError(String),
208
209    /// Distortion correction failed
210    #[error("Distortion correction failed: {0}")]
211    DistortionError(String),
212
213    /// Rolling shutter correction failed
214    #[error("Rolling shutter correction failed: {0}")]
215    RollingShutterError(String),
216
217    /// Generic error from core
218    #[error("Core error: {0}")]
219    Core(#[from] oximedia_core::error::OxiError),
220}
221
222/// 2D point
223#[derive(Debug, Clone, Copy, PartialEq)]
224pub struct Point2D {
225    /// X coordinate
226    pub x: f64,
227    /// Y coordinate
228    pub y: f64,
229}
230
231impl Point2D {
232    /// Create a new 2D point
233    #[must_use]
234    pub fn new(x: f64, y: f64) -> Self {
235        Self { x, y }
236    }
237
238    /// Compute Euclidean distance to another point
239    #[must_use]
240    pub fn distance(&self, other: &Self) -> f64 {
241        let dx = self.x - other.x;
242        let dy = self.y - other.y;
243        (dx * dx + dy * dy).sqrt()
244    }
245
246    /// Compute squared distance (faster than distance)
247    #[must_use]
248    pub fn distance_squared(&self, other: &Self) -> f64 {
249        let dx = self.x - other.x;
250        let dy = self.y - other.y;
251        dx * dx + dy * dy
252    }
253}
254
255/// Time offset between two streams
256#[derive(Debug, Clone, Copy, PartialEq)]
257pub struct TimeOffset {
258    /// Offset in samples (for audio) or frames (for video)
259    pub samples: i64,
260    /// Confidence score (0.0 to 1.0)
261    pub confidence: f64,
262    /// Cross-correlation peak value
263    pub correlation: f64,
264}
265
266impl TimeOffset {
267    /// Create a new time offset
268    #[must_use]
269    pub fn new(samples: i64, confidence: f64, correlation: f64) -> Self {
270        Self {
271            samples,
272            confidence,
273            correlation,
274        }
275    }
276
277    /// Convert offset to seconds
278    #[must_use]
279    pub fn to_seconds(&self, sample_rate: u32) -> f64 {
280        self.samples as f64 / f64::from(sample_rate)
281    }
282
283    /// Convert offset to milliseconds
284    #[must_use]
285    pub fn to_milliseconds(&self, sample_rate: u32) -> f64 {
286        self.to_seconds(sample_rate) * 1000.0
287    }
288}
289
290#[cfg(test)]
291mod tests {
292    use super::*;
293
294    #[test]
295    fn test_point2d_distance() {
296        let p1 = Point2D::new(0.0, 0.0);
297        let p2 = Point2D::new(3.0, 4.0);
298        assert!((p1.distance(&p2) - 5.0).abs() < f64::EPSILON);
299        assert!((p1.distance_squared(&p2) - 25.0).abs() < f64::EPSILON);
300    }
301
302    #[test]
303    fn test_time_offset_conversion() {
304        let offset = TimeOffset::new(48000, 0.95, 0.85);
305        assert!((offset.to_seconds(48000) - 1.0).abs() < f64::EPSILON);
306        assert!((offset.to_milliseconds(48000) - 1000.0).abs() < f64::EPSILON);
307    }
308}