1use crate::core::types::Point;
2use crate::error::{IrisError, Result};
3use crate::image::Image;
4use burn::tensor::{Tensor, TensorData, backend::Backend};
5
6impl<B: Backend> Image<B> {
7 pub fn add(&self, other: &Self) -> Result<Self> {
9 if self.shape() != other.shape() {
10 return Err(IrisError::DimensionMismatch {
11 expected: self.shape().to_vec(),
12 actual: other.shape().to_vec(),
13 });
14 }
15 let added = self.tensor.clone().add(other.tensor.clone());
16 Ok(Image::new(added))
17 }
18
19 pub fn subtract(&self, other: &Self) -> Result<Self> {
21 if self.shape() != other.shape() {
22 return Err(IrisError::DimensionMismatch {
23 expected: self.shape().to_vec(),
24 actual: other.shape().to_vec(),
25 });
26 }
27 let subbed = self.tensor.clone().sub(other.tensor.clone());
28 Ok(Image::new(subbed))
29 }
30
31 pub fn multiply(&self, other: &Self) -> Result<Self> {
33 if self.shape() != other.shape() {
34 return Err(IrisError::DimensionMismatch {
35 expected: self.shape().to_vec(),
36 actual: other.shape().to_vec(),
37 });
38 }
39 let muled = self.tensor.clone().mul(other.tensor.clone());
40 Ok(Image::new(muled))
41 }
42
43 pub fn divide(&self, other: &Self) -> Result<Self> {
45 if self.shape() != other.shape() {
46 return Err(IrisError::DimensionMismatch {
47 expected: self.shape().to_vec(),
48 actual: other.shape().to_vec(),
49 });
50 }
51 let dived = self.tensor.clone().div(other.tensor.clone());
52 Ok(Image::new(dived))
53 }
54
55 pub fn absdiff(&self, other: &Self) -> Result<Self> {
57 if self.shape() != other.shape() {
58 return Err(IrisError::DimensionMismatch {
59 expected: self.shape().to_vec(),
60 actual: other.shape().to_vec(),
61 });
62 }
63 let diff = self.tensor.clone().sub(other.tensor.clone()).abs();
64 Ok(Image::new(diff))
65 }
66
67 pub fn bitwise_and(&self, other: &Self) -> Result<Self> {
69 self.bitwise_op(other, |a, b| a & b)
70 }
71
72 pub fn bitwise_or(&self, other: &Self) -> Result<Self> {
74 self.bitwise_op(other, |a, b| a | b)
75 }
76
77 pub fn bitwise_xor(&self, other: &Self) -> Result<Self> {
79 self.bitwise_op(other, |a, b| a ^ b)
80 }
81
82 pub fn bitwise_not(&self) -> Result<Self> {
84 let dims = self.tensor.dims();
85 let c = dims[0];
86 let h = dims[1];
87 let w = dims[2];
88
89 let device = self.tensor.device();
90 let tensor_data = self.tensor.clone().into_data();
91 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
92 let mut out_vals = vec![0.0f32; c * h * w];
93
94 {
95 use rayon::prelude::*;
96 out_vals.par_iter_mut().enumerate().for_each(|(i, val)| {
97 let pixel_val = (flat_vals[i].clamp(0.0, 1.0) * 255.0) as u8;
98 *val = f32::from(!pixel_val) / 255.0;
99 });
100 }
101
102 let new_data = TensorData::new(out_vals, [c, h, w]);
103 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
104 Ok(Image::new(new_tensor))
105 }
106
107 pub fn mean(&self) -> Result<Vec<f64>> {
109 let dims = self.tensor.dims();
110 let c = dims[0];
111 let h = dims[1];
112 let w = dims[2];
113
114 let tensor_data = self.tensor.clone().into_data();
115 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
116 let mut channel_means = vec![0.0; c];
117
118 for ch in 0..c {
119 let mut sum = 0.0;
120 for y in 0..h {
121 for x in 0..w {
122 sum += f64::from(flat_vals[ch * h * w + y * w + x]);
123 }
124 }
125 channel_means[ch] = sum / ((h * w) as f64);
126 }
127
128 Ok(channel_means)
129 }
130
131 pub fn mean_std_dev(&self) -> Result<(Vec<f64>, Vec<f64>)> {
133 let dims = self.tensor.dims();
134 let c = dims[0];
135 let h = dims[1];
136 let w = dims[2];
137
138 let tensor_data = self.tensor.clone().into_data();
139 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
140
141 let mut channel_means = vec![0.0; c];
142 let mut channel_stddevs = vec![0.0; c];
143
144 let n = (h * w) as f64;
145
146 for ch in 0..c {
147 let mut sum = 0.0;
148 for y in 0..h {
149 for x in 0..w {
150 sum += f64::from(flat_vals[ch * h * w + y * w + x]);
151 }
152 }
153 let mean = sum / n;
154 channel_means[ch] = mean;
155
156 let mut sq_sum = 0.0;
157 for y in 0..h {
158 for x in 0..w {
159 let diff = f64::from(flat_vals[ch * h * w + y * w + x]) - mean;
160 sq_sum += diff * diff;
161 }
162 }
163 channel_stddevs[ch] = (sq_sum / n).sqrt();
164 }
165
166 Ok((channel_means, channel_stddevs))
167 }
168
169 pub fn min_max_loc(&self) -> Result<(f64, f64, Point<usize>, Point<usize>)> {
171 let dims = self.tensor.dims();
172 let c = dims[0];
173 let h = dims[1];
174 let w = dims[2];
175
176 if c != 1 {
177 return Err(IrisError::InvalidParameter(
178 "min_max_loc requires a single-channel image".into(),
179 ));
180 }
181
182 let tensor_data = self.tensor.clone().into_data();
183 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
184
185 let mut min_val = f64::MAX;
186 let mut max_val = f64::MIN;
187 let mut min_loc = Point::new(0, 0);
188 let mut max_loc = Point::new(0, 0);
189
190 for y in 0..h {
191 for x in 0..w {
192 let val = f64::from(flat_vals[y * w + x]);
193 if val < min_val {
194 min_val = val;
195 min_loc = Point::new(x, y);
196 }
197 if val > max_val {
198 max_val = val;
199 max_loc = Point::new(x, y);
200 }
201 }
202 }
203
204 Ok((min_val, max_val, min_loc, max_loc))
205 }
206
207 pub fn count_non_zero(&self) -> Result<usize> {
209 let tensor_data = self.tensor.clone().into_data();
210 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
211 let count = flat_vals.iter().filter(|&&x| x > 0.0).count();
212 Ok(count)
213 }
214
215 pub fn in_range(&self, low: &[f32], high: &[f32]) -> Result<Self> {
218 let dims = self.tensor.dims();
219 let c = dims[0];
220 let h = dims[1];
221 let w = dims[2];
222
223 if low.len() != c || high.len() != c {
224 return Err(IrisError::InvalidParameter(format!(
225 "low/high length ({}/{}) must match channels ({c})",
226 low.len(),
227 high.len(),
228 )));
229 }
230
231 let device = self.tensor.device();
232 let tensor_data = self.tensor.clone().into_data();
233 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
234 let mut out_vals = vec![0.0f32; h * w];
235
236 let pixels = h * w;
237 for i in 0..pixels {
238 let mut in_range = true;
239 for ch in 0..c {
240 let val = flat_vals[ch * pixels + i];
241 if val < low[ch] || val > high[ch] {
242 in_range = false;
243 break;
244 }
245 }
246 out_vals[i] = if in_range { 1.0 } else { 0.0 };
247 }
248
249 let new_data = TensorData::new(out_vals, [1, h, w]);
250 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
251 Ok(Image::new(new_tensor))
252 }
253
254 pub fn normalize(&self, min_val: f32, max_val: f32) -> Result<Self> {
256 let dims = self.tensor.dims();
257 let c = dims[0];
258 let h = dims[1];
259 let w = dims[2];
260
261 let device = self.tensor.device();
262 let tensor_data = self.tensor.clone().into_data();
263 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
264 let mut out_vals = vec![0.0f32; c * h * w];
265
266 for ch in 0..c {
267 let mut ch_min = f32::MAX;
268 let mut ch_max = f32::MIN;
269 let pixels = h * w;
270 for i in 0..pixels {
271 let v = flat_vals[ch * pixels + i];
272 if v < ch_min {
273 ch_min = v;
274 }
275 if v > ch_max {
276 ch_max = v;
277 }
278 }
279 let range = ch_max - ch_min;
280 for i in 0..pixels {
281 let v = flat_vals[ch * pixels + i];
282 out_vals[ch * pixels + i] = if range.abs() < 1e-10 {
283 min_val
284 } else {
285 min_val + (v - ch_min) / range * (max_val - min_val)
286 };
287 }
288 }
289
290 let new_data = TensorData::new(out_vals, [c, h, w]);
291 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
292 Ok(Image::new(new_tensor))
293 }
294
295 fn bitwise_op(&self, other: &Self, op: impl Fn(u8, u8) -> u8 + Sync + Send) -> Result<Self> {
297 if self.shape() != other.shape() {
298 return Err(IrisError::DimensionMismatch {
299 expected: self.shape().to_vec(),
300 actual: other.shape().to_vec(),
301 });
302 }
303 let dims = self.tensor.dims();
304 let c = dims[0];
305 let h = dims[1];
306 let w = dims[2];
307
308 let device = self.tensor.device();
309 let data_self = self.tensor.clone().into_data();
310 let data_other = other.tensor.clone().into_data();
311
312 let vals_self: Vec<f32> = data_self.iter::<f32>().collect();
313 let vals_other: Vec<f32> = data_other.iter::<f32>().collect();
314 let mut out_vals = vec![0.0f32; c * h * w];
315
316 {
317 use rayon::prelude::*;
318 out_vals.par_iter_mut().enumerate().for_each(|(i, val)| {
319 let b1 = (vals_self[i].clamp(0.0, 1.0) * 255.0) as u8;
320 let b2 = (vals_other[i].clamp(0.0, 1.0) * 255.0) as u8;
321 *val = f32::from(op(b1, b2)) / 255.0;
322 });
323 }
324
325 let new_data = TensorData::new(out_vals, [c, h, w]);
326 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
327 Ok(Image::new(new_tensor))
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334 use crate::test_helpers::{TestBackend, test_device};
335 use burn::backend::ndarray::NdArrayDevice;
336
337 fn get_test_device() -> NdArrayDevice {
338 test_device()
339 }
340
341 #[test]
342 fn test_image_math_and_bitwise() {
343 let device = get_test_device();
344 let data1 = TensorData::new(vec![0.5f32; 3 * 4 * 4], [3, 4, 4]);
345 let data2 = TensorData::new(vec![0.2f32; 3 * 4 * 4], [3, 4, 4]);
346
347 let img1 = Image::new(Tensor::<TestBackend, 3>::from_data(data1, &device));
348 let img2 = Image::new(Tensor::<TestBackend, 3>::from_data(data2, &device));
349
350 let added = img1.add(&img2).unwrap();
352 assert_eq!(added.shape(), [3, 4, 4]);
353
354 let subbed = img1.subtract(&img2).unwrap();
355 assert_eq!(subbed.shape(), [3, 4, 4]);
356
357 let absdiff = img1.absdiff(&img2).unwrap();
358 assert_eq!(absdiff.shape(), [3, 4, 4]);
359
360 let bit_and = img1.bitwise_and(&img2).unwrap();
362 assert_eq!(bit_and.shape(), [3, 4, 4]);
363
364 let bit_not = img1.bitwise_not().unwrap();
365 assert_eq!(bit_not.shape(), [3, 4, 4]);
366
367 let mean_vals = img1.mean().unwrap();
369 assert!((mean_vals[0] - 0.5).abs() < 1e-4);
370
371 let count = img1.count_non_zero().unwrap();
372 assert_eq!(count, 3 * 4 * 4);
373 }
374
375 #[test]
376 fn test_in_range() {
377 let device = get_test_device();
378 let data = vec![0.1, 0.5, 0.9, 0.3, 0.6, 0.2, 0.7, 0.8, 0.4];
379 let img = Image::new(Tensor::<TestBackend, 3>::from_data(
380 TensorData::new(data, [3, 1, 3]),
381 &device,
382 ));
383
384 let mask = img.in_range(&[0.2, 0.2, 0.2], &[0.8, 0.8, 0.8]).unwrap();
385 assert_eq!(mask.shape(), [1, 1, 3]);
386 let vals: Vec<f32> = mask.tensor.into_data().iter::<f32>().collect();
387 assert!((vals[0]).abs() < 1e-5);
389 assert!((vals[1] - 1.0).abs() < 1e-5);
390 assert!((vals[2]).abs() < 1e-5);
391 }
392
393 #[test]
394 fn test_normalize() {
395 let device = get_test_device();
396 let data = vec![0.2, 0.4, 0.6, 0.8];
397 let img = Image::new(Tensor::<TestBackend, 3>::from_data(
398 TensorData::new(data, [1, 1, 4]),
399 &device,
400 ));
401
402 let normalized = img.normalize(0.0, 1.0).unwrap();
403 assert_eq!(normalized.shape(), [1, 1, 4]);
404 let vals: Vec<f32> = normalized.tensor.into_data().iter::<f32>().collect();
405 assert!((vals[0]).abs() < 1e-5); assert!((vals[3] - 1.0).abs() < 1e-5); }
408
409 #[test]
410 fn test_in_range_invalid_length() {
411 let device = get_test_device();
412 let data = vec![0.5f32; 3 * 4 * 4];
413 let img = Image::new(Tensor::<TestBackend, 3>::from_data(
414 TensorData::new(data, [3, 4, 4]),
415 &device,
416 ));
417 assert!(img.in_range(&[0.0], &[0.5, 0.5, 0.5]).is_err());
418 }
419}