1use nalgebra::{self as na, RealField};
9use opencv_ros_camera::RosOpenCvIntrinsics;
10
11use crate::MvgError;
12
13trait ToF32 {
16 fn f32(&self) -> f32;
17}
18
19impl<R> ToF32 for R
20where
21 R: RealField,
22{
23 #[inline]
24 fn f32(&self) -> f32 {
25 <R as simba::scalar::SupersetOf<f32>>::to_subset(self).unwrap()
26 }
27}
28
29trait ToR<T: RealField> {
30 #[allow(non_snake_case)]
31 fn R(&self) -> T;
32}
33
34impl<T: RealField> ToR<T> for f32 {
35 #[inline]
36 fn R(&self) -> T {
37 na::convert(*self as f64)
38 }
39}
40
41impl<T: RealField> ToR<T> for f64 {
42 #[inline]
43 fn R(&self) -> T {
44 na::convert(*self)
45 }
46}
47
48fn rr_translation_and_rotation<R: RealField>(
51 extrinsics: &cam_geom::ExtrinsicParameters<R>,
52) -> (
53 re_types::components::Translation3D,
54 re_types::components::RotationQuat,
55) {
56 use re_types::{components::RotationQuat, datatypes::Quaternion};
57 let r = &extrinsics.pose().rotation.coords;
58 let rquat = RotationQuat(Quaternion::from_xyzw([
60 r.x.f32(),
61 r.y.f32(),
62 r.z.f32(),
63 r.w.f32(),
64 ]));
65 let t = extrinsics.translation();
66 let translation = re_types::datatypes::Vec3D([t[0].f32(), t[1].f32(), t[2].f32()]).into();
67 (translation, rquat)
68}
69
70pub trait AsRerunTransform3D {
88 fn as_rerun_transform3d(&self) -> impl Into<re_types::archetypes::Transform3D>;
95}
96
97impl AsRerunTransform3D for cam_geom::ExtrinsicParameters<f64> {
98 fn as_rerun_transform3d(&self) -> impl Into<re_types::archetypes::Transform3D> {
99 use re_types::components::TransformRelation;
100 let (t, r) = rr_translation_and_rotation(self);
101 re_types::archetypes::Transform3D::from_translation_rotation(t, r)
102 .with_relation(TransformRelation::ChildFromParent)
103 }
104}
105
106#[test]
107fn test_rerun_transform3d() {
108 use nalgebra::{Matrix3xX, Vector3};
109 use re_types::external::glam::{self, Affine3A};
110
111 let cc = Vector3::new(3.0, 2.0, 1.0);
115 let lookat = Vector3::new(0.0, 0.0, 0.0);
116 let up = Vector3::new(0.0, 0.0, 1.0);
117 let up_unit = na::core::Unit::new_normalize(up);
118
119 let extrinsics = cam_geom::ExtrinsicParameters::from_view(&cc, &lookat, &up_unit);
120
121 #[rustfmt::skip]
122 let points3d = Matrix3xX::<f64>::from_column_slice(
123 &[
124 0.0, 0.0, 0.0,
125 1.0, 0.0, 0.0,
126 1.0, 1.0, 0.0,
127 0.0, 1.0, 0.0,
128 0.0, 0.0, 1.0,
129 1.0, 0.0, 1.0,
130 1.0, 1.0, 1.0,
131 0.0, 1.0, 1.0,
132 ]);
133
134 let world = cam_geom::Points::new(points3d.transpose());
136 let cam = extrinsics.world_to_camera(&world);
137
138 let (rrt, rrq) = rr_translation_and_rotation(&extrinsics);
140 let glam_t: Affine3A = rrt.into();
141 let glam_r: Affine3A = rrq.try_into().unwrap();
142 for i in 0..points3d.ncols() {
143 let (x, y, z) = (points3d[(0, i)], points3d[(1, i)], points3d[(2, i)]);
144 let pt = glam::Vec3::new(x as f32, y as f32, z as f32);
145 let p2 = glam_t.transform_point3(glam_r.transform_point3(pt));
146
147 approx::assert_relative_eq!(p2.x as f64, cam.data[(i, 0)], epsilon = 1e-6);
149 approx::assert_relative_eq!(p2.y as f64, cam.data[(i, 1)], epsilon = 1e-6);
150 approx::assert_relative_eq!(p2.z as f64, cam.data[(i, 2)], epsilon = 1e-6);
151 }
152}
153
154fn pinhole_projection_component<R: RealField>(
157 cam: &RosOpenCvIntrinsics<R>,
158) -> Result<re_types::components::PinholeProjection, MvgError> {
159 if !cam.distortion.is_linear() {
161 return Err(MvgError::RerunUnsupportedIntrinsics);
162 }
163 for loc in [(0, 1), (1, 0), (2, 0), (2, 1)] {
165 if cam.p[loc].clone().abs() > 1e-16.R() {
166 return Err(MvgError::RerunUnsupportedIntrinsics);
167 }
168 }
169 if (cam.p[(2, 2)].clone() - na::one()).abs() > 1e-16.R() {
170 return Err(MvgError::RerunUnsupportedIntrinsics);
171 }
172
173 let fx = cam.p[(0, 0)].f32();
174 let fy = cam.p[(1, 1)].f32();
175 let cx = cam.p[(0, 2)].f32();
176 let cy = cam.p[(1, 2)].f32();
177
178 Ok(
179 re_types::components::PinholeProjection::from_focal_length_and_principal_point(
180 (fx, fy),
181 (cx, cy),
182 ),
183 )
184}
185
186pub fn cam_geom_to_rr_pinhole_archetype<R: RealField>(
228 intrinsics: &cam_geom::IntrinsicParametersPerspective<R>,
229 width: usize,
230 height: usize,
231) -> Result<re_types::archetypes::Pinhole, MvgError> {
232 let i = intrinsics;
233 if i.skew().f32().abs() > 1e-10 {
234 return Err(MvgError::RerunUnsupportedIntrinsics);
235 }
236 let image_from_camera =
237 re_types::components::PinholeProjection::from_focal_length_and_principal_point(
238 (i.fx().f32(), i.fy().f32()),
239 (i.cx().f32(), i.cy().f32()),
240 );
241 let resolution: re_types::datatypes::Vec2D = (width as f32, height as f32).into();
242 Ok(re_types::archetypes::Pinhole::new(image_from_camera).with_resolution(resolution))
243}
244
245#[cfg(test)]
246fn opencv_intrinsics<R: RealField>(
247 orig: &re_types::components::PinholeProjection,
248) -> RosOpenCvIntrinsics<R> {
249 let m = orig.0;
250 let col0 = m.col(0);
251 let col1 = m.col(1);
252 let col2 = m.col(2);
253 let fx = col0[0].R();
254 let skew = col1[0].R();
255 let fy = col1[1].R();
256 let cx = col2[0].R();
257 let cy = col2[1].R();
258 RosOpenCvIntrinsics::from_params(fx, skew, fy, cx, cy)
259}
260
261impl<R: RealField + Copy> crate::Camera<R> {
262 fn rr_resolution_component(&self) -> re_types::components::Resolution {
264 re_types::components::Resolution(re_types::datatypes::Vec2D::new(
265 self.width() as f32,
266 self.height() as f32,
267 ))
268 }
269
270 pub fn rr_pinhole_archetype(&self) -> Result<re_types::archetypes::Pinhole, MvgError> {
275 let pinhole_projection = pinhole_projection_component(self.intrinsics())?;
276 let resolution = self.rr_resolution_component();
277 Ok(re_types::archetypes::Pinhole::new(pinhole_projection).with_resolution(resolution))
278 }
279}
280
281#[test]
282fn test_intrinsics_rerun_roundtrip() {
283 let orig = crate::Camera::<f64>::default();
285 let rr = pinhole_projection_component(orig.intrinsics()).unwrap();
287 let intrinsics = opencv_intrinsics::<f64>(&rr);
289 assert_eq!(orig.intrinsics(), &intrinsics);
291}
292
293#[test]
294fn test_intrinsics_rerun() {
295 use cam_geom::Points;
296 use nalgebra::{OMatrix, U3, U7};
297
298 let orig = crate::Camera::<f64>::default();
300 let rr = pinhole_projection_component(orig.intrinsics()).unwrap();
302
303 let orig_intrinsics = orig.intrinsics();
304
305 #[rustfmt::skip]
306 let pts = Points::new(
307 OMatrix::<f64, U7, U3>::from_row_slice(
308 &[0.0, 0.0, 1.0,
309 1.0, 0.0, 1.0,
310 0.0, 1.0, 1.0,
311 1.0, 1.0, 1.0,
312 -1.0, 0.0, 1.0,
313 0.0, -1.0, 1.0,
314 -1.0, -1.0, 1.0]
315 )
316 );
317
318 let pixels_orig = orig_intrinsics.camera_to_undistorted_pixel(&pts);
319
320 for (pt3d, px_orig) in pts.data.row_iter().zip(pixels_orig.data.row_iter()) {
321 let pt3d_v2 =
322 re_types::external::glam::Vec3::new(pt3d[0] as f32, pt3d[1] as f32, pt3d[2] as f32);
323 let pix_rr = rr.project(pt3d_v2);
324 approx::assert_relative_eq!(pix_rr.x, px_orig[0] as f32, epsilon = 1e-10);
325 approx::assert_relative_eq!(pix_rr.y, px_orig[1] as f32, epsilon = 1e-10);
326 }
327}