1use crate::error::{Result, VisionError};
7use scirs2_core::ndarray::Array2;
8use std::collections::VecDeque;
9
10#[derive(Debug, Clone)]
20pub struct DepthMap {
21 pub data: Array2<f64>,
23 pub scale: f64,
26 pub min_depth: f64,
28 pub max_depth: f64,
30}
31
32impl DepthMap {
33 pub fn new(data: Array2<f64>, scale: f64, min_depth: f64, max_depth: f64) -> Self {
35 Self {
36 data,
37 scale,
38 min_depth,
39 max_depth,
40 }
41 }
42
43 pub fn from_data(data: Array2<f64>, scale: f64) -> Self {
60 let mut min_depth = f64::INFINITY;
61 let mut max_depth = f64::NEG_INFINITY;
62 for &v in data.iter() {
63 if v > 0.0 && v.is_finite() {
64 if v < min_depth {
65 min_depth = v;
66 }
67 if v > max_depth {
68 max_depth = v;
69 }
70 }
71 }
72 if !min_depth.is_finite() {
73 min_depth = 0.0;
74 max_depth = 0.0;
75 }
76 Self {
77 data,
78 scale,
79 min_depth,
80 max_depth,
81 }
82 }
83
84 pub fn to_metric(&self) -> DepthMap {
87 DepthMap {
88 data: self.data.mapv(|v| v * self.scale),
89 scale: 1.0,
90 min_depth: self.min_depth * self.scale,
91 max_depth: self.max_depth * self.scale,
92 }
93 }
94
95 pub fn dim(&self) -> (usize, usize) {
97 self.data.dim()
98 }
99}
100
101pub fn scale_invariant_loss(pred: &Array2<f64>, gt: &Array2<f64>) -> Result<f64> {
137 if pred.dim() != gt.dim() {
138 return Err(VisionError::DimensionMismatch(
139 "pred and gt must have the same shape".to_string(),
140 ));
141 }
142
143 let mut sum_d = 0.0_f64;
144 let mut sum_d2 = 0.0_f64;
145 let mut n = 0usize;
146
147 for (&p, &g) in pred.iter().zip(gt.iter()) {
148 if p > 0.0 && g > 0.0 && p.is_finite() && g.is_finite() {
149 let d = p.ln() - g.ln();
150 sum_d += d;
151 sum_d2 += d * d;
152 n += 1;
153 }
154 }
155
156 if n == 0 {
157 return Err(VisionError::InvalidParameter(
158 "No valid pixels (pred > 0 and gt > 0) found".to_string(),
159 ));
160 }
161
162 let n_f = n as f64;
163 Ok(sum_d2 / n_f - (sum_d * sum_d) / (n_f * n_f))
164}
165
166pub fn absolute_relative_error(pred: &Array2<f64>, gt: &Array2<f64>) -> Result<f64> {
187 if pred.dim() != gt.dim() {
188 return Err(VisionError::DimensionMismatch(
189 "pred and gt must have the same shape".to_string(),
190 ));
191 }
192
193 let mut sum = 0.0_f64;
194 let mut n = 0usize;
195
196 for (&p, &g) in pred.iter().zip(gt.iter()) {
197 if g > 0.0 && g.is_finite() {
198 sum += (p - g).abs() / g;
199 n += 1;
200 }
201 }
202
203 if n == 0 {
204 return Err(VisionError::InvalidParameter(
205 "No valid pixels (gt > 0) found".to_string(),
206 ));
207 }
208
209 Ok(sum / n as f64)
210}
211
212pub fn threshold_accuracy(pred: &Array2<f64>, gt: &Array2<f64>, threshold: f64) -> Result<f64> {
242 if pred.dim() != gt.dim() {
243 return Err(VisionError::DimensionMismatch(
244 "pred and gt must have the same shape".to_string(),
245 ));
246 }
247 if threshold <= 1.0 {
248 return Err(VisionError::InvalidParameter(
249 "threshold must be greater than 1.0".to_string(),
250 ));
251 }
252
253 let mut correct = 0usize;
254 let mut n = 0usize;
255
256 for (&p, &g) in pred.iter().zip(gt.iter()) {
257 if p > 0.0 && g > 0.0 && p.is_finite() && g.is_finite() {
258 let ratio = (p / g).max(g / p);
259 if ratio < threshold {
260 correct += 1;
261 }
262 n += 1;
263 }
264 }
265
266 if n == 0 {
267 return Err(VisionError::InvalidParameter(
268 "No valid pixels (pred > 0 and gt > 0) found".to_string(),
269 ));
270 }
271
272 Ok(correct as f64 / n as f64)
273}
274
275#[derive(Debug, Clone)]
283pub struct CameraPose {
284 pub r: [[f64; 3]; 3],
286 pub t: [f64; 3],
288}
289
290impl CameraPose {
291 pub fn new(r: [[f64; 3]; 3], t: [f64; 3]) -> Self {
293 Self { r, t }
294 }
295
296 pub fn identity() -> Self {
298 Self {
299 r: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
300 t: [0.0, 0.0, 0.0],
301 }
302 }
303}
304
305#[derive(Debug, Clone, Copy)]
307pub struct KeyPoint2D {
308 pub x: f64,
310 pub y: f64,
312}
313
314impl KeyPoint2D {
315 pub fn new(x: f64, y: f64) -> Self {
317 Self { x, y }
318 }
319}
320
321#[derive(Debug, Clone, Copy)]
323pub struct KeyPointMatch {
324 pub idx_i: usize,
326 pub idx_j: usize,
328 pub cam_i: usize,
330 pub cam_j: usize,
332}
333
334impl KeyPointMatch {
335 pub fn new(idx_i: usize, idx_j: usize, cam_i: usize, cam_j: usize) -> Self {
337 Self {
338 idx_i,
339 idx_j,
340 cam_i,
341 cam_j,
342 }
343 }
344}
345
346pub fn depth_from_sfm(
394 camera_poses: &[CameraPose],
395 keypoints: &[Vec<KeyPoint2D>],
396 matches: &[KeyPointMatch],
397) -> Result<Vec<(f64, f64, f64)>> {
398 if camera_poses.is_empty() {
399 return Err(VisionError::InvalidParameter(
400 "camera_poses must not be empty".to_string(),
401 ));
402 }
403 if keypoints.is_empty() {
404 return Err(VisionError::InvalidParameter(
405 "keypoints must not be empty".to_string(),
406 ));
407 }
408
409 let mut points = Vec::with_capacity(matches.len());
410
411 for m in matches {
412 if m.cam_i >= camera_poses.len() || m.cam_j >= camera_poses.len() {
413 continue;
414 }
415 if m.cam_i >= keypoints.len() || m.cam_j >= keypoints.len() {
416 continue;
417 }
418 let kps_i = &keypoints[m.cam_i];
419 let kps_j = &keypoints[m.cam_j];
420 if m.idx_i >= kps_i.len() || m.idx_j >= kps_j.len() {
421 continue;
422 }
423
424 let pose_i = &camera_poses[m.cam_i];
425 let pose_j = &camera_poses[m.cam_j];
426 let kp_i = kps_i[m.idx_i];
427 let kp_j = kps_j[m.idx_j];
428
429 let pi = pose_to_projection(pose_i);
433 let pj = pose_to_projection(pose_j);
434
435 if let Some(pt) = triangulate_dlt(&pi, &pj, (kp_i.x, kp_i.y), (kp_j.x, kp_j.y)) {
436 points.push(pt);
437 }
438 }
439
440 Ok(points)
441}
442
443fn pose_to_projection(pose: &CameraPose) -> [[f64; 4]; 3] {
448 let r = pose.r;
450 let t_world = pose.t;
451 let tc = [
452 -(r[0][0] * t_world[0] + r[0][1] * t_world[1] + r[0][2] * t_world[2]),
453 -(r[1][0] * t_world[0] + r[1][1] * t_world[1] + r[1][2] * t_world[2]),
454 -(r[2][0] * t_world[0] + r[2][1] * t_world[1] + r[2][2] * t_world[2]),
455 ];
456 [
457 [r[0][0], r[0][1], r[0][2], tc[0]],
458 [r[1][0], r[1][1], r[1][2], tc[1]],
459 [r[2][0], r[2][1], r[2][2], tc[2]],
460 ]
461}
462
463fn triangulate_dlt(
469 p1: &[[f64; 4]; 3],
470 p2: &[[f64; 4]; 3],
471 (u1, v1): (f64, f64),
472 (u2, v2): (f64, f64),
473) -> Option<(f64, f64, f64)> {
474 let a: [[f64; 4]; 4] = [
480 [
481 u1 * p1[2][0] - p1[0][0],
482 u1 * p1[2][1] - p1[0][1],
483 u1 * p1[2][2] - p1[0][2],
484 u1 * p1[2][3] - p1[0][3],
485 ],
486 [
487 v1 * p1[2][0] - p1[1][0],
488 v1 * p1[2][1] - p1[1][1],
489 v1 * p1[2][2] - p1[1][2],
490 v1 * p1[2][3] - p1[1][3],
491 ],
492 [
493 u2 * p2[2][0] - p2[0][0],
494 u2 * p2[2][1] - p2[0][1],
495 u2 * p2[2][2] - p2[0][2],
496 u2 * p2[2][3] - p2[0][3],
497 ],
498 [
499 v2 * p2[2][0] - p2[1][0],
500 v2 * p2[2][1] - p2[1][1],
501 v2 * p2[2][2] - p2[1][2],
502 v2 * p2[2][3] - p2[1][3],
503 ],
504 ];
505
506 let x = solve_4x4_nullspace(&a)?;
510
511 if x[3].abs() < 1e-10 {
513 return None;
514 }
515 let w = x[3];
516 Some((x[0] / w, x[1] / w, x[2] / w))
517}
518
519fn solve_4x4_nullspace(a: &[[f64; 4]; 4]) -> Option<[f64; 4]> {
523 let mut ata = [[0.0_f64; 4]; 4];
525 #[allow(clippy::needless_range_loop)]
526 for i in 0..4 {
527 for j in 0..4 {
528 for k in 0..4 {
529 ata[i][j] += a[k][i] * a[k][j];
530 }
531 }
532 }
533
534 let mut v = [[0.0_f64; 4]; 4];
536 #[allow(clippy::needless_range_loop)]
537 for i in 0..4 {
538 v[i][i] = 1.0;
539 }
540 let mut b = ata;
541
542 for _ in 0..200 {
543 let mut max_val = 0.0_f64;
544 let mut p = 0;
545 let mut q = 1;
546 #[allow(clippy::needless_range_loop)]
547 for i in 0..4 {
548 for j in (i + 1)..4 {
549 if b[i][j].abs() > max_val {
550 max_val = b[i][j].abs();
551 p = i;
552 q = j;
553 }
554 }
555 }
556 if max_val < 1e-14 {
557 break;
558 }
559
560 let mpq = b[p][q];
561 if mpq.abs() < 1e-30 {
562 continue;
563 }
564 let theta = (b[q][q] - b[p][p]) / (2.0 * mpq);
565 let t = if theta >= 0.0 {
566 1.0 / (theta + (1.0 + theta * theta).sqrt())
567 } else {
568 1.0 / (theta - (1.0 + theta * theta).sqrt())
569 };
570 let cos = 1.0 / (1.0 + t * t).sqrt();
571 let sin = t * cos;
572 let tau = sin / (1.0 + cos);
573
574 b[p][p] -= t * mpq;
575 b[q][q] += t * mpq;
576 b[p][q] = 0.0;
577 b[q][p] = 0.0;
578
579 #[allow(clippy::needless_range_loop)]
580 for r in 0..4 {
581 if r != p && r != q {
582 let brp = b[r][p];
583 let brq = b[r][q];
584 b[r][p] = brp - sin * (brq + tau * brp);
585 b[p][r] = b[r][p];
586 b[r][q] = brq + sin * (brp - tau * brq);
587 b[q][r] = b[r][q];
588 }
589 }
590
591 #[allow(clippy::needless_range_loop)]
592 for r in 0..4 {
593 let vrp = v[r][p];
594 let vrq = v[r][q];
595 v[r][p] = vrp - sin * (vrq + tau * vrp);
596 v[r][q] = vrq + sin * (vrp - tau * vrq);
597 }
598 }
599
600 let eigenvalues = [b[0][0], b[1][1], b[2][2], b[3][3]];
602 let min_idx = eigenvalues
603 .iter()
604 .enumerate()
605 .min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
606 .map(|(i, _)| i)?;
607
608 Some([v[0][min_idx], v[1][min_idx], v[2][min_idx], v[3][min_idx]])
609}
610
611pub fn dense_depth_from_sparse(
654 sparse_depth: &Array2<f64>,
655 image: &Array2<f64>,
656) -> Result<Array2<f64>> {
657 let (h, w) = sparse_depth.dim();
658 if image.dim() != (h, w) {
659 return Err(VisionError::DimensionMismatch(
660 "sparse_depth and image must have the same shape".to_string(),
661 ));
662 }
663
664 let mut output = sparse_depth.clone();
665 let mut weight = Array2::zeros((h, w));
667
668 for y in 0..h {
670 for x in 0..w {
671 if sparse_depth[[y, x]] > 0.0 {
672 weight[[y, x]] = 1.0;
673 }
674 }
675 }
676
677 let mut edge = Array2::zeros((h, w));
680 for y in 1..h - 1 {
681 for x in 1..w - 1 {
682 let dx = (image[[y, x + 1]] - image[[y, x - 1]]) * 0.5;
683 let dy = (image[[y + 1, x]] - image[[y - 1, x]]) * 0.5;
684 edge[[y, x]] = (dx * dx + dy * dy).sqrt();
685 }
686 }
687 let max_edge = edge.iter().cloned().fold(0.0_f64, f64::max).max(1.0);
688
689 let mut queue: VecDeque<(usize, usize)> = VecDeque::new();
691 for y in 0..h {
692 for x in 0..w {
693 if sparse_depth[[y, x]] > 0.0 {
694 queue.push_back((y, x));
695 }
696 }
697 }
698
699 let neighbours: [(i64, i64); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)];
700
701 while let Some((cy, cx)) = queue.pop_front() {
702 let cur_depth = output[[cy, cx]];
703 let cur_w = weight[[cy, cx]];
704
705 for &(dy, dx) in &neighbours {
706 let ny = cy as i64 + dy;
707 let nx = cx as i64 + dx;
708 if ny < 0 || ny >= h as i64 || nx < 0 || nx >= w as i64 {
709 continue;
710 }
711 let ny = ny as usize;
712 let nx = nx as usize;
713
714 let edge_penalty = 1.0 + edge[[ny, nx]] / max_edge;
716 let prop_w = cur_w / edge_penalty;
717
718 if prop_w > weight[[ny, nx]] {
719 output[[ny, nx]] = cur_depth;
722 weight[[ny, nx]] = prop_w;
723 queue.push_back((ny, nx));
724 }
725 }
726 }
727
728 Ok(output)
729}
730
731#[cfg(test)]
736mod tests {
737 use super::*;
738 use scirs2_core::ndarray::Array2;
739
740 #[test]
741 fn test_depth_map_from_data() {
742 let data = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
743 .expect("from_shape_vec should succeed with correct element count");
744 let dm = DepthMap::from_data(data, 1.0);
745 assert!((dm.min_depth - 1.0).abs() < 1e-9);
746 assert!((dm.max_depth - 4.0).abs() < 1e-9);
747 }
748
749 #[test]
750 fn test_depth_map_to_metric() {
751 let data = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
752 .expect("from_shape_vec should succeed with correct element count");
753 let dm = DepthMap::new(data, 0.5, 0.5, 2.0);
754 let metric = dm.to_metric();
755 assert!((metric.data[[0, 0]] - 0.5).abs() < 1e-9);
756 assert!((metric.data[[1, 1]] - 2.0).abs() < 1e-9);
757 assert!((metric.scale - 1.0).abs() < 1e-9);
758 }
759
760 #[test]
761 fn test_scale_invariant_loss_perfect() {
762 let pred = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
763 .expect("from_shape_vec should succeed with correct element count");
764 let gt = pred.clone();
765 let loss = scale_invariant_loss(&pred, >)
766 .expect("scale_invariant_loss should succeed on identical arrays");
767 assert!(loss.abs() < 1e-10);
768 }
769
770 #[test]
771 fn test_scale_invariant_loss_scaled() {
772 let gt = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
774 .expect("from_shape_vec should succeed with correct element count");
775 let pred = gt.mapv(|v| v * 2.0);
776 let loss = scale_invariant_loss(&pred, >)
777 .expect("scale_invariant_loss should succeed with valid scaled inputs");
778 assert!(loss.abs() < 1e-10, "loss = {loss}");
779 }
780
781 #[test]
782 fn test_scale_invariant_loss_no_valid_pixels() {
783 let zeros = Array2::zeros((2, 2));
784 assert!(scale_invariant_loss(&zeros, &zeros).is_err());
785 }
786
787 #[test]
788 fn test_scale_invariant_loss_shape_mismatch() {
789 let a = Array2::from_elem((2, 2), 1.0_f64);
790 let b = Array2::from_elem((2, 3), 1.0_f64);
791 assert!(scale_invariant_loss(&a, &b).is_err());
792 }
793
794 #[test]
795 fn test_absolute_relative_error_perfect() {
796 let pred = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
797 .expect("from_shape_vec should succeed with correct element count");
798 let err = absolute_relative_error(&pred, &pred)
799 .expect("absolute_relative_error should succeed on identical arrays");
800 assert!(err.abs() < 1e-10);
801 }
802
803 #[test]
804 fn test_absolute_relative_error_value() {
805 let gt = Array2::from_shape_vec((1, 1), vec![4.0])
806 .expect("from_shape_vec should succeed with correct element count");
807 let pred = Array2::from_shape_vec((1, 1), vec![5.0])
808 .expect("from_shape_vec should succeed with correct element count");
809 let err = absolute_relative_error(&pred, >)
810 .expect("absolute_relative_error should succeed with valid inputs");
811 assert!((err - 0.25).abs() < 1e-10, "err={err}");
813 }
814
815 #[test]
816 fn test_threshold_accuracy_perfect() {
817 let pred = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
818 .expect("from_shape_vec should succeed with correct element count");
819 let acc = threshold_accuracy(&pred, &pred, 1.25)
820 .expect("threshold_accuracy should succeed on identical arrays");
821 assert!((acc - 1.0).abs() < 1e-10);
822 }
823
824 #[test]
825 fn test_threshold_accuracy_none_pass() {
826 let gt = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
827 .expect("from_shape_vec should succeed with correct element count");
828 let pred = gt.mapv(|v| v * 10.0); let acc = threshold_accuracy(&pred, >, 1.25)
830 .expect("threshold_accuracy should succeed with valid inputs");
831 assert!((acc).abs() < 1e-10, "acc={acc}");
832 }
833
834 #[test]
835 fn test_threshold_accuracy_bad_threshold() {
836 let img = Array2::from_elem((2, 2), 1.0_f64);
837 assert!(threshold_accuracy(&img, &img, 1.0).is_err());
838 assert!(threshold_accuracy(&img, &img, 0.5).is_err());
839 }
840
841 #[test]
842 fn test_depth_from_sfm_basic() {
843 let poses = vec![
845 CameraPose::identity(),
846 CameraPose::new(
847 [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
848 [1.0, 0.0, 0.0],
849 ),
850 ];
851 let kps = vec![
856 vec![KeyPoint2D::new(0.0, 0.0)],
857 vec![KeyPoint2D::new(-0.2, 0.0)],
858 ];
859 let matches = vec![KeyPointMatch::new(0, 0, 0, 1)];
860 let pts = depth_from_sfm(&poses, &kps, &matches)
861 .expect("depth_from_sfm should succeed with valid inputs");
862 assert_eq!(pts.len(), 1);
863 let (x, y, z) = pts[0];
864 assert!(x.abs() < 0.5, "x={x}");
866 assert!(y.abs() < 0.5, "y={y}");
867 assert!((z - 5.0).abs() < 1.0, "z={z}");
868 }
869
870 #[test]
871 fn test_depth_from_sfm_empty_poses() {
872 let kps: Vec<Vec<KeyPoint2D>> = vec![vec![]];
873 let matches: Vec<KeyPointMatch> = vec![];
874 assert!(depth_from_sfm(&[], &kps, &matches).is_err());
875 }
876
877 #[test]
878 fn test_dense_depth_from_sparse() {
879 let mut sparse = Array2::zeros((6, 6));
880 sparse[[2, 2]] = 5.0;
881 let image = Array2::from_elem((6, 6), 128.0_f64);
882 let dense = dense_depth_from_sparse(&sparse, &image)
883 .expect("dense_depth_from_sparse should succeed with valid inputs");
884 assert_eq!(dense.dim(), (6, 6));
885 assert!((dense[[2, 2]] - 5.0).abs() < 1e-9);
886 assert!(dense[[2, 3]] > 0.0);
888 assert!(dense[[3, 2]] > 0.0);
889 }
890
891 #[test]
892 fn test_dense_depth_from_sparse_shape_mismatch() {
893 let sparse = Array2::zeros((4, 4));
894 let image = Array2::zeros((4, 5));
895 assert!(dense_depth_from_sparse(&sparse, &image).is_err());
896 }
897
898 #[test]
899 fn test_dense_depth_no_seeds() {
900 let sparse = Array2::zeros((4, 4));
901 let image = Array2::from_elem((4, 4), 100.0_f64);
902 let dense = dense_depth_from_sparse(&sparse, &image)
903 .expect("dense_depth_from_sparse should succeed with all-zero sparse depth");
904 assert!(dense.iter().all(|&v| v == 0.0));
906 }
907}