1use std::{collections::BTreeMap, path::Path};
5
6use serde::{Deserialize, Serialize};
7
8use braid_types::MiniArenaConfig;
9
10use crate::{MyFloat, Result, bundled_data::BundledAllCamsOneFrameUndistorted};
11
12#[derive(Debug, Clone, Copy, PartialEq)]
19pub(crate) struct MiniArenaIndex(u8);
20impl MiniArenaIndex {
21 pub(crate) fn new(val: u8) -> Self {
22 MiniArenaIndex(val)
23 }
24 pub(crate) fn idx(&self) -> usize {
25 self.0 as usize
26 }
27}
28
29const NO_MINI_ARENA_MARKER: u8 = 255;
30
31pub(crate) enum MiniArenaLocator {
32 OutOfBounds,
34 NotInMiniArena,
36 Index(MiniArenaIndex),
38 OneArena,
40}
41
42pub(crate) struct MiniArenaImage {
44 width: usize,
45 data: Vec<u8>,
47}
48
49impl std::fmt::Debug for MiniArenaImage {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 f.debug_struct("MiniArenaImage")
52 .field("width", &self.width)
53 .field("data.len()", &self.data.len())
54 .finish_non_exhaustive()
55 }
56}
57
58impl MiniArenaImage {
59 pub(crate) fn get_mini_arena(&self, x: usize, y: usize) -> MiniArenaLocator {
61 let idx = y * self.width + x;
62 match self.data.get(idx) {
63 None => MiniArenaLocator::OutOfBounds,
64 Some(&NO_MINI_ARENA_MARKER) => MiniArenaLocator::NotInMiniArena,
65 Some(idx) => MiniArenaLocator::Index(MiniArenaIndex::new(*idx)),
66 }
67 }
68}
69
70pub struct MiniArenaDebugConfig {
71 pub output_png_path: camino::Utf8PathBuf,
73 pub background_image_jpeg_buf: Option<Vec<u8>>,
75 pub april_detections: Option<Vec<braid_apriltag_types::AprilTagCoords2D>>,
76}
77
78impl std::fmt::Debug for MiniArenaDebugConfig {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 f.debug_struct("MiniArenaDebugConfig")
81 .finish_non_exhaustive()
82 }
83}
84
85pub(crate) fn build_mini_arena_images(
87 recon: Option<&flydra_mvg::FlydraMultiCameraSystem<MyFloat>>,
88 mini_arena_config: &MiniArenaConfig,
89 mini_arena_debug_cfg: Option<&MiniArenaDebugConfig>,
90) -> Result<BTreeMap<String, MiniArenaImage>> {
91 let mut mini_arena_images = BTreeMap::new();
92 let recon = match recon {
93 None => {
94 return Ok(mini_arena_images);
95 }
96 Some(recon) => recon,
97 };
98 match mini_arena_config {
99 MiniArenaConfig::NoMiniArena => {}
100 MiniArenaConfig::XYGrid(xy_grid_cfg) => {
101 for cam in recon.cameras() {
102 let sz = cam.width() * cam.height();
103 let mut mini_arena_image = vec![NO_MINI_ARENA_MARKER; sz];
104
105 for row in 0..cam.height() {
106 for col in 0..cam.width() {
107 let pt = braid_mvg::DistortedPixel {
108 coords: nalgebra::Point2::new(col as f64, row as f64),
109 };
110 let ray = cam.project_distorted_pixel_to_ray(&pt);
111 let coords_3d = crate::flat_2d::ray_to_flat_3d(&ray);
112 if let Some(coords_3d) = coords_3d {
113 let coords = [coords_3d.x, coords_3d.y, coords_3d.z];
114 if let Some(arena_idx) = xy_grid_cfg.get_arena_index(&coords).idx() {
115 let coords_idx = row * cam.width() + col;
116 mini_arena_image[coords_idx] = arena_idx;
117 }
118 }
119 }
120 }
121
122 if let Some(cfg) = &mini_arena_debug_cfg {
123 use machine_vision_formats::pixel_format::Mono8;
125 let well_image = machine_vision_formats::image_ref::ImageRef::<Mono8>::new(
126 cam.width().try_into().unwrap(),
127 cam.height().try_into().unwrap(),
128 cam.width(),
129 &mini_arena_image,
130 )
131 .unwrap();
132 let well_jpeg_buf = convert_image::frame_to_encoded_buffer(
133 &well_image,
134 convert_image::EncoderOptions::Jpeg(100),
135 )
136 .unwrap();
137
138 annotate_mini_arena_image(&well_jpeg_buf, cfg, &cam, xy_grid_cfg).unwrap();
139 }
140
141 mini_arena_images.insert(
142 cam.name().to_string(),
143 MiniArenaImage {
144 width: cam.width(),
145 data: mini_arena_image,
146 },
147 );
148 }
149 }
150 }
151 Ok(mini_arena_images)
152}
153
154fn annotate_mini_arena_image(
156 well_jpeg_buf: &[u8],
157 cfg: &MiniArenaDebugConfig,
158 cam: &flydra_mvg::MultiCamera<f64>,
159 xy_grid_cfg: &braid_types::XYGridConfig,
160) -> eyre::Result<()> {
161 let svg_width = cam.width();
162 let svg_height = cam.height();
163 let mut wtr = tagger::new(tagger::upgrade_write(Vec::<u8>::new()));
165 wtr.elem("svg", |d| {
168 d.attr("xmlns", "http://www.w3.org/2000/svg")?;
169 d.attr("xmlns:xlink", "http://www.w3.org/1999/xlink")?;
170 d.attr("viewBox", format_args!("0 0 {} {}", svg_width, svg_height))
171 })?
172 .build(|w| {
173 use base64::Engine;
174
175 if let Some(jpeg_buf) = &cfg.background_image_jpeg_buf {
177 let jpeg_base64_buf = base64::engine::general_purpose::STANDARD.encode(jpeg_buf);
178 let data_url = format!("data:image/jpeg;base64,{}", jpeg_base64_buf);
179 w.single("image", |d| {
180 d.attr("x", 0)?;
181 d.attr("y", 0)?;
182 d.attr("width", svg_width)?;
183 d.attr("height", svg_height)?;
184 d.attr("xlink:href", data_url)
185 })?;
186 }
187
188 {
190 let jpeg_base64_buf = base64::engine::general_purpose::STANDARD.encode(well_jpeg_buf);
191 let data_url = format!("data:image/jpeg;base64,{}", jpeg_base64_buf);
192 w.single("image", |d| {
193 d.attr("x", 0)?;
194 d.attr("y", 0)?;
195 d.attr("width", svg_width)?;
196 d.attr("height", svg_height)?;
197 d.attr("opacity", "0.3")?;
198 d.attr("xlink:href", data_url)
199 })?;
200 }
201
202 if let Some(detections) = &cfg.april_detections {
204 for detection in detections.iter() {
205
206 let id_str = if detection.vertical_flip {
207 format!("{} (v)", detection.id)
208 } else {
209 format!("{}", detection.id)
210 };
211 w.elem("text", |d| {
212 d.attr("x", format!("{}", detection.x))?;
213 d.attr("y", format!("{}", detection.y))?;
214 d.attr("text-anchor ", "middle")?;
215 d.attr("dominant-baseline", "middle")?;
216 d.attr(
217 "style",
218 "font-family: Arial, Helvetica, sans-serif; font-size: 30px; fill: red;",
219 )?;
220 Ok(())
221 })?
222 .build(|w| w.put_raw(id_str))?;
223 }
224 }
225
226 for center in xy_grid_cfg.iter_centers() {
228 let coord = [center.0, center.1, 0.0];
229 let idx = xy_grid_cfg.get_arena_index(&coord).idx().unwrap();
231
232 let pt = cam
235 .project_3d_to_distorted_pixel(&braid_mvg::PointWorldFrame::from(coord))
236 .coords;
237
238 w.elem("text", |d| {
239 d.attr("x", format!("{}", pt.x))?;
240 d.attr("y", format!("{}", pt.y))?;
241 d.attr("text-anchor ", "middle")?;
242 d.attr("dominant-baseline", "middle")?;
243 d.attr(
244 "style",
245 "font-family: Arial, Helvetica, sans-serif; font-size: 40px; fill: deepskyblue;",
246 )?;
247 Ok(())
248 })?
249 .build(|w| w.put_raw(format!("{}", idx)))?;
250 }
251
252 Ok(())
253 })?;
254 let fmt_wtr = wtr.into_writer();
256 let svg_buf = {
257 fmt_wtr.error?;
258 fmt_wtr.inner
259 };
260
261 let mut usvg_opt = usvg::Options::default();
262 usvg_opt.fontdb_mut().load_system_fonts();
263
264 let rtree = usvg::Tree::from_data(&svg_buf, &usvg_opt)?;
266 let pixmap_size = rtree.size().to_int_size();
268 let mut pixmap =
269 resvg::tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap();
270 resvg::render(
271 &rtree,
272 resvg::tiny_skia::Transform::default(),
273 &mut pixmap.as_mut(),
274 );
275
276 pixmap.save_png(&cfg.output_png_path)?;
277 tracing::info!("Saved well image to {}", cfg.output_png_path);
278
279 Ok(())
280}
281
282#[derive(Clone, Debug, Serialize, Deserialize)]
286struct MiniArenaPointPerCamFlat {
287 frame: u64,
288 cam_name: String,
289 mini_arena_idx: usize,
290 undistorted_x: f64,
291 undistorted_y: f64,
292 distorted_idx: u8,
293 distorted_x: f64,
294 distorted_y: f64,
295}
296
297impl MiniArenaPointPerCamFlat {
298 fn new(
299 frame: u64,
300 cam_name: String,
301 mini_arena_idx: usize,
302 orig: &crate::bundled_data::MiniArenaPointPerCam,
303 ) -> Self {
304 Self {
305 frame,
306 cam_name,
307 mini_arena_idx,
308 undistorted_x: orig.undistorted.x,
309 undistorted_y: orig.undistorted.y,
310 distorted_idx: orig.numbered_raw_udp_point.idx,
311 distorted_x: orig.numbered_raw_udp_point.pt.x0_abs,
312 distorted_y: orig.numbered_raw_udp_point.pt.y0_abs,
313 }
314 }
315}
316
317pub(crate) struct MiniArenaAssignmentDebug {
320 wtr: csv::Writer<std::io::BufWriter<std::fs::File>>,
321}
322
323impl MiniArenaAssignmentDebug {
324 pub(crate) fn new<P: AsRef<Path>>(path: P) -> crate::Result<Self> {
325 let fd = std::fs::File::create(path)?;
326 let bufwriter = std::io::BufWriter::new(fd);
327 let wtr = csv::Writer::from_writer(bufwriter);
328 Ok(Self { wtr })
329 }
330
331 pub(crate) fn write_frame(
332 &mut self,
333 undistorted: &BundledAllCamsOneFrameUndistorted,
334 ) -> crate::Result<()> {
335 let frame = undistorted.tdpt.frame.0;
336
337 for (mini_arena_idx, mini_arena) in undistorted.per_mini_arena.iter().enumerate() {
338 for (mini_arena_cam, mini_arena_data) in mini_arena.per_cam.iter() {
339 for orig in mini_arena_data.iter() {
340 let row = MiniArenaPointPerCamFlat::new(
341 frame,
342 mini_arena_cam.as_str().to_string(),
343 mini_arena_idx,
344 orig,
345 );
346
347 self.wtr.serialize(row)?;
348 }
349 }
350 }
351
352 Ok(())
353 }
354}