re_components/pinhole.rs
1use arrow2_convert::{ArrowDeserialize, ArrowField, ArrowSerialize};
2
3use super::{mat::Mat3x3, Vec2D};
4
5/// Camera perspective projection (a.k.a. intrinsics).
6///
7/// This component is a "mono-component". See [the crate level docs](crate) for details.
8///
9/// ```
10/// use re_components::Pinhole;
11/// use arrow2_convert::field::ArrowField;
12/// use arrow2::datatypes::{DataType, Field};
13///
14/// assert_eq!(
15/// Pinhole::data_type(),
16/// DataType::Struct(vec![
17/// Field::new(
18/// "image_from_cam",
19/// DataType::FixedSizeList(
20/// Box::new(Field::new("item", DataType::Float32, false)),
21/// 9
22/// ),
23/// false,
24/// ),
25/// Field::new(
26/// "resolution",
27/// DataType::FixedSizeList(
28/// Box::new(Field::new("item", DataType::Float32, false)),
29/// 2
30/// ),
31/// true,
32/// ),
33/// ]),
34/// );
35/// ```
36#[derive(Copy, Clone, Debug, PartialEq, ArrowField, ArrowSerialize, ArrowDeserialize)]
37#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
38pub struct Pinhole {
39 /// Column-major projection matrix.
40 ///
41 /// Child from parent.
42 /// Image coordinates from camera view coordinates.
43 ///
44 /// Example:
45 /// ```text
46 /// [[1496.1, 0.0, 0.0], // col 0
47 /// [0.0, 1496.1, 0.0], // col 1
48 /// [980.5, 744.5, 1.0]] // col 2
49 /// ```
50 pub image_from_cam: Mat3x3,
51
52 /// Pixel resolution (usually integers) of child image space. Width and height.
53 ///
54 /// Example:
55 /// ```text
56 /// [1920.0, 1440.0]
57 /// ```
58 ///
59 /// [`Self::image_from_cam`] project onto the space spanned by `(0,0)` and `resolution - 1`.
60 pub resolution: Option<Vec2D>,
61}
62
63impl re_log_types::LegacyComponent for Pinhole {
64 #[inline]
65 fn legacy_name() -> re_log_types::ComponentName {
66 "rerun.pinhole".into()
67 }
68}
69
70impl Pinhole {
71 /// Field of View on the Y axis, i.e. the angle between top and bottom (in radians).
72 #[inline]
73 pub fn fov_y(&self) -> Option<f32> {
74 self.resolution
75 .map(|resolution| 2.0 * (0.5 * resolution[1] / self.image_from_cam[1][1]).atan())
76 }
77
78 /// X & Y focal length in pixels.
79 ///
80 /// [see definition of intrinsic matrix](https://en.wikipedia.org/wiki/Camera_resectioning#Intrinsic_parameters)
81 #[inline]
82 pub fn focal_length_in_pixels(&self) -> Vec2D {
83 [self.image_from_cam[0][0], self.image_from_cam[1][1]].into()
84 }
85
86 /// Focal length.
87 #[inline]
88 pub fn focal_length(&self) -> Option<f32> {
89 // Use only the first element of the focal length vector, as we don't support non-square pixels.
90 self.resolution.map(|r| self.image_from_cam[0][0] / r[0])
91 }
92
93 /// Principal point of the pinhole camera,
94 /// i.e. the intersection of the optical axis and the image plane.
95 ///
96 /// [see definition of intrinsic matrix](https://en.wikipedia.org/wiki/Camera_resectioning#Intrinsic_parameters)
97 #[cfg(feature = "glam")]
98 #[inline]
99 pub fn principal_point(&self) -> glam::Vec2 {
100 glam::vec2(self.image_from_cam[2][0], self.image_from_cam[2][1])
101 }
102
103 #[inline]
104 #[cfg(feature = "glam")]
105 pub fn resolution(&self) -> Option<glam::Vec2> {
106 self.resolution.map(|r| r.into())
107 }
108
109 #[inline]
110 pub fn aspect_ratio(&self) -> Option<f32> {
111 self.resolution.map(|r| r[0] / r[1])
112 }
113
114 /// Project camera-space coordinates into pixel coordinates,
115 /// returning the same z/depth.
116 #[cfg(feature = "glam")]
117 #[inline]
118 pub fn project(&self, pixel: glam::Vec3) -> glam::Vec3 {
119 ((pixel.truncate() * glam::Vec2::from(self.focal_length_in_pixels())) / pixel.z
120 + self.principal_point())
121 .extend(pixel.z)
122 }
123
124 /// Given pixel coordinates and a world-space depth,
125 /// return a position in the camera space.
126 ///
127 /// The returned z is the same as the input z (depth).
128 #[cfg(feature = "glam")]
129 #[inline]
130 pub fn unproject(&self, pixel: glam::Vec3) -> glam::Vec3 {
131 ((pixel.truncate() - self.principal_point()) * pixel.z
132 / glam::Vec2::from(self.focal_length_in_pixels()))
133 .extend(pixel.z)
134 }
135}
136
137re_log_types::component_legacy_shim!(Pinhole);
138
139#[test]
140fn test_pinhole_roundtrip() {
141 use arrow2::array::Array;
142 use arrow2_convert::{deserialize::TryIntoCollection, serialize::TryIntoArrow};
143
144 let pinholes_in = vec![
145 Pinhole {
146 image_from_cam: [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]].into(),
147 resolution: None,
148 },
149 Pinhole {
150 image_from_cam: [[21.0, 22.0, 23.0], [24.0, 25.0, 26.0], [27.0, 28.0, 29.0]].into(),
151 resolution: Some([123.0, 456.0].into()),
152 },
153 ];
154 let array: Box<dyn Array> = pinholes_in.try_into_arrow().unwrap();
155 let pinholes_out: Vec<Pinhole> = TryIntoCollection::try_into_collection(array).unwrap();
156 assert_eq!(pinholes_in, pinholes_out);
157}