Skip to main content

cam_geom/
intrinsics_orthographic.rs

1use nalgebra::{
2    allocator::Allocator,
3    base::storage::{Owned, Storage},
4    convert, DefaultAllocator, Dim, OMatrix, RealField, U2, U3,
5};
6
7#[cfg(feature = "serde-serialize")]
8use serde::{Deserialize, Serialize};
9
10use crate::{
11    coordinate_system::CameraFrame, Bundle, IntrinsicParameters, Pixels, Points, RayBundle,
12};
13
14use crate::ray_bundle_types::SharedDirectionRayBundle;
15
16// TODO: implement ortho camera with near and far clipping?
17
18/// Parameters defining the intrinsic part of an orthographic camera model.
19///
20/// These parameters describe the intrinsic parameters, the transformation from
21/// camera coordinates to pixel coordinates, for an orthographic camera model.
22/// For a full transformation from world coordinates to pixel coordinates, use a
23/// [`Camera`](struct.Camera.html), which can be constructed with these intinsic
24/// parameters and extrinsic parameters.
25///
26/// Read more about the [orthographic
27/// projection](https://en.wikipedia.org/wiki/Orthographic_projection).
28///
29/// Can be converted into
30/// [`IntrinsicParametersOrthographic`](struct.IntrinsicParametersOrthographic.html)
31/// via the `.into()` method like so:
32///
33/// ```
34/// use cam_geom::*;
35/// let params = OrthographicParams {
36///     sx: 100.0,
37///     sy: 100.0,
38///     cx: 640.0,
39///     cy: 480.0,
40/// };
41/// let intrinsics: IntrinsicParametersOrthographic<_> = params.into();
42/// ```
43#[derive(Debug, Clone, PartialEq)]
44#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
45pub struct OrthographicParams<R: RealField> {
46    /// Horizontal scale.
47    pub sx: R,
48    /// Vertical scale.
49    pub sy: R,
50    /// Horizontal component of image center.
51    pub cx: R,
52    /// Vertical component of image center.
53    pub cy: R,
54}
55
56impl<R: RealField> From<OrthographicParams<R>> for IntrinsicParametersOrthographic<R> {
57    #[inline]
58    fn from(params: OrthographicParams<R>) -> Self {
59        Self { params }
60    }
61}
62
63/// An orthographic camera model. Implements [`IntrinsicParameters`](trait.IntrinsicParameters.html).
64///
65/// Create an `IntrinsicParametersOrthographic` as described for
66/// [`OrthographicParams`](struct.OrthographicParams.html) by using `.into()`.
67#[derive(Debug, Clone, PartialEq)]
68#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
69pub struct IntrinsicParametersOrthographic<R: RealField> {
70    params: OrthographicParams<R>,
71}
72
73impl<R> IntrinsicParameters<R> for IntrinsicParametersOrthographic<R>
74where
75    R: RealField,
76{
77    type BundleType = SharedDirectionRayBundle<R>;
78
79    fn pixel_to_camera<IN, NPTS>(
80        &self,
81        pixels: &Pixels<R, NPTS, IN>,
82    ) -> RayBundle<CameraFrame, Self::BundleType, R, NPTS, Owned<R, NPTS, U3>>
83    where
84        Self::BundleType: Bundle<R>,
85        IN: Storage<R, NPTS, U2>,
86        NPTS: Dim,
87        DefaultAllocator: Allocator<NPTS, U3>,
88    {
89        let zero: R = convert(0.0);
90
91        // allocate zeros, fill later
92        let mut result = RayBundle::new_shared_plusz_direction(OMatrix::zeros_generic(
93            NPTS::from_usize(pixels.data.nrows()),
94            U3::from_usize(3),
95        ));
96
97        let origin = &mut result.data;
98
99        // It seems broadcasting is not (yet) supported in nalgebra, so we loop
100        // through the data. See
101        // https://discourse.nphysics.org/t/array-broadcasting-support/375/3 .
102
103        for i in 0..pixels.data.nrows() {
104            let u = pixels.data[(i, 0)].clone();
105            let v = pixels.data[(i, 1)].clone();
106
107            let x: R = (u - self.params.cx.clone()) / self.params.sx.clone();
108            let y: R = (v - self.params.cy.clone()) / self.params.sy.clone();
109
110            origin[(i, 0)] = x;
111            origin[(i, 1)] = y;
112            origin[(i, 2)] = zero.clone();
113        }
114        result
115    }
116
117    fn camera_to_pixel<IN, NPTS>(
118        &self,
119        camera: &Points<CameraFrame, R, NPTS, IN>,
120    ) -> Pixels<R, NPTS, Owned<R, NPTS, U2>>
121    where
122        IN: Storage<R, NPTS, U3>,
123        NPTS: Dim,
124        DefaultAllocator: Allocator<NPTS, U2>,
125    {
126        let mut result = Pixels::new(OMatrix::zeros_generic(
127            NPTS::from_usize(camera.data.nrows()),
128            U2::from_usize(2),
129        ));
130
131        // It seems broadcasting is not (yet) supported in nalgebra, so we loop
132        // through the data. See
133        // https://discourse.nphysics.org/t/array-broadcasting-support/375/3 .
134
135        for i in 0..camera.data.nrows() {
136            result.data[(i, 0)] =
137                camera.data[(i, 0)].clone() * self.params.sx.clone() + self.params.cx.clone();
138            result.data[(i, 1)] =
139                camera.data[(i, 1)].clone() * self.params.sy.clone() + self.params.cy.clone();
140        }
141        result
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use nalgebra::Vector3;
148
149    use super::{IntrinsicParametersOrthographic, OrthographicParams};
150    use crate::camera::{roundtrip_camera, Camera};
151    use crate::extrinsics::ExtrinsicParameters;
152    use crate::intrinsic_test_utils::roundtrip_intrinsics;
153
154    #[test]
155    fn roundtrip() {
156        let params = OrthographicParams {
157            sx: 100.0,
158            sy: 102.0,
159            cx: 321.0,
160            cy: 239.9,
161        };
162        let cam: IntrinsicParametersOrthographic<_> = params.into();
163
164        roundtrip_intrinsics(&cam, 640, 480, 5, 0, nalgebra::convert(1e-10));
165
166        let extrinsics = ExtrinsicParameters::from_view(
167            &Vector3::new(1.2, 3.4, 5.6),                                // camcenter
168            &Vector3::new(2.2, 3.4, 5.6),                                // lookat
169            &nalgebra::Unit::new_normalize(Vector3::new(0.0, 0.0, 1.0)), // up
170        );
171
172        let full_cam = Camera::new(cam, extrinsics);
173        roundtrip_camera(full_cam, 640, 480, 5, 0, nalgebra::convert(1e-10));
174    }
175
176    #[test]
177    #[cfg(feature = "serde-serialize")]
178    fn test_serde() {
179        let params = OrthographicParams {
180            sx: 100.0,
181            sy: 102.0,
182            cx: 321.0,
183            cy: 239.9,
184        };
185        let expected: IntrinsicParametersOrthographic<_> = params.into();
186
187        let buf = serde_json::to_string(&expected).unwrap();
188        let actual: IntrinsicParametersOrthographic<f64> = serde_json::from_str(&buf).unwrap();
189        assert!(expected == actual);
190    }
191}