1#![deny(unsafe_code, missing_docs)]
2
3use nalgebra as na;
4
5use na::{
6 allocator::Allocator, base::storage::Owned, DefaultAllocator, Dim, Dyn, Matrix3, OMatrix,
7 RealField, SMatrix, Vector3, U1, U3,
8};
9
10use itertools::izip;
11
12use crate::{CoordinateSystem, Error, Points, Ray};
13
14macro_rules! sum_as_f64 {
16 ($iter:expr,$R:ty) => {{
17 $iter
18 .map(|x| na::try_convert::<$R, f64>(x.clone()).unwrap())
19 .sum()
20 }};
21}
22macro_rules! refsum_as_f64 {
24 ($iter:expr,$R:ty) => {{
25 $iter.map(|x| na::try_convert::<$R, f64>(x).unwrap()).sum()
26 }};
27}
28
29macro_rules! despecialize {
31 ($a:expr, $R:ty, $rows:expr, $cols:expr) => {{
32 SMatrix::<$R, $rows, $cols>::from_iterator(
33 $a.as_slice().into_iter().map(|x| na::convert(*x)),
34 )
35 }};
36}
37
38pub type SinglePoint<Coords, R> = Points<Coords, R, U1, Owned<R, U1, U3>>;
40
41#[allow(non_snake_case)]
43pub fn best_intersection_of_rays<Coords, R>(
44 rays: &[Ray<Coords, R>],
45) -> Result<SinglePoint<Coords, R>, Error>
46where
47 Coords: CoordinateSystem,
48 R: RealField,
49 DefaultAllocator: Allocator<Dyn, U3>,
50 DefaultAllocator: Allocator<U1, Dyn>,
51 DefaultAllocator: Allocator<U1, U3>,
52{
53 if rays.len() < 2 {
54 return Err(Error::MinimumTwoRaysNeeded);
55 }
56
57 let npts = Dyn(rays.len());
58 let u1 = U1::from_usize(1);
59 let u3 = U3::from_usize(3);
60
61 let mut line_dirs = OMatrix::<R, Dyn, U3>::zeros_generic(npts, u3);
62 let mut line_points = OMatrix::<R, Dyn, U3>::zeros_generic(npts, u3);
63
64 for i in 0..rays.len() {
65 let ray_wc = rays.get(i).unwrap();
66 let d = &ray_wc.direction;
67
68 let dir = nalgebra::base::Unit::new_normalize(Vector3::new(
70 d.data.0[0][0].clone(),
71 d.data.0[1][0].clone(),
72 d.data.0[2][0].clone(),
73 ));
74
75 line_dirs[(i, 0)] = dir[0].clone();
76 line_dirs[(i, 1)] = dir[1].clone();
77 line_dirs[(i, 2)] = dir[2].clone();
78
79 line_points[(i, 0)] = ray_wc.center.data.0[0][0].clone();
80 line_points[(i, 1)] = ray_wc.center.data.0[1][0].clone();
81 line_points[(i, 2)] = ray_wc.center.data.0[2][0].clone();
82 }
83
84 let mut xxm1 = OMatrix::<R, U1, Dyn>::zeros_generic(u1, npts);
85 let mut yym1 = OMatrix::<R, U1, Dyn>::zeros_generic(u1, npts);
86 let mut zzm1 = OMatrix::<R, U1, Dyn>::zeros_generic(u1, npts);
87 let mut xy = OMatrix::<R, U1, Dyn>::zeros_generic(u1, npts);
88 let mut xz = OMatrix::<R, U1, Dyn>::zeros_generic(u1, npts);
89 let mut yz = OMatrix::<R, U1, Dyn>::zeros_generic(u1, npts);
90
91 let nx = line_dirs.column(0);
94 let ny = line_dirs.column(1);
95 let nz = line_dirs.column(2);
96
97 let minusone: R = na::convert(-1.0);
98
99 for (x, xxm1) in nx.iter().zip(xxm1.iter_mut()) {
100 *xxm1 = x.clone().powi(2).add(minusone.clone());
101 }
102
103 for (y, yym1) in ny.iter().zip(yym1.iter_mut()) {
104 *yym1 = y.clone().powi(2).add(minusone.clone());
105 }
106
107 for (z, zzm1) in nz.iter().zip(zzm1.iter_mut()) {
108 *zzm1 = z.clone().powi(2).add(minusone.clone());
109 }
110
111 for (x, y, xy) in izip!(nx.iter(), ny.iter(), xy.iter_mut()) {
112 *xy = x.clone() * y.clone();
113 }
114
115 for (x, z, xz) in izip!(nx.iter(), nz.iter(), xz.iter_mut()) {
116 *xz = x.clone() * z.clone();
117 }
118
119 for (y, z, yz) in izip!(ny.iter(), nz.iter(), yz.iter_mut()) {
120 *yz = y.clone() * z.clone();
121 }
122
123 let SXX: f64 = sum_as_f64!(xxm1.iter(), R);
124 let SYY: f64 = sum_as_f64!(yym1.iter(), R);
125 let SZZ: f64 = sum_as_f64!(zzm1.iter(), R);
126 let SXY: f64 = sum_as_f64!(xy.iter(), R);
127 let SXZ: f64 = sum_as_f64!(xz.iter(), R);
128 let SYZ: f64 = sum_as_f64!(yz.iter(), R);
129
130 let S = Matrix3::new(SXX, SXY, SXZ, SXY, SYY, SYZ, SXZ, SYZ, SZZ);
131
132 let px = line_points.column(0);
133 let py = line_points.column(1);
134 let pz = line_points.column(2);
135
136 let xt1 = px
137 .iter()
138 .zip(xxm1.iter())
139 .map(|(a, b)| a.clone() * b.clone());
140 let xt2 = py.iter().zip(xy.iter()).map(|(a, b)| a.clone() * b.clone());
141 let xt3 = pz.iter().zip(xz.iter()).map(|(a, b)| a.clone() * b.clone());
142 let CX: f64 = refsum_as_f64!(izip!(xt1, xt2, xt3).map(|(t1, t2, t3)| t1 + t2 + t3), R);
143 let CX: R = na::convert(CX);
144
145 let yt1 = px.iter().zip(xy.iter()).map(|(a, b)| a.clone() * b.clone());
146 let yt2 = py
147 .iter()
148 .zip(yym1.iter())
149 .map(|(a, b)| a.clone() * b.clone());
150 let yt3 = pz.iter().zip(yz.iter()).map(|(a, b)| a.clone() * b.clone());
151 let CY: f64 = refsum_as_f64!(izip!(yt1, yt2, yt3).map(|(t1, t2, t3)| t1 + t2 + t3), R);
152 let CY: R = na::convert(CY);
153
154 let zt1 = px.iter().zip(xz.iter()).map(|(a, b)| a.clone() * b.clone());
155 let zt2 = py.iter().zip(yz.iter()).map(|(a, b)| a.clone() * b.clone());
156 let zt3 = pz
157 .iter()
158 .zip(zzm1.iter())
159 .map(|(a, b)| a.clone() * b.clone());
160 let CZ: f64 = refsum_as_f64!(izip!(zt1, zt2, zt3).map(|(t1, t2, t3)| t1 + t2 + t3), R);
161 let CZ: R = na::convert(CZ);
162
163 let C = Vector3::new(CX, CY, CZ);
164
165 let s_f64_pinv = my_pinv(&S)?;
166 let s_pinv = despecialize!(s_f64_pinv, R, 3, 3);
167 let r: Vector3<R> = s_pinv * C;
168
169 let mut result = crate::Points::new(nalgebra::OMatrix::<R, U1, U3>::zeros_generic(u1, u3));
170 for j in 0..3 {
171 result.data[(0, j)] = r[j].clone();
172 }
173 Ok(result)
174}
175
176fn my_pinv<R: RealField>(m: &SMatrix<R, 3, 3>) -> Result<SMatrix<R, 3, 3>, Error> {
177 Ok(
178 na::linalg::SVD::try_new(m.clone(), true, true, na::convert(1e-7), 100)
179 .ok_or(Error::SvdFailed)?
180 .pseudo_inverse(na::convert(1.0e-7))
181 .unwrap(),
182 )
183 }
186
187#[cfg(test)]
188mod tests {
189 use nalgebra::Vector3;
190
191 use crate::{Camera, ExtrinsicParameters, IntrinsicParametersPerspective, PerspectiveParams};
192
193 use super::*;
194
195 #[test]
196 fn roundtrip() {
197 let i1: IntrinsicParametersPerspective<_> = PerspectiveParams {
198 fx: 100.0,
199 fy: 102.0,
200 skew: 0.1,
201 cx: 321.0,
202 cy: 239.9,
203 }
204 .into();
205 let e1 = ExtrinsicParameters::from_view(
206 &Vector3::new(1.2, 3.4, 5.6), &Vector3::new(2.2, 3.4, 5.6), &nalgebra::Unit::new_normalize(Vector3::new(0.0, 0.0, 1.0)), );
210 let cam1 = Camera::new(i1, e1);
211
212 let i2: IntrinsicParametersPerspective<_> = PerspectiveParams {
213 fx: 200.0,
214 fy: 202.0,
215 skew: 0.01,
216 cx: 321.0,
217 cy: 239.9,
218 }
219 .into();
220 let e2 = ExtrinsicParameters::from_view(
221 &Vector3::new(3.4, 1.2, 5.6), &Vector3::new(2.2, 3.4, 5.6), &nalgebra::Unit::new_normalize(Vector3::new(0.0, 0.0, 1.0)), );
225 let cam2 = Camera::new(i2, e2);
226
227 let expected_point = Points::new(SMatrix::<_, 1, 3>::new(2.21, 3.41, 5.61));
228
229 let image1 = cam1.world_to_pixel(&expected_point);
230
231 let image2 = cam2.world_to_pixel(&expected_point);
232
233 let ray1 = cam1.pixel_to_world(&image1).to_single_ray();
234 let ray2 = cam2.pixel_to_world(&image2).to_single_ray();
235 let rays = [ray1, ray2];
236
237 let point3d = best_intersection_of_rays(&rays).unwrap();
238
239 let image1_actual = cam1.world_to_pixel(&point3d);
241 approx::assert_abs_diff_eq!(
242 image1_actual.data,
243 image1.data,
244 epsilon = nalgebra::convert(1e-10)
245 );
246
247 let image2_actual = cam2.world_to_pixel(&point3d);
249 approx::assert_abs_diff_eq!(
250 image2_actual.data,
251 image2.data,
252 epsilon = nalgebra::convert(1e-10)
253 );
254
255 approx::assert_abs_diff_eq!(
257 point3d.data,
258 expected_point.data,
259 epsilon = nalgebra::convert(1e-10)
260 );
261 }
262}