1use crate::{CameraModel, CameraModelError, DistortionModel, PinholeParams};
44use nalgebra::{DVector, SMatrix, Vector2, Vector3};
45
46#[derive(Debug, Clone, Copy, PartialEq)]
48pub struct EucmCamera {
49 pub pinhole: PinholeParams,
50 pub distortion: DistortionModel,
51}
52
53impl EucmCamera {
54 pub fn new(
69 pinhole: PinholeParams,
70 distortion: DistortionModel,
71 ) -> Result<Self, CameraModelError> {
72 let camera = Self {
73 pinhole,
74 distortion,
75 };
76 camera.validate_params()?;
77 Ok(camera)
78 }
79
80 fn distortion_params(&self) -> (f64, f64) {
87 match self.distortion {
88 DistortionModel::EUCM { alpha, beta } => (alpha, beta),
89 _ => (0.0, 0.0),
90 }
91 }
92
93 fn check_projection_condition(&self, z: f64, denom: f64) -> bool {
104 let (alpha, _) = self.distortion_params();
105 let mut condition = true;
106 if alpha > 0.5 {
107 let c = (alpha - 1.0) / (2.0 * alpha - 1.0);
108 if z < denom * c {
109 condition = false;
110 }
111 }
112 condition
113 }
114
115 fn check_unprojection_condition(&self, r_squared: f64) -> bool {
125 let (alpha, beta) = self.distortion_params();
126 let mut condition = true;
127 if alpha > 0.5 && r_squared > (1.0 / beta * (2.0 * alpha - 1.0)) {
128 condition = false;
129 }
130 condition
131 }
132
133 pub fn linear_estimation(
148 &mut self,
149 points_3d: &nalgebra::Matrix3xX<f64>,
150 points_2d: &nalgebra::Matrix2xX<f64>,
151 ) -> Result<(), CameraModelError> {
152 if points_2d.ncols() != points_3d.ncols() {
153 return Err(CameraModelError::InvalidParams(
154 "Number of 2D and 3D points must match".to_string(),
155 ));
156 }
157
158 let num_points = points_2d.ncols();
159 if num_points < 1 {
160 return Err(CameraModelError::InvalidParams(
161 "Need at least 1 point for EUCM linear estimation".to_string(),
162 ));
163 }
164
165 let mut a = nalgebra::DMatrix::zeros(num_points * 2, 1);
166 let mut b = nalgebra::DVector::zeros(num_points * 2);
167
168 for i in 0..num_points {
169 let x = points_3d[(0, i)];
170 let y = points_3d[(1, i)];
171 let z = points_3d[(2, i)];
172 let u = points_2d[(0, i)];
173 let v = points_2d[(1, i)];
174
175 let d = (x * x + y * y + z * z).sqrt();
176 let u_cx = u - self.pinhole.cx;
177 let v_cy = v - self.pinhole.cy;
178
179 a[(i * 2, 0)] = u_cx * (d - z);
180 a[(i * 2 + 1, 0)] = v_cy * (d - z);
181
182 b[i * 2] = self.pinhole.fx * x - u_cx * z;
183 b[i * 2 + 1] = self.pinhole.fy * y - v_cy * z;
184 }
185
186 let svd = a.svd(true, true);
187 let solution = match svd.solve(&b, 1e-10) {
188 Ok(sol) => sol,
189 Err(err_msg) => {
190 return Err(CameraModelError::NumericalError {
191 operation: "svd_solve".to_string(),
192 details: err_msg.to_string(),
193 });
194 }
195 };
196
197 self.distortion = DistortionModel::EUCM {
199 alpha: solution[0],
200 beta: 1.0,
201 };
202
203 self.validate_params()?;
205
206 Ok(())
207 }
208}
209
210impl From<&EucmCamera> for DVector<f64> {
216 fn from(camera: &EucmCamera) -> Self {
217 let (alpha, beta) = camera.distortion_params();
218 DVector::from_vec(vec![
219 camera.pinhole.fx,
220 camera.pinhole.fy,
221 camera.pinhole.cx,
222 camera.pinhole.cy,
223 alpha,
224 beta,
225 ])
226 }
227}
228
229impl From<&EucmCamera> for [f64; 6] {
235 fn from(camera: &EucmCamera) -> Self {
236 let (alpha, beta) = camera.distortion_params();
237 [
238 camera.pinhole.fx,
239 camera.pinhole.fy,
240 camera.pinhole.cx,
241 camera.pinhole.cy,
242 alpha,
243 beta,
244 ]
245 }
246}
247
248impl TryFrom<&[f64]> for EucmCamera {
258 type Error = CameraModelError;
259
260 fn try_from(params: &[f64]) -> Result<Self, Self::Error> {
261 if params.len() < 6 {
262 return Err(CameraModelError::InvalidParams(format!(
263 "EucmCamera requires at least 6 parameters, got {}",
264 params.len()
265 )));
266 }
267 Ok(Self {
268 pinhole: PinholeParams {
269 fx: params[0],
270 fy: params[1],
271 cx: params[2],
272 cy: params[3],
273 },
274 distortion: DistortionModel::EUCM {
275 alpha: params[4],
276 beta: params[5],
277 },
278 })
279 }
280}
281
282impl From<[f64; 6]> for EucmCamera {
288 fn from(params: [f64; 6]) -> Self {
289 Self {
290 pinhole: PinholeParams {
291 fx: params[0],
292 fy: params[1],
293 cx: params[2],
294 cy: params[3],
295 },
296 distortion: DistortionModel::EUCM {
297 alpha: params[4],
298 beta: params[5],
299 },
300 }
301 }
302}
303
304pub fn try_from_params(params: &[f64]) -> Result<EucmCamera, CameraModelError> {
314 let camera = EucmCamera::try_from(params)?;
315 camera.validate_params()?;
316 Ok(camera)
317}
318
319impl CameraModel for EucmCamera {
320 const INTRINSIC_DIM: usize = 6;
321 type IntrinsicJacobian = SMatrix<f64, 2, 6>;
322 type PointJacobian = SMatrix<f64, 2, 3>;
323
324 fn project(&self, p_cam: &Vector3<f64>) -> Result<Vector2<f64>, CameraModelError> {
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 if denom < crate::GEOMETRIC_PRECISION {
357 return Err(CameraModelError::DenominatorTooSmall {
358 denom,
359 threshold: crate::GEOMETRIC_PRECISION,
360 });
361 }
362
363 if !self.check_projection_condition(z, denom) {
364 return Err(CameraModelError::PointBehindCamera {
365 z,
366 min_z: crate::GEOMETRIC_PRECISION,
367 });
368 }
369
370 Ok(Vector2::new(
371 self.pinhole.fx * x / denom + self.pinhole.cx,
372 self.pinhole.fy * y / denom + self.pinhole.cy,
373 ))
374 }
375
376 fn unproject(&self, point_2d: &Vector2<f64>) -> Result<Vector3<f64>, CameraModelError> {
395 let u = point_2d.x;
396 let v = point_2d.y;
397
398 let (alpha, beta) = self.distortion_params();
399 let mx = (u - self.pinhole.cx) / self.pinhole.fx;
400 let my = (v - self.pinhole.cy) / self.pinhole.fy;
401
402 let r2 = mx * mx + my * my;
403 let beta_r2 = beta * r2;
404
405 let gamma = 1.0 - alpha;
406 let gamma_sq = gamma * gamma;
407
408 let discriminant = beta_r2 * gamma_sq + gamma_sq;
409 if discriminant < 0.0 || !self.check_unprojection_condition(r2) {
410 return Err(CameraModelError::PointOutsideImage { x: u, y: v });
411 }
412
413 let sqrt_disc = discriminant.sqrt();
414 let denom = beta_r2 + 1.0;
415
416 if denom.abs() < crate::GEOMETRIC_PRECISION {
417 return Err(CameraModelError::NumericalError {
418 operation: "unprojection".to_string(),
419 details: "Division by near-zero in EUCM unprojection".to_string(),
420 });
421 }
422
423 let mz = (gamma * sqrt_disc) / denom;
424
425 let point3d = Vector3::new(mx, my, mz);
426 Ok(point3d.normalize())
427 }
428
429 fn jacobian_point(&self, p_cam: &Vector3<f64>) -> Self::PointJacobian {
515 let x = p_cam[0];
516 let y = p_cam[1];
517 let z = p_cam[2];
518
519 let (alpha, beta) = self.distortion_params();
520 let r2 = x * x + y * y;
521 let d = (beta * r2 + z * z).sqrt();
522 let denom = alpha * d + (1.0 - alpha) * z;
523
524 let dd_dx = beta * x / d;
526 let dd_dy = beta * y / d;
527 let dd_dz = z / d;
528
529 let ddenom_dx = alpha * dd_dx;
531 let ddenom_dy = alpha * dd_dy;
532 let ddenom_dz = alpha * dd_dz + (1.0 - alpha);
533
534 let denom2 = denom * denom;
535
536 let du_dx = self.pinhole.fx * (denom - x * ddenom_dx) / denom2;
538 let du_dy = self.pinhole.fx * (-x * ddenom_dy) / denom2;
539 let du_dz = self.pinhole.fx * (-x * ddenom_dz) / denom2;
540
541 let dv_dx = self.pinhole.fy * (-y * ddenom_dx) / denom2;
542 let dv_dy = self.pinhole.fy * (denom - y * ddenom_dy) / denom2;
543 let dv_dz = self.pinhole.fy * (-y * ddenom_dz) / denom2;
544
545 SMatrix::<f64, 2, 3>::new(du_dx, du_dy, du_dz, dv_dx, dv_dy, dv_dz)
546 }
547
548 fn jacobian_intrinsics(&self, p_cam: &Vector3<f64>) -> Self::IntrinsicJacobian {
653 let x = p_cam[0];
654 let y = p_cam[1];
655 let z = p_cam[2];
656
657 let (alpha, beta) = self.distortion_params();
658 let r2 = x * x + y * y;
659 let d = (beta * r2 + z * z).sqrt();
660 let denom = alpha * d + (1.0 - alpha) * z;
661
662 let x_norm = x / denom;
663 let y_norm = y / denom;
664
665 let ddenom_dalpha = d - z;
670
671 let dd_dbeta = r2 / (2.0 * d);
672 let ddenom_dbeta = alpha * dd_dbeta;
673
674 let du_dalpha = -self.pinhole.fx * x * ddenom_dalpha / (denom * denom);
675 let dv_dalpha = -self.pinhole.fy * y * ddenom_dalpha / (denom * denom);
676
677 let du_dbeta = -self.pinhole.fx * x * ddenom_dbeta / (denom * denom);
678 let dv_dbeta = -self.pinhole.fy * y * ddenom_dbeta / (denom * denom);
679
680 SMatrix::<f64, 2, 6>::new(
681 x_norm, 0.0, 1.0, 0.0, du_dalpha, du_dbeta, 0.0, y_norm, 0.0, 1.0, dv_dalpha, dv_dbeta,
682 )
683 }
684
685 fn validate_params(&self) -> Result<(), CameraModelError> {
699 self.pinhole.validate()?;
700 self.get_distortion().validate()
701 }
702
703 fn get_pinhole_params(&self) -> PinholeParams {
709 PinholeParams {
710 fx: self.pinhole.fx,
711 fy: self.pinhole.fy,
712 cx: self.pinhole.cx,
713 cy: self.pinhole.cy,
714 }
715 }
716
717 fn get_distortion(&self) -> DistortionModel {
723 self.distortion
724 }
725
726 fn get_model_name(&self) -> &'static str {
732 "eucm"
733 }
734}
735
736#[cfg(test)]
737mod tests {
738 use super::*;
739 use nalgebra::{Matrix2xX, Matrix3xX};
740
741 type TestResult = Result<(), Box<dyn std::error::Error>>;
742
743 #[test]
744 fn test_eucm_camera_creation() -> TestResult {
745 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
746 let distortion = DistortionModel::EUCM {
747 alpha: 0.5,
748 beta: 1.0,
749 };
750 let camera = EucmCamera::new(pinhole, distortion)?;
751
752 assert_eq!(camera.pinhole.fx, 300.0);
753 assert_eq!(camera.distortion_params(), (0.5, 1.0));
754 Ok(())
755 }
756
757 #[test]
758 fn test_projection_at_optical_axis() -> TestResult {
759 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
760 let distortion = DistortionModel::EUCM {
761 alpha: 0.5,
762 beta: 1.0,
763 };
764 let camera = EucmCamera::new(pinhole, distortion)?;
765
766 let p_cam = Vector3::new(0.0, 0.0, 1.0);
767 let uv = camera.project(&p_cam)?;
768
769 assert!((uv.x - 320.0).abs() < crate::PROJECTION_TEST_TOLERANCE);
770 assert!((uv.y - 240.0).abs() < crate::PROJECTION_TEST_TOLERANCE);
771
772 Ok(())
773 }
774
775 #[test]
776 fn test_jacobian_point_numerical() -> TestResult {
777 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
778 let distortion = DistortionModel::EUCM {
779 alpha: 0.6,
780 beta: 1.2,
781 };
782 let camera = EucmCamera::new(pinhole, distortion)?;
783
784 let p_cam = Vector3::new(0.1, 0.2, 1.0);
785
786 let jac_analytical = camera.jacobian_point(&p_cam);
787 let eps = crate::NUMERICAL_DERIVATIVE_EPS;
788
789 for i in 0..3 {
790 let mut p_plus = p_cam;
791 let mut p_minus = p_cam;
792 p_plus[i] += eps;
793 p_minus[i] -= eps;
794
795 let uv_plus = camera.project(&p_plus)?;
796 let uv_minus = camera.project(&p_minus)?;
797 let num_jac = (uv_plus - uv_minus) / (2.0 * eps);
798
799 for r in 0..2 {
800 assert!(
801 jac_analytical[(r, i)].is_finite(),
802 "Jacobian [{r},{i}] is not finite"
803 );
804 let diff = (jac_analytical[(r, i)] - num_jac[r]).abs();
805 assert!(
806 diff < crate::JACOBIAN_TEST_TOLERANCE,
807 "Mismatch at ({}, {})",
808 r,
809 i
810 );
811 }
812 }
813 Ok(())
814 }
815
816 #[test]
817 fn test_jacobian_intrinsics_numerical() -> TestResult {
818 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
819 let distortion = DistortionModel::EUCM {
820 alpha: 0.6,
821 beta: 1.2,
822 };
823 let camera = EucmCamera::new(pinhole, distortion)?;
824
825 let p_cam = Vector3::new(0.1, 0.2, 1.0);
826
827 let jac_analytical = camera.jacobian_intrinsics(&p_cam);
828 let params: DVector<f64> = (&camera).into();
829 let eps = crate::NUMERICAL_DERIVATIVE_EPS;
830
831 for i in 0..6 {
832 let mut params_plus = params.clone();
833 let mut params_minus = params.clone();
834 params_plus[i] += eps;
835 params_minus[i] -= eps;
836
837 let cam_plus = EucmCamera::try_from(params_plus.as_slice())?;
838 let cam_minus = EucmCamera::try_from(params_minus.as_slice())?;
839
840 let uv_plus = cam_plus.project(&p_cam)?;
841 let uv_minus = cam_minus.project(&p_cam)?;
842 let num_jac = (uv_plus - uv_minus) / (2.0 * eps);
843
844 for r in 0..2 {
845 assert!(
846 jac_analytical[(r, i)].is_finite(),
847 "Jacobian [{r},{i}] is not finite"
848 );
849 let diff = (jac_analytical[(r, i)] - num_jac[r]).abs();
850 assert!(
851 diff < crate::JACOBIAN_TEST_TOLERANCE,
852 "Mismatch at ({}, {})",
853 r,
854 i
855 );
856 }
857 }
858 Ok(())
859 }
860
861 #[test]
862 fn test_eucm_from_into_traits() -> TestResult {
863 let pinhole = PinholeParams::new(400.0, 410.0, 320.0, 240.0)?;
864 let distortion = DistortionModel::EUCM {
865 alpha: 0.7,
866 beta: 1.5,
867 };
868 let camera = EucmCamera::new(pinhole, distortion)?;
869
870 let params: DVector<f64> = (&camera).into();
872 assert_eq!(params.len(), 6);
873 assert_eq!(params[0], 400.0);
874 assert_eq!(params[1], 410.0);
875 assert_eq!(params[2], 320.0);
876 assert_eq!(params[3], 240.0);
877 assert_eq!(params[4], 0.7);
878 assert_eq!(params[5], 1.5);
879
880 let arr: [f64; 6] = (&camera).into();
882 assert_eq!(arr, [400.0, 410.0, 320.0, 240.0, 0.7, 1.5]);
883
884 let params_slice = [450.0, 460.0, 330.0, 250.0, 0.8, 1.8];
886 let camera2 = EucmCamera::try_from(¶ms_slice[..])?;
887 assert_eq!(camera2.pinhole.fx, 450.0);
888 assert_eq!(camera2.pinhole.fy, 460.0);
889 assert_eq!(camera2.pinhole.cx, 330.0);
890 assert_eq!(camera2.pinhole.cy, 250.0);
891 assert_eq!(camera2.distortion_params(), (0.8, 1.8));
892
893 let camera3 = EucmCamera::from([500.0, 510.0, 340.0, 260.0, 0.9, 2.0]);
895 assert_eq!(camera3.pinhole.fx, 500.0);
896 assert_eq!(camera3.pinhole.fy, 510.0);
897 assert_eq!(camera3.distortion_params(), (0.9, 2.0));
898
899 Ok(())
900 }
901
902 #[test]
903 fn test_linear_estimation() -> TestResult {
904 let gt_pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
906 let gt_distortion = DistortionModel::EUCM {
907 alpha: 0.5,
908 beta: 1.0,
909 };
910 let gt_camera = EucmCamera::new(gt_pinhole, gt_distortion)?;
911
912 let n_points = 50;
914 let mut pts_3d = Matrix3xX::zeros(n_points);
915 let mut pts_2d = Matrix2xX::zeros(n_points);
916 let mut valid = 0;
917
918 for i in 0..n_points {
919 let angle = i as f64 * 2.0 * std::f64::consts::PI / n_points as f64;
920 let r = 0.1 + 0.3 * (i as f64 / n_points as f64);
921 let p3d = Vector3::new(r * angle.cos(), r * angle.sin(), 1.0);
922
923 if let Ok(p2d) = gt_camera.project(&p3d) {
924 pts_3d.set_column(valid, &p3d);
925 pts_2d.set_column(valid, &p2d);
926 valid += 1;
927 }
928 }
929 let pts_3d = pts_3d.columns(0, valid).into_owned();
930 let pts_2d = pts_2d.columns(0, valid).into_owned();
931
932 let init_pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
934 let init_distortion = DistortionModel::EUCM {
935 alpha: 0.0,
936 beta: 1.0,
937 };
938 let mut camera = EucmCamera::new(init_pinhole, init_distortion)?;
939
940 camera.linear_estimation(&pts_3d, &pts_2d)?;
941
942 for i in 0..valid {
944 let col = pts_3d.column(i);
945 let projected = camera.project(&Vector3::new(col[0], col[1], col[2]))?;
946 let err = ((projected.x - pts_2d[(0, i)]).powi(2)
947 + (projected.y - pts_2d[(1, i)]).powi(2))
948 .sqrt();
949 assert!(err < 1.0, "Reprojection error too large: {err}");
950 }
951
952 Ok(())
953 }
954
955 #[test]
956 fn test_project_unproject_round_trip() -> TestResult {
957 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
959 let distortion = DistortionModel::EUCM {
960 alpha: 0.5,
961 beta: 1.0,
962 };
963 let camera = EucmCamera::new(pinhole, distortion)?;
964
965 let p_cam = Vector3::new(0.0, 0.0, 1.0);
967 let uv = camera.project(&p_cam)?;
968 let ray = camera.unproject(&uv)?;
969 let dot = ray.dot(&p_cam.normalize());
970 assert!(
971 (dot - 1.0).abs() < 1e-6,
972 "Round-trip failed for on-axis point: dot={dot}, expected ~1.0"
973 );
974
975 Ok(())
976 }
977
978 #[test]
979 fn test_project_returns_error_behind_camera() -> TestResult {
980 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
981 let distortion = DistortionModel::EUCM {
982 alpha: 0.5,
983 beta: 1.0,
984 };
985 let camera = EucmCamera::new(pinhole, distortion)?;
986 assert!(camera.project(&Vector3::new(0.0, 0.0, -1.0)).is_err());
987 Ok(())
988 }
989
990 #[test]
991 fn test_project_at_min_depth_boundary() -> TestResult {
992 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
993 let distortion = DistortionModel::EUCM {
994 alpha: 0.5,
995 beta: 1.0,
996 };
997 let camera = EucmCamera::new(pinhole, distortion)?;
998 let p_min = Vector3::new(0.0, 0.0, crate::MIN_DEPTH);
999 if let Ok(uv) = camera.project(&p_min) {
1000 assert!(uv.x.is_finite() && uv.y.is_finite());
1001 }
1002 Ok(())
1003 }
1004
1005 #[test]
1006 fn test_projection_off_axis() -> TestResult {
1007 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
1008 let distortion = DistortionModel::EUCM {
1009 alpha: 0.5,
1010 beta: 1.0,
1011 };
1012 let camera = EucmCamera::new(pinhole, distortion)?;
1013 let p_cam = Vector3::new(0.3, 0.0, 1.0);
1014 let uv = camera.project(&p_cam)?;
1015 assert!(
1016 uv.x > 320.0,
1017 "off-axis point should project right of principal point"
1018 );
1019 assert!(
1020 (uv.y - 240.0).abs() < 1.0,
1021 "y should be close to cy for horizontal offset"
1022 );
1023 Ok(())
1024 }
1025
1026 #[test]
1027 fn test_unproject_center_pixel() -> TestResult {
1028 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
1029 let distortion = DistortionModel::EUCM {
1030 alpha: 0.5,
1031 beta: 1.0,
1032 };
1033 let camera = EucmCamera::new(pinhole, distortion)?;
1034 let uv = Vector2::new(320.0, 240.0);
1035 let ray = camera.unproject(&uv)?;
1036 assert!(ray.x.abs() < 1e-6, "x should be ~0, got {}", ray.x);
1037 assert!(ray.y.abs() < 1e-6, "y should be ~0, got {}", ray.y);
1038 assert!((ray.z - 1.0).abs() < 1e-6, "z should be ~1, got {}", ray.z);
1039 Ok(())
1040 }
1041
1042 #[test]
1043 fn test_batch_projection_matches_individual() -> TestResult {
1044 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
1045 let distortion = DistortionModel::EUCM {
1046 alpha: 0.5,
1047 beta: 1.0,
1048 };
1049 let camera = EucmCamera::new(pinhole, distortion)?;
1050 let pts = Matrix3xX::from_columns(&[
1051 Vector3::new(0.0, 0.0, 1.0),
1052 Vector3::new(0.3, 0.2, 1.5),
1053 Vector3::new(-0.4, 0.1, 2.0),
1054 ]);
1055 let batch = camera.project_batch(&pts);
1056 for i in 0..3 {
1057 let col = pts.column(i);
1058 let p = camera.project(&Vector3::new(col[0], col[1], col[2]))?;
1059 assert!(
1060 (batch[(0, i)] - p.x).abs() < 1e-10,
1061 "batch u mismatch at col {i}"
1062 );
1063 assert!(
1064 (batch[(1, i)] - p.y).abs() < 1e-10,
1065 "batch v mismatch at col {i}"
1066 );
1067 }
1068 Ok(())
1069 }
1070
1071 #[test]
1072 fn test_jacobian_dimensions() -> TestResult {
1073 let pinhole = PinholeParams::new(300.0, 300.0, 320.0, 240.0)?;
1074 let distortion = DistortionModel::EUCM {
1075 alpha: 0.5,
1076 beta: 1.0,
1077 };
1078 let camera = EucmCamera::new(pinhole, distortion)?;
1079 let p_cam = Vector3::new(0.1, 0.2, 1.0);
1080 let jac_point = camera.jacobian_point(&p_cam);
1081 assert_eq!(jac_point.nrows(), 2);
1082 assert_eq!(jac_point.ncols(), 3);
1083 let jac_intr = camera.jacobian_intrinsics(&p_cam);
1084 assert_eq!(jac_intr.nrows(), 2);
1085 assert_eq!(jac_intr.ncols(), 6); Ok(())
1087 }
1088}