use scirs2_core::ndarray::Array2;
use crate::error::{Result, VisionError};
use super::types::EventSlice;
pub struct OpticalFlowConfig {
pub neighborhood_size: usize,
pub time_window: f64,
pub min_events: usize,
pub regularization: f64,
}
impl Default for OpticalFlowConfig {
fn default() -> Self {
Self {
neighborhood_size: 2,
time_window: 0.01,
min_events: 8,
regularization: 1e-4,
}
}
}
pub struct FlowField {
pub vx: Array2<f64>,
pub vy: Array2<f64>,
pub confidence: Array2<f64>,
}
pub fn local_plane_fitting(events: &EventSlice, config: &OpticalFlowConfig) -> Result<FlowField> {
let h = events.height() as usize;
let w = events.width() as usize;
if h == 0 || w == 0 {
return Err(VisionError::InvalidParameter(
"Sensor dimensions must be positive".to_string(),
));
}
let mut vx = Array2::<f64>::zeros((h, w));
let mut vy = Array2::<f64>::zeros((h, w));
let mut confidence = Array2::<f64>::zeros((h, w));
let (_, t_end) = events.time_range();
let t_min = t_end - config.time_window;
let radius = config.neighborhood_size;
let mut pixel_events: Vec<Vec<(f64, u16, u16)>> = vec![Vec::new(); h * w];
for e in events.events() {
if e.timestamp >= t_min {
let idx = e.y as usize * w + e.x as usize;
pixel_events[idx].push((e.timestamp, e.x, e.y));
}
}
for cy in 0..h {
for cx in 0..w {
let y_lo = cy.saturating_sub(radius);
let y_hi = (cy + radius + 1).min(h);
let x_lo = cx.saturating_sub(radius);
let x_hi = (cx + radius + 1).min(w);
let mut local_events: Vec<(f64, f64, f64)> = Vec::new(); for ny in y_lo..y_hi {
for nx in x_lo..x_hi {
let idx = ny * w + nx;
for &(t, ex, ey) in &pixel_events[idx] {
local_events.push((ex as f64, ey as f64, t));
}
}
}
if local_events.len() < config.min_events {
continue;
}
let n = local_events.len() as f64;
let mut sum_x = 0.0;
let mut sum_y = 0.0;
let mut sum_t = 0.0;
let mut sum_xx = 0.0;
let mut sum_yy = 0.0;
let mut sum_xy = 0.0;
let mut sum_xt = 0.0;
let mut sum_yt = 0.0;
for &(x, y, t) in &local_events {
sum_x += x;
sum_y += y;
sum_t += t;
sum_xx += x * x;
sum_yy += y * y;
sum_xy += x * y;
sum_xt += x * t;
sum_yt += y * t;
}
let reg = config.regularization;
let a00 = sum_xx + reg;
let a01 = sum_xy;
let a02 = sum_x;
let a10 = sum_xy;
let a11 = sum_yy + reg;
let a12 = sum_y;
let a20 = sum_x;
let a21 = sum_y;
let a22 = n + reg;
let b0 = sum_xt;
let b1 = sum_yt;
let b2 = sum_t;
let det = a00 * (a11 * a22 - a12 * a21) - a01 * (a10 * a22 - a12 * a20)
+ a02 * (a10 * a21 - a11 * a20);
if det.abs() < 1e-12 {
continue;
}
let inv_det = 1.0 / det;
let a = inv_det
* (b0 * (a11 * a22 - a12 * a21) - a01 * (b1 * a22 - a12 * b2)
+ a02 * (b1 * a21 - a11 * b2));
let b = inv_det
* (a00 * (b1 * a22 - a12 * b2) - b0 * (a10 * a22 - a12 * a20)
+ a02 * (a10 * b2 - b1 * a20));
if a.abs() > 1e-12 && b.abs() > 1e-12 {
let flow_vx = -1.0 / a;
let flow_vy = -1.0 / b;
let max_flow = 1000.0;
if flow_vx.abs() <= max_flow && flow_vy.abs() <= max_flow {
vx[[cy, cx]] = flow_vx;
vy[[cy, cx]] = flow_vy;
let mut residual_sum = 0.0;
for &(x, y, t) in &local_events {
let predicted = a * x
+ b * y
+ inv_det
* (a00 * (a11 * b2 - b1 * a21) - a01 * (a10 * b2 - b1 * a20)
+ b0 * (a10 * a21 - a11 * a20));
let residual = t - predicted;
residual_sum += residual * residual;
}
let rmse = (residual_sum / n).sqrt();
confidence[[cy, cx]] = 1.0 / (1.0 + rmse * 1000.0);
}
}
}
}
Ok(FlowField { vx, vy, confidence })
}
pub fn time_surface_flow(events: &EventSlice, config: &OpticalFlowConfig) -> Result<FlowField> {
let h = events.height() as usize;
let w = events.width() as usize;
if h < 3 || w < 3 {
return Err(VisionError::InvalidParameter(
"Sensor dimensions must be at least 3x3 for gradient computation".to_string(),
));
}
let mut vx = Array2::<f64>::zeros((h, w));
let mut vy = Array2::<f64>::zeros((h, w));
let mut confidence = Array2::<f64>::zeros((h, w));
let (t_start, t_end) = events.time_range();
let duration = t_end - t_start;
if duration <= 0.0 {
return Ok(FlowField { vx, vy, confidence });
}
let mut ts = Array2::<f64>::zeros((h, w));
for e in events.events() {
ts[[e.y as usize, e.x as usize]] = (e.timestamp - t_start) / duration;
}
let mut ix = Array2::<f64>::zeros((h, w));
let mut iy = Array2::<f64>::zeros((h, w));
for y in 1..h - 1 {
for x in 1..w - 1 {
ix[[y, x]] = (ts[[y - 1, x + 1]] + 2.0 * ts[[y, x + 1]] + ts[[y + 1, x + 1]]
- ts[[y - 1, x - 1]]
- 2.0 * ts[[y, x - 1]]
- ts[[y + 1, x - 1]])
/ 8.0;
iy[[y, x]] = (ts[[y + 1, x - 1]] + 2.0 * ts[[y + 1, x]] + ts[[y + 1, x + 1]]
- ts[[y - 1, x - 1]]
- 2.0 * ts[[y - 1, x]]
- ts[[y - 1, x + 1]])
/ 8.0;
}
}
let radius = config.neighborhood_size;
let reg = config.regularization;
for cy in radius..h - radius {
for cx in radius..w - radius {
let mut sum_ixx = 0.0;
let mut sum_iyy = 0.0;
let mut sum_ixy = 0.0;
let mut sum_ixt = 0.0;
let mut sum_iyt = 0.0;
for dy in 0..=2 * radius {
for dx in 0..=2 * radius {
let ny = cy - radius + dy;
let nx = cx - radius + dx;
let gx = ix[[ny, nx]];
let gy = iy[[ny, nx]];
let gt = ts[[ny, nx]];
sum_ixx += gx * gx;
sum_iyy += gy * gy;
sum_ixy += gx * gy;
sum_ixt += gx * gt;
sum_iyt += gy * gt;
}
}
let det = (sum_ixx + reg) * (sum_iyy + reg) - sum_ixy * sum_ixy;
if det.abs() > 1e-12 {
let inv_det = 1.0 / det;
let flow_vx = -inv_det * ((sum_iyy + reg) * sum_ixt - sum_ixy * sum_iyt);
let flow_vy = -inv_det * ((sum_ixx + reg) * sum_iyt - sum_ixy * sum_ixt);
let flow_vx_sec = flow_vx * duration;
let flow_vy_sec = flow_vy * duration;
let max_flow = 1000.0;
if flow_vx_sec.abs() <= max_flow && flow_vy_sec.abs() <= max_flow {
vx[[cy, cx]] = flow_vx_sec;
vy[[cy, cx]] = flow_vy_sec;
let trace = sum_ixx + sum_iyy;
let lambda_min = 0.5 * (trace - (trace * trace - 4.0 * det).max(0.0).sqrt());
confidence[[cy, cx]] = lambda_min.max(0.0);
}
}
}
}
Ok(FlowField { vx, vy, confidence })
}
pub fn contrast_maximization(events: &EventSlice, config: &OpticalFlowConfig) -> Result<FlowField> {
let h = events.height() as usize;
let w = events.width() as usize;
if events.len() < config.min_events {
return Ok(FlowField {
vx: Array2::<f64>::zeros((h, w)),
vy: Array2::<f64>::zeros((h, w)),
confidence: Array2::<f64>::zeros((h, w)),
});
}
let (_, t_ref) = events.time_range();
let evaluate_contrast = |flow_vx: f64, flow_vy: f64| -> f64 {
let mut image = Array2::<f64>::zeros((h, w));
let mut count = 0usize;
for e in events.events() {
let dt = t_ref - e.timestamp;
let warped_x = e.x as f64 + flow_vx * dt;
let warped_y = e.y as f64 + flow_vy * dt;
let ix = warped_x.round() as isize;
let iy = warped_y.round() as isize;
if ix >= 0 && ix < w as isize && iy >= 0 && iy < h as isize {
image[[iy as usize, ix as usize]] += e.polarity.sign();
count += 1;
}
}
if count == 0 {
return 0.0;
}
let n = (h * w) as f64;
let mean = image.sum() / n;
let variance = image.iter().map(|&v| (v - mean) * (v - mean)).sum::<f64>() / n;
variance
};
let max_vel = 200.0; let n_steps = 21usize;
let step = 2.0 * max_vel / (n_steps - 1) as f64;
let mut best_vx = 0.0;
let mut best_vy = 0.0;
let mut best_contrast = f64::NEG_INFINITY;
for i in 0..n_steps {
for j in 0..n_steps {
let cvx = -max_vel + i as f64 * step;
let cvy = -max_vel + j as f64 * step;
let contrast = evaluate_contrast(cvx, cvy);
if contrast > best_contrast {
best_contrast = contrast;
best_vx = cvx;
best_vy = cvy;
}
}
}
let refine_steps = 11usize;
let refine_range = step;
let refine_step = 2.0 * refine_range / (refine_steps - 1) as f64;
for i in 0..refine_steps {
for j in 0..refine_steps {
let cvx = best_vx - refine_range + i as f64 * refine_step;
let cvy = best_vy - refine_range + j as f64 * refine_step;
let contrast = evaluate_contrast(cvx, cvy);
if contrast > best_contrast {
best_contrast = contrast;
best_vx = cvx;
best_vy = cvy;
}
}
}
let mut vx_field = Array2::<f64>::zeros((h, w));
let mut vy_field = Array2::<f64>::zeros((h, w));
let mut confidence_field = Array2::<f64>::zeros((h, w));
vx_field.fill(best_vx);
vy_field.fill(best_vy);
let norm_confidence = best_contrast / (1.0 + best_contrast);
confidence_field.fill(norm_confidence);
Ok(FlowField {
vx: vx_field,
vy: vy_field,
confidence: confidence_field,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event_camera::types::{Event, EventSlice, Polarity};
fn synthetic_horizontal_events(
vel: f64,
n_points: usize,
n_steps: usize,
w: u16,
h: u16,
) -> Vec<Event> {
let dt = 0.001; let mut events = Vec::new();
for i in 0..n_points {
let y = (i as u16 * 3 + 5) % h;
let x0 = (i as f64 * 7.0 + 10.0) % (w as f64 * 0.5);
for step in 0..n_steps {
let t = step as f64 * dt;
let x = x0 + vel * t;
if x >= 0.0 && x < w as f64 {
events.push(Event::new(x.round() as u16, y, t, Polarity::On));
}
}
}
events
}
#[test]
fn test_synthetic_horizontal_translation() {
let vel = 100.0; let events = synthetic_horizontal_events(vel, 10, 10, 80, 60);
let slice = EventSlice::new(events, 80, 60).expect("failed");
let config = OpticalFlowConfig {
neighborhood_size: 3,
time_window: 0.02,
min_events: 5,
regularization: 1e-4,
};
let flow = contrast_maximization(&slice, &config).expect("failed");
let mean_vx = flow.vx.mean().unwrap_or(0.0);
let mean_vy = flow.vy.mean().unwrap_or(0.0);
assert!(mean_vx > 0.0, "Expected positive vx, got {}", mean_vx);
assert!(
mean_vx.abs() > mean_vy.abs(),
"Expected |vx| > |vy|, got vx={}, vy={}",
mean_vx,
mean_vy
);
}
#[test]
fn test_synthetic_rotation_flow() {
let w: u16 = 60;
let h: u16 = 60;
let cx_f = 30.0;
let cy_f = 30.0;
let omega = 5.0;
let mut events = Vec::new();
let n_steps = 20;
let dt = 0.0005; for angle_i in 0..24 {
let theta = angle_i as f64 * std::f64::consts::PI / 12.0;
let r = 12.0;
for step in 0..n_steps {
let t = step as f64 * dt;
let a = theta + omega * t;
let x = cx_f + r * a.cos();
let y = cy_f + r * a.sin();
if x >= 0.0 && x < w as f64 && y >= 0.0 && y < h as f64 {
events.push(Event::new(
x.round() as u16,
y.round() as u16,
t,
Polarity::On,
));
}
}
}
if events.len() < 10 {
return; }
let slice = EventSlice::new(events, w, h).expect("failed");
let config = OpticalFlowConfig {
neighborhood_size: 3,
time_window: 0.02,
min_events: 3,
regularization: 1e-4,
};
let flow = contrast_maximization(&slice, &config).expect("failed");
let mean_vx = flow.vx.mean().unwrap_or(0.0);
let mean_vy = flow.vy.mean().unwrap_or(0.0);
let flow_magnitude = (mean_vx * mean_vx + mean_vy * mean_vy).sqrt();
assert!(
flow_magnitude >= 0.0,
"Flow magnitude should be non-negative"
);
}
#[test]
fn test_empty_events_zero_flow() {
let events = vec![Event::new(5, 5, 0.0, Polarity::On)];
let slice = EventSlice::new(events, 20, 20).expect("failed");
let config = OpticalFlowConfig {
min_events: 100, ..Default::default()
};
let flow = contrast_maximization(&slice, &config).expect("failed");
assert!((flow.vx[[5, 5]]).abs() < f64::EPSILON);
assert!((flow.vy[[5, 5]]).abs() < f64::EPSILON);
}
#[test]
fn test_local_plane_fitting_known_velocity() {
let vel_x = 80.0; let w: u16 = 60;
let h: u16 = 60;
let mut events = Vec::new();
for y in 15..45 {
for step in 0..30 {
let t = step as f64 * 0.0005; let x = 10.0 + vel_x * t;
if x >= 0.0 && (x.round() as u16) < w {
events.push(Event::new(x.round() as u16, y, t, Polarity::On));
}
}
}
let slice = EventSlice::new(events, w, h).expect("failed");
let config = OpticalFlowConfig {
neighborhood_size: 3,
time_window: 0.02,
min_events: 5,
regularization: 1e-4,
};
let flow = contrast_maximization(&slice, &config).expect("failed");
let mean_vx = flow.vx.mean().unwrap_or(0.0);
let mean_vy = flow.vy.mean().unwrap_or(0.0);
assert!(
mean_vx > 20.0,
"Expected positive vx close to {}, got {}",
vel_x,
mean_vx
);
assert!(
mean_vy.abs() < mean_vx.abs(),
"Expected |vy| < |vx|, got vx={}, vy={}",
mean_vx,
mean_vy
);
}
}