#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct DisparityMap {
data: Vec<f32>,
width: usize,
height: usize,
min_valid: f32,
}
impl DisparityMap {
pub fn new(data: Vec<f32>, width: usize, height: usize) -> Self {
assert_eq!(
data.len(),
width * height,
"data length must equal width × height"
);
Self {
data,
width,
height,
min_valid: 1.0,
}
}
pub fn constant(width: usize, height: usize, value: f32) -> Self {
Self {
data: vec![value; width * height],
width,
height,
min_valid: 1.0,
}
}
pub fn at(&self, x: usize, y: usize) -> Option<f32> {
if x >= self.width || y >= self.height {
return None;
}
Some(self.data[y * self.width + x])
}
#[must_use]
pub fn set_min_valid(mut self, min: f32) -> Self {
self.min_valid = min;
self
}
pub fn is_valid_at(&self, x: usize, y: usize) -> bool {
self.at(x, y).is_some_and(|d| d >= self.min_valid)
}
#[allow(clippy::cast_precision_loss)]
pub fn avg_disparity(&self) -> f32 {
let valid: Vec<f32> = self
.data
.iter()
.copied()
.filter(|&d| d >= self.min_valid)
.collect();
if valid.is_empty() {
return 0.0;
}
valid.iter().sum::<f32>() / valid.len() as f32
}
pub fn max_disparity(&self) -> f32 {
self.data
.iter()
.copied()
.filter(|&d| d >= self.min_valid)
.fold(f32::NEG_INFINITY, f32::max)
}
pub fn valid_pixel_count(&self) -> usize {
self.data.iter().filter(|&&d| d >= self.min_valid).count()
}
pub fn dimensions(&self) -> (usize, usize) {
(self.width, self.height)
}
pub fn pixel_count(&self) -> usize {
self.width * self.height
}
}
#[derive(Debug, Clone, Copy)]
pub struct DepthEstimate {
pub depth_m: f32,
pub confidence: f32,
pub disparity: f32,
}
impl DepthEstimate {
pub fn new(depth_m: f32, confidence: f32, disparity: f32) -> Self {
Self {
depth_m,
confidence,
disparity,
}
}
pub fn is_reliable(&self) -> bool {
self.confidence >= 0.5
}
pub fn is_reliable_at(&self, threshold: f32) -> bool {
self.confidence >= threshold
}
pub fn depth_cm(&self) -> f32 {
self.depth_m * 100.0
}
}
#[derive(Debug, Clone)]
pub struct StereoParams {
pub baseline_m: f32,
pub focal_px: f32,
}
impl StereoParams {
pub fn new(baseline_m: f32, focal_px: f32) -> Self {
Self {
baseline_m,
focal_px,
}
}
pub fn disparity_to_depth(&self, disparity: f32) -> f32 {
if disparity < 1e-6 {
return f32::INFINITY;
}
(self.baseline_m * self.focal_px) / disparity
}
}
#[derive(Debug)]
pub struct DepthEstimator {
params: StereoParams,
max_depth_m: f32,
}
impl DepthEstimator {
pub fn new(params: StereoParams) -> Self {
Self {
params,
max_depth_m: 50.0,
}
}
#[must_use]
pub fn with_max_depth(mut self, max_m: f32) -> Self {
self.max_depth_m = max_m;
self
}
pub fn estimate_from_stereo(&self, map: &DisparityMap) -> Vec<DepthEstimate> {
map.data
.iter()
.map(|&disp| self.estimate_single(disp, map.min_valid))
.collect()
}
pub fn estimate_at(&self, map: &DisparityMap, x: usize, y: usize) -> Option<DepthEstimate> {
let disp = map.at(x, y)?;
Some(self.estimate_single(disp, map.min_valid))
}
#[allow(clippy::cast_precision_loss)]
pub fn average_depth(&self, map: &DisparityMap) -> f32 {
let estimates = self.estimate_from_stereo(map);
let reliable: Vec<f32> = estimates
.iter()
.filter(|e| e.is_reliable() && e.depth_m.is_finite())
.map(|e| e.depth_m)
.collect();
if reliable.is_empty() {
return 0.0;
}
reliable.iter().sum::<f32>() / reliable.len() as f32
}
fn estimate_single(&self, disp: f32, min_valid: f32) -> DepthEstimate {
if disp < min_valid {
return DepthEstimate::new(f32::INFINITY, 0.0, disp);
}
let depth = self.params.disparity_to_depth(disp);
let confidence = if depth.is_infinite() || depth > self.max_depth_m {
0.1
} else {
1.0 - (depth / self.max_depth_m).min(1.0)
};
DepthEstimate::new(depth, confidence, disp)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_map_4x4(value: f32) -> DisparityMap {
DisparityMap::constant(4, 4, value)
}
#[test]
fn test_disparity_map_at_valid() {
let m = make_map_4x4(5.0);
assert_eq!(m.at(0, 0), Some(5.0));
assert_eq!(m.at(3, 3), Some(5.0));
}
#[test]
fn test_disparity_map_at_out_of_bounds() {
let m = make_map_4x4(5.0);
assert!(m.at(4, 0).is_none());
assert!(m.at(0, 4).is_none());
}
#[test]
fn test_disparity_map_avg() {
let m = make_map_4x4(10.0);
assert!((m.avg_disparity() - 10.0).abs() < 1e-5);
}
#[test]
fn test_disparity_map_avg_with_invalids() {
let mut data = vec![0.0f32; 4]; data[3] = 8.0;
let m = DisparityMap::new(data, 4, 1);
assert!((m.avg_disparity() - 8.0).abs() < 1e-5);
}
#[test]
fn test_disparity_map_dimensions() {
let m = DisparityMap::constant(8, 6, 1.0);
assert_eq!(m.dimensions(), (8, 6));
assert_eq!(m.pixel_count(), 48);
}
#[test]
fn test_disparity_map_valid_pixel_count() {
let data = vec![0.0, 2.0, 3.0, 0.0, 5.0];
let m = DisparityMap::new(data, 5, 1);
assert_eq!(m.valid_pixel_count(), 3);
}
#[test]
fn test_disparity_map_max_disparity() {
let data = vec![2.0f32, 5.0, 3.0, 10.0, 1.0];
let m = DisparityMap::new(data, 5, 1);
assert!((m.max_disparity() - 10.0).abs() < 1e-5);
}
#[test]
fn test_depth_estimate_is_reliable() {
let reliable = DepthEstimate::new(5.0, 0.8, 10.0);
let unreliable = DepthEstimate::new(5.0, 0.3, 10.0);
assert!(reliable.is_reliable());
assert!(!unreliable.is_reliable());
}
#[test]
fn test_depth_estimate_is_reliable_custom() {
let e = DepthEstimate::new(5.0, 0.7, 10.0);
assert!(e.is_reliable_at(0.5));
assert!(!e.is_reliable_at(0.9));
}
#[test]
fn test_depth_estimate_depth_cm() {
let e = DepthEstimate::new(2.5, 0.9, 10.0);
assert!((e.depth_cm() - 250.0).abs() < 1e-4);
}
#[test]
fn test_stereo_params_disparity_to_depth() {
let p = StereoParams::new(0.1, 600.0); let depth = p.disparity_to_depth(10.0);
assert!((depth - 6.0).abs() < 1e-4);
}
#[test]
fn test_stereo_params_zero_disparity_gives_infinity() {
let p = StereoParams::new(0.1, 600.0);
assert!(p.disparity_to_depth(0.0).is_infinite());
}
#[test]
fn test_estimator_estimate_from_stereo_length() {
let p = StereoParams::new(0.1, 600.0);
let est = DepthEstimator::new(p);
let map = DisparityMap::constant(4, 4, 10.0);
let estimates = est.estimate_from_stereo(&map);
assert_eq!(estimates.len(), 16);
}
#[test]
fn test_estimator_all_reliable_close_range() {
let p = StereoParams::new(0.1, 600.0);
let est = DepthEstimator::new(p);
let map = DisparityMap::constant(2, 2, 30.0);
let estimates = est.estimate_from_stereo(&map);
assert!(estimates.iter().all(|e| e.is_reliable()));
}
#[test]
fn test_estimator_average_depth() {
let p = StereoParams::new(0.1, 600.0);
let est = DepthEstimator::new(p);
let map = DisparityMap::constant(3, 3, 30.0);
let avg = est.average_depth(&map);
assert!((avg - 2.0).abs() < 0.01);
}
#[test]
fn test_estimator_invalid_disparity_low_confidence() {
let p = StereoParams::new(0.1, 600.0);
let est = DepthEstimator::new(p);
let map = DisparityMap::constant(1, 1, 0.0); let estimates = est.estimate_from_stereo(&map);
assert_eq!(estimates[0].confidence, 0.0);
}
}