Skip to main content

braid_mvg/
camera.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4#![allow(non_snake_case)]
5
6use serde::Deserialize;
7
8use na::core::dimension::{U1, U2, U3, U4};
9use na::core::{Matrix3, Matrix4, OMatrix, Vector3, Vector5};
10use na::geometry::{Point2, Point3, Rotation3, UnitQuaternion};
11use na::{DefaultAllocator, RealField, allocator::Allocator};
12use nalgebra as na;
13use num_traits::{One, Zero};
14
15use opencv_ros_camera::UndistortedPixels;
16
17use crate::pymvg_support::PymvgCamera;
18use crate::{
19    DistortedPixel, Distortion, ExtrinsicParameters, MvgError, PointWorldFrame, Result,
20    RosOpenCvIntrinsics, UndistortedPixel,
21};
22
23#[derive(Clone, PartialEq)]
24/// A calibrated camera with both intrinsic and extrinsic parameters.
25///
26/// This structure represents a complete camera model including:
27/// - **Intrinsic parameters**: focal length, principal point, distortion
28///   coefficients
29/// - **Extrinsic parameters**: position and orientation in 3D space
30/// - **Image dimensions**: width and height in pixels
31///
32/// The camera follows the standard computer vision coordinate conventions:
33/// - Camera frame: X→right, Y→down, Z→forward (optical axis)
34/// - Image coordinates: origin at top-left, X→right, Y→down
35///
36/// # Mathematical Model
37///
38/// The camera implements the projective camera model:
39/// ```text
40/// s[u v 1]ᵀ = K[R|t][X Y Z 1]ᵀ
41/// ```
42/// where:
43/// - `(X,Y,Z)` are 3D world coordinates
44/// - `(u,v)` are 2D undistorted image coordinates
45/// - `K` is the intrinsic matrix
46/// - `[R|t]` represents rotation and translation (extrinsics)
47/// - `s` is a scaling factor
48///
49/// Lens distortion is supported via the
50/// [`opencv-ros-camera`](https://docs.rs/opencv-ros-camera) crate.
51///
52/// The parameters for the intrinsic matrix (focal length, principal point, and
53/// skew) in addition to the distortion parameters together comprise the
54/// intrinsic parameters, or "intrinsics".
55///
56/// # Example
57///
58/// ```rust
59/// use braid_mvg::{Camera, extrinsics, make_default_intrinsics};
60///
61/// // Create a camera with default parameters
62/// let extrinsics = extrinsics::make_default_extrinsics::<f64>();
63/// let intrinsics = make_default_intrinsics::<f64>();
64/// let camera = Camera::new(640, 480, extrinsics, intrinsics).unwrap();
65///
66/// // Project a 3D point to 2D
67/// use braid_mvg::PointWorldFrame;
68/// use nalgebra::Point3;
69/// let point_3d = PointWorldFrame { coords: Point3::new(0.0, 0.0, 5.0) };
70/// let pixel = camera.project_3d_to_pixel(&point_3d);
71/// ```
72pub struct Camera<R: RealField> {
73    pub(crate) width: usize,
74    pub(crate) height: usize,
75    pub(crate) inner: cam_geom::Camera<R, RosOpenCvIntrinsics<R>>,
76    pub(crate) cache: CameraCache<R>,
77}
78
79impl<R: RealField + Copy> Camera<R> {
80    /// Create a new camera from intrinsic and extrinsic parameters.
81    ///
82    /// This constructor creates a complete camera model by combining:
83    /// - Image dimensions (width, height)
84    /// - Extrinsic parameters (camera pose in world coordinates)
85    /// - Intrinsic parameters (focal length, principal point, distortion)
86    ///
87    /// # Arguments
88    ///
89    /// * `width` - Image width in pixels
90    /// * `height` - Image height in pixels
91    /// * `extrinsics` - Camera position and orientation in world coordinates
92    /// * `intrinsics` - Camera intrinsic parameters including distortion model
93    ///
94    /// # Returns
95    ///
96    /// A new [`Camera`] instance, or [`MvgError`] if the parameters are invalid.
97    ///
98    /// # Errors
99    ///
100    /// Returns an error if:
101    /// - The projection matrix cannot be computed
102    /// - The camera parameters are mathematically inconsistent
103    /// - SVD decomposition fails during initialization
104    ///
105    /// # Example
106    ///
107    /// ```rust
108    /// use braid_mvg::{Camera, extrinsics, make_default_intrinsics};
109    ///
110    /// let extrinsics = extrinsics::make_default_extrinsics::<f64>();
111    /// let intrinsics = make_default_intrinsics::<f64>();
112    /// let camera = Camera::new(640, 480, extrinsics, intrinsics)?;
113    /// # Ok::<(), braid_mvg::MvgError>(())
114    /// ```
115    pub fn new(
116        width: usize,
117        height: usize,
118        extrinsics: ExtrinsicParameters<R>,
119        intrinsics: RosOpenCvIntrinsics<R>,
120    ) -> Result<Self> {
121        let inner = cam_geom::Camera::new(intrinsics, extrinsics);
122        Self::new_from_cam_geom(width, height, inner)
123    }
124
125    /// Create a new camera from a cam-geom Camera instance.
126    ///
127    /// This constructor wraps an existing cam-geom Camera with additional
128    /// image dimension information and caching for performance.
129    ///
130    /// # Arguments
131    ///
132    /// * `width` - Image width in pixels
133    /// * `height` - Image height in pixels
134    /// * `inner` - A pre-constructed cam-geom Camera instance
135    ///
136    /// # Returns
137    ///
138    /// A new [`Camera`] instance, or [`MvgError`] if the camera cannot be constructed.
139    ///
140    /// # Example
141    ///
142    /// ```rust
143    /// use braid_mvg::{Camera, extrinsics, make_default_intrinsics};
144    /// use cam_geom;
145    ///
146    /// let extrinsics = extrinsics::make_default_extrinsics::<f64>();
147    /// let intrinsics = make_default_intrinsics::<f64>();
148    /// let cam_geom_camera = cam_geom::Camera::new(intrinsics, extrinsics);
149    /// let camera = Camera::new_from_cam_geom(640, 480, cam_geom_camera)?;
150    /// # Ok::<(), braid_mvg::MvgError>(())
151    /// ```
152    pub fn new_from_cam_geom(
153        width: usize,
154        height: usize,
155        inner: cam_geom::Camera<R, RosOpenCvIntrinsics<R>>,
156    ) -> Result<Self> {
157        let intrinsics = inner.intrinsics();
158        let extrinsics = inner.extrinsics();
159        let m = {
160            let p33 = intrinsics.p.fixed_view::<3, 3>(0, 0);
161            p33 * extrinsics.matrix()
162        };
163
164        // flip sign if focal length < 0
165        let m = if m[(0, 0)] < na::convert(0.0) { -m } else { m };
166
167        let m = m / m[(2, 3)]; // normalize
168
169        let pinv = my_pinv(&m)?;
170        let cache = CameraCache { m, pinv };
171        Ok(Self {
172            width,
173            height,
174            inner,
175            cache,
176        })
177    }
178
179    /// Create a camera from a 3×4 projection matrix.
180    ///
181    /// This method decomposes a camera projection matrix into intrinsic and extrinsic
182    /// parameters using QR decomposition. It assumes no lens distortion (pinhole model).
183    ///
184    /// # Mathematical Background
185    ///
186    /// The projection matrix P has the form:
187    /// ```text
188    /// P = K[R|t]
189    /// ```
190    /// where K is the 3×3 intrinsic matrix and [R|t] is the 3×4 extrinsic matrix.
191    ///
192    /// # Arguments
193    ///
194    /// * `width` - Image width in pixels
195    /// * `height` - Image height in pixels
196    /// * `pmat` - 3×4 projection matrix
197    ///
198    /// # Returns
199    ///
200    /// A new [`Camera`] instance with no distortion, or [`MvgError`] if decomposition fails.
201    ///
202    /// # Errors
203    ///
204    /// Returns an error if:
205    /// - The projection matrix is singular or ill-conditioned
206    /// - QR decomposition fails
207    /// - The resulting parameters are invalid
208    ///
209    /// # Example
210    ///
211    /// ```rust
212    /// use braid_mvg::Camera;
213    /// use nalgebra::{OMatrix, U3, U4};
214    ///
215    /// // Create a simple projection matrix
216    /// let pmat = OMatrix::<f64, U3, U4>::new(
217    ///     1000.0, 0.0, 320.0, 100.0,
218    ///     0.0, 1000.0, 240.0, 200.0,
219    ///     0.0, 0.0, 1.0, 0.01
220    /// );
221    /// let camera = Camera::from_pmat(640, 480, &pmat)?;
222    /// # Ok::<(), braid_mvg::MvgError>(())
223    /// ```
224    pub fn from_pmat(width: usize, height: usize, pmat: &OMatrix<R, U3, U4>) -> Result<Self> {
225        let distortion = Distortion::zero();
226        Self::from_pmat_with_distortion(width, height, pmat, distortion)
227    }
228
229    fn from_pmat_with_distortion(
230        width: usize,
231        height: usize,
232        pmat: &OMatrix<R, U3, U4>,
233        distortion: Distortion<R>,
234    ) -> Result<Self> {
235        let m = (*pmat).remove_column(3);
236        let (rquat, k) = rq_decomposition(m)?;
237
238        let k22: R = k[(2, 2)];
239
240        let one: R = One::one();
241
242        let k = k * (one / k22); // normalize
243        let fx = k[(0, 0)];
244        let skew = k[(0, 1)];
245        let fy = k[(1, 1)];
246        let cx = k[(0, 2)];
247        let cy = k[(1, 2)];
248
249        let intrinsics =
250            RosOpenCvIntrinsics::from_params_with_distortion(fx, skew, fy, cx, cy, distortion);
251        let camcenter = pmat2cam_center(pmat);
252        let extrinsics = ExtrinsicParameters::from_rotation_and_camcenter(rquat, camcenter);
253
254        Camera::new(width, height, extrinsics, intrinsics)
255    }
256
257    /// convert, if possible, into a 3x4 matrix
258    pub fn as_pmat(&self) -> Option<&OMatrix<R, U3, U4>> {
259        let d = &self.intrinsics().distortion;
260        if d.is_linear() {
261            Some(&self.cache.m)
262        } else {
263            None
264        }
265    }
266
267    /// Get the linear projection matrix (3×4) for this camera.
268    ///
269    /// This returns the cached projection matrix that represents a linearized
270    /// version of the camera model (without lens distortion). The matrix has
271    /// the form P = K[R|t] where K is the intrinsic matrix and [R|t] are
272    /// the extrinsic parameters.
273    ///
274    /// # Returns
275    ///
276    /// Reference to the 3×4 projection matrix
277    ///
278    /// # Example
279    ///
280    /// ```rust
281    /// use braid_mvg::{Camera, extrinsics, make_default_intrinsics};
282    ///
283    /// let camera = Camera::new(640, 480,
284    ///     extrinsics::make_default_extrinsics::<f64>(),
285    ///     make_default_intrinsics::<f64>())?;
286    /// let pmat = camera.linear_part_as_pmat();
287    /// println!("Projection matrix shape: {}×{}", pmat.nrows(), pmat.ncols());
288    /// # Ok::<(), braid_mvg::MvgError>(())
289    /// ```
290    pub fn linear_part_as_pmat(&self) -> &OMatrix<R, U3, U4> {
291        &self.cache.m
292    }
293
294    /// Return a linearized copy of self.
295    ///
296    /// The returned camera will not have distortion. In other words, the raw
297    /// projected ("distorted") pixels are identical with the "undistorted"
298    /// variant. The camera model is a perfect linear pinhole.
299    pub fn linearize_to_cam_geom(
300        &self,
301    ) -> cam_geom::Camera<R, cam_geom::IntrinsicParametersPerspective<R>> {
302        let fx = self.intrinsics().k[(0, 0)];
303        let skew = self.intrinsics().k[(0, 1)];
304        let fy = self.intrinsics().k[(1, 1)];
305        let cx = self.intrinsics().k[(0, 2)];
306        let cy = self.intrinsics().k[(1, 2)];
307
308        let intrinsics =
309            cam_geom::IntrinsicParametersPerspective::from(cam_geom::PerspectiveParams {
310                fx,
311                fy,
312                skew,
313                cx,
314                cy,
315            });
316
317        let pose = self.extrinsics().clone();
318        cam_geom::Camera::new(intrinsics, pose)
319    }
320
321    /// Transform this camera using a similarity transformation.
322    ///
323    /// This method applies a similarity transformation (scale, rotation, translation)
324    /// to align the camera coordinate system. This is commonly used in:
325    /// - Multi-camera system calibration
326    /// - Coordinate system alignment
327    /// - Scale recovery in structure-from-motion
328    ///
329    /// # Mathematical Details
330    ///
331    /// The transformation applies: `X' = s*R*X + t` where:
332    /// - `s` is the uniform scale factor
333    /// - `R` is the 3×3 rotation matrix
334    /// - `t` is the 3×1 translation vector
335    /// - `X` are the original 3D points
336    ///
337    /// # Arguments
338    ///
339    /// * `s` - Uniform scale factor (positive)
340    /// * `rot` - 3×3 rotation matrix (must be orthogonal with det=1)
341    /// * `t` - 3×1 translation vector
342    ///
343    /// # Returns
344    ///
345    /// A new aligned [`Camera`] instance, or [`MvgError`] if transformation fails.
346    ///
347    /// # Errors
348    ///
349    /// Returns an error if:
350    /// - The rotation matrix is invalid (not orthogonal or det≠1)
351    /// - The scale factor is non-positive
352    /// - Camera reconstruction fails after transformation
353    ///
354    /// # Example
355    ///
356    /// ```rust
357    /// use braid_mvg::{Camera, extrinsics, make_default_intrinsics};
358    /// use nalgebra::{Matrix3, Vector3};
359    ///
360    /// let camera = Camera::new(640, 480,
361    ///     extrinsics::make_default_extrinsics::<f64>(),
362    ///     make_default_intrinsics::<f64>())?;
363    ///
364    /// let scale = 2.0;
365    /// let rotation = Matrix3::identity();
366    /// let translation = Vector3::new(1.0, 0.0, 0.0);
367    ///
368    /// let aligned_camera = camera.align(scale, rotation, translation)?;
369    /// # Ok::<(), braid_mvg::MvgError>(())
370    /// ```
371    pub fn align(&self, s: R, rot: Matrix3<R>, t: Vector3<R>) -> Result<Self> {
372        let m = build_xform(s, rot, t);
373        let mi = my_pinv_4x4(&m)?;
374
375        let pmat = &self.cache.m;
376        let aligned_pmat = pmat * mi;
377
378        Self::from_pmat_with_distortion(
379            self.width,
380            self.height,
381            &aligned_pmat,
382            self.intrinsics().distortion.clone(),
383        )
384    }
385
386    /// return a copy of this camera looking in the opposite direction
387    ///
388    /// The returned camera has the same 3D->2D projection. (The 2D->3D
389    /// projection results in a vector in the opposite direction.)
390    pub fn flip(&self) -> Option<Camera<R>> {
391        use crate::intrinsics::{MirrorAxis::LeftRight, mirror};
392        if !self.intrinsics().rect.is_identity(na::convert(1.0e-7)) {
393            return None;
394        }
395
396        let cc = self.extrinsics().camcenter();
397
398        let lv = self.extrinsics().forward();
399        let lv2 = -lv;
400        let la2 = cc.coords + lv2.as_ref();
401
402        let up = self.extrinsics().up();
403        let up2 = -up;
404
405        let extrinsics2 = crate::ExtrinsicParameters::from_view(&cc.coords, &la2, &up2);
406        let mut intinsics2 = mirror(self.intrinsics(), LeftRight)?;
407
408        intinsics2.p[(0, 1)] = -intinsics2.p[(0, 1)];
409        intinsics2.k[(0, 1)] = -intinsics2.k[(0, 1)];
410
411        let mut d = intinsics2.distortion.clone();
412        *d.tangential2_mut() = -d.tangential2();
413
414        Some(Camera::new(self.width(), self.height(), extrinsics2, intinsics2).unwrap())
415    }
416
417    /// Get the camera's intrinsic parameters.
418    #[inline]
419    pub fn intrinsics(&self) -> &RosOpenCvIntrinsics<R> {
420        self.inner.intrinsics()
421    }
422
423    /// Get the camera's extrinsic parameters.
424    #[inline]
425    pub fn extrinsics(&self) -> &ExtrinsicParameters<R> {
426        self.inner.extrinsics()
427    }
428
429    /// Convert this camera to PyMVG format.
430    ///
431    /// PyMVG is a Python library for multiple view geometry. This method converts
432    /// the camera parameters to the PyMVG JSON schema format for interoperability.
433    ///
434    /// # Arguments
435    ///
436    /// * `name` - Name to assign to the camera in the PyMVG format
437    ///
438    /// # Returns
439    ///
440    /// A [`PymvgCamera`] struct containing the camera parameters in PyMVG format
441    ///
442    /// # Example
443    ///
444    /// ```rust
445    /// use braid_mvg::{Camera, extrinsics, make_default_intrinsics};
446    ///
447    /// let camera = Camera::new(640, 480,
448    ///     extrinsics::make_default_extrinsics::<f64>(),
449    ///     make_default_intrinsics::<f64>())?;
450    /// let pymvg_camera = camera.to_pymvg("camera1");
451    /// # Ok::<(), braid_mvg::MvgError>(())
452    /// ```
453    pub fn to_pymvg(&self, name: &str) -> PymvgCamera<R> {
454        let d = &self.intrinsics().distortion;
455        let dvec = Vector5::new(
456            d.radial1(),
457            d.radial2(),
458            d.tangential1(),
459            d.tangential2(),
460            d.radial3(),
461        );
462        PymvgCamera {
463            name: name.to_string(),
464            width: self.width,
465            height: self.height,
466            P: self.intrinsics().p,
467            K: self.intrinsics().k,
468            D: dvec,
469            R: self.intrinsics().rect,
470            Q: *self.extrinsics().rotation().matrix(),
471            translation: *self.extrinsics().translation(),
472        }
473    }
474
475    pub(crate) fn from_pymvg(cam: &PymvgCamera<R>) -> Result<(String, Self)> {
476        let name = cam.name.clone();
477
478        let rquat = right_handed_rotation_quat_new(&cam.Q)?;
479        let extrinsics = crate::extrinsics::from_rquat_translation(rquat, cam.translation);
480        let distortion = Distortion::from_opencv_vec(cam.D);
481        let intrinsics = RosOpenCvIntrinsics::from_components(cam.P, cam.K, distortion, cam.R)?;
482        let cam = Self::new(cam.width, cam.height, extrinsics, intrinsics)?;
483        Ok((name, cam))
484    }
485
486    /// Get the image width in pixels.
487    #[inline]
488    pub fn width(&self) -> usize {
489        self.width
490    }
491
492    /// Get the image height in pixels.
493    #[inline]
494    pub fn height(&self) -> usize {
495        self.height
496    }
497
498    /// Project a 3D world point to undistorted 2D image coordinates.
499    ///
500    /// This method performs the core camera projection operation, transforming
501    /// a 3D point in world coordinates to 2D pixel coordinates. The result
502    /// represents the undistorted pixel coordinates (as if using a perfect
503    /// pinhole camera model).
504    pub fn project_3d_to_pixel(&self, pt3d: &PointWorldFrame<R>) -> UndistortedPixel<R> {
505        let coords: Point3<R> = pt3d.coords;
506
507        let cc = self.cache.m * coords.to_homogeneous();
508        UndistortedPixel {
509            coords: Point2::new(cc[0] / cc[2], cc[1] / cc[2]),
510        }
511    }
512
513    /// Project a 3D world point to distorted 2D image coordinates.
514    ///
515    /// This method projects a 3D point to 2D image coordinates and then applies
516    /// lens distortion to get the actual pixel coordinates as they would appear
517    /// in the raw camera image.
518    ///
519    /// # Arguments
520    ///
521    /// * `pt3d` - 3D point in world coordinates
522    ///
523    /// # Returns
524    ///
525    /// [`DistortedPixel`] containing the 2D image coordinates with distortion applied
526    ///
527    /// # Example
528    ///
529    /// ```rust
530    /// use braid_mvg::{Camera, PointWorldFrame, extrinsics, make_default_intrinsics};
531    /// use nalgebra::Point3;
532    ///
533    /// let camera = Camera::new(640, 480,
534    ///     extrinsics::make_default_extrinsics::<f64>(),
535    ///     make_default_intrinsics::<f64>())?;
536    /// let point_3d = PointWorldFrame { coords: Point3::new(0.0, 0.0, 5.0) };
537    /// let distorted_pixel = camera.project_3d_to_distorted_pixel(&point_3d);
538    /// # Ok::<(), braid_mvg::MvgError>(())
539    /// ```
540    pub fn project_3d_to_distorted_pixel(&self, pt3d: &PointWorldFrame<R>) -> DistortedPixel<R> {
541        let undistorted = self.project_3d_to_pixel(pt3d);
542        let ud = UndistortedPixels {
543            data: OMatrix::<R, U1, U2>::new(undistorted.coords[0], undistorted.coords[1]),
544        };
545        self.intrinsics().distort(&ud).into()
546    }
547
548    /// Back-project a 2D undistorted pixel to a 3D point at a given distance.
549    ///
550    /// This method performs the inverse camera projection, taking a 2D pixel
551    /// coordinate and a distance to compute the corresponding 3D world point.
552    /// This is useful for depth-based reconstruction and ray casting.
553    ///
554    /// # Arguments
555    ///
556    /// * `pt2d` - 2D pixel coordinates (undistorted)
557    /// * `dist` - Distance from camera center to the 3D point
558    ///
559    /// # Returns
560    ///
561    /// [`PointWorldFrame`] containing the 3D world coordinates
562    ///
563    /// # Example
564    ///
565    /// ```rust
566    /// use braid_mvg::{Camera, UndistortedPixel, extrinsics, make_default_intrinsics};
567    /// use nalgebra::Point2;
568    ///
569    /// let camera = Camera::new(640, 480,
570    ///     extrinsics::make_default_extrinsics::<f64>(),
571    ///     make_default_intrinsics::<f64>())?;
572    /// let pixel = UndistortedPixel { coords: Point2::new(320.0, 240.0) };
573    /// let point_3d = camera.project_pixel_to_3d_with_dist(&pixel, 5.0);
574    /// # Ok::<(), braid_mvg::MvgError>(())
575    /// ```
576    pub fn project_pixel_to_3d_with_dist(
577        &self,
578        pt2d: &UndistortedPixel<R>,
579        dist: R,
580    ) -> PointWorldFrame<R>
581    where
582        DefaultAllocator: Allocator<U1, U2>,
583        DefaultAllocator: Allocator<U1, U3>,
584    {
585        let ray_cam = self.intrinsics().undistorted_pixel_to_camera(&pt2d.into());
586        let pt_cam = ray_cam.point_on_ray_at_distance(dist);
587        self.extrinsics().camera_to_world(&pt_cam).into()
588    }
589
590    /// Back-project a 2D distorted pixel to a 3D point at a given distance.
591    ///
592    /// This method first removes lens distortion from the pixel coordinates,
593    /// then back-projects to 3D space at the specified distance from the camera.
594    ///
595    /// # Arguments
596    ///
597    /// * `pt2d` - 2D pixel coordinates (with distortion)
598    /// * `dist` - Distance from camera center to the 3D point
599    ///
600    /// # Returns
601    ///
602    /// [`PointWorldFrame`] containing the 3D world coordinates
603    ///
604    /// # Example
605    ///
606    /// ```rust
607    /// use braid_mvg::{Camera, DistortedPixel, extrinsics, make_default_intrinsics};
608    /// use nalgebra::Point2;
609    ///
610    /// let camera = Camera::new(640, 480,
611    ///     extrinsics::make_default_extrinsics::<f64>(),
612    ///     make_default_intrinsics::<f64>())?;
613    /// let pixel = DistortedPixel { coords: Point2::new(320.0, 240.0) };
614    /// let point_3d = camera.project_distorted_pixel_to_3d_with_dist(&pixel, 5.0);
615    /// # Ok::<(), braid_mvg::MvgError>(())
616    /// ```
617    pub fn project_distorted_pixel_to_3d_with_dist(
618        &self,
619        pt2d: &DistortedPixel<R>,
620        dist: R,
621    ) -> PointWorldFrame<R> {
622        use cam_geom::IntrinsicParameters;
623        let ray_cam = self.intrinsics().pixel_to_camera(&pt2d.into());
624        let pt_cam = ray_cam.point_on_ray_at_distance(dist);
625        self.extrinsics().camera_to_world(&pt_cam).into()
626    }
627}
628
629impl<R: RealField + Copy> std::default::Default for Camera<R> {
630    fn default() -> Camera<R> {
631        let extrinsics = crate::extrinsics::make_default_extrinsics();
632        let intrinsics = crate::make_default_intrinsics();
633        Camera::new(640, 480, extrinsics, intrinsics).unwrap()
634    }
635}
636
637impl<R: RealField + Copy> std::fmt::Debug for Camera<R> {
638    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
639        f.debug_struct("Camera")
640            .field("width", &self.width)
641            .field("height", &self.height)
642            .field("inner", &self.inner)
643            .finish()
644    }
645}
646
647impl<R: RealField + Copy> AsRef<cam_geom::Camera<R, RosOpenCvIntrinsics<R>>> for Camera<R> {
648    #[inline]
649    fn as_ref(&self) -> &cam_geom::Camera<R, RosOpenCvIntrinsics<R>> {
650        &self.inner
651    }
652}
653
654impl<R: RealField + serde::Serialize + Copy> serde::Serialize for Camera<R> {
655    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
656    where
657        S: serde::Serializer,
658    {
659        use serde::ser::SerializeStruct;
660
661        // 5 is the number of fields we serialize from the struct.
662        let mut state = serializer.serialize_struct("Camera", 5)?;
663        state.serialize_field("width", &self.width)?;
664        state.serialize_field("height", &self.height)?;
665        state.serialize_field("extrinsics", &self.extrinsics())?;
666        state.serialize_field("intrinsics", &self.intrinsics())?;
667        state.end()
668    }
669}
670
671impl<'de, R: RealField + serde::Deserialize<'de> + Copy> serde::Deserialize<'de> for Camera<R> {
672    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
673    where
674        D: serde::Deserializer<'de>,
675    {
676        use serde::de;
677        use std::fmt;
678
679        #[derive(Deserialize)]
680        #[serde(field_identifier, rename_all = "lowercase")]
681        enum Field {
682            Width,
683            Height,
684            Extrinsics,
685            Intrinsics,
686        }
687
688        struct CameraVisitor<'de, R2: RealField + serde::Deserialize<'de>>(
689            std::marker::PhantomData<&'de R2>,
690        );
691
692        impl<'de, R2: RealField + serde::Deserialize<'de> + Copy> serde::de::Visitor<'de>
693            for CameraVisitor<'de, R2>
694        {
695            type Value = Camera<R2>;
696
697            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
698                formatter.write_str("struct Camera")
699            }
700
701            fn visit_seq<V>(self, mut seq: V) -> std::result::Result<Camera<R2>, V::Error>
702            where
703                V: serde::de::SeqAccess<'de>,
704            {
705                let width = seq
706                    .next_element()?
707                    .ok_or_else(|| de::Error::invalid_length(0, &self))?;
708                let height = seq
709                    .next_element()?
710                    .ok_or_else(|| de::Error::invalid_length(1, &self))?;
711                let extrinsics = seq
712                    .next_element()?
713                    .ok_or_else(|| de::Error::invalid_length(0, &self))?;
714                let intrinsics = seq
715                    .next_element()?
716                    .ok_or_else(|| de::Error::invalid_length(1, &self))?;
717                Camera::new(width, height, extrinsics, intrinsics)
718                    .map_err(|e| de::Error::custom(format!("failed creating Camera: {e}")))
719            }
720
721            fn visit_map<V>(self, mut map: V) -> std::result::Result<Camera<R2>, V::Error>
722            where
723                V: serde::de::MapAccess<'de>,
724            {
725                let mut width = None;
726                let mut height = None;
727                let mut extrinsics = None;
728                let mut intrinsics = None;
729                while let Some(key) = map.next_key()? {
730                    match key {
731                        Field::Width => {
732                            if width.is_some() {
733                                return Err(de::Error::duplicate_field("width"));
734                            }
735                            width = Some(map.next_value()?);
736                        }
737                        Field::Height => {
738                            if height.is_some() {
739                                return Err(de::Error::duplicate_field("height"));
740                            }
741                            height = Some(map.next_value()?);
742                        }
743                        Field::Extrinsics => {
744                            if extrinsics.is_some() {
745                                return Err(de::Error::duplicate_field("extrinsics"));
746                            }
747                            extrinsics = Some(map.next_value()?);
748                        }
749                        Field::Intrinsics => {
750                            if intrinsics.is_some() {
751                                return Err(de::Error::duplicate_field("intrinsics"));
752                            }
753                            intrinsics = Some(map.next_value()?);
754                        }
755                    }
756                }
757                let width = width.ok_or_else(|| de::Error::missing_field("width"))?;
758                let height = height.ok_or_else(|| de::Error::missing_field("height"))?;
759                let extrinsics =
760                    extrinsics.ok_or_else(|| de::Error::missing_field("extrinsics"))?;
761                let intrinsics =
762                    intrinsics.ok_or_else(|| de::Error::missing_field("intrinsics"))?;
763                Camera::new(width, height, extrinsics, intrinsics)
764                    .map_err(|e| de::Error::custom(format!("failed creating Camera: {e}")))
765            }
766        }
767
768        const FIELDS: &[&str] = &["width", "height", "extrinsics", "intrinsics"];
769        deserializer.deserialize_struct("Camera", FIELDS, CameraVisitor(std::marker::PhantomData))
770    }
771}
772
773fn _test_camera_is_serialize() {
774    // Compile-time test to ensure Camera implements Serialize trait.
775    fn implements<T: serde::Serialize>() {}
776    implements::<Camera<f64>>();
777}
778
779fn _test_camera_is_deserialize() {
780    // Compile-time test to ensure Camera implements Deserialize trait.
781    fn implements<'de, T: serde::Deserialize<'de>>() {}
782    implements::<Camera<f64>>();
783}
784
785#[derive(Clone, PartialEq)]
786pub(crate) struct CameraCache<R: RealField> {
787    pub(crate) m: OMatrix<R, U3, U4>,
788    pub(crate) pinv: OMatrix<R, U4, U3>,
789}
790
791const SVD_MAX_ITERATIONS: usize = 1_000_000;
792
793fn my_pinv<R: RealField + Copy>(m: &OMatrix<R, U3, U4>) -> Result<OMatrix<R, U4, U3>> {
794    na::linalg::SVD::try_new(*m, true, true, na::convert(1e-7), SVD_MAX_ITERATIONS)
795        .ok_or(MvgError::SvdFailed)?
796        .pseudo_inverse(na::convert(1.0e-7))
797        .map_err(|e| MvgError::PinvError {
798            error: format!("inverse failed {e}"),
799        })
800}
801
802fn my_pinv_4x4<R: RealField + Copy>(m: &OMatrix<R, U4, U4>) -> Result<OMatrix<R, U4, U4>> {
803    na::linalg::SVD::try_new(*m, true, true, na::convert(1e-7), SVD_MAX_ITERATIONS)
804        .ok_or(MvgError::SvdFailed)?
805        .pseudo_inverse(na::convert(1.0e-7))
806        .map_err(|e| MvgError::PinvError {
807            error: format!("inverse failed {e}"),
808        })
809}
810
811fn build_xform<R: RealField + Copy>(s: R, rot: Matrix3<R>, t: Vector3<R>) -> Matrix4<R> {
812    let mut m1 = Matrix4::zero();
813    for i in 0..3 {
814        for j in 0..3 {
815            m1[(i, j)] = rot[(i, j)];
816        }
817    }
818    let mut m2 = m1 * s;
819    for i in 0..3 {
820        m2[(i, 3)] = t[i];
821    }
822    m2[(3, 3)] = R::one();
823    m2
824}
825
826#[expect(clippy::many_single_char_names)]
827fn pmat2cam_center<R: RealField + Copy>(p: &OMatrix<R, U3, U4>) -> Point3<R> {
828    let x = (*p).remove_column(0).determinant();
829    let y = -(*p).remove_column(1).determinant();
830    let z = (*p).remove_column(2).determinant();
831    let w = -(*p).remove_column(3).determinant();
832    Point3::from(Vector3::new(x / w, y / w, z / w))
833}
834
835/// Calculate angle of quaternion
836///
837/// This is the implementation from prior to
838/// https://github.com/rustsim/nalgebra/commit/74aefd9c23dadd12ee654c7d0206b0a96d22040c
839fn my_quat_angle<R: RealField + Copy>(quat: &na::UnitQuaternion<R>) -> R {
840    let w = quat.quaternion().scalar().abs();
841
842    // Handle inaccuracies that make break `.acos`.
843    if w >= R::one() {
844        R::zero()
845    } else {
846        w.acos() * na::convert(2.0f64)
847    }
848}
849
850/// convert a 3x3 matrix into a valid right-handed rotation
851fn right_handed_rotation_quat_new<R: RealField + Copy>(
852    orig: &Matrix3<R>,
853) -> Result<UnitQuaternion<R>> {
854    let r1 = *orig;
855    let rotmat = Rotation3::from_matrix_unchecked(r1);
856    let rquat = UnitQuaternion::from_rotation_matrix(&rotmat);
857    {
858        // Check for valid rotation matrix by converting back to rotation
859        // matrix and back again to quat then comparing quats. Probably
860        // there is a much faster and better way.
861        let rotmat2 = rquat.to_rotation_matrix();
862        let rquat2 = UnitQuaternion::from_rotation_matrix(&rotmat2);
863        let delta = rquat.rotation_to(&rquat2);
864        let angle = my_quat_angle(&delta);
865        let epsilon = na::convert(1.0e-7);
866        if angle.abs() > epsilon {
867            return Err(MvgError::InvalidRotationMatrix);
868        }
869    }
870    Ok(rquat)
871}
872
873fn rq<R: RealField + Copy>(A: Matrix3<R>) -> (Matrix3<R>, Matrix3<R>) {
874    let zero: R = Zero::zero();
875    let one: R = One::one();
876
877    // see https://math.stackexchange.com/a/1640762
878    let P = Matrix3::<R>::new(zero, zero, one, zero, one, zero, one, zero, zero);
879    let Atilde = P * A;
880
881    let (Qtilde, Rtilde) = {
882        let qrm = na::linalg::QR::new(Atilde.transpose());
883        (qrm.q(), qrm.r())
884    };
885    let Q = P * Qtilde.transpose();
886    let R = P * Rtilde.transpose() * P;
887    (R, Q)
888}
889
890/// perform RQ decomposition and return results as right-handed quaternion and intrinsics matrix
891pub fn rq_decomposition<R: RealField + Copy>(
892    orig: Matrix3<R>,
893) -> Result<(UnitQuaternion<R>, Matrix3<R>)> {
894    let (mut intrin, mut q) = rq(orig);
895    let zero: R = Zero::zero();
896    for i in 0..3 {
897        if intrin[(i, i)] < zero {
898            for j in 0..3 {
899                intrin[(j, i)] = -intrin[(j, i)];
900                q[(i, j)] = -q[(i, j)];
901            }
902        }
903    }
904
905    match right_handed_rotation_quat_new(&q) {
906        Ok(rquat) => Ok((rquat, intrin)),
907        Err(error) => {
908            match error {
909                MvgError::InvalidRotationMatrix => {
910                    // convert left-handed rotation to right-handed rotation
911                    let q = -q;
912                    let intrin = -intrin;
913                    let rquat = right_handed_rotation_quat_new(&q)?;
914                    Ok((rquat, intrin))
915                }
916                e => Err(e),
917            }
918        }
919    }
920}
921
922#[cfg(test)]
923mod tests {
924    use crate::{DistortedPixel, PointWorldFrame};
925    use na::core::dimension::{U3, U4};
926    use na::core::{OMatrix, Vector4};
927    use na::geometry::{Point2, Point3};
928    use nalgebra as na;
929
930    fn is_pmat_same(cam: &crate::Camera<f64>, pmat: &OMatrix<f64, U3, U4>) -> bool {
931        let world_pts = [
932            PointWorldFrame {
933                coords: Point3::new(1.23, 4.56, 7.89),
934            },
935            PointWorldFrame {
936                coords: Point3::new(1.0, 2.0, 3.0),
937            },
938        ];
939
940        let pts1: Vec<DistortedPixel<_>> = world_pts
941            .iter()
942            .map(|world| cam.project_3d_to_distorted_pixel(world))
943            .collect();
944
945        let pts2: Vec<DistortedPixel<_>> = world_pts
946            .iter()
947            .map(|world| {
948                let world_h = Vector4::new(world.coords.x, world.coords.y, world.coords.z, 1.0);
949                let rst = pmat * world_h;
950                DistortedPixel {
951                    coords: Point2::new(rst[0] / rst[2], rst[1] / rst[2]),
952                }
953            })
954            .collect();
955
956        let epsilon = 1e-10;
957
958        for (im1, im2) in pts1.iter().zip(pts2) {
959            println!("im1: {im1:?}");
960            println!("im2: {im2:?}");
961            let diff = im1.coords - im2.coords;
962            let dist_squared = diff.dot(&diff);
963            if dist_squared.is_nan() {
964                continue;
965            }
966            println!("dist_squared: {dist_squared:?}");
967            if dist_squared > epsilon {
968                return false;
969            }
970        }
971        true
972    }
973
974    fn is_similar(cam1: &crate::Camera<f64>, cam2: &crate::Camera<f64>) -> bool {
975        let world_pts = [
976            PointWorldFrame {
977                coords: Point3::new(1.23, 4.56, 7.89),
978            },
979            PointWorldFrame {
980                coords: Point3::new(1.0, 2.0, 3.0),
981            },
982        ];
983
984        let pts1: Vec<DistortedPixel<_>> = world_pts
985            .iter()
986            .map(|world| cam1.project_3d_to_distorted_pixel(world))
987            .collect();
988
989        let pts2: Vec<DistortedPixel<_>> = world_pts
990            .iter()
991            .map(|world| cam2.project_3d_to_distorted_pixel(world))
992            .collect();
993
994        let epsilon = 1e-10;
995
996        for (im1, im2) in pts1.iter().zip(pts2) {
997            let diff = im1.coords - im2.coords;
998            let dist_squared = diff.dot(&diff);
999            if dist_squared.is_nan() {
1000                continue;
1001            }
1002            if dist_squared > epsilon {
1003                return false;
1004            }
1005        }
1006        true
1007    }
1008
1009    #[test]
1010    fn test_to_from_pmat() {
1011        for (name, cam1) in crate::tests::get_test_cameras().iter() {
1012            println!("\n\n\ntesting camera {name}");
1013            let pmat = match cam1.as_pmat() {
1014                Some(pmat) => pmat,
1015                None => {
1016                    println!("skipping camera {name}: no pmat");
1017                    continue;
1018                }
1019            };
1020            assert!(is_pmat_same(cam1, pmat));
1021            let cam2 = crate::Camera::from_pmat(cam1.width(), cam1.height(), pmat).unwrap();
1022            assert!(is_similar(cam1, &cam2));
1023        }
1024    }
1025
1026    #[test]
1027    fn test_flipped_camera() {
1028        for (name, cam1) in crate::tests::get_test_cameras().iter() {
1029            println!("testing camera {name}");
1030            let cam2 = cam1.flip().expect("flip cam");
1031            if !is_similar(cam1, &cam2) {
1032                panic!("results not similar for cam {name}");
1033            }
1034        }
1035    }
1036
1037    #[test]
1038    fn test_rq() {
1039        let a = na::Matrix3::new(1.2, 3.4, 5.6, 7.8, 9.8, 7.6, 5.4, 3.2, 1.0);
1040        let (r, q) = crate::camera::rq(a);
1041        println!("r {r:?}");
1042        println!("q {q:?}");
1043
1044        // check it is a real decomposition
1045        let a2 = r * q;
1046        println!("a {a:?}");
1047        println!("a2 {a2:?}");
1048
1049        approx::assert_abs_diff_eq!(a, a2, epsilon = 1e-10);
1050
1051        // check that q is orthonormal
1052        let actual = q * q.transpose();
1053        let expected = na::Matrix3::identity();
1054        approx::assert_abs_diff_eq!(actual, expected, epsilon = 1e-10);
1055
1056        // check that r is upper triangular
1057        approx::assert_abs_diff_eq!(r[(1, 0)], 0.0, epsilon = 1e-10);
1058        approx::assert_abs_diff_eq!(r[(2, 0)], 0.0, epsilon = 1e-10);
1059        approx::assert_abs_diff_eq!(r[(2, 1)], 0.0, epsilon = 1e-10);
1060    }
1061
1062    #[test]
1063    fn test_rotation_matrices_and_quaternions() {
1064        use na::geometry::{Rotation3, UnitQuaternion};
1065
1066        #[rustfmt::skip]
1067        let r1 = na::Matrix3::from_column_slice(
1068            &[-0.9999999999999998, -0.00000000000000042632564145606005, -0.0000000000000002220446049250313,
1069            0.0000000000000004263256414560601, -1.0, 0.0,
1070            -0.0000000000000002220446049250313, -0.00000000000000000000000000000004930380657631324, -0.9999999999999998]);
1071
1072        let rotmat = Rotation3::from_matrix_unchecked(r1);
1073
1074        let rquat = UnitQuaternion::from_rotation_matrix(&rotmat);
1075
1076        let rotmat2 = rquat.to_rotation_matrix();
1077
1078        let rquat2 = UnitQuaternion::from_rotation_matrix(&rotmat2);
1079
1080        let angle = rquat.angle_to(&rquat2);
1081        let delta = rquat.rotation_to(&rquat2);
1082        let my_angle = crate::camera::my_quat_angle(&delta);
1083
1084        println!("r1 {r1:?}");
1085        println!("rotmat {rotmat:?}");
1086        println!("rquat {rquat:?}");
1087        println!("rotmat2 {rotmat2:?}");
1088        println!("rquat2 {rquat2:?}");
1089        println!("angle: {angle:?}");
1090        println!("delta {delta:?}");
1091        println!("my_angle: {my_angle:?}");
1092
1093        let q = na::Quaternion::new(
1094            -0.000000000000000000000000000000002756166576353432,
1095            0.000000000000000024825341532472726,
1096            -0.00000000000000004766465574234759,
1097            0.5590169943749475,
1098        );
1099        let uq = UnitQuaternion::from_quaternion(q); // hmm, this conversion doesn't give me the delta from above :(
1100        println!("q: {q:?}");
1101        println!("uq: {uq:?}");
1102        println!("uq.angle(): {:?}", uq.angle());
1103    }
1104
1105    #[test]
1106    fn test_serde() {
1107        let expected = crate::Camera::<f64>::default();
1108        let buf = serde_json::to_string(&expected).unwrap();
1109        let actual: crate::Camera<f64> = serde_json::from_str(&buf).unwrap();
1110        assert!(expected == actual);
1111    }
1112}