braid_sim/projection.rs
1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Project 3D ground-truth points into each camera's distorted pixel, with
5//! field-of-view culling.
6
7use braid_mvg::PointWorldFrame;
8use flydra_mvg::FlydraMultiCameraSystem;
9
10use crate::scenario::Scenario;
11
12/// A single camera's observation of a 3D point: the distorted pixel, or `None`
13/// if the point projects outside the image (culled).
14#[derive(Debug, Clone, PartialEq)]
15pub struct Observation {
16 /// Camera name (see [`Scenario::camera_name`]).
17 pub cam_name: String,
18 /// Pixel `(x, y)`, or `None` if outside the image bounds.
19 pub pixel: Option<(f64, f64)>,
20}
21
22/// Whether pixel `(x, y)` lies within a `width` x `height` image.
23fn in_image(x: f64, y: f64, width: usize, height: usize) -> bool {
24 x >= 0.0 && y >= 0.0 && x < width as f64 && y < height as f64
25}
26
27/// Project a 3D point into a single named camera, returning the pixel `(x, y)`
28/// or `None` if the camera is unknown or the point lands outside a
29/// `width` x `height` image.
30pub fn project_pixel(
31 system: &FlydraMultiCameraSystem<f64>,
32 cam_name: &str,
33 width: usize,
34 height: usize,
35 pt: &PointWorldFrame<f64>,
36) -> Option<(f64, f64)> {
37 let cam = system.cam_by_name(cam_name)?;
38 let dp = cam.project_3d_to_distorted_pixel(pt);
39 let (x, y) = (dp.coords.x, dp.coords.y);
40 if in_image(x, y, width, height) {
41 Some((x, y))
42 } else {
43 None
44 }
45}
46
47/// Project a 3D point into every camera of `system`, culling points that land
48/// outside the image.
49///
50/// Note: this culls on image bounds only. For the perfect-world ring geometry,
51/// all arena points are in front of all cameras; if cameras are later placed so
52/// that points can fall behind a camera, add a camera-frame depth (`z > 0`)
53/// check here.
54pub fn project_all(
55 system: &FlydraMultiCameraSystem<f64>,
56 scenario: &Scenario,
57 pt: &PointWorldFrame<f64>,
58) -> Vec<Observation> {
59 (0..scenario.cameras.count)
60 .map(|k| {
61 let cam_name = Scenario::camera_name(k);
62 let pixel = system.cam_by_name(&cam_name).and_then(|cam| {
63 let dp = cam.project_3d_to_distorted_pixel(pt);
64 let (x, y) = (dp.coords.x, dp.coords.y);
65 if in_image(
66 x,
67 y,
68 scenario.cameras.image_width,
69 scenario.cameras.image_height,
70 ) {
71 Some((x, y))
72 } else {
73 None
74 }
75 });
76 Observation { cam_name, pixel }
77 })
78 .collect()
79}