data_contracts/
preprocess.rs1use thiserror::Error;
2
3#[derive(Debug, Clone, Copy)]
4pub struct ImageStats {
5 pub mean: [f32; 3],
6 pub std: [f32; 3],
7 pub aspect: f32,
8}
9
10impl ImageStats {
11 pub fn tiny_input(&self) -> [f32; 4] {
12 [self.mean[0], self.mean[1], self.mean[2], self.aspect]
13 }
14
15 pub fn feature_vector(&self, box_count: f32) -> [f32; 8] {
16 [
17 self.mean[0],
18 self.mean[1],
19 self.mean[2],
20 self.std[0],
21 self.std[1],
22 self.std[2],
23 self.aspect,
24 box_count,
25 ]
26 }
27}
28
29#[derive(Debug, Error)]
30pub enum ImageStatsError {
31 #[error("expected {expected} values, got {actual}")]
32 InvalidLength { expected: usize, actual: usize },
33 #[error("image dimensions must be non-zero")]
34 ZeroDimensions,
35}
36
37pub fn stats_from_rgb_u8(
38 width: u32,
39 height: u32,
40 rgb: &[u8],
41) -> Result<ImageStats, ImageStatsError> {
42 if width == 0 || height == 0 {
43 return Err(ImageStatsError::ZeroDimensions);
44 }
45 let expected = width as usize * height as usize * 3;
46 if rgb.len() != expected {
47 return Err(ImageStatsError::InvalidLength {
48 expected,
49 actual: rgb.len(),
50 });
51 }
52
53 let mut sum = [0f32; 3];
54 let mut sumsq = [0f32; 3];
55 for chunk in rgb.chunks_exact(3) {
56 let r = chunk[0] as f32 / 255.0;
57 let g = chunk[1] as f32 / 255.0;
58 let b = chunk[2] as f32 / 255.0;
59 sum[0] += r;
60 sum[1] += g;
61 sum[2] += b;
62 sumsq[0] += r * r;
63 sumsq[1] += g * g;
64 sumsq[2] += b * b;
65 }
66
67 stats_from_sums(width as usize, height as usize, sum, sumsq)
68}
69
70pub fn stats_from_rgba_u8(
71 width: u32,
72 height: u32,
73 rgba: &[u8],
74) -> Result<ImageStats, ImageStatsError> {
75 if width == 0 || height == 0 {
76 return Err(ImageStatsError::ZeroDimensions);
77 }
78 let expected = width as usize * height as usize * 4;
79 if rgba.len() != expected {
80 return Err(ImageStatsError::InvalidLength {
81 expected,
82 actual: rgba.len(),
83 });
84 }
85
86 let mut sum = [0f32; 3];
87 let mut sumsq = [0f32; 3];
88 for chunk in rgba.chunks_exact(4) {
89 let r = chunk[0] as f32 / 255.0;
90 let g = chunk[1] as f32 / 255.0;
91 let b = chunk[2] as f32 / 255.0;
92 sum[0] += r;
93 sum[1] += g;
94 sum[2] += b;
95 sumsq[0] += r * r;
96 sumsq[1] += g * g;
97 sumsq[2] += b * b;
98 }
99
100 stats_from_sums(width as usize, height as usize, sum, sumsq)
101}
102
103pub fn stats_from_chw_f32(
104 width: usize,
105 height: usize,
106 chw: &[f32],
107) -> Result<ImageStats, ImageStatsError> {
108 if width == 0 || height == 0 {
109 return Err(ImageStatsError::ZeroDimensions);
110 }
111 let expected = width * height * 3;
112 if chw.len() != expected {
113 return Err(ImageStatsError::InvalidLength {
114 expected,
115 actual: chw.len(),
116 });
117 }
118
119 let mut sum = [0f32; 3];
120 let mut sumsq = [0f32; 3];
121 let pixels_per_channel = width * height;
122 for c in 0..3 {
123 let start = c * pixels_per_channel;
124 let slice = &chw[start..start + pixels_per_channel];
125 for v in slice {
126 sum[c] += *v;
127 sumsq[c] += *v * *v;
128 }
129 }
130
131 stats_from_sums(width, height, sum, sumsq)
132}
133
134fn stats_from_sums(
135 width: usize,
136 height: usize,
137 sum: [f32; 3],
138 sumsq: [f32; 3],
139) -> Result<ImageStats, ImageStatsError> {
140 let pix_count = (width * height) as f32;
141 let mean = [sum[0] / pix_count, sum[1] / pix_count, sum[2] / pix_count];
142 let std = [
143 (sumsq[0] / pix_count - mean[0] * mean[0]).max(0.0).sqrt(),
144 (sumsq[1] / pix_count - mean[1] * mean[1]).max(0.0).sqrt(),
145 (sumsq[2] / pix_count - mean[2] * mean[2]).max(0.0).sqrt(),
146 ];
147 Ok(ImageStats {
148 mean,
149 std,
150 aspect: width as f32 / height as f32,
151 })
152}