optical_flow_lk/
float_image.rs1use std::fmt;
2
3#[derive(Clone, Copy, Debug)]
12pub struct FloatImageView<'a> {
13 pixels: &'a [f32],
14 width: u32,
15 height: u32,
16}
17
18impl<'a> FloatImageView<'a> {
19 pub const MAX_ABS_VALUE: f32 = 16_777_216.0;
22
23 pub fn new(pixels: &'a [f32], width: u32, height: u32) -> Result<Self, FloatImageError> {
27 let expected = width as u64 * height as u64;
28 if pixels.len() as u64 != expected {
29 return Err(FloatImageError::SizeMismatch {
30 expected,
31 actual: pixels.len(),
32 });
33 }
34 if let Some(index) = first_invalid(pixels) {
35 return Err(FloatImageError::InvalidValue {
36 index,
37 value: pixels[index],
38 });
39 }
40 Ok(Self {
41 pixels,
42 width,
43 height,
44 })
45 }
46
47 pub fn width(&self) -> u32 {
49 self.width
50 }
51
52 pub fn height(&self) -> u32 {
54 self.height
55 }
56
57 pub fn dimensions(&self) -> (u32, u32) {
59 (self.width, self.height)
60 }
61
62 pub fn pixels(&self) -> &'a [f32] {
64 self.pixels
65 }
66}
67
68#[derive(Clone, Copy, Debug, PartialEq)]
70pub enum FloatImageError {
71 SizeMismatch {
73 expected: u64,
75 actual: usize,
77 },
78 InvalidValue {
81 index: usize,
83 value: f32,
85 },
86}
87
88impl fmt::Display for FloatImageError {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 match self {
91 Self::SizeMismatch { expected, actual } => {
92 write!(f, "image needs {expected} pixels, got {actual}")
93 }
94 Self::InvalidValue { index, value } => write!(
95 f,
96 "pixel {index} is {value}; values must be finite and at most {} in magnitude",
97 FloatImageView::MAX_ABS_VALUE
98 ),
99 }
100 }
101}
102
103impl std::error::Error for FloatImageError {}
104
105fn first_invalid(pixels: &[f32]) -> Option<usize> {
107 let valid = |value: f32| value.abs() <= FloatImageView::MAX_ABS_VALUE;
109 const CHUNK: usize = 256;
110 for (chunk_index, chunk) in pixels.chunks(CHUNK).enumerate() {
111 let all_valid = chunk.iter().fold(true, |all, &value| all & valid(value));
113 if !all_valid {
114 let offset = chunk.iter().position(|&value| !valid(value));
115 return offset.map(|offset| chunk_index * CHUNK + offset);
116 }
117 }
118 None
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn accepts_finite_values_within_range_and_empty_images() {
127 let max = FloatImageView::MAX_ABS_VALUE;
128 let pixels = [0.0, -0.0, 1e-30, -max, max, 25.5];
129 let view = FloatImageView::new(&pixels, 3, 2).unwrap();
130 assert_eq!(view.dimensions(), (3, 2));
131 assert_eq!(view.pixels(), &pixels);
132 assert!(FloatImageView::new(&[], 0, 7).is_ok());
133 }
134
135 #[test]
136 fn rejects_size_mismatch_without_overflow() {
137 assert_eq!(
138 FloatImageView::new(&[0.0; 5], 2, 3).unwrap_err(),
139 FloatImageError::SizeMismatch {
140 expected: 6,
141 actual: 5
142 }
143 );
144 let error = FloatImageView::new(&[0.0; 4], u32::MAX, u32::MAX).unwrap_err();
145 assert!(matches!(error, FloatImageError::SizeMismatch { .. }));
146 }
147
148 #[test]
149 fn reports_the_first_invalid_value() {
150 let max = FloatImageView::MAX_ABS_VALUE;
151 for (bad, index) in [
152 (f32::NAN, 0),
153 (f32::INFINITY, 300),
154 (f32::NEG_INFINITY, 511),
155 (max * 2.0, 999),
156 (-max * 2.0, 257),
157 ] {
158 let mut pixels = vec![1.0f32; 1000];
159 pixels[index] = bad;
160 if index + 2 < pixels.len() {
162 pixels[index + 2] = f32::NAN;
163 }
164 match FloatImageView::new(&pixels, 100, 10).unwrap_err() {
165 FloatImageError::InvalidValue { index: got, value } => {
166 assert_eq!(got, index);
167 assert_eq!(value.to_bits(), bad.to_bits());
168 }
169 other => panic!("unexpected {other:?}"),
170 }
171 }
172 }
173}