data_contracts/
capture.rs1use serde::{Deserialize, Serialize};
2use thiserror::Error;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct PolypLabel {
6 pub center_world: [f32; 3],
7 pub bbox_px: Option<[f32; 4]>,
8 pub bbox_norm: Option<[f32; 4]>,
9}
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct CaptureMetadata {
13 pub frame_id: u64,
14 pub sim_time: f64,
15 pub unix_time: f64,
16 pub image: String,
17 pub image_present: bool,
18 pub camera_active: bool,
19 pub polyp_seed: u64,
20 pub polyp_labels: Vec<PolypLabel>,
21}
22
23#[derive(Debug, Error)]
24pub enum ValidationError {
25 #[error("bbox_px invalid order or negative: {0:?}")]
26 InvalidBboxPx([f32; 4]),
27 #[error("bbox_norm out of range: {0:?}")]
28 InvalidBboxNorm([f32; 4]),
29 #[error("missing image path for present frame")]
30 MissingImage,
31}
32
33impl PolypLabel {
34 pub fn validate(&self) -> Result<(), ValidationError> {
35 if let Some(px) = self.bbox_px {
36 if px[0].is_nan()
37 || px[1].is_nan()
38 || px[2].is_nan()
39 || px[3].is_nan()
40 || px[0] > px[2]
41 || px[1] > px[3]
42 {
43 return Err(ValidationError::InvalidBboxPx(px));
44 }
45 }
46 if let Some(norm) = self.bbox_norm {
47 let in_range = norm.iter().all(|v| !v.is_nan() && *v >= 0.0 && *v <= 1.0);
48 if !in_range || norm[0] > norm[2] || norm[1] > norm[3] {
49 return Err(ValidationError::InvalidBboxNorm(norm));
50 }
51 }
52 Ok(())
53 }
54}
55
56impl CaptureMetadata {
57 pub fn validate(&self) -> Result<(), ValidationError> {
58 if self.image_present && self.image.trim().is_empty() {
59 return Err(ValidationError::MissingImage);
60 }
61 for label in &self.polyp_labels {
62 label.validate()?;
63 }
64 Ok(())
65 }
66}