1pub mod bilateral;
2
3use crate::error::{IrisError, Result};
4use crate::image::Image;
5use burn::tensor::{Tensor, TensorData, backend::Backend};
6
7const SQRT_2: f32 = std::f32::consts::SQRT_2;
9
10impl<B: Backend> Image<B> {
11 pub fn box_blur(self, kernel_size: usize) -> Result<Self> {
13 if kernel_size.is_multiple_of(2) {
14 return Err(IrisError::InvalidParameter(
15 "Kernel size must be odd".into(),
16 ));
17 }
18
19 let dims = self.tensor.dims();
20 let c = dims[0];
21 let h = dims[1];
22 let w = dims[2];
23
24 let device = self.tensor.device();
25 let tensor_data = self.tensor.into_data();
26 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
27 let mut out_vals = vec![0.0f32; c * h * w];
28
29 let rad = (kernel_size / 2) as isize;
30
31 {
32 use rayon::prelude::*;
33 out_vals
34 .par_chunks_exact_mut(w)
35 .enumerate()
36 .for_each(|(idx, row)| {
37 let ch = idx / h;
38 let y = idx % h;
39 for x in 0..w {
40 let mut sum = 0.0f32;
41 let mut count = 0.0f32;
42
43 for ky in -rad..=rad {
44 let py = y as isize + ky;
45 if py >= 0 && py < h as isize {
46 for kx in -rad..=rad {
47 let px = x as isize + kx;
48 if px >= 0 && px < w as isize {
49 sum += flat_vals
50 [ch * h * w + (py as usize) * w + (px as usize)];
51 count += 1.0;
52 }
53 }
54 }
55 }
56 row[x] = sum / count;
57 }
58 });
59 }
60
61 let new_data = TensorData::new(out_vals, [c, h, w]);
62 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
63 Ok(Image::new(new_tensor))
64 }
65
66 pub fn gaussian_blur(self, kernel_size: usize, sigma: f64) -> Result<Self> {
68 if kernel_size.is_multiple_of(2) {
69 return Err(IrisError::InvalidParameter(
70 "Kernel size must be odd".into(),
71 ));
72 }
73
74 let dims = self.tensor.dims();
75 let c = dims[0];
76 let h = dims[1];
77 let w = dims[2];
78
79 let device = self.tensor.device();
80 let tensor_data = self.tensor.into_data();
81 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
82 let mut out_vals = vec![0.0f32; c * h * w];
83
84 let rad = (kernel_size / 2) as isize;
85
86 let mut kernel = vec![vec![0.0f64; kernel_size]; kernel_size];
88 let mut sum = 0.0f64;
89
90 let s2 = 2.0 * sigma * sigma;
91
92 for ky in -rad..=rad {
93 for kx in -rad..=rad {
94 let r = (kx * kx + ky * ky) as f64;
95 let val = (-r / s2).exp();
96 kernel[(ky + rad) as usize][(kx + rad) as usize] = val;
97 sum += val;
98 }
99 }
100
101 for row in &mut kernel {
103 for val in row {
104 *val /= sum;
105 }
106 }
107
108 {
109 use rayon::prelude::*;
110 out_vals
111 .par_chunks_exact_mut(w)
112 .enumerate()
113 .for_each(|(idx, row)| {
114 let ch = idx / h;
115 let y = idx % h;
116 for x in 0..w {
117 let mut blur_sum = 0.0f64;
118 for ky in -rad..=rad {
119 let py = y as isize + ky;
120 let py_clamped = py.clamp(0, h as isize - 1) as usize;
121 for kx in -rad..=rad {
122 let px = x as isize + kx;
123 let px_clamped = px.clamp(0, w as isize - 1) as usize;
124 let weight = kernel[(ky + rad) as usize][(kx + rad) as usize];
125 blur_sum +=
126 f64::from(flat_vals[ch * h * w + py_clamped * w + px_clamped])
127 * weight;
128 }
129 }
130 row[x] = blur_sum as f32;
131 }
132 });
133 }
134
135 let new_data = TensorData::new(out_vals, [c, h, w]);
136 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
137 Ok(Image::new(new_tensor))
138 }
139
140 pub fn median_blur(self, kernel_size: usize) -> Result<Self> {
142 if kernel_size.is_multiple_of(2) {
143 return Err(IrisError::InvalidParameter(
144 "Kernel size must be odd".into(),
145 ));
146 }
147
148 let dims = self.tensor.dims();
149 let c = dims[0];
150 let h = dims[1];
151 let w = dims[2];
152
153 let device = self.tensor.device();
154 let tensor_data = self.tensor.into_data();
155 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
156 let mut out_vals = vec![0.0f32; c * h * w];
157
158 let rad = (kernel_size / 2) as isize;
159
160 {
161 use rayon::prelude::*;
162 out_vals
163 .par_chunks_exact_mut(w)
164 .enumerate()
165 .for_each(|(idx, row)| {
166 let ch = idx / h;
167 let y = idx % h;
168 for x in 0..w {
169 let mut neighbors = Vec::with_capacity(kernel_size * kernel_size);
170
171 for ky in -rad..=rad {
172 let py = y as isize + ky;
173 if py >= 0 && py < h as isize {
174 for kx in -rad..=rad {
175 let px = x as isize + kx;
176 if px >= 0 && px < w as isize {
177 neighbors.push(
178 flat_vals
179 [ch * h * w + (py as usize) * w + (px as usize)],
180 );
181 }
182 }
183 }
184 }
185 neighbors
186 .sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
187 let median = neighbors[neighbors.len() / 2];
188 row[x] = median;
189 }
190 });
191 }
192
193 let new_data = TensorData::new(out_vals, [c, h, w]);
194 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
195 Ok(Image::new(new_tensor))
196 }
197
198 pub fn distance_transform(&self) -> Result<Self> {
202 let gray = self.grayscale()?;
203 let dims = gray.tensor.dims();
204 let h = dims[1];
205 let w = dims[2];
206
207 let tensor_data = gray.tensor.clone().into_data();
208 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
209
210 let mut binary = vec![false; h * w];
212 for (i, &v) in flat_vals.iter().enumerate() {
213 binary[i] = v > 0.5;
214 }
215
216 let inf = (h * w) as f32;
217 let mut dt = vec![inf; h * w];
218
219 for y in 0..h {
221 for x in 0..w {
222 let idx = y * w + x;
223 if binary[idx] {
224 dt[idx] = 0.0;
225 } else {
226 if x > 0 {
227 let prev = dt[idx - 1] + 1.0;
228 if prev < dt[idx] {
229 dt[idx] = prev;
230 }
231 }
232 if y > 0 {
233 let prev = dt[(y - 1) * w + x] + 1.0;
234 if prev < dt[idx] {
235 dt[idx] = prev;
236 }
237 }
238 if x > 0 && y > 0 {
239 let prev = dt[(y - 1) * w + (x - 1)] + SQRT_2;
240 if prev < dt[idx] {
241 dt[idx] = prev;
242 }
243 }
244 if x < w - 1 && y > 0 {
245 let prev = dt[(y - 1) * w + (x + 1)] + SQRT_2;
246 if prev < dt[idx] {
247 dt[idx] = prev;
248 }
249 }
250 }
251 }
252 }
253
254 for y in (0..h).rev() {
256 for x in (0..w).rev() {
257 let idx = y * w + x;
258 if x < w - 1 {
259 let next = dt[idx + 1] + 1.0;
260 if next < dt[idx] {
261 dt[idx] = next;
262 }
263 }
264 if y < h - 1 {
265 let next = dt[(y + 1) * w + x] + 1.0;
266 if next < dt[idx] {
267 dt[idx] = next;
268 }
269 }
270 if x < w - 1 && y < h - 1 {
271 let next = dt[(y + 1) * w + (x + 1)] + SQRT_2;
272 if next < dt[idx] {
273 dt[idx] = next;
274 }
275 }
276 if x > 0 && y < h - 1 {
277 let next = dt[(y + 1) * w + (x - 1)] + SQRT_2;
278 if next < dt[idx] {
279 dt[idx] = next;
280 }
281 }
282 }
283 }
284
285 let max_dt = dt.iter().cloned().fold(0.0f32, f32::max);
287 if max_dt > 0.0 {
288 for v in &mut dt {
289 *v /= max_dt;
290 }
291 }
292
293 let device = gray.tensor.device();
294 let data = TensorData::new(dt, [1, h, w]);
295 let tensor = Tensor::<B, 3>::from_data(data, &device);
296 Ok(Image::new(tensor))
297 }
298
299 pub fn filter2d(
304 &self,
305 kernel: &[&[f32]],
306 anchor: Option<(isize, isize)>,
307 delta: f32,
308 ) -> Result<Self> {
309 let kh = kernel.len();
310 if kh == 0 || kernel[0].is_empty() {
311 return Err(IrisError::InvalidParameter(
312 "Kernel must be non-empty".into(),
313 ));
314 }
315 let kw = kernel[0].len();
316
317 let dims = self.tensor.dims();
318 let c = dims[0];
319 let h = dims[1];
320 let w = dims[2];
321
322 let (ax, ay) = anchor.unwrap_or((kw as isize / 2, kh as isize / 2));
323
324 let device = self.tensor.device();
325 let tensor_data = self.tensor.clone().into_data();
326 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
327 let mut out_vals = vec![0.0f32; c * h * w];
328
329 for ch in 0..c {
330 for y in 0..h {
331 for x in 0..w {
332 let mut sum = 0.0f64;
333 for ky in 0..kh {
334 for kx in 0..kw {
335 let sy = y as isize + ky as isize - ay;
336 let sx = x as isize + kx as isize - ax;
337 if sy >= 0 && sy < h as isize && sx >= 0 && sx < w as isize {
338 sum += flat_vals[ch * h * w + sy as usize * w + sx as usize] as f64
339 * kernel[ky][kx] as f64;
340 }
341 }
342 }
343 out_vals[ch * h * w + y * w + x] = sum as f32 + delta;
344 }
345 }
346 }
347
348 let new_data = TensorData::new(out_vals, [c, h, w]);
349 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
350 Ok(Image::new(new_tensor))
351 }
352
353 pub fn add_weighted(&self, other: &Self, alpha: f32, beta: f32, gamma: f32) -> Result<Self> {
355 if self.shape() != other.shape() {
356 return Err(IrisError::DimensionMismatch {
357 expected: self.shape().to_vec(),
358 actual: other.shape().to_vec(),
359 });
360 }
361 let result = self
362 .tensor
363 .clone()
364 .mul_scalar(alpha)
365 .add(other.tensor.clone().mul_scalar(beta))
366 .add_scalar(gamma);
367 Ok(Image::new(result))
368 }
369
370 pub fn convert_scale_abs(&self, scale: f32, shift: f32) -> Result<Self> {
372 let dims = self.tensor.dims();
373 let c = dims[0];
374 let h = dims[1];
375 let w = dims[2];
376
377 let device = self.tensor.device();
378 let tensor_data = self.tensor.clone().into_data();
379 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
380 let mut out_vals = vec![0.0f32; c * h * w];
381
382 for i in 0..(c * h * w) {
383 let val = (flat_vals[i] * scale + shift).abs().min(1.0);
384 out_vals[i] = val;
385 }
386
387 let new_data = TensorData::new(out_vals, [c, h, w]);
388 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
389 Ok(Image::new(new_tensor))
390 }
391
392 pub fn copy_to(&self, dst: &mut Self, mask: Option<&Self>) -> Result<()> {
395 if self.shape() != dst.shape() {
396 return Err(IrisError::DimensionMismatch {
397 expected: self.shape().to_vec(),
398 actual: dst.shape().to_vec(),
399 });
400 }
401
402 let dims = self.tensor.dims();
403 let c = dims[0];
404 let h = dims[1];
405 let w = dims[2];
406
407 let src_data = self.tensor.clone().into_data();
408 let src_vals: Vec<f32> = src_data.iter::<f32>().collect();
409 let dst_data = dst.tensor.clone().into_data();
410 let mut dst_vals: Vec<f32> = dst_data.iter::<f32>().collect();
411
412 let mask_vals: Option<Vec<f32>> = mask.map(|m| {
413 let d = m.tensor.clone().into_data();
414 d.iter::<f32>().collect()
415 });
416
417 let pixels = h * w;
418 for i in 0..pixels {
419 let dominated = match &mask_vals {
420 Some(mv) => mv[i] > 0.0,
421 None => true,
422 };
423 if dominated {
424 for ch in 0..c {
425 dst_vals[ch * pixels + i] = src_vals[ch * pixels + i];
426 }
427 }
428 }
429
430 *dst = Image::new(Tensor::<B, 3>::from_data(
431 TensorData::new(dst_vals, [c, h, w]),
432 &dst.tensor.device(),
433 ));
434 Ok(())
435 }
436
437 pub fn laplacian_of_gaussian(&self, sigma: f32) -> Result<Self> {
441 let gray = self.grayscale()?;
442 let dims = gray.tensor.dims();
443 let h = dims[1];
444 let w = dims[2];
445
446 let k_size = ((6.0 * sigma as f64) as usize) | 1;
448 let half = k_size / 2;
449
450 let sigma2 = (sigma as f64) * (sigma as f64);
452 let sigma4 = sigma2 * sigma2;
453 let mut kernel = Vec::with_capacity(k_size * k_size);
454 let mut kernel_sum = 0.0f64;
455
456 for ky in 0..k_size {
457 for kx in 0..k_size {
458 let dx = (kx as f64) - (half as f64);
459 let dy = (ky as f64) - (half as f64);
460 let r2 = dx * dx + dy * dy;
461 let val = -(1.0 / (std::f64::consts::PI * sigma4))
462 * (1.0 - r2 / (2.0 * sigma2))
463 * (-r2 / (2.0 * sigma2)).exp();
464 kernel.push(val);
465 kernel_sum += val;
466 }
467 }
468
469 let mean = kernel_sum / (k_size * k_size) as f64;
471 for k in &mut kernel {
472 *k -= mean;
473 }
474
475 let tensor_data = gray.tensor.clone().into_data();
477 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
478 let mut out_vals = vec![0.0f32; h * w];
479
480 for y in 0..h {
481 for x in 0..w {
482 let mut sum = 0.0f64;
483 for ky in 0..k_size {
484 for kx in 0..k_size {
485 let sy = (y + ky).min(h - 1);
486 let sx = (x + kx).min(w - 1);
487 sum += flat_vals[sy * w + sx] as f64 * kernel[ky * k_size + kx];
488 }
489 }
490 out_vals[y * w + x] = sum as f32;
491 }
492 }
493
494 let device = gray.tensor.device();
495 let data = TensorData::new(out_vals, [1, h, w]);
496 let tensor = Tensor::<B, 3>::from_data(data, &device);
497 Ok(Image::new(tensor))
498 }
499}
500
501#[cfg(test)]
502mod tests {
503 use super::*;
504 use crate::test_helpers::{TestBackend, test_device};
505
506 #[test]
507 fn test_filters_blur() {
508 let device = test_device();
509 let flat_data = vec![0.5f32; 3 * 8 * 8];
510 let tensor_data = TensorData::new(flat_data, [3, 8, 8]);
511 let img = Image::new(Tensor::<TestBackend, 3>::from_data(tensor_data, &device));
512
513 let boxed = img.clone().box_blur(3).unwrap();
514 assert_eq!(boxed.shape(), [3, 8, 8]);
515
516 let gauss = img.clone().gaussian_blur(3, 1.0).unwrap();
517 assert_eq!(gauss.shape(), [3, 8, 8]);
518
519 let median = img.clone().median_blur(3).unwrap();
520 assert_eq!(median.shape(), [3, 8, 8]);
521 }
522
523 #[test]
524 fn test_distance_transform() {
525 let device = test_device();
526 let flat_data = vec![
527 0.0f32, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
528 ];
529 let tensor =
530 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [1, 4, 4]), &device);
531 let img = Image::new(tensor);
532 let dt = img.distance_transform().unwrap();
533 assert_eq!(dt.shape(), [1, 4, 4]);
534 }
535
536 #[test]
537 fn test_laplacian_of_gaussian() {
538 let device = test_device();
539 let flat_data = vec![0.5f32; 3 * 16 * 16];
540 let tensor =
541 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [3, 16, 16]), &device);
542 let img = Image::new(tensor);
543 let log = img.laplacian_of_gaussian(1.0).unwrap();
544 assert_eq!(log.shape(), [1, 16, 16]);
545 }
546
547 #[test]
548 fn test_filter2d() {
549 let device = test_device();
550 let data = TensorData::new(vec![0.5f32; 3 * 8 * 8], [3, 8, 8]);
551 let img = Image::new(Tensor::<TestBackend, 3>::from_data(data, &device));
552
553 let kernel: Vec<&[f32]> = vec![
555 &[1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0],
556 &[1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0],
557 &[1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0],
558 ];
559 let result = img.filter2d(&kernel, None, 0.0).unwrap();
560 assert_eq!(result.shape(), [3, 8, 8]);
561 }
562
563 #[test]
564 fn test_add_weighted() {
565 let device = test_device();
566 let data1 = TensorData::new(vec![0.5f32; 3 * 4 * 4], [3, 4, 4]);
567 let data2 = TensorData::new(vec![0.3f32; 3 * 4 * 4], [3, 4, 4]);
568 let img1 = Image::new(Tensor::<TestBackend, 3>::from_data(data1, &device));
569 let img2 = Image::new(Tensor::<TestBackend, 3>::from_data(data2, &device));
570
571 let result = img1.add_weighted(&img2, 0.6, 0.4, 0.0).unwrap();
572 assert_eq!(result.shape(), [3, 4, 4]);
573 }
574
575 #[test]
576 fn test_convert_scale_abs() {
577 let device = test_device();
578 let data = TensorData::new(vec![-0.5f32, -0.1, 0.0, 0.3, 0.8], [1, 1, 5]);
579 let img = Image::new(Tensor::<TestBackend, 3>::from_data(data, &device));
580 let result = img.convert_scale_abs(1.0, 0.0).unwrap();
581 assert_eq!(result.shape(), [1, 1, 5]);
582 let vals: Vec<f32> = result.tensor.into_data().iter::<f32>().collect();
583 assert!((vals[0] - 0.5).abs() < 1e-5);
584 assert!((vals[1] - 0.1).abs() < 1e-5);
585 }
586
587 #[test]
588 fn test_copy_to_with_mask() {
589 let device = test_device();
590 let data = TensorData::new(vec![1.0f32; 3 * 4 * 4], [3, 4, 4]);
591 let mask_data = TensorData::new(
592 vec![
593 1.0f32, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0,
594 ],
595 [1, 4, 4],
596 );
597 let src = Image::new(Tensor::<TestBackend, 3>::from_data(data, &device));
598 let mut dst = Image::new(Tensor::<TestBackend, 3>::from_data(
599 TensorData::new(vec![0.0f32; 3 * 4 * 4], [3, 4, 4]),
600 &device,
601 ));
602 let mask = Image::new(Tensor::<TestBackend, 3>::from_data(mask_data, &device));
603 src.copy_to(&mut dst, Some(&mask)).unwrap();
604 assert_eq!(dst.shape(), [3, 4, 4]);
605 }
606}