1use nalgebra::{
5 DefaultAllocator, Dyn, Matrix, Matrix3, Matrix3x1, OMatrix, RealField, U1, U3, VecStorage,
6 allocator::Allocator,
7};
8use num_traits::float::TotalOrder;
9
10use crate::{MvgError, Result};
11
12pub enum Algorithm {
18 KabschUmeyama,
24 RobustArun,
30}
31
32pub fn align_points<T>(
47 x: &OMatrix<T, U3, Dyn>,
48 y: &OMatrix<T, U3, Dyn>,
49 algorithm: Algorithm,
50) -> Result<(T, Matrix3<T>, Matrix3x1<T>)>
51where
52 T: RealField + Copy + TotalOrder,
53{
54 let n = x.ncols();
55
56 if n != y.ncols() {
57 return Err(MvgError::InvalidShape);
58 }
59 if n < 1 {
60 return Err(MvgError::InvalidShape);
61 }
62
63 let mu_x = x.column_mean();
65 let mu_y = y.column_mean();
66
67 let x_center = x - bcast(&mu_x, n);
69 let y_center = y - bcast(&mu_y, n);
70
71 let (robust_scale, cov_xy) = match algorithm {
73 Algorithm::RobustArun => {
74 let dx = x.columns(1, n - 1) - x.columns(0, n - 1);
75 let dy = y.columns(1, n - 1) - y.columns(0, n - 1);
76 let dx = sqrt(&square(&dx).row_sum());
77 let dy = sqrt(&square(&dy).row_sum());
78 let scales = dy.component_div(&dx);
79
80 let scale = median(&scales).unwrap();
81
82 let x_centered_scaled = &x_center * scale;
83
84 let cov_xy = &x_centered_scaled * y_center.transpose();
85 (Some(scale), cov_xy)
86 }
87 Algorithm::KabschUmeyama => {
88 let cov_xy = (y_center * x_center.transpose()) / nalgebra::convert::<_, T>(n as f64);
89 (None, cov_xy)
90 }
91 };
92
93 const SVD_MAX_ITERATIONS: usize = 1_000_000;
95
96 let svd = if let Some(svd) = nalgebra::linalg::SVD::try_new(
97 cov_xy,
98 true,
99 true,
100 nalgebra::convert(1e-7),
101 SVD_MAX_ITERATIONS,
102 ) {
103 svd
104 } else {
105 return Err(MvgError::SvdFailed);
106 };
107 let u = svd.u.unwrap();
108 let d = svd.singular_values;
109 let vh = svd.v_t.unwrap();
110
111 let (c, r) = if let Some(scale) = robust_scale {
113 let v = vh.transpose();
114 let ut = u.transpose();
115 (scale, v * ut)
116 } else {
117 let mut s = nalgebra::Matrix3::<T>::identity();
118
119 if u.determinant() * vh.determinant() < nalgebra::convert(0.0) {
121 s[(2, 2)] = nalgebra::convert(-1.0);
122 }
123
124 let var_x = square(&x_center).row_sum().mean();
126 let c = (nalgebra::Matrix3::from_diagonal(&d) * s).trace() / var_x;
127 (c, u * s * vh)
128 };
129
130 let t = mu_y - (r * mu_x) * c;
132
133 Ok((c, r, t))
134}
135
136fn bcast<T, R>(m: &OMatrix<T, R, U1>, n: usize) -> OMatrix<T, R, Dyn>
137where
138 T: RealField + Copy,
139 R: nalgebra::DimName,
140 DefaultAllocator: Allocator<R>,
141{
142 let mut result = OMatrix::<T, R, Dyn>::zeros(n);
144 for i in 0..R::dim() {
145 for j in 0..n {
146 result[(i, j)] = m[(i, 0)];
147 }
148 }
149 result
150}
151
152fn sqrt<T, R, C>(m: &OMatrix<T, R, C>) -> OMatrix<T, R, C>
153where
154 T: RealField + Copy,
155 R: nalgebra::Dim,
156 C: nalgebra::Dim,
157 DefaultAllocator: Allocator<R, C>,
158{
159 let mut result = m.clone();
160 sqrt_in_place(&mut result);
161 result
162}
163
164fn sqrt_in_place<T, R, C>(m: &mut OMatrix<T, R, C>)
165where
166 T: RealField + Copy,
167 R: nalgebra::Dim,
168 C: nalgebra::Dim,
169 DefaultAllocator: Allocator<R, C>,
170{
171 for el in m.iter_mut() {
172 let val: T = *el;
173 *el = val.sqrt();
174 }
175}
176
177fn square<T, R, C>(m: &OMatrix<T, R, C>) -> OMatrix<T, R, C>
178where
179 T: RealField + Copy,
180 R: nalgebra::Dim,
181 C: nalgebra::Dim,
182 DefaultAllocator: Allocator<R, C>,
183{
184 m.component_mul(m)
185}
186
187fn median<T, C>(scales: &Matrix<T, U1, C, VecStorage<T, U1, C>>) -> Option<T>
188where
189 T: RealField + Copy + TotalOrder,
190 C: nalgebra::Dim,
191 DefaultAllocator: Allocator<U1, C>,
192{
193 let mut scales = scales.data.as_slice().to_vec(); scales.as_mut_slice().sort_by(|a, b| a.total_cmp(b));
196
197 let n = scales.len();
198 if n == 0 {
199 None
200 } else if n == 1 {
201 Some(scales[0])
202 } else if n.is_multiple_of(2) {
203 let s1 = scales[n / 2 - 1];
204 let s2 = scales[n / 2];
205 Some((s1 + s2) * nalgebra::convert(0.5))
206 } else {
207 Some(scales[n / 2])
209 }
210}
211
212#[test]
213fn test_median() {
214 let mut a = OMatrix::<f64, U1, Dyn>::zeros(3);
215 a[(0, 0)] = 1.0;
216 a[(0, 1)] = 2.0;
217 a[(0, 2)] = 3.0;
218 assert_eq!(median(&a), Some(2.0));
219
220 let mut a = OMatrix::<f64, U1, Dyn>::zeros(2);
221 a[(0, 0)] = 1.0;
222 a[(0, 1)] = 2.0;
223 assert_eq!(median(&a), Some(1.5));
224}
225
226#[test]
227fn test_square() {
228 let a = nalgebra::Matrix2::new(0., 1., 2., 3.);
229 let b = square(&a);
230 assert_eq!(b, nalgebra::Matrix2::new(0., 1., 4., 9.));
231}
232
233#[test]
234fn test_align_points() {
235 use nalgebra::{Matrix3, Vector3};
236
237 #[rustfmt::skip]
238 let x1 = nalgebra::base::Matrix3xX::from_column_slice(&[
240 3.36748406,1.61036404,3.55147255,
241 3.58702265,0.06676394,3.64695356,
242 0.28452026,-0.11188296,3.78947735,
243 0.25482713,1.57828256,3.6900808,
244 3.54938525,1.74057692,5.13329681,
245 3.6855626,0.10335229,5.26344841,
246 0.25025385,-0.06146044,5.57085135,
247 0.20742481,1.71073272,5.41823085]);
248
249 #[rustfmt::skip]
250 let x2_noisy = nalgebra::base::Matrix3xX::from_column_slice(&[
251 3.048,1.524,1.524,
252 3.048,0.0,1.524,
253 0.0,0.0,1.524,
254 0.0,1.524,1.524,
255 3.048,1.524,0.0,
256 3.048,0.0,0.0,
257 0.0,0.0,0.0,
258 0.0,1.524,0.0]);
259
260 for algorithm in [Algorithm::KabschUmeyama, Algorithm::RobustArun] {
261 let c_expected = 0.1;
263 let r_expected = *nalgebra::geometry::Rotation3::from_euler_angles(
264 std::f64::consts::FRAC_PI_4,
265 0.0,
266 0.0,
267 )
268 .matrix();
269 let t_expected = Vector3::new(-0.2, 0.3, -0.4);
270
271 let x2 = c_expected * r_expected * &x1 + bcast(&t_expected, 8);
272
273 let (c, r, t) = align_points(&x1, &x2, algorithm).unwrap();
274
275 approx::assert_abs_diff_eq!(c, c_expected);
276 approx::assert_abs_diff_eq!(r, r_expected, epsilon = 1e-10);
277 approx::assert_abs_diff_eq!(t, t_expected, epsilon = 1e-10);
278 }
279
280 let (c, r, t) = align_points(&x1, &x2_noisy, Algorithm::RobustArun).unwrap();
283
284 let c_expected = 0.920734586302497;
287 #[rustfmt::skip]
288 let r_expected = {
289 Matrix3::new(
290 0.997554805278945, 0.03689676080610408, -0.05935519780863721,
291 -0.04056669686950421, 0.9972599534887207, -0.06186217158144404,
292 -0.05691004805816868, -0.06411875084319214, -0.9963182384260189,
293 )
294 };
295 let t_expected = Vector3::new(
296 -0.0013862645696010034,
297 0.3279319869522358,
298 5.0458138154244985,
299 );
300
301 approx::assert_abs_diff_eq!(c, c_expected);
302 approx::assert_abs_diff_eq!(r, r_expected, epsilon = 1e-10);
303 approx::assert_abs_diff_eq!(t, t_expected, epsilon = 1e-10);
304
305 }