1use crate::error::{AlgorithmError, Result};
12use oxigdal_core::buffer::RasterBuffer;
13
14#[cfg(not(feature = "std"))]
15use alloc::vec::Vec;
16
17#[derive(Debug, Clone, Copy, PartialEq)]
19pub enum StructuringElement {
20 Square {
22 size: usize,
24 },
25 Cross {
27 size: usize,
29 },
30 Disk {
32 radius: usize,
34 },
35 HorizontalLine {
37 length: usize,
39 },
40 VerticalLine {
42 length: usize,
44 },
45}
46
47impl StructuringElement {
48 fn generate(&self) -> Result<Vec<Vec<bool>>> {
50 use oxigdal_core::OxiGdalError;
51
52 match self {
53 Self::Square { size } => {
54 if *size == 0 {
55 return Err(OxiGdalError::invalid_parameter_builder(
56 "size",
57 "must be positive, got 0",
58 )
59 .with_parameter("value", "0")
60 .with_parameter("min", "1")
61 .with_operation("morphology_square")
62 .with_suggestion("Use positive size. Common values: 3, 5, 7 for standard morphological operations")
63 .build()
64 .into());
65 }
66 Ok(vec![vec![true; *size]; *size])
67 }
68 Self::Cross { size } => {
69 if *size == 0 || *size % 2 == 0 {
70 let suggested = if *size == 0 { 3 } else { size + 1 };
71 return Err(OxiGdalError::invalid_parameter_builder(
72 "size",
73 format!("must be odd and positive, got {}", size),
74 )
75 .with_parameter("value", size.to_string())
76 .with_parameter("min", "1")
77 .with_operation("morphology_cross")
78 .with_suggestion(format!(
79 "Use odd positive size. Try {} instead. Common values: 3, 5, 7",
80 suggested
81 ))
82 .build()
83 .into());
84 }
85
86 let center = size / 2;
87 let mut mask = vec![vec![false; *size]; *size];
88
89 for i in 0..*size {
90 mask[center][i] = true;
91 mask[i][center] = true;
92 }
93
94 Ok(mask)
95 }
96 Self::Disk { radius } => {
97 if *radius == 0 {
98 return Err(OxiGdalError::invalid_parameter_builder(
99 "radius",
100 "must be positive, got 0",
101 )
102 .with_parameter("value", "0")
103 .with_parameter("min", "1")
104 .with_operation("morphology_disk")
105 .with_suggestion("Use positive radius. Common values: 1, 2, 3, 5 (in pixels)")
106 .build()
107 .into());
108 }
109
110 let size = 2 * radius + 1;
111 let center = *radius as i32;
112 let mut mask = vec![vec![false; size]; size];
113 let r_sq = (radius * radius) as i32;
114
115 for y in 0..size {
116 for x in 0..size {
117 let dx = x as i32 - center;
118 let dy = y as i32 - center;
119 if dx * dx + dy * dy <= r_sq {
120 mask[y][x] = true;
121 }
122 }
123 }
124
125 Ok(mask)
126 }
127 Self::HorizontalLine { length } => {
128 if *length == 0 {
129 return Err(OxiGdalError::invalid_parameter_builder(
130 "length",
131 "must be positive, got 0",
132 )
133 .with_parameter("value", "0")
134 .with_parameter("min", "1")
135 .with_operation("morphology_horizontal_line")
136 .with_suggestion(
137 "Use positive length. Common values: 3, 5, 7, 9 for edge detection",
138 )
139 .build()
140 .into());
141 }
142
143 Ok(vec![vec![true; *length]])
144 }
145 Self::VerticalLine { length } => {
146 if *length == 0 {
147 return Err(OxiGdalError::invalid_parameter_builder(
148 "length",
149 "must be positive, got 0",
150 )
151 .with_parameter("value", "0")
152 .with_parameter("min", "1")
153 .with_operation("morphology_vertical_line")
154 .with_suggestion(
155 "Use positive length. Common values: 3, 5, 7, 9 for edge detection",
156 )
157 .build()
158 .into());
159 }
160
161 Ok(vec![vec![true]; *length])
162 }
163 }
164 }
165
166 fn size(&self) -> (usize, usize) {
168 match self {
169 Self::Square { size } => (*size, *size),
170 Self::Cross { size } => (*size, *size),
171 Self::Disk { radius } => (2 * radius + 1, 2 * radius + 1),
172 Self::HorizontalLine { length } => (*length, 1),
173 Self::VerticalLine { length } => (1, *length),
174 }
175 }
176}
177
178pub fn dilate(src: &RasterBuffer, element: StructuringElement) -> Result<RasterBuffer> {
191 let mask = element.generate()?;
192 let (se_width, se_height) = element.size();
193
194 let width = src.width();
195 let height = src.height();
196 let mut dst = RasterBuffer::zeros(width, height, src.data_type());
197
198 let offset_x = (se_width / 2) as i64;
199 let offset_y = (se_height / 2) as i64;
200
201 for y in 0..height {
202 for x in 0..width {
203 let mut max_val = f64::NEG_INFINITY;
204
205 for (se_y, row) in mask.iter().enumerate() {
207 for (se_x, &active) in row.iter().enumerate() {
208 if !active {
209 continue;
210 }
211
212 let px = x as i64 + se_x as i64 - offset_x;
213 let py = y as i64 + se_y as i64 - offset_y;
214
215 if px >= 0 && px < width as i64 && py >= 0 && py < height as i64 {
216 let val = src
217 .get_pixel(px as u64, py as u64)
218 .map_err(AlgorithmError::Core)?;
219
220 if val.is_finite() && !src.is_nodata(val) && val > max_val {
221 max_val = val;
222 }
223 }
224 }
225 }
226
227 if max_val.is_finite() {
228 dst.set_pixel(x, y, max_val).map_err(AlgorithmError::Core)?;
229 }
230 }
231 }
232
233 Ok(dst)
234}
235
236pub fn erode(src: &RasterBuffer, element: StructuringElement) -> Result<RasterBuffer> {
249 let mask = element.generate()?;
250 let (se_width, se_height) = element.size();
251
252 let width = src.width();
253 let height = src.height();
254 let mut dst = RasterBuffer::zeros(width, height, src.data_type());
255
256 let offset_x = (se_width / 2) as i64;
257 let offset_y = (se_height / 2) as i64;
258
259 for y in 0..height {
260 for x in 0..width {
261 let mut min_val = f64::INFINITY;
262
263 for (se_y, row) in mask.iter().enumerate() {
265 for (se_x, &active) in row.iter().enumerate() {
266 if !active {
267 continue;
268 }
269
270 let px = x as i64 + se_x as i64 - offset_x;
271 let py = y as i64 + se_y as i64 - offset_y;
272
273 if px >= 0 && px < width as i64 && py >= 0 && py < height as i64 {
274 let val = src
275 .get_pixel(px as u64, py as u64)
276 .map_err(AlgorithmError::Core)?;
277
278 if val.is_finite() && !src.is_nodata(val) && val < min_val {
279 min_val = val;
280 }
281 }
282 }
283 }
284
285 if min_val.is_finite() {
286 dst.set_pixel(x, y, min_val).map_err(AlgorithmError::Core)?;
287 }
288 }
289 }
290
291 Ok(dst)
292}
293
294pub fn open(src: &RasterBuffer, element: StructuringElement) -> Result<RasterBuffer> {
308 let eroded = erode(src, element)?;
309 dilate(&eroded, element)
310}
311
312pub fn close(src: &RasterBuffer, element: StructuringElement) -> Result<RasterBuffer> {
326 let dilated = dilate(src, element)?;
327 erode(&dilated, element)
328}
329
330pub fn morphological_gradient(
344 src: &RasterBuffer,
345 element: StructuringElement,
346) -> Result<RasterBuffer> {
347 let dilated = dilate(src, element)?;
348 let eroded = erode(src, element)?;
349
350 let width = src.width();
351 let height = src.height();
352 let mut gradient = RasterBuffer::zeros(width, height, src.data_type());
353
354 for y in 0..height {
355 for x in 0..width {
356 let dil_val = dilated.get_pixel(x, y).map_err(AlgorithmError::Core)?;
357 let ero_val = eroded.get_pixel(x, y).map_err(AlgorithmError::Core)?;
358 let grad = dil_val - ero_val;
359
360 gradient
361 .set_pixel(x, y, grad)
362 .map_err(AlgorithmError::Core)?;
363 }
364 }
365
366 Ok(gradient)
367}
368
369pub fn top_hat(src: &RasterBuffer, element: StructuringElement) -> Result<RasterBuffer> {
383 let opened = open(src, element)?;
384
385 let width = src.width();
386 let height = src.height();
387 let mut result = RasterBuffer::zeros(width, height, src.data_type());
388
389 for y in 0..height {
390 for x in 0..width {
391 let orig = src.get_pixel(x, y).map_err(AlgorithmError::Core)?;
392 let open_val = opened.get_pixel(x, y).map_err(AlgorithmError::Core)?;
393 let top = orig - open_val;
394
395 result.set_pixel(x, y, top).map_err(AlgorithmError::Core)?;
396 }
397 }
398
399 Ok(result)
400}
401
402pub fn black_hat(src: &RasterBuffer, element: StructuringElement) -> Result<RasterBuffer> {
416 let closed = close(src, element)?;
417
418 let width = src.width();
419 let height = src.height();
420 let mut result = RasterBuffer::zeros(width, height, src.data_type());
421
422 for y in 0..height {
423 for x in 0..width {
424 let close_val = closed.get_pixel(x, y).map_err(AlgorithmError::Core)?;
425 let orig = src.get_pixel(x, y).map_err(AlgorithmError::Core)?;
426 let black = close_val - orig;
427
428 result
429 .set_pixel(x, y, black)
430 .map_err(AlgorithmError::Core)?;
431 }
432 }
433
434 Ok(result)
435}
436
437pub fn internal_gradient(src: &RasterBuffer, element: StructuringElement) -> Result<RasterBuffer> {
450 let eroded = erode(src, element)?;
451
452 let width = src.width();
453 let height = src.height();
454 let mut gradient = RasterBuffer::zeros(width, height, src.data_type());
455
456 for y in 0..height {
457 for x in 0..width {
458 let orig = src.get_pixel(x, y).map_err(AlgorithmError::Core)?;
459 let ero_val = eroded.get_pixel(x, y).map_err(AlgorithmError::Core)?;
460 let grad = orig - ero_val;
461
462 gradient
463 .set_pixel(x, y, grad)
464 .map_err(AlgorithmError::Core)?;
465 }
466 }
467
468 Ok(gradient)
469}
470
471pub fn external_gradient(src: &RasterBuffer, element: StructuringElement) -> Result<RasterBuffer> {
484 let dilated = dilate(src, element)?;
485
486 let width = src.width();
487 let height = src.height();
488 let mut gradient = RasterBuffer::zeros(width, height, src.data_type());
489
490 for y in 0..height {
491 for x in 0..width {
492 let dil_val = dilated.get_pixel(x, y).map_err(AlgorithmError::Core)?;
493 let orig = src.get_pixel(x, y).map_err(AlgorithmError::Core)?;
494 let grad = dil_val - orig;
495
496 gradient
497 .set_pixel(x, y, grad)
498 .map_err(AlgorithmError::Core)?;
499 }
500 }
501
502 Ok(gradient)
503}
504
505#[cfg(test)]
506mod tests {
507 use super::*;
508 use oxigdal_core::types::RasterDataType;
509
510 #[test]
511 fn test_structuring_elements() {
512 let square = StructuringElement::Square { size: 3 };
513 let mask = square.generate();
514 assert!(mask.is_ok());
515 let m = mask.expect("Should generate");
516 assert_eq!(m.len(), 3);
517 assert_eq!(m[0].len(), 3);
518
519 let cross = StructuringElement::Cross { size: 3 };
520 let mask = cross.generate();
521 assert!(mask.is_ok());
522
523 let disk = StructuringElement::Disk { radius: 2 };
524 let mask = disk.generate();
525 assert!(mask.is_ok());
526 }
527
528 #[test]
529 fn test_dilate() {
530 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
531
532 src.set_pixel(5, 5, 100.0).ok();
534
535 let element = StructuringElement::Square { size: 3 };
536 let result = dilate(&src, element);
537 assert!(result.is_ok());
538
539 let dilated = result.expect("Should succeed");
540
541 let val = dilated.get_pixel(4, 4).expect("Should get pixel");
543 assert!((val - 100.0).abs() < f64::EPSILON);
544 }
545
546 #[test]
547 fn test_erode() {
548 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
549
550 for y in 3..8 {
552 for x in 3..8 {
553 src.set_pixel(x, y, 100.0).ok();
554 }
555 }
556
557 let element = StructuringElement::Square { size: 3 };
558 let result = erode(&src, element);
559 assert!(result.is_ok());
560 }
561
562 #[test]
563 fn test_opening() {
564 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
565
566 for y in 0..10 {
567 for x in 0..10 {
568 src.set_pixel(x, y, 50.0).ok();
569 }
570 }
571
572 src.set_pixel(5, 5, 100.0).ok();
574
575 let element = StructuringElement::Square { size: 3 };
576 let result = open(&src, element);
577 assert!(result.is_ok());
578 }
579
580 #[test]
581 fn test_closing() {
582 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
583
584 for y in 0..10 {
585 for x in 0..10 {
586 src.set_pixel(x, y, 100.0).ok();
587 }
588 }
589
590 src.set_pixel(5, 5, 0.0).ok();
592
593 let element = StructuringElement::Square { size: 3 };
594 let result = close(&src, element);
595 assert!(result.is_ok());
596 }
597
598 #[test]
599 fn test_morphological_gradient() {
600 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
601
602 for y in 3..7 {
604 for x in 3..7 {
605 src.set_pixel(x, y, 100.0).ok();
606 }
607 }
608
609 let element = StructuringElement::Square { size: 3 };
610 let result = morphological_gradient(&src, element);
611 assert!(result.is_ok());
612 }
613
614 #[test]
615 fn test_top_hat() {
616 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
617
618 for y in 0..10 {
619 for x in 0..10 {
620 src.set_pixel(x, y, 50.0).ok();
621 }
622 }
623
624 src.set_pixel(5, 5, 100.0).ok();
625
626 let element = StructuringElement::Square { size: 3 };
627 let result = top_hat(&src, element);
628 assert!(result.is_ok());
629 }
630
631 #[test]
634 fn test_black_hat() {
635 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
636
637 for y in 0..10 {
638 for x in 0..10 {
639 src.set_pixel(x, y, 100.0).ok();
640 }
641 }
642
643 src.set_pixel(5, 5, 0.0).ok();
645
646 let element = StructuringElement::Square { size: 3 };
647 let result = black_hat(&src, element);
648 assert!(result.is_ok());
649 }
650
651 #[test]
652 fn test_internal_gradient() {
653 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
654
655 for y in 0..10 {
657 for x in 0..10 {
658 let dx = x as f64 - 5.0;
659 let dy = y as f64 - 5.0;
660 if (dx * dx + dy * dy).sqrt() < 3.0 {
661 src.set_pixel(x, y, 100.0).ok();
662 }
663 }
664 }
665
666 let element = StructuringElement::Square { size: 3 };
667 let result = internal_gradient(&src, element);
668 assert!(result.is_ok());
669 }
670
671 #[test]
672 fn test_external_gradient() {
673 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
674
675 for y in 3..7 {
677 for x in 3..7 {
678 src.set_pixel(x, y, 100.0).ok();
679 }
680 }
681
682 let element = StructuringElement::Square { size: 3 };
683 let result = external_gradient(&src, element);
684 assert!(result.is_ok());
685 }
686
687 #[test]
690 fn test_disk_element() {
691 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
692 src.set_pixel(5, 5, 100.0).ok();
693
694 let element = StructuringElement::Disk { radius: 2 };
695 let result = dilate(&src, element);
696 assert!(result.is_ok());
697 }
698
699 #[test]
700 fn test_cross_element() {
701 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
702 src.set_pixel(5, 5, 100.0).ok();
703
704 let element = StructuringElement::Cross { size: 5 };
705 let result = dilate(&src, element);
706 assert!(result.is_ok());
707 }
708
709 #[test]
710 fn test_horizontal_line_element() {
711 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
712 src.set_pixel(5, 5, 100.0).ok();
713
714 let element = StructuringElement::HorizontalLine { length: 5 };
715 let result = dilate(&src, element);
716 assert!(result.is_ok());
717 }
718
719 #[test]
720 fn test_vertical_line_element() {
721 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
722 src.set_pixel(5, 5, 100.0).ok();
723
724 let element = StructuringElement::VerticalLine { length: 5 };
725 let result = dilate(&src, element);
726 assert!(result.is_ok());
727 }
728
729 #[test]
732 fn test_structuring_element_zero_size() {
733 let element = StructuringElement::Square { size: 0 };
734 let result = element.generate();
735 assert!(result.is_err());
736 }
737
738 #[test]
739 fn test_cross_even_size() {
740 let element = StructuringElement::Cross { size: 4 };
741 let result = element.generate();
742 assert!(result.is_err());
743 }
744
745 #[test]
746 fn test_disk_zero_radius() {
747 let element = StructuringElement::Disk { radius: 0 };
748 let result = element.generate();
749 assert!(result.is_err());
750 }
751
752 #[test]
753 fn test_dilate_single_pixel() {
754 let mut src = RasterBuffer::zeros(1, 1, RasterDataType::Float32);
755 src.set_pixel(0, 0, 100.0).ok();
756
757 let element = StructuringElement::Square { size: 1 };
758 let result = dilate(&src, element);
759 assert!(result.is_ok());
760 let dilated = result.expect("Should succeed");
761 let val = dilated.get_pixel(0, 0).expect("Should get pixel");
762 assert!((val - 100.0).abs() < f64::EPSILON);
763 }
764
765 #[test]
766 fn test_erode_single_pixel() {
767 let mut src = RasterBuffer::zeros(1, 1, RasterDataType::Float32);
768 src.set_pixel(0, 0, 100.0).ok();
769
770 let element = StructuringElement::Square { size: 1 };
771 let result = erode(&src, element);
772 assert!(result.is_ok());
773 }
774
775 #[test]
778 fn test_opening_closing_inverse() {
779 let mut src = RasterBuffer::zeros(15, 15, RasterDataType::Float32);
780
781 for y in 0..15 {
783 for x in 0..15 {
784 src.set_pixel(x, y, 50.0).ok();
785 }
786 }
787
788 src.set_pixel(5, 5, 100.0).ok();
790 src.set_pixel(10, 10, 0.0).ok();
792
793 let element = StructuringElement::Square { size: 3 };
794
795 let opened = open(&src, element);
796 assert!(opened.is_ok());
797
798 let closed = close(&src, element);
799 assert!(closed.is_ok());
800 }
801
802 #[test]
803 fn test_gradient_types_comparison() {
804 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
805
806 for y in 3..7 {
808 for x in 3..7 {
809 src.set_pixel(x, y, 100.0).ok();
810 }
811 }
812
813 let element = StructuringElement::Square { size: 3 };
814
815 let morph_grad = morphological_gradient(&src, element);
816 assert!(morph_grad.is_ok());
817
818 let int_grad = internal_gradient(&src, element);
819 assert!(int_grad.is_ok());
820
821 let ext_grad = external_gradient(&src, element);
822 assert!(ext_grad.is_ok());
823 }
824
825 #[test]
826 fn test_dilation_erosion_properties() {
827 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
828
829 for y in 4..6 {
830 for x in 4..6 {
831 src.set_pixel(x, y, 100.0).ok();
832 }
833 }
834
835 let element = StructuringElement::Square { size: 3 };
836
837 let dilated = dilate(&src, element);
838 assert!(dilated.is_ok());
839 let dil = dilated.expect("Should succeed");
840
841 let val = dil.get_pixel(3, 3).expect("Should get pixel");
843 assert!((val - 100.0).abs() < f64::EPSILON);
844
845 let eroded = erode(&src, element);
846 assert!(eroded.is_ok());
847 let ero = eroded.expect("Should succeed");
848
849 let val_ero = ero.get_pixel(4, 4).expect("Should get pixel");
851 assert!(val_ero <= 100.0);
852 }
853
854 #[test]
855 fn test_top_hat_black_hat_duality() {
856 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
857
858 for y in 0..10 {
859 for x in 0..10 {
860 src.set_pixel(x, y, 50.0).ok();
861 }
862 }
863
864 src.set_pixel(3, 3, 100.0).ok();
866 src.set_pixel(7, 7, 0.0).ok();
867
868 let element = StructuringElement::Square { size: 3 };
869
870 let top = top_hat(&src, element);
871 assert!(top.is_ok());
872
873 let black = black_hat(&src, element);
874 assert!(black.is_ok());
875 }
876
877 #[test]
880 fn test_large_disk_element() {
881 let mut src = RasterBuffer::zeros(20, 20, RasterDataType::Float32);
882 src.set_pixel(10, 10, 100.0).ok();
883
884 let element = StructuringElement::Disk { radius: 5 };
885 let result = dilate(&src, element);
886 assert!(result.is_ok());
887 }
888
889 #[test]
890 fn test_large_square_element() {
891 let mut src = RasterBuffer::zeros(20, 20, RasterDataType::Float32);
892
893 for y in 8..12 {
894 for x in 8..12 {
895 src.set_pixel(x, y, 100.0).ok();
896 }
897 }
898
899 let element = StructuringElement::Square { size: 7 };
900 let result = erode(&src, element);
901 assert!(result.is_ok());
902 }
903
904 #[test]
907 fn test_binary_image_cleanup() {
908 let mut src = RasterBuffer::zeros(15, 15, RasterDataType::Float32);
909
910 for y in 0..15 {
912 for x in 0..15 {
913 if (x > 3 && x < 12) && (y > 3 && y < 12) {
914 src.set_pixel(x, y, 1.0).ok();
915 } else {
916 src.set_pixel(x, y, 0.0).ok();
917 }
918 }
919 }
920
921 src.set_pixel(1, 1, 1.0).ok(); src.set_pixel(7, 7, 0.0).ok(); let element = StructuringElement::Square { size: 3 };
926
927 let opened = open(&src, element);
929 assert!(opened.is_ok());
930
931 let closed = close(&src, element);
933 assert!(closed.is_ok());
934 }
935
936 #[test]
937 fn test_boundary_extraction() {
938 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
939
940 for y in 3..7 {
942 for x in 3..7 {
943 src.set_pixel(x, y, 100.0).ok();
944 }
945 }
946
947 let element = StructuringElement::Square { size: 3 };
948
949 let boundary = internal_gradient(&src, element);
951 assert!(boundary.is_ok());
952 let bound = boundary.expect("Should succeed");
953
954 let val = bound.get_pixel(3, 3).expect("Should get pixel");
956 assert!(val > 0.0);
957 }
958}