1use crate::{CameraModel, CameraModelError, DistortionModel, PinholeParams};
9use nalgebra::{DVector, SMatrix, Vector2, Vector3};
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct FovCamera {
14 pub pinhole: PinholeParams,
15 pub distortion: DistortionModel,
16}
17
18impl FovCamera {
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 {
51 match self.distortion {
52 DistortionModel::FOV { w } => w,
53 _ => 0.0,
54 }
55 }
56
57 pub fn linear_estimation(
61 &mut self,
62 points_3d: &nalgebra::Matrix3xX<f64>,
63 points_2d: &nalgebra::Matrix2xX<f64>,
64 ) -> Result<(), CameraModelError> {
65 if points_2d.ncols() != points_3d.ncols() {
66 return Err(CameraModelError::InvalidParams(
67 "Number of 2D and 3D points must match".to_string(),
68 ));
69 }
70
71 let num_points = points_2d.ncols();
72
73 if num_points < 2 {
74 return Err(CameraModelError::InvalidParams(
75 "Need at least 2 point correspondences for linear estimation".to_string(),
76 ));
77 }
78
79 let mut best_w = 1.0;
80 let mut best_error = f64::INFINITY;
81
82 for w_test in (10..300).map(|i| i as f64 / 100.0) {
83 let mut error_sum = 0.0;
84 let mut valid_count = 0;
85
86 for i in 0..num_points {
87 let x = points_3d[(0, i)];
88 let y = points_3d[(1, i)];
89 let z = points_3d[(2, i)];
90 let u_observed = points_2d[(0, i)];
91 let v_observed = points_2d[(1, i)];
92
93 let r2 = x * x + y * y;
94 let r = r2.sqrt();
95
96 let tan_w_half = (w_test / 2.0).tan();
97 let atan_wrd = (2.0 * tan_w_half * r).atan2(z);
98
99 let eps_sqrt = f64::EPSILON.sqrt();
100 let rd = if r2 < eps_sqrt {
101 2.0 * tan_w_half / w_test
102 } else {
103 atan_wrd / (r * w_test)
104 };
105
106 let mx = x * rd;
107 let my = y * rd;
108
109 let u_predicted = self.pinhole.fx * mx + self.pinhole.cx;
110 let v_predicted = self.pinhole.fy * my + self.pinhole.cy;
111
112 let error = ((u_predicted - u_observed).powi(2)
113 + (v_predicted - v_observed).powi(2))
114 .sqrt();
115
116 if error.is_finite() {
117 error_sum += error;
118 valid_count += 1;
119 }
120 }
121
122 if valid_count > 0 {
123 let avg_error = error_sum / valid_count as f64;
124 if avg_error < best_error {
125 best_error = avg_error;
126 best_w = w_test;
127 }
128 }
129 }
130
131 self.distortion = DistortionModel::FOV { w: best_w };
132
133 self.validate_params()?;
134
135 Ok(())
136 }
137}
138
139impl From<&FovCamera> for DVector<f64> {
141 fn from(camera: &FovCamera) -> Self {
142 let w = camera.distortion_params();
143 DVector::from_vec(vec![
144 camera.pinhole.fx,
145 camera.pinhole.fy,
146 camera.pinhole.cx,
147 camera.pinhole.cy,
148 w,
149 ])
150 }
151}
152
153impl From<&FovCamera> for [f64; 5] {
155 fn from(camera: &FovCamera) -> Self {
156 let w = camera.distortion_params();
157 [
158 camera.pinhole.fx,
159 camera.pinhole.fy,
160 camera.pinhole.cx,
161 camera.pinhole.cy,
162 w,
163 ]
164 }
165}
166
167impl TryFrom<&[f64]> for FovCamera {
170 type Error = CameraModelError;
171
172 fn try_from(params: &[f64]) -> Result<Self, Self::Error> {
173 if params.len() < 5 {
174 return Err(CameraModelError::InvalidParams(format!(
175 "FovCamera requires at least 5 parameters, got {}",
176 params.len()
177 )));
178 }
179 Ok(Self {
180 pinhole: PinholeParams {
181 fx: params[0],
182 fy: params[1],
183 cx: params[2],
184 cy: params[3],
185 },
186 distortion: DistortionModel::FOV { w: params[4] },
187 })
188 }
189}
190
191impl From<[f64; 5]> for FovCamera {
193 fn from(params: [f64; 5]) -> Self {
194 Self {
195 pinhole: PinholeParams {
196 fx: params[0],
197 fy: params[1],
198 cx: params[2],
199 cy: params[3],
200 },
201 distortion: DistortionModel::FOV { w: params[4] },
202 }
203 }
204}
205
206pub fn try_from_params(params: &[f64]) -> Result<FovCamera, CameraModelError> {
210 let camera = FovCamera::try_from(params)?;
211 camera.validate_params()?;
212 Ok(camera)
213}
214
215impl CameraModel for FovCamera {
216 const INTRINSIC_DIM: usize = 5;
217 type IntrinsicJacobian = SMatrix<f64, 2, 5>;
218 type PointJacobian = SMatrix<f64, 2, 3>;
219
220 fn project(&self, p_cam: &Vector3<f64>) -> Result<Vector2<f64>, CameraModelError> {
226 let x = p_cam[0];
227 let y = p_cam[1];
228 let z = p_cam[2];
229
230 if z < crate::GEOMETRIC_PRECISION {
231 return Err(CameraModelError::ProjectionOutOfBounds);
232 }
233
234 let r = (x * x + y * y).sqrt();
235 let w = self.distortion_params();
236 let tan_w_2 = (w / 2.0).tan();
237 let mul2tanwby2 = tan_w_2 * 2.0;
238
239 let rd = if r > crate::GEOMETRIC_PRECISION {
240 let atan_wrd = (mul2tanwby2 * r / z).atan();
241 atan_wrd / (r * w)
242 } else {
243 mul2tanwby2 / w
244 };
245
246 let mx = x * rd;
247 let my = y * rd;
248
249 Ok(Vector2::new(
250 self.pinhole.fx * mx + self.pinhole.cx,
251 self.pinhole.fy * my + self.pinhole.cy,
252 ))
253 }
254
255 fn projection_deficit(&self, p_cam: &Vector3<f64>) -> (f64, Vector3<f64>) {
265 (
266 crate::GEOMETRIC_PRECISION - p_cam.z,
267 Vector3::new(0.0, 0.0, -1.0),
268 )
269 }
270
271 fn unproject(&self, point_2d: &Vector2<f64>) -> Result<Vector3<f64>, CameraModelError> {
274 let u = point_2d.x;
275 let v = point_2d.y;
276
277 let w = self.distortion_params();
278 let tan_w_2 = (w / 2.0).tan();
279 let mul2tanwby2 = tan_w_2 * 2.0;
280
281 let mx = (u - self.pinhole.cx) / self.pinhole.fx;
282 let my = (v - self.pinhole.cy) / self.pinhole.fy;
283
284 let r2 = mx * mx + my * my;
285 let rd = r2.sqrt();
286
287 if rd < crate::GEOMETRIC_PRECISION {
288 return Ok(Vector3::new(0.0, 0.0, 1.0));
289 }
290
291 let ru = (rd * w).tan() / mul2tanwby2;
292
293 let norm_factor = (1.0 + ru * ru).sqrt();
294 let x = mx * ru / (rd * norm_factor);
295 let y = my * ru / (rd * norm_factor);
296 let z = 1.0 / norm_factor;
297
298 Ok(Vector3::new(x, y, z))
299 }
300
301 fn jacobian_point(&self, p_cam: &Vector3<f64>) -> Self::PointJacobian {
304 let x = p_cam[0];
305 let y = p_cam[1];
306 let z = p_cam[2];
307
308 let r = (x * x + y * y).sqrt();
309 let w = self.distortion_params();
310 let tan_w_2 = (w / 2.0).tan();
311 let mul2tanwby2 = tan_w_2 * 2.0;
312
313 if r < crate::GEOMETRIC_PRECISION {
314 let rd = mul2tanwby2 / w;
315 return SMatrix::<f64, 2, 3>::new(
316 self.pinhole.fx * rd,
317 0.0,
318 0.0,
319 0.0,
320 self.pinhole.fy * rd,
321 0.0,
322 );
323 }
324
325 let atan_wrd = (mul2tanwby2 * r / z).atan();
326 let rd = atan_wrd / (r * w);
327
328 let datan_dr = mul2tanwby2 * z / (z * z + mul2tanwby2 * mul2tanwby2 * r * r);
330 let datan_dz = -mul2tanwby2 * r / (z * z + mul2tanwby2 * mul2tanwby2 * r * r);
331
332 let drd_dr = (datan_dr * r - atan_wrd) / (r * r * w);
333 let drd_dz = datan_dz / (r * w);
334
335 let dr_dx = x / r;
336 let dr_dy = y / r;
337
338 let dmx_dx = rd + x * drd_dr * dr_dx;
339 let dmx_dy = x * drd_dr * dr_dy;
340 let dmx_dz = x * drd_dz;
341
342 let dmy_dx = y * drd_dr * dr_dx;
343 let dmy_dy = rd + y * drd_dr * dr_dy;
344 let dmy_dz = y * drd_dz;
345
346 SMatrix::<f64, 2, 3>::new(
347 self.pinhole.fx * dmx_dx,
348 self.pinhole.fx * dmx_dy,
349 self.pinhole.fx * dmx_dz,
350 self.pinhole.fy * dmy_dx,
351 self.pinhole.fy * dmy_dy,
352 self.pinhole.fy * dmy_dz,
353 )
354 }
355
356 fn jacobian_intrinsics(&self, p_cam: &Vector3<f64>) -> Self::IntrinsicJacobian {
359 let x = p_cam[0];
360 let y = p_cam[1];
361 let z = p_cam[2];
362
363 let r = (x * x + y * y).sqrt();
364 let w = self.distortion_params();
365 let tan_w_2 = (w / 2.0).tan();
366 let mul2tanwby2 = tan_w_2 * 2.0;
367
368 let rd = if r > crate::GEOMETRIC_PRECISION {
369 let atan_wrd = (mul2tanwby2 * r / z).atan();
370 atan_wrd / (r * w)
371 } else {
372 mul2tanwby2 / w
373 };
374
375 let mx = x * rd;
376 let my = y * rd;
377
378 let drd_dw = if r > crate::GEOMETRIC_PRECISION {
383 let tan_w_2 = (w / 2.0).tan();
384 let alpha = 2.0 * tan_w_2 * r / z;
385 let atan_alpha = alpha.atan();
386
387 let sec2_w_2 = 1.0 + tan_w_2 * tan_w_2;
389 let dalpha_dw = sec2_w_2 * r / z;
390
391 let datan_dw = dalpha_dw / (1.0 + alpha * alpha);
393 (datan_dw * r * w - atan_alpha * r) / (r * r * w * w)
394 } else {
395 let tan_w_2 = (w / 2.0).tan();
396 let sec2_w_2 = 1.0 + tan_w_2 * tan_w_2;
397 (sec2_w_2 * w - 2.0 * tan_w_2) / (w * w)
401 };
402
403 let du_dw = self.pinhole.fx * x * drd_dw;
404 let dv_dw = self.pinhole.fy * y * drd_dw;
405
406 SMatrix::<f64, 2, 5>::new(mx, 0.0, 1.0, 0.0, du_dw, 0.0, my, 0.0, 1.0, dv_dw)
407 }
408
409 fn validate_params(&self) -> Result<(), CameraModelError> {
421 self.pinhole.validate()?;
422 self.get_distortion().validate()
423 }
424
425 fn get_pinhole_params(&self) -> PinholeParams {
427 PinholeParams {
428 fx: self.pinhole.fx,
429 fy: self.pinhole.fy,
430 cx: self.pinhole.cx,
431 cy: self.pinhole.cy,
432 }
433 }
434
435 fn get_distortion(&self) -> DistortionModel {
437 self.distortion
438 }
439
440 fn get_model_name(&self) -> &'static str {
442 "fov"
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449 use nalgebra::{Matrix2xX, Matrix3xX};
450
451 type TestResult = Result<(), Box<dyn std::error::Error>>;
452
453 #[test]
454 fn test_fov_camera_creation() -> TestResult {
455 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
456 let distortion = DistortionModel::FOV { w: 1.5 };
457 let camera = FovCamera::new(pinhole, distortion)?;
458
459 assert_eq!(camera.pinhole.fx, 300.0);
460 assert_eq!(camera.distortion_params(), 1.5);
461 Ok(())
462 }
463
464 #[test]
465 fn test_projection_at_optical_axis() -> TestResult {
466 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
467 let distortion = DistortionModel::FOV { w: 1.5 };
468 let camera = FovCamera::new(pinhole, distortion)?;
469
470 let p_cam = Vector3::new(0.0, 0.0, 1.0);
471 let uv = camera.project(&p_cam)?;
472
473 assert!((uv.x - 320.0).abs() < 1e-4);
474 assert!((uv.y - 240.0).abs() < 1e-4);
475
476 Ok(())
477 }
478
479 #[test]
480 fn test_jacobian_point_numerical() -> TestResult {
481 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
482 let distortion = DistortionModel::FOV { w: 1.5 };
483 let camera = FovCamera::new(pinhole, distortion)?;
484
485 let p_cam = Vector3::new(0.1, 0.2, 1.0);
486
487 let jac_analytical = camera.jacobian_point(&p_cam);
488 let eps = crate::NUMERICAL_DERIVATIVE_EPS;
489
490 for i in 0..3 {
491 let mut p_plus = p_cam;
492 let mut p_minus = p_cam;
493 p_plus[i] += eps;
494 p_minus[i] -= eps;
495
496 let uv_plus = camera.project(&p_plus)?;
497 let uv_minus = camera.project(&p_minus)?;
498 let num_jac = (uv_plus - uv_minus) / (2.0 * eps);
499
500 for r in 0..2 {
501 assert!(
502 jac_analytical[(r, i)].is_finite(),
503 "Jacobian [{r},{i}] is not finite"
504 );
505 let diff = (jac_analytical[(r, i)] - num_jac[r]).abs();
506 assert!(
507 diff < crate::JACOBIAN_TEST_TOLERANCE,
508 "Mismatch at ({}, {})",
509 r,
510 i
511 );
512 }
513 }
514 Ok(())
515 }
516
517 #[test]
518 fn test_jacobian_intrinsics_numerical() -> TestResult {
519 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
520 let distortion = DistortionModel::FOV { w: 1.5 };
521 let camera = FovCamera::new(pinhole, distortion)?;
522
523 let p_cam = Vector3::new(0.1, 0.2, 1.0);
524
525 let jac_analytical = camera.jacobian_intrinsics(&p_cam);
526 let params: DVector<f64> = (&camera).into();
527 let eps = crate::NUMERICAL_DERIVATIVE_EPS;
528
529 for i in 0..5 {
530 let mut params_plus = params.clone();
531 let mut params_minus = params.clone();
532 params_plus[i] += eps;
533 params_minus[i] -= eps;
534
535 let cam_plus = FovCamera::try_from(params_plus.as_slice())?;
536 let cam_minus = FovCamera::try_from(params_minus.as_slice())?;
537
538 let uv_plus = cam_plus.project(&p_cam)?;
539 let uv_minus = cam_minus.project(&p_cam)?;
540 let num_jac = (uv_plus - uv_minus) / (2.0 * eps);
541
542 for r in 0..2 {
543 assert!(
544 jac_analytical[(r, i)].is_finite(),
545 "Jacobian [{r},{i}] is not finite"
546 );
547 let diff = (jac_analytical[(r, i)] - num_jac[r]).abs();
548 assert!(diff < 1e-4, "Mismatch at ({}, {})", r, i);
549 }
550 }
551 Ok(())
552 }
553
554 #[test]
555 fn test_fov_from_into_traits() -> TestResult {
556 let pinhole = PinholeParams::new(400.0, 410.0, 320.0, 240.0)?;
557 let distortion = DistortionModel::FOV { w: 1.8 };
558 let camera = FovCamera::new(pinhole, distortion)?;
559
560 let params: DVector<f64> = (&camera).into();
562 assert_eq!(params.len(), 5);
563 assert_eq!(params[0], 400.0);
564 assert_eq!(params[1], 410.0);
565 assert_eq!(params[2], 320.0);
566 assert_eq!(params[3], 240.0);
567 assert_eq!(params[4], 1.8);
568
569 let arr: [f64; 5] = (&camera).into();
571 assert_eq!(arr, [400.0, 410.0, 320.0, 240.0, 1.8]);
572
573 let params_slice = [450.0, 460.0, 330.0, 250.0, 2.0];
575 let camera2 = FovCamera::try_from(¶ms_slice[..])?;
576 assert_eq!(camera2.pinhole.fx, 450.0);
577 assert_eq!(camera2.pinhole.fy, 460.0);
578 assert_eq!(camera2.pinhole.cx, 330.0);
579 assert_eq!(camera2.pinhole.cy, 250.0);
580 assert_eq!(camera2.distortion_params(), 2.0);
581
582 let camera3 = FovCamera::from([500.0, 510.0, 340.0, 260.0, 2.5]);
584 assert_eq!(camera3.pinhole.fx, 500.0);
585 assert_eq!(camera3.pinhole.fy, 510.0);
586 assert_eq!(camera3.distortion_params(), 2.5);
587
588 Ok(())
589 }
590
591 #[test]
592 fn test_linear_estimation() -> TestResult {
593 let gt_pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
595 let gt_distortion = DistortionModel::FOV { w: 1.0 };
596 let gt_camera = FovCamera::new(gt_pinhole, gt_distortion)?;
597
598 let n_points = 50;
600 let mut pts_3d = Matrix3xX::zeros(n_points);
601 let mut pts_2d = Matrix2xX::zeros(n_points);
602 let mut valid = 0;
603
604 for i in 0..n_points {
605 let angle = i as f64 * 2.0 * std::f64::consts::PI / n_points as f64;
606 let r = 0.1 + 0.3 * (i as f64 / n_points as f64);
607 let p3d = Vector3::new(r * angle.cos(), r * angle.sin(), 1.0);
608
609 if let Ok(p2d) = gt_camera.project(&p3d) {
610 pts_3d.set_column(valid, &p3d);
611 pts_2d.set_column(valid, &p2d);
612 valid += 1;
613 }
614 }
615 let pts_3d = pts_3d.columns(0, valid).into_owned();
616 let pts_2d = pts_2d.columns(0, valid).into_owned();
617
618 let init_pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
620 let init_distortion = DistortionModel::FOV { w: 0.5 };
621 let mut camera = FovCamera::new(init_pinhole, init_distortion)?;
622
623 camera.linear_estimation(&pts_3d, &pts_2d)?;
624
625 for i in 0..valid {
627 let col = pts_3d.column(i);
628 let projected = camera.project(&Vector3::new(col[0], col[1], col[2]))?;
629 let err = ((projected.x - pts_2d[(0, i)]).powi(2)
630 + (projected.y - pts_2d[(1, i)]).powi(2))
631 .sqrt();
632 assert!(err < 5.0, "Reprojection error too large: {err}");
633 }
634
635 Ok(())
636 }
637
638 #[test]
639 fn test_project_unproject_round_trip() -> TestResult {
640 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
641 let distortion = DistortionModel::FOV { w: 1.5 };
642 let camera = FovCamera::new(pinhole, distortion)?;
643
644 let test_points = [
645 Vector3::new(0.1, 0.2, 1.0),
646 Vector3::new(-0.3, 0.1, 2.0),
647 Vector3::new(0.05, -0.1, 0.5),
648 ];
649
650 for p_cam in &test_points {
651 let uv = camera.project(p_cam)?;
652 let ray = camera.unproject(&uv)?;
653 let dot = ray.dot(&p_cam.normalize());
654 assert!(
655 (dot - 1.0).abs() < 1e-6,
656 "Round-trip failed: dot={dot}, expected ~1.0"
657 );
658 }
659
660 Ok(())
661 }
662
663 #[test]
664 fn test_project_returns_error_behind_camera() -> TestResult {
665 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
666 let distortion = DistortionModel::FOV { w: 1.5 };
667 let camera = FovCamera::new(pinhole, distortion)?;
668 assert!(camera.project(&Vector3::new(0.0, 0.0, -1.0)).is_err());
669 Ok(())
670 }
671
672 #[test]
673 fn test_project_at_min_depth_boundary() -> TestResult {
674 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
675 let distortion = DistortionModel::FOV { w: 1.5 };
676 let camera = FovCamera::new(pinhole, distortion)?;
677 let p_min = Vector3::new(0.0, 0.0, crate::MIN_DEPTH);
678 if let Ok(uv) = camera.project(&p_min) {
679 assert!(uv.x.is_finite() && uv.y.is_finite());
680 }
681 Ok(())
682 }
683
684 #[test]
685 fn test_projection_off_axis() -> TestResult {
686 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
687 let distortion = DistortionModel::FOV { w: 1.5 };
688 let camera = FovCamera::new(pinhole, distortion)?;
689 let p_cam = Vector3::new(0.3, 0.0, 1.0);
690 let uv = camera.project(&p_cam)?;
691 assert!(
692 uv.x > 320.0,
693 "off-axis point should project right of principal point"
694 );
695 assert!(
696 (uv.y - 240.0).abs() < 1.0,
697 "y should be close to cy for horizontal offset"
698 );
699 Ok(())
700 }
701
702 #[test]
703 fn test_unproject_center_pixel() -> TestResult {
704 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
705 let distortion = DistortionModel::FOV { w: 1.5 };
706 let camera = FovCamera::new(pinhole, distortion)?;
707 let uv = Vector2::new(320.0, 240.0);
708 let ray = camera.unproject(&uv)?;
709 assert!(ray.x.abs() < 1e-6, "x should be ~0, got {}", ray.x);
710 assert!(ray.y.abs() < 1e-6, "y should be ~0, got {}", ray.y);
711 assert!((ray.z - 1.0).abs() < 1e-6, "z should be ~1, got {}", ray.z);
712 Ok(())
713 }
714
715 #[test]
716 fn test_batch_projection_matches_individual() -> TestResult {
717 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
718 let distortion = DistortionModel::FOV { w: 1.5 };
719 let camera = FovCamera::new(pinhole, distortion)?;
720 let pts = Matrix3xX::from_columns(&[
721 Vector3::new(0.0, 0.0, 1.0),
722 Vector3::new(0.3, 0.2, 1.5),
723 Vector3::new(-0.4, 0.1, 2.0),
724 ]);
725 let batch = camera.project_batch(&pts);
726 for i in 0..3 {
727 let col = pts.column(i);
728 let p = camera.project(&Vector3::new(col[0], col[1], col[2]))?;
729 assert!(
730 (batch[(0, i)] - p.x).abs() < 1e-10,
731 "batch u mismatch at col {i}"
732 );
733 assert!(
734 (batch[(1, i)] - p.y).abs() < 1e-10,
735 "batch v mismatch at col {i}"
736 );
737 }
738 Ok(())
739 }
740
741 #[test]
742 fn test_jacobian_dimensions() -> TestResult {
743 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
744 let distortion = DistortionModel::FOV { w: 1.5 };
745 let camera = FovCamera::new(pinhole, distortion)?;
746 let p_cam = Vector3::new(0.1, 0.2, 1.0);
747 let jac_point = camera.jacobian_point(&p_cam);
748 assert_eq!(jac_point.nrows(), 2);
749 assert_eq!(jac_point.ncols(), 3);
750 let jac_intr = camera.jacobian_intrinsics(&p_cam);
751 assert_eq!(jac_intr.nrows(), 2);
752 assert_eq!(jac_intr.ncols(), 5); Ok(())
754 }
755}