1pub mod ops;
2
3pub use ops::{MorphOp, MorphShape, Morphology};
4
5use crate::error::{IrisError, Result};
6use crate::image::Image;
7use burn::tensor::{Tensor, TensorData, backend::Backend};
8
9impl<B: Backend> Image<B> {
10 pub fn dilate_with_kernel(self, kernel: &[&[u8]]) -> Result<Self> {
14 let kh = kernel.len();
15 if kh == 0 || kernel[0].is_empty() {
16 return Err(IrisError::InvalidParameter(
17 "Kernel must be non-empty".into(),
18 ));
19 }
20 let kw = kernel[0].len();
21
22 let dims = self.tensor.dims();
23 let c = dims[0];
24 let h = dims[1];
25 let w = dims[2];
26
27 let device = self.tensor.device();
28 let tensor_data = self.tensor.into_data();
29 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
30 let mut out_vals = vec![0.0f32; c * h * w];
31
32 let ay = kh as isize / 2;
33 let ax = kw as isize / 2;
34
35 for ch in 0..c {
36 for y in 0..h {
37 for x in 0..w {
38 let mut max_val = f32::MIN;
39 for ky in 0..kh {
40 for kx in 0..kw {
41 if kernel[ky][kx] == 0 {
42 continue;
43 }
44 let sy = y as isize + ky as isize - ay;
45 let sx = x as isize + kx as isize - ax;
46 if sy >= 0 && sy < h as isize && sx >= 0 && sx < w as isize {
47 let val = flat_vals[ch * h * w + sy as usize * w + sx as usize];
48 if val > max_val {
49 max_val = val;
50 }
51 }
52 }
53 }
54 out_vals[ch * h * w + y * w + x] = if max_val == f32::MIN {
55 flat_vals[ch * h * w + y * w + x]
56 } else {
57 max_val
58 };
59 }
60 }
61 }
62
63 let new_data = TensorData::new(out_vals, [c, h, w]);
64 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
65 Ok(Image::new(new_tensor))
66 }
67
68 pub fn erode_with_kernel(self, kernel: &[&[u8]]) -> Result<Self> {
72 let kh = kernel.len();
73 if kh == 0 || kernel[0].is_empty() {
74 return Err(IrisError::InvalidParameter(
75 "Kernel must be non-empty".into(),
76 ));
77 }
78 let kw = kernel[0].len();
79
80 let dims = self.tensor.dims();
81 let c = dims[0];
82 let h = dims[1];
83 let w = dims[2];
84
85 let device = self.tensor.device();
86 let tensor_data = self.tensor.into_data();
87 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
88 let mut out_vals = vec![0.0f32; c * h * w];
89
90 let ay = kh as isize / 2;
91 let ax = kw as isize / 2;
92
93 for ch in 0..c {
94 for y in 0..h {
95 for x in 0..w {
96 let mut min_val = f32::MAX;
97 for ky in 0..kh {
98 for kx in 0..kw {
99 if kernel[ky][kx] == 0 {
100 continue;
101 }
102 let sy = y as isize + ky as isize - ay;
103 let sx = x as isize + kx as isize - ax;
104 if sy >= 0 && sy < h as isize && sx >= 0 && sx < w as isize {
105 let val = flat_vals[ch * h * w + sy as usize * w + sx as usize];
106 if val < min_val {
107 min_val = val;
108 }
109 }
110 }
111 }
112 out_vals[ch * h * w + y * w + x] = if min_val == f32::MAX {
113 flat_vals[ch * h * w + y * w + x]
114 } else {
115 min_val
116 };
117 }
118 }
119 }
120
121 let new_data = TensorData::new(out_vals, [c, h, w]);
122 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
123 Ok(Image::new(new_tensor))
124 }
125
126 pub fn dilate(self, kernel_size: usize) -> Result<Self> {
129 if kernel_size.is_multiple_of(2) {
130 return Err(IrisError::InvalidParameter(
131 "Kernel size must be odd".into(),
132 ));
133 }
134
135 let dims = self.tensor.dims();
136 let c = dims[0];
137 let h = dims[1];
138 let w = dims[2];
139
140 let device = self.tensor.device();
141 let tensor_data = self.tensor.into_data();
142 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
143 let mut out_vals = vec![0.0f32; c * h * w];
144
145 let rad = (kernel_size / 2) as isize;
146
147 {
148 use rayon::prelude::*;
149 out_vals
150 .par_chunks_exact_mut(w)
151 .enumerate()
152 .for_each(|(idx, row)| {
153 let ch = idx / h;
154 let y = idx % h;
155
156 for x in 0..w {
157 let mut max_val = f32::MIN;
158 for ky in -rad..=rad {
159 let py = y as isize + ky;
160 if py >= 0 && py < h as isize {
161 for kx in -rad..=rad {
162 let px = x as isize + kx;
163 if px >= 0 && px < w as isize {
164 let val = flat_vals
165 [ch * h * w + (py as usize) * w + (px as usize)];
166 if val > max_val {
167 max_val = val;
168 }
169 }
170 }
171 }
172 }
173 row[x] = max_val;
174 }
175 });
176 }
177
178 let new_data = TensorData::new(out_vals, [c, h, w]);
179 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
180 Ok(Image::new(new_tensor))
181 }
182
183 pub fn erode(self, kernel_size: usize) -> Result<Self> {
186 if kernel_size.is_multiple_of(2) {
187 return Err(IrisError::InvalidParameter(
188 "Kernel size must be odd".into(),
189 ));
190 }
191
192 let dims = self.tensor.dims();
193 let c = dims[0];
194 let h = dims[1];
195 let w = dims[2];
196
197 let device = self.tensor.device();
198 let tensor_data = self.tensor.into_data();
199 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
200 let mut out_vals = vec![0.0f32; c * h * w];
201
202 let rad = (kernel_size / 2) as isize;
203
204 {
205 use rayon::prelude::*;
206 out_vals
207 .par_chunks_exact_mut(w)
208 .enumerate()
209 .for_each(|(idx, row)| {
210 let ch = idx / h;
211 let y = idx % h;
212
213 for x in 0..w {
214 let mut min_val = f32::MAX;
215 for ky in -rad..=rad {
216 let py = y as isize + ky;
217 if py >= 0 && py < h as isize {
218 for kx in -rad..=rad {
219 let px = x as isize + kx;
220 if px >= 0 && px < w as isize {
221 let val = flat_vals
222 [ch * h * w + (py as usize) * w + (px as usize)];
223 if val < min_val {
224 min_val = val;
225 }
226 }
227 }
228 }
229 }
230 row[x] = min_val;
231 }
232 });
233 }
234
235 let new_data = TensorData::new(out_vals, [c, h, w]);
236 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
237 Ok(Image::new(new_tensor))
238 }
239
240 pub fn morph_open(self, kernel_size: usize) -> Result<Self> {
242 self.erode(kernel_size)?.dilate(kernel_size)
243 }
244
245 pub fn morph_close(self, kernel_size: usize) -> Result<Self> {
247 self.dilate(kernel_size)?.erode(kernel_size)
248 }
249
250 pub fn hit_or_miss(&self, pattern: &[&[u8]], bg_pattern: &[&[u8]]) -> Result<Self> {
257 let dims = self.tensor.dims();
258 let c = dims[0];
259 let h = dims[1];
260 let w = dims[2];
261
262 if pattern.is_empty() || pattern[0].is_empty() {
263 return Err(IrisError::InvalidParameter(
264 "Pattern must be non-empty".into(),
265 ));
266 }
267 if bg_pattern.len() != pattern.len() || bg_pattern[0].len() != pattern[0].len() {
268 return Err(IrisError::InvalidParameter(
269 "Background pattern must match foreground pattern dimensions".into(),
270 ));
271 }
272
273 let ph = pattern.len();
274 let pw = pattern[0].len();
275 let ay = ph as isize / 2;
276 let ax = pw as isize / 2;
277
278 let device = self.tensor.device();
279 let tensor_data = self.tensor.clone().into_data();
280 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
281 let mut out_vals = vec![0.0f32; c * h * w];
282
283 for ch in 0..c {
284 for y in 0..h {
285 for x in 0..w {
286 let mut matched = true;
287
288 for ky in 0..ph {
289 for kx in 0..pw {
290 let sy = y as isize + ky as isize - ay;
291 let sx = x as isize + kx as isize - ax;
292
293 if sy < 0 || sy >= h as isize || sx < 0 || sx >= w as isize {
294 if pattern[ky][kx] == 1 || bg_pattern[ky][kx] == 1 {
295 matched = false;
296 break;
297 }
298 continue;
299 }
300
301 let val = flat_vals[ch * h * w + sy as usize * w + sx as usize];
302 let is_foreground = val > 0.5;
303
304 if pattern[ky][kx] == 1 && !is_foreground {
305 matched = false;
306 break;
307 }
308 if bg_pattern[ky][kx] == 1 && is_foreground {
309 matched = false;
310 break;
311 }
312 }
313 if !matched {
314 break;
315 }
316 }
317
318 if matched {
319 out_vals[ch * h * w + y * w + x] = 1.0;
320 }
321 }
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 pub fn thin(&self) -> Result<Self> {
336 let dims = self.tensor.dims();
337 let c = dims[0];
338 let h = dims[1];
339 let w = dims[2];
340
341 let device = self.tensor.device();
342 let tensor_data = self.tensor.clone().into_data();
343 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
344
345 let mut grid: Vec<u8> = flat_vals
346 .iter()
347 .map(|&v| if v > 0.5 { 1u8 } else { 0u8 })
348 .collect();
349
350 let count_transitions = |grid: &[u8], w: usize, h: usize, x: isize, y: isize| -> u8 {
351 let dx = [1, 1, 0, -1, -1, -1, 0, 1];
352 let dy = [0, 1, 1, 1, 0, -1, -1, -1];
353 let mut count = 0u8;
354 for i in 0..8 {
355 let i2 = (i + 1) % 8;
356 let x1 = x + dx[i];
357 let y1 = y + dy[i];
358 let x2 = x + dx[i2];
359 let y2 = y + dy[i2];
360
361 let v1 = if x1 >= 0 && x1 < w as isize && y1 >= 0 && y1 < h as isize {
362 grid[y1 as usize * w + x1 as usize]
363 } else {
364 0
365 };
366 let v2 = if x2 >= 0 && x2 < w as isize && y2 >= 0 && y2 < h as isize {
367 grid[y2 as usize * w + x2 as usize]
368 } else {
369 0
370 };
371
372 if v1 == 0 && v2 == 1 {
373 count += 1;
374 }
375 }
376 count
377 };
378
379 let count_neighbors = |grid: &[u8], w: usize, h: usize, x: isize, y: isize| -> u32 {
380 let dx = [1, 1, 0, -1, -1, -1, 0, 1];
381 let dy = [0, 1, 1, 1, 0, -1, -1, -1];
382 let mut sum = 0u32;
383 for i in 0..8 {
384 let nx = x + dx[i];
385 let ny = y + dy[i];
386 if nx >= 0 && nx < w as isize && ny >= 0 && ny < h as isize {
387 sum += grid[ny as usize * w + nx as usize] as u32;
388 }
389 }
390 sum
391 };
392
393 let mut changed = true;
394 while changed {
395 changed = false;
396
397 let mut to_remove = Vec::new();
399 for y in 1..(h - 1) {
400 for x in 1..(w - 1) {
401 let xi = x as isize;
402 let yi = y as isize;
403
404 let p = grid[yi as usize * w + xi as usize];
405 if p != 1 {
406 continue;
407 }
408
409 let n = count_neighbors(&grid, w, h, xi, yi);
410 let t = count_transitions(&grid, w, h, xi, yi);
411
412 let p2 = grid[((yi - 1).max(0)) as usize * w + xi as usize];
413 let p4 = grid[yi as usize * w + ((xi + 1).min(w as isize - 1)) as usize];
414 let p6 = grid[((yi + 1).min(h as isize - 1)) as usize * w + xi as usize];
415 let p8 = grid[yi as usize * w + ((xi - 1).max(0)) as usize];
416
417 if (2..=6).contains(&n) && t == 1 && p4 == 0 && p6 == 0 && (p2 == 0 || p8 == 0)
418 {
419 to_remove.push((y, x));
420 }
421 }
422 }
423
424 for (y, x) in &to_remove {
425 grid[y * w + x] = 0;
426 changed = true;
427 }
428
429 let mut to_remove = Vec::new();
431 for y in 1..(h - 1) {
432 for x in 1..(w - 1) {
433 let xi = x as isize;
434 let yi = y as isize;
435
436 let p = grid[yi as usize * w + xi as usize];
437 if p != 1 {
438 continue;
439 }
440
441 let n = count_neighbors(&grid, w, h, xi, yi);
442 let t = count_transitions(&grid, w, h, xi, yi);
443
444 let p2 = grid[((yi - 1).max(0)) as usize * w + xi as usize];
445 let p4 = grid[yi as usize * w + ((xi + 1).min(w as isize - 1)) as usize];
446 let p6 = grid[((yi + 1).min(h as isize - 1)) as usize * w + xi as usize];
447 let p8 = grid[yi as usize * w + ((xi - 1).max(0)) as usize];
448
449 if (2..=6).contains(&n) && t == 1 && p2 == 0 && p8 == 0 && (p4 == 0 || p6 == 0)
450 {
451 to_remove.push((y, x));
452 }
453 }
454 }
455
456 for (y, x) in &to_remove {
457 grid[y * w + x] = 0;
458 changed = true;
459 }
460 }
461
462 let out_vals: Vec<f32> = grid.iter().map(|&v| v as f32).collect();
463 let new_data = TensorData::new(out_vals, [c, h, w]);
464 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
465 Ok(Image::new(new_tensor))
466 }
467
468 pub fn skeleton(&self) -> Result<Self> {
474 let dims = self.tensor.dims();
475 let c = dims[0];
476 let h = dims[1];
477 let w = dims[2];
478
479 let device = self.tensor.device();
480 let tensor_data = self.tensor.clone().into_data();
481 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
482
483 let cross_kernel: Vec<&[u8]> = vec![&[0, 1, 0], &[1, 1, 1], &[0, 1, 0]];
484
485 let mut current = flat_vals.clone();
486 let mut skeleton = vec![0.0f32; c * h * w];
487
488 let mut iter_count = 0;
489 let max_iters = h + w; while iter_count < max_iters {
492 iter_count += 1;
493
494 let eroded = Self::erode_flat(¤t, c, h, w, &cross_kernel);
496
497 let opened = Self::dilate_flat(&eroded, c, h, w, &cross_kernel);
499
500 let mut temp = vec![0.0f32; c * h * w];
502 let mut any_nonzero = false;
503 for i in 0..(c * h * w) {
504 let diff = current[i] - opened[i];
505 if diff > 0.5 {
506 temp[i] = 1.0;
507 skeleton[i] = 1.0;
508 any_nonzero = true;
509 }
510 }
511
512 current = opened;
514
515 if !any_nonzero {
516 break;
517 }
518 }
519
520 let new_data = TensorData::new(skeleton, [c, h, w]);
521 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
522 Ok(Image::new(new_tensor))
523 }
524
525 fn erode_flat(input: &[f32], c: usize, h: usize, w: usize, kernel: &[&[u8]]) -> Vec<f32> {
527 let kh = kernel.len();
528 let kw = kernel[0].len();
529 let ay = kh as isize / 2;
530 let ax = kw as isize / 2;
531 let mut out = vec![0.0f32; c * h * w];
532
533 for ch in 0..c {
534 for y in 0..h {
535 for x in 0..w {
536 let mut min_val = f32::MAX;
537 for ky in 0..kh {
538 for kx in 0..kw {
539 if kernel[ky][kx] == 0 {
540 continue;
541 }
542 let sy = y as isize + ky as isize - ay;
543 let sx = x as isize + kx as isize - ax;
544 if sy >= 0 && sy < h as isize && sx >= 0 && sx < w as isize {
545 let val = input[ch * h * w + sy as usize * w + sx as usize];
546 if val < min_val {
547 min_val = val;
548 }
549 }
550 }
551 }
552 out[ch * h * w + y * w + x] = if min_val == f32::MAX {
553 input[ch * h * w + y * w + x]
554 } else {
555 min_val
556 };
557 }
558 }
559 }
560 out
561 }
562
563 fn dilate_flat(input: &[f32], c: usize, h: usize, w: usize, kernel: &[&[u8]]) -> Vec<f32> {
565 let kh = kernel.len();
566 let kw = kernel[0].len();
567 let ay = kh as isize / 2;
568 let ax = kw as isize / 2;
569 let mut out = vec![0.0f32; c * h * w];
570
571 for ch in 0..c {
572 for y in 0..h {
573 for x in 0..w {
574 let mut max_val = f32::MIN;
575 for ky in 0..kh {
576 for kx in 0..kw {
577 if kernel[ky][kx] == 0 {
578 continue;
579 }
580 let sy = y as isize + ky as isize - ay;
581 let sx = x as isize + kx as isize - ax;
582 if sy >= 0 && sy < h as isize && sx >= 0 && sx < w as isize {
583 let val = input[ch * h * w + sy as usize * w + sx as usize];
584 if val > max_val {
585 max_val = val;
586 }
587 }
588 }
589 }
590 out[ch * h * w + y * w + x] = if max_val == f32::MIN {
591 input[ch * h * w + y * w + x]
592 } else {
593 max_val
594 };
595 }
596 }
597 }
598 out
599 }
600}
601
602#[cfg(test)]
603mod tests {
604 use super::*;
605 use crate::test_helpers::{TestBackend, test_device};
606
607 #[test]
608 fn test_morphology() {
609 let device = test_device();
610 let flat_data = vec![0.5f32; 3 * 8 * 8];
611 let tensor_data = TensorData::new(flat_data, [3, 8, 8]);
612 let img = Image::new(Tensor::<TestBackend, 3>::from_data(tensor_data, &device));
613
614 let dilated = img.clone().dilate(3).unwrap();
615 assert_eq!(dilated.shape(), [3, 8, 8]);
616
617 let eroded = img.clone().erode(3).unwrap();
618 assert_eq!(eroded.shape(), [3, 8, 8]);
619
620 let opened = img.clone().morph_open(3).unwrap();
621 assert_eq!(opened.shape(), [3, 8, 8]);
622
623 let closed = img.clone().morph_close(3).unwrap();
624 assert_eq!(closed.shape(), [3, 8, 8]);
625 }
626
627 #[test]
628 fn test_dilate_with_cross_kernel() {
629 let device = test_device();
630 let flat_data = vec![0.0f32; 3 * 8 * 8];
631 let tensor_data = TensorData::new(flat_data, [3, 8, 8]);
632 let img = Image::new(Tensor::<TestBackend, 3>::from_data(tensor_data, &device));
633
634 let kernel: Vec<&[u8]> = vec![&[0, 1, 0], &[1, 1, 1], &[0, 1, 0]];
635 let dilated = img.dilate_with_kernel(&kernel).unwrap();
636 assert_eq!(dilated.shape(), [3, 8, 8]);
637 }
638
639 #[test]
640 fn test_erode_with_ellipse_kernel() {
641 let device = test_device();
642 let flat_data = vec![0.5f32; 3 * 8 * 8];
643 let tensor_data = TensorData::new(flat_data, [3, 8, 8]);
644 let img = Image::new(Tensor::<TestBackend, 3>::from_data(tensor_data, &device));
645
646 let kernel: Vec<&[u8]> = vec![&[0, 1, 0], &[1, 1, 1], &[0, 1, 0]];
647 let eroded = img.erode_with_kernel(&kernel).unwrap();
648 assert_eq!(eroded.shape(), [3, 8, 8]);
649 }
650
651 #[test]
652 fn test_empty_kernel() {
653 let device = test_device();
654 let data = vec![0.5f32; 3 * 8 * 8];
655 let tensor = Tensor::<TestBackend, 3>::from_data(TensorData::new(data, [3, 8, 8]), &device);
656 let img = Image::new(tensor);
657 let empty: Vec<&[u8]> = vec![];
658 assert!(img.dilate_with_kernel(&empty).is_err());
659 }
660
661 #[test]
662 fn test_hit_or_miss() {
663 let device = test_device();
664 let mut flat_data = vec![0.0f32; 8 * 8];
666 for y in 1..7 {
667 flat_data[y * 8 + 4] = 1.0;
668 }
669 let tensor =
670 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [1, 8, 8]), &device);
671 let img = Image::new(tensor);
672
673 let pattern: Vec<&[u8]> = vec![&[0, 0, 0], &[0, 1, 0], &[0, 1, 0], &[0, 1, 0], &[0, 0, 0]];
675 let bg_pattern: Vec<&[u8]> =
677 vec![&[0, 0, 0], &[1, 0, 1], &[1, 0, 1], &[1, 0, 1], &[0, 0, 0]];
678
679 let result = img.hit_or_miss(&pattern, &bg_pattern).unwrap();
680 assert_eq!(result.shape(), [1, 8, 8]);
681 let vals: Vec<f32> = result.tensor.into_data().iter::<f32>().collect();
682 assert!(vals.iter().any(|&v| v > 0.5));
684 }
685
686 #[test]
687 fn test_thin() {
688 let device = test_device();
689 let mut flat_data = vec![0.0f32; 10 * 10];
691 for y in 2..7 {
692 for x in 2..7 {
693 flat_data[y * 10 + x] = 1.0;
694 }
695 }
696 let orig_count = flat_data.iter().filter(|&&v| v > 0.5).count();
697 let tensor =
698 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [1, 10, 10]), &device);
699 let img = Image::new(tensor);
700
701 let result = img.thin().unwrap();
702 assert_eq!(result.shape(), [1, 10, 10]);
703 let vals: Vec<f32> = result.tensor.into_data().iter::<f32>().collect();
704 let thin_count = vals.iter().filter(|&&v| v > 0.5).count();
705 assert!(thin_count <= orig_count);
706 assert!(thin_count > 0);
707 }
708
709 #[test]
710 fn test_skeleton() {
711 let device = test_device();
712 let mut flat_data = vec![0.0f32; 12 * 12];
714 for y in 2..10 {
715 for x in 2..10 {
716 flat_data[y * 12 + x] = 1.0;
717 }
718 }
719 let tensor =
720 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [1, 12, 12]), &device);
721 let img = Image::new(tensor);
722
723 let result = img.skeleton().unwrap();
724 assert_eq!(result.shape(), [1, 12, 12]);
725 let vals: Vec<f32> = result.tensor.into_data().iter::<f32>().collect();
726 let skel_count = vals.iter().filter(|&&v| v > 0.5).count();
727 assert!(skel_count > 0);
729 }
730}