1use crate::{CameraModel, CameraModelError, DistortionModel, PinholeParams};
9use nalgebra::{DVector, SMatrix, Vector2, Vector3};
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct EucmCamera {
14 pub pinhole: PinholeParams,
15 pub distortion: DistortionModel,
16}
17
18impl EucmCamera {
19 pub fn new(
38 pinhole: PinholeParams,
39 distortion: DistortionModel,
40 ) -> Result<Self, CameraModelError> {
41 let camera = Self {
42 pinhole,
43 distortion,
44 };
45 camera.validate_params()?;
46 Ok(camera)
47 }
48
49 fn distortion_params(&self) -> (f64, f64) {
52 match self.distortion {
53 DistortionModel::EUCM { alpha, beta } => (alpha, beta),
54 _ => (0.0, 0.0),
55 }
56 }
57
58 fn check_projection_condition(&self, z: f64, denom: f64) -> bool {
61 let (alpha, _) = self.distortion_params();
62 let mut condition = true;
63 if alpha > 0.5 {
64 let c = (alpha - 1.0) / (2.0 * alpha - 1.0);
65 if z < denom * c {
66 condition = false;
67 }
68 }
69 condition
70 }
71
72 fn check_unprojection_condition(&self, r_squared: f64) -> bool {
75 let (alpha, beta) = self.distortion_params();
76 if alpha > 0.5 {
77 let bound = 1.0 / ((2.0 * alpha - 1.0) * beta);
78 if r_squared > bound {
79 return false;
80 }
81 }
82 true
83 }
84
85 pub fn linear_estimation(
89 &mut self,
90 points_3d: &nalgebra::Matrix3xX<f64>,
91 points_2d: &nalgebra::Matrix2xX<f64>,
92 ) -> Result<(), CameraModelError> {
93 if points_2d.ncols() != points_3d.ncols() {
94 return Err(CameraModelError::InvalidParams(
95 "Number of 2D and 3D points must match".to_string(),
96 ));
97 }
98
99 let num_points = points_2d.ncols();
100 if num_points < 1 {
101 return Err(CameraModelError::InvalidParams(
102 "Need at least 1 point for EUCM linear estimation".to_string(),
103 ));
104 }
105
106 let mut a = nalgebra::DMatrix::zeros(num_points * 2, 1);
107 let mut b = nalgebra::DVector::zeros(num_points * 2);
108
109 for i in 0..num_points {
110 let x = points_3d[(0, i)];
111 let y = points_3d[(1, i)];
112 let z = points_3d[(2, i)];
113 let u = points_2d[(0, i)];
114 let v = points_2d[(1, i)];
115
116 let d = (x * x + y * y + z * z).sqrt();
117 let u_cx = u - self.pinhole.cx;
118 let v_cy = v - self.pinhole.cy;
119
120 a[(i * 2, 0)] = u_cx * (d - z);
121 a[(i * 2 + 1, 0)] = v_cy * (d - z);
122
123 b[i * 2] = self.pinhole.fx * x - u_cx * z;
124 b[i * 2 + 1] = self.pinhole.fy * y - v_cy * z;
125 }
126
127 let svd = a.svd(true, true);
128 let solution = match svd.solve(&b, 1e-10) {
129 Ok(sol) => sol,
130 Err(err_msg) => {
131 return Err(CameraModelError::NumericalError {
132 operation: "svd_solve".to_string(),
133 details: err_msg.to_string(),
134 });
135 }
136 };
137
138 self.distortion = DistortionModel::EUCM {
139 alpha: solution[0],
140 beta: 1.0,
141 };
142
143 self.validate_params()?;
144
145 Ok(())
146 }
147}
148
149impl From<&EucmCamera> for DVector<f64> {
151 fn from(camera: &EucmCamera) -> Self {
152 let (alpha, beta) = camera.distortion_params();
153 DVector::from_vec(vec![
154 camera.pinhole.fx,
155 camera.pinhole.fy,
156 camera.pinhole.cx,
157 camera.pinhole.cy,
158 alpha,
159 beta,
160 ])
161 }
162}
163
164impl From<&EucmCamera> for [f64; 6] {
166 fn from(camera: &EucmCamera) -> Self {
167 let (alpha, beta) = camera.distortion_params();
168 [
169 camera.pinhole.fx,
170 camera.pinhole.fy,
171 camera.pinhole.cx,
172 camera.pinhole.cy,
173 alpha,
174 beta,
175 ]
176 }
177}
178
179impl TryFrom<&[f64]> for EucmCamera {
182 type Error = CameraModelError;
183
184 fn try_from(params: &[f64]) -> Result<Self, Self::Error> {
185 if params.len() < 6 {
186 return Err(CameraModelError::InvalidParams(format!(
187 "EucmCamera requires at least 6 parameters, got {}",
188 params.len()
189 )));
190 }
191 Ok(Self {
192 pinhole: PinholeParams {
193 fx: params[0],
194 fy: params[1],
195 cx: params[2],
196 cy: params[3],
197 },
198 distortion: DistortionModel::EUCM {
199 alpha: params[4],
200 beta: params[5],
201 },
202 })
203 }
204}
205
206impl From<[f64; 6]> for EucmCamera {
208 fn from(params: [f64; 6]) -> Self {
209 Self {
210 pinhole: PinholeParams {
211 fx: params[0],
212 fy: params[1],
213 cx: params[2],
214 cy: params[3],
215 },
216 distortion: DistortionModel::EUCM {
217 alpha: params[4],
218 beta: params[5],
219 },
220 }
221 }
222}
223
224pub fn try_from_params(params: &[f64]) -> Result<EucmCamera, CameraModelError> {
228 let camera = EucmCamera::try_from(params)?;
229 camera.validate_params()?;
230 Ok(camera)
231}
232
233impl CameraModel for EucmCamera {
234 const INTRINSIC_DIM: usize = 6;
235 type IntrinsicJacobian = SMatrix<f64, 2, 6>;
236 type PointJacobian = SMatrix<f64, 2, 3>;
237
238 fn project(&self, p_cam: &Vector3<f64>) -> Result<Vector2<f64>, CameraModelError> {
242 let x = p_cam[0];
243 let y = p_cam[1];
244 let z = p_cam[2];
245
246 let (alpha, beta) = self.distortion_params();
247 let r2 = x * x + y * y;
248 let d = (beta * r2 + z * z).sqrt();
249 let denom = alpha * d + (1.0 - alpha) * z;
250
251 if denom < crate::GEOMETRIC_PRECISION {
252 return Err(CameraModelError::DenominatorTooSmall {
253 denom,
254 threshold: crate::GEOMETRIC_PRECISION,
255 });
256 }
257
258 if !self.check_projection_condition(z, denom) {
259 return Err(CameraModelError::PointBehindCamera {
260 z,
261 min_z: crate::GEOMETRIC_PRECISION,
262 });
263 }
264
265 Ok(Vector2::new(
266 self.pinhole.fx * x / denom + self.pinhole.cx,
267 self.pinhole.fy * y / denom + self.pinhole.cy,
268 ))
269 }
270
271 fn unproject(&self, point_2d: &Vector2<f64>) -> Result<Vector3<f64>, CameraModelError> {
276 let u = point_2d.x;
277 let v = point_2d.y;
278
279 let (alpha, beta) = self.distortion_params();
280 let mx = (u - self.pinhole.cx) / self.pinhole.fx;
281 let my = (v - self.pinhole.cy) / self.pinhole.fy;
282 let r2 = mx * mx + my * my;
283
284 if !self.check_unprojection_condition(r2) {
285 return Err(CameraModelError::PointOutsideImage { x: u, y: v });
286 }
287
288 let mz_num = 1.0 - beta * alpha * alpha * r2;
292 let radicand = 1.0 - (2.0 * alpha - 1.0) * beta * r2;
293 if radicand < 0.0 {
294 return Err(CameraModelError::PointOutsideImage { x: u, y: v });
295 }
296 let mz_denom = alpha * radicand.sqrt() + (1.0 - alpha);
297 if mz_denom.abs() < crate::GEOMETRIC_PRECISION {
298 return Err(CameraModelError::NumericalError {
299 operation: "unprojection".to_string(),
300 details: "Division by near-zero in EUCM unprojection".to_string(),
301 });
302 }
303
304 let mz = mz_num / mz_denom;
305 Ok(Vector3::new(mx, my, mz).normalize())
306 }
307
308 fn jacobian_point(&self, p_cam: &Vector3<f64>) -> Self::PointJacobian {
311 let x = p_cam[0];
312 let y = p_cam[1];
313 let z = p_cam[2];
314
315 let (alpha, beta) = self.distortion_params();
316 let r2 = x * x + y * y;
317 let d = (beta * r2 + z * z).sqrt();
318 let denom = alpha * d + (1.0 - alpha) * z;
319
320 let dd_dx = beta * x / d;
322 let dd_dy = beta * y / d;
323 let dd_dz = z / d;
324
325 let ddenom_dx = alpha * dd_dx;
327 let ddenom_dy = alpha * dd_dy;
328 let ddenom_dz = alpha * dd_dz + (1.0 - alpha);
329
330 let denom2 = denom * denom;
331
332 let du_dx = self.pinhole.fx * (denom - x * ddenom_dx) / denom2;
334 let du_dy = self.pinhole.fx * (-x * ddenom_dy) / denom2;
335 let du_dz = self.pinhole.fx * (-x * ddenom_dz) / denom2;
336
337 let dv_dx = self.pinhole.fy * (-y * ddenom_dx) / denom2;
338 let dv_dy = self.pinhole.fy * (denom - y * ddenom_dy) / denom2;
339 let dv_dz = self.pinhole.fy * (-y * ddenom_dz) / denom2;
340
341 SMatrix::<f64, 2, 3>::new(du_dx, du_dy, du_dz, dv_dx, dv_dy, dv_dz)
342 }
343
344 fn jacobian_intrinsics(&self, p_cam: &Vector3<f64>) -> Self::IntrinsicJacobian {
347 let x = p_cam[0];
348 let y = p_cam[1];
349 let z = p_cam[2];
350
351 let (alpha, beta) = self.distortion_params();
352 let r2 = x * x + y * y;
353 let d = (beta * r2 + z * z).sqrt();
354 let denom = alpha * d + (1.0 - alpha) * z;
355
356 let x_norm = x / denom;
357 let y_norm = y / denom;
358
359 let ddenom_dalpha = d - z;
364
365 let dd_dbeta = r2 / (2.0 * d);
366 let ddenom_dbeta = alpha * dd_dbeta;
367
368 let du_dalpha = -self.pinhole.fx * x * ddenom_dalpha / (denom * denom);
369 let dv_dalpha = -self.pinhole.fy * y * ddenom_dalpha / (denom * denom);
370
371 let du_dbeta = -self.pinhole.fx * x * ddenom_dbeta / (denom * denom);
372 let dv_dbeta = -self.pinhole.fy * y * ddenom_dbeta / (denom * denom);
373
374 SMatrix::<f64, 2, 6>::new(
375 x_norm, 0.0, 1.0, 0.0, du_dalpha, du_dbeta, 0.0, y_norm, 0.0, 1.0, dv_dalpha, dv_dbeta,
376 )
377 }
378
379 fn validate_params(&self) -> Result<(), CameraModelError> {
392 self.pinhole.validate()?;
393 self.get_distortion().validate()
394 }
395
396 fn get_pinhole_params(&self) -> PinholeParams {
398 PinholeParams {
399 fx: self.pinhole.fx,
400 fy: self.pinhole.fy,
401 cx: self.pinhole.cx,
402 cy: self.pinhole.cy,
403 }
404 }
405
406 fn get_distortion(&self) -> DistortionModel {
408 self.distortion
409 }
410
411 fn get_model_name(&self) -> &'static str {
413 "eucm"
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420 use nalgebra::{Matrix2xX, Matrix3xX};
421
422 type TestResult = Result<(), Box<dyn std::error::Error>>;
423
424 #[test]
425 fn test_eucm_camera_creation() -> TestResult {
426 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
427 let distortion = DistortionModel::EUCM {
428 alpha: 0.5,
429 beta: 1.0,
430 };
431 let camera = EucmCamera::new(pinhole, distortion)?;
432
433 assert_eq!(camera.pinhole.fx, 300.0);
434 assert_eq!(camera.distortion_params(), (0.5, 1.0));
435 Ok(())
436 }
437
438 #[test]
439 fn test_projection_at_optical_axis() -> TestResult {
440 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
441 let distortion = DistortionModel::EUCM {
442 alpha: 0.5,
443 beta: 1.0,
444 };
445 let camera = EucmCamera::new(pinhole, distortion)?;
446
447 let p_cam = Vector3::new(0.0, 0.0, 1.0);
448 let uv = camera.project(&p_cam)?;
449
450 assert!((uv.x - 320.0).abs() < crate::PROJECTION_TEST_TOLERANCE);
451 assert!((uv.y - 240.0).abs() < crate::PROJECTION_TEST_TOLERANCE);
452
453 Ok(())
454 }
455
456 #[test]
457 fn test_jacobian_point_numerical() -> TestResult {
458 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
459 let distortion = DistortionModel::EUCM {
460 alpha: 0.6,
461 beta: 1.2,
462 };
463 let camera = EucmCamera::new(pinhole, distortion)?;
464
465 let p_cam = Vector3::new(0.1, 0.2, 1.0);
466
467 let jac_analytical = camera.jacobian_point(&p_cam);
468 let eps = crate::NUMERICAL_DERIVATIVE_EPS;
469
470 for i in 0..3 {
471 let mut p_plus = p_cam;
472 let mut p_minus = p_cam;
473 p_plus[i] += eps;
474 p_minus[i] -= eps;
475
476 let uv_plus = camera.project(&p_plus)?;
477 let uv_minus = camera.project(&p_minus)?;
478 let num_jac = (uv_plus - uv_minus) / (2.0 * eps);
479
480 for r in 0..2 {
481 assert!(
482 jac_analytical[(r, i)].is_finite(),
483 "Jacobian [{r},{i}] is not finite"
484 );
485 let diff = (jac_analytical[(r, i)] - num_jac[r]).abs();
486 assert!(
487 diff < crate::JACOBIAN_TEST_TOLERANCE,
488 "Mismatch at ({}, {})",
489 r,
490 i
491 );
492 }
493 }
494 Ok(())
495 }
496
497 #[test]
498 fn test_jacobian_intrinsics_numerical() -> TestResult {
499 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
500 let distortion = DistortionModel::EUCM {
501 alpha: 0.6,
502 beta: 1.2,
503 };
504 let camera = EucmCamera::new(pinhole, distortion)?;
505
506 let p_cam = Vector3::new(0.1, 0.2, 1.0);
507
508 let jac_analytical = camera.jacobian_intrinsics(&p_cam);
509 let params: DVector<f64> = (&camera).into();
510 let eps = crate::NUMERICAL_DERIVATIVE_EPS;
511
512 for i in 0..6 {
513 let mut params_plus = params.clone();
514 let mut params_minus = params.clone();
515 params_plus[i] += eps;
516 params_minus[i] -= eps;
517
518 let cam_plus = EucmCamera::try_from(params_plus.as_slice())?;
519 let cam_minus = EucmCamera::try_from(params_minus.as_slice())?;
520
521 let uv_plus = cam_plus.project(&p_cam)?;
522 let uv_minus = cam_minus.project(&p_cam)?;
523 let num_jac = (uv_plus - uv_minus) / (2.0 * eps);
524
525 for r in 0..2 {
526 assert!(
527 jac_analytical[(r, i)].is_finite(),
528 "Jacobian [{r},{i}] is not finite"
529 );
530 let diff = (jac_analytical[(r, i)] - num_jac[r]).abs();
531 assert!(
532 diff < crate::JACOBIAN_TEST_TOLERANCE,
533 "Mismatch at ({}, {})",
534 r,
535 i
536 );
537 }
538 }
539 Ok(())
540 }
541
542 #[test]
543 fn test_eucm_from_into_traits() -> TestResult {
544 let pinhole = PinholeParams::new(400.0, 410.0, 320.0, 240.0)?;
545 let distortion = DistortionModel::EUCM {
546 alpha: 0.7,
547 beta: 1.5,
548 };
549 let camera = EucmCamera::new(pinhole, distortion)?;
550
551 let params: DVector<f64> = (&camera).into();
553 assert_eq!(params.len(), 6);
554 assert_eq!(params[0], 400.0);
555 assert_eq!(params[1], 410.0);
556 assert_eq!(params[2], 320.0);
557 assert_eq!(params[3], 240.0);
558 assert_eq!(params[4], 0.7);
559 assert_eq!(params[5], 1.5);
560
561 let arr: [f64; 6] = (&camera).into();
563 assert_eq!(arr, [400.0, 410.0, 320.0, 240.0, 0.7, 1.5]);
564
565 let params_slice = [450.0, 460.0, 330.0, 250.0, 0.8, 1.8];
567 let camera2 = EucmCamera::try_from(¶ms_slice[..])?;
568 assert_eq!(camera2.pinhole.fx, 450.0);
569 assert_eq!(camera2.pinhole.fy, 460.0);
570 assert_eq!(camera2.pinhole.cx, 330.0);
571 assert_eq!(camera2.pinhole.cy, 250.0);
572 assert_eq!(camera2.distortion_params(), (0.8, 1.8));
573
574 let camera3 = EucmCamera::from([500.0, 510.0, 340.0, 260.0, 0.9, 2.0]);
576 assert_eq!(camera3.pinhole.fx, 500.0);
577 assert_eq!(camera3.pinhole.fy, 510.0);
578 assert_eq!(camera3.distortion_params(), (0.9, 2.0));
579
580 Ok(())
581 }
582
583 #[test]
584 fn test_linear_estimation() -> TestResult {
585 let gt_pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
587 let gt_distortion = DistortionModel::EUCM {
588 alpha: 0.5,
589 beta: 1.0,
590 };
591 let gt_camera = EucmCamera::new(gt_pinhole, gt_distortion)?;
592
593 let n_points = 50;
595 let mut pts_3d = Matrix3xX::zeros(n_points);
596 let mut pts_2d = Matrix2xX::zeros(n_points);
597 let mut valid = 0;
598
599 for i in 0..n_points {
600 let angle = i as f64 * 2.0 * std::f64::consts::PI / n_points as f64;
601 let r = 0.1 + 0.3 * (i as f64 / n_points as f64);
602 let p3d = Vector3::new(r * angle.cos(), r * angle.sin(), 1.0);
603
604 if let Ok(p2d) = gt_camera.project(&p3d) {
605 pts_3d.set_column(valid, &p3d);
606 pts_2d.set_column(valid, &p2d);
607 valid += 1;
608 }
609 }
610 let pts_3d = pts_3d.columns(0, valid).into_owned();
611 let pts_2d = pts_2d.columns(0, valid).into_owned();
612
613 let init_pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
615 let init_distortion = DistortionModel::EUCM {
616 alpha: 0.0,
617 beta: 1.0,
618 };
619 let mut camera = EucmCamera::new(init_pinhole, init_distortion)?;
620
621 camera.linear_estimation(&pts_3d, &pts_2d)?;
622
623 for i in 0..valid {
625 let col = pts_3d.column(i);
626 let projected = camera.project(&Vector3::new(col[0], col[1], col[2]))?;
627 let err = ((projected.x - pts_2d[(0, i)]).powi(2)
628 + (projected.y - pts_2d[(1, i)]).powi(2))
629 .sqrt();
630 assert!(err < 1.0, "Reprojection error too large: {err}");
631 }
632
633 Ok(())
634 }
635
636 #[test]
637 fn test_project_unproject_round_trip() -> TestResult {
638 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
642 let distortion = DistortionModel::EUCM {
643 alpha: 0.6,
644 beta: 1.1,
645 };
646 let camera = EucmCamera::new(pinhole, distortion)?;
647
648 let test_points = [
649 Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.1, 0.2, 1.0),
651 Vector3::new(-0.3, 0.1, 2.0),
652 Vector3::new(0.6, 0.0, 0.8), Vector3::new(0.4, -0.5, 0.7), ];
655
656 for p_cam in &test_points {
657 let uv = camera.project(p_cam)?;
658 let ray = camera.unproject(&uv)?;
659 let dot = ray.dot(&p_cam.normalize());
660 assert!(
661 (dot - 1.0).abs() < 1e-8,
662 "Round-trip failed: dot={dot}, expected ~1.0 (p_cam = {p_cam:?})"
663 );
664 }
665
666 Ok(())
667 }
668
669 #[test]
670 fn test_project_returns_error_behind_camera() -> TestResult {
671 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
672 let distortion = DistortionModel::EUCM {
673 alpha: 0.5,
674 beta: 1.0,
675 };
676 let camera = EucmCamera::new(pinhole, distortion)?;
677 assert!(camera.project(&Vector3::new(0.0, 0.0, -1.0)).is_err());
678 Ok(())
679 }
680
681 #[test]
682 fn test_project_at_min_depth_boundary() -> TestResult {
683 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
684 let distortion = DistortionModel::EUCM {
685 alpha: 0.5,
686 beta: 1.0,
687 };
688 let camera = EucmCamera::new(pinhole, distortion)?;
689 let p_min = Vector3::new(0.0, 0.0, crate::MIN_DEPTH);
690 if let Ok(uv) = camera.project(&p_min) {
691 assert!(uv.x.is_finite() && uv.y.is_finite());
692 }
693 Ok(())
694 }
695
696 #[test]
697 fn test_projection_off_axis() -> TestResult {
698 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
699 let distortion = DistortionModel::EUCM {
700 alpha: 0.5,
701 beta: 1.0,
702 };
703 let camera = EucmCamera::new(pinhole, distortion)?;
704 let p_cam = Vector3::new(0.3, 0.0, 1.0);
705 let uv = camera.project(&p_cam)?;
706 assert!(
707 uv.x > 320.0,
708 "off-axis point should project right of principal point"
709 );
710 assert!(
711 (uv.y - 240.0).abs() < 1.0,
712 "y should be close to cy for horizontal offset"
713 );
714 Ok(())
715 }
716
717 #[test]
718 fn test_unproject_center_pixel() -> TestResult {
719 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
720 let distortion = DistortionModel::EUCM {
721 alpha: 0.5,
722 beta: 1.0,
723 };
724 let camera = EucmCamera::new(pinhole, distortion)?;
725 let uv = Vector2::new(320.0, 240.0);
726 let ray = camera.unproject(&uv)?;
727 assert!(ray.x.abs() < 1e-6, "x should be ~0, got {}", ray.x);
728 assert!(ray.y.abs() < 1e-6, "y should be ~0, got {}", ray.y);
729 assert!((ray.z - 1.0).abs() < 1e-6, "z should be ~1, got {}", ray.z);
730 Ok(())
731 }
732
733 #[test]
734 fn test_batch_projection_matches_individual() -> TestResult {
735 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
736 let distortion = DistortionModel::EUCM {
737 alpha: 0.5,
738 beta: 1.0,
739 };
740 let camera = EucmCamera::new(pinhole, distortion)?;
741 let pts = Matrix3xX::from_columns(&[
742 Vector3::new(0.0, 0.0, 1.0),
743 Vector3::new(0.3, 0.2, 1.5),
744 Vector3::new(-0.4, 0.1, 2.0),
745 ]);
746 let batch = camera.project_batch(&pts);
747 for i in 0..3 {
748 let col = pts.column(i);
749 let p = camera.project(&Vector3::new(col[0], col[1], col[2]))?;
750 assert!(
751 (batch[(0, i)] - p.x).abs() < 1e-10,
752 "batch u mismatch at col {i}"
753 );
754 assert!(
755 (batch[(1, i)] - p.y).abs() < 1e-10,
756 "batch v mismatch at col {i}"
757 );
758 }
759 Ok(())
760 }
761
762 #[test]
763 fn test_jacobian_dimensions() -> TestResult {
764 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
765 let distortion = DistortionModel::EUCM {
766 alpha: 0.5,
767 beta: 1.0,
768 };
769 let camera = EucmCamera::new(pinhole, distortion)?;
770 let p_cam = Vector3::new(0.1, 0.2, 1.0);
771 let jac_point = camera.jacobian_point(&p_cam);
772 assert_eq!(jac_point.nrows(), 2);
773 assert_eq!(jac_point.ncols(), 3);
774 let jac_intr = camera.jacobian_intrinsics(&p_cam);
775 assert_eq!(jac_intr.nrows(), 2);
776 assert_eq!(jac_intr.ncols(), 6); Ok(())
778 }
779}