1use crate::error::{AlgorithmError, Result};
41use oxigdal_core::buffer::RasterBuffer;
42use oxigdal_core::types::RasterDataType;
43
44#[cfg(feature = "parallel")]
45use rayon::prelude::*;
46
47#[derive(Debug, Clone, PartialEq)]
49pub enum WindowShape {
50 Rectangular { width: usize, height: usize },
52
53 Circular { radius: f64 },
55
56 Custom {
58 mask: Vec<bool>,
59 width: usize,
60 height: usize,
61 },
62}
63
64impl WindowShape {
65 pub fn rectangular(width: usize, height: usize) -> Result<Self> {
76 use oxigdal_core::OxiGdalError;
77
78 if width == 0 || height == 0 {
79 return Err(OxiGdalError::invalid_parameter_builder(
80 "window_size",
81 format!(
82 "window dimensions must be greater than zero, got {}x{}",
83 width, height
84 ),
85 )
86 .with_parameter("width", width.to_string())
87 .with_parameter("height", height.to_string())
88 .with_parameter("min", "1")
89 .with_operation("focal_operation")
90 .with_suggestion("Use positive window dimensions. Common values: 3, 5, 7, 9, 11")
91 .build()
92 .into());
93 }
94 if width % 2 == 0 || height % 2 == 0 {
95 let suggested_width = if width % 2 == 0 { width + 1 } else { width };
96 let suggested_height = if height % 2 == 0 { height + 1 } else { height };
97 return Err(OxiGdalError::invalid_parameter_builder(
98 "window_size",
99 format!("window dimensions must be odd, got {}x{}", width, height),
100 )
101 .with_parameter("width", width.to_string())
102 .with_parameter("height", height.to_string())
103 .with_operation("focal_operation")
104 .with_suggestion(format!(
105 "Use odd window dimensions for symmetric neighborhood. Try {}x{} instead",
106 suggested_width, suggested_height
107 ))
108 .build()
109 .into());
110 }
111 Ok(Self::Rectangular { width, height })
112 }
113
114 pub fn circular(radius: f64) -> Result<Self> {
124 use oxigdal_core::OxiGdalError;
125
126 if radius <= 0.0 {
127 return Err(OxiGdalError::invalid_parameter_builder(
128 "radius",
129 format!("window radius must be positive, got {}", radius),
130 )
131 .with_parameter("value", radius.to_string())
132 .with_parameter("min", "0.0")
133 .with_operation("focal_operation")
134 .with_suggestion(
135 "Use positive radius value. Common values: 1.0, 1.5, 2.0, 3.0 (in pixels)",
136 )
137 .build()
138 .into());
139 }
140 Ok(Self::Circular { radius })
141 }
142
143 pub fn custom(mask: Vec<bool>, width: usize, height: usize) -> Result<Self> {
155 use oxigdal_core::OxiGdalError;
156
157 if mask.len() != width * height {
158 let expected = width * height;
159 return Err(OxiGdalError::invalid_parameter_builder(
160 "mask",
161 format!(
162 "mask size must match width * height, got {} but expected {}",
163 mask.len(),
164 expected
165 ),
166 )
167 .with_parameter("mask_length", mask.len().to_string())
168 .with_parameter("width", width.to_string())
169 .with_parameter("height", height.to_string())
170 .with_parameter("expected_length", expected.to_string())
171 .with_operation("focal_operation")
172 .with_suggestion(format!(
173 "Provide a mask with exactly {} elements ({}x{})",
174 expected, width, height
175 ))
176 .build()
177 .into());
178 }
179 if width % 2 == 0 || height % 2 == 0 {
180 let suggested_width = if width % 2 == 0 { width + 1 } else { width };
181 let suggested_height = if height % 2 == 0 { height + 1 } else { height };
182 return Err(OxiGdalError::invalid_parameter_builder(
183 "window_size",
184 format!("window dimensions must be odd, got {}x{}", width, height),
185 )
186 .with_parameter("width", width.to_string())
187 .with_parameter("height", height.to_string())
188 .with_operation("focal_operation")
189 .with_suggestion(format!(
190 "Use odd window dimensions for symmetric neighborhood. Try {}x{} instead",
191 suggested_width, suggested_height
192 ))
193 .build()
194 .into());
195 }
196 Ok(Self::Custom {
197 mask,
198 width,
199 height,
200 })
201 }
202
203 #[must_use]
205 pub fn dimensions(&self) -> (usize, usize) {
206 match self {
207 Self::Rectangular { width, height } => (*width, *height),
208 Self::Circular { radius } => {
209 let size = (radius.ceil() * 2.0 + 1.0) as usize;
210 (size, size)
211 }
212 Self::Custom { width, height, .. } => (*width, *height),
213 }
214 }
215
216 #[must_use]
218 pub fn includes(&self, dx: i64, dy: i64) -> bool {
219 match self {
220 Self::Rectangular { width, height } => {
221 let hw = (*width / 2) as i64;
222 let hh = (*height / 2) as i64;
223 dx.abs() <= hw && dy.abs() <= hh
224 }
225 Self::Circular { radius } => {
226 let dist = ((dx * dx + dy * dy) as f64).sqrt();
227 dist <= *radius
228 }
229 Self::Custom {
230 mask,
231 width,
232 height,
233 } => {
234 let hw = (*width / 2) as i64;
235 let hh = (*height / 2) as i64;
236 if dx.abs() > hw || dy.abs() > hh {
237 return false;
238 }
239 let x = (dx + hw) as usize;
240 let y = (dy + hh) as usize;
241 mask[y * width + x]
242 }
243 }
244 }
245
246 #[must_use]
248 pub fn offsets(&self) -> Vec<(i64, i64)> {
249 let (width, height) = self.dimensions();
250 let hw = (width / 2) as i64;
251 let hh = (height / 2) as i64;
252
253 let mut result = Vec::new();
254 for dy in -hh..=hh {
255 for dx in -hw..=hw {
256 if self.includes(dx, dy) {
257 result.push((dx, dy));
258 }
259 }
260 }
261 result
262 }
263
264 #[must_use]
266 pub fn cell_count(&self) -> usize {
267 self.offsets().len()
268 }
269}
270
271#[derive(Debug, Clone, Copy, PartialEq, Eq)]
273pub enum BoundaryMode {
274 Ignore,
276
277 Constant(i64),
279
280 Reflect,
282
283 Wrap,
285
286 Edge,
288}
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292pub enum FocalOperation {
293 Mean,
295
296 Median,
298
299 Range,
301
302 Variety,
304
305 Min,
307
308 Max,
310
311 Sum,
313
314 StdDev,
316
317 Majority,
319}
320
321pub fn focal_mean(
333 src: &RasterBuffer,
334 window: &WindowShape,
335 boundary: &BoundaryMode,
336) -> Result<RasterBuffer> {
337 focal_operation(src, window, boundary, FocalOperation::Mean)
338}
339
340pub fn focal_median(
352 src: &RasterBuffer,
353 window: &WindowShape,
354 boundary: &BoundaryMode,
355) -> Result<RasterBuffer> {
356 focal_operation(src, window, boundary, FocalOperation::Median)
357}
358
359pub fn focal_range(
371 src: &RasterBuffer,
372 window: &WindowShape,
373 boundary: &BoundaryMode,
374) -> Result<RasterBuffer> {
375 focal_operation(src, window, boundary, FocalOperation::Range)
376}
377
378pub fn focal_variety(
390 src: &RasterBuffer,
391 window: &WindowShape,
392 boundary: &BoundaryMode,
393) -> Result<RasterBuffer> {
394 focal_operation(src, window, boundary, FocalOperation::Variety)
395}
396
397pub fn focal_min(
409 src: &RasterBuffer,
410 window: &WindowShape,
411 boundary: &BoundaryMode,
412) -> Result<RasterBuffer> {
413 focal_operation(src, window, boundary, FocalOperation::Min)
414}
415
416pub fn focal_max(
428 src: &RasterBuffer,
429 window: &WindowShape,
430 boundary: &BoundaryMode,
431) -> Result<RasterBuffer> {
432 focal_operation(src, window, boundary, FocalOperation::Max)
433}
434
435pub fn focal_sum(
447 src: &RasterBuffer,
448 window: &WindowShape,
449 boundary: &BoundaryMode,
450) -> Result<RasterBuffer> {
451 focal_operation(src, window, boundary, FocalOperation::Sum)
452}
453
454pub fn focal_stddev(
466 src: &RasterBuffer,
467 window: &WindowShape,
468 boundary: &BoundaryMode,
469) -> Result<RasterBuffer> {
470 focal_operation(src, window, boundary, FocalOperation::StdDev)
471}
472
473pub fn focal_majority(
485 src: &RasterBuffer,
486 window: &WindowShape,
487 boundary: &BoundaryMode,
488) -> Result<RasterBuffer> {
489 focal_operation(src, window, boundary, FocalOperation::Majority)
490}
491
492fn focal_operation(
494 src: &RasterBuffer,
495 window: &WindowShape,
496 boundary: &BoundaryMode,
497 operation: FocalOperation,
498) -> Result<RasterBuffer> {
499 let width = src.width();
500 let height = src.height();
501 let mut dst = RasterBuffer::zeros(width, height, src.data_type());
502
503 let offsets = window.offsets();
504
505 #[cfg(feature = "parallel")]
506 {
507 let results: Result<Vec<_>> = (0..height)
508 .into_par_iter()
509 .map(|y| {
510 let mut row_data = Vec::with_capacity(width as usize);
511 for x in 0..width {
512 let value = compute_focal_value(src, x, y, &offsets, boundary, operation)?;
513 row_data.push(value);
514 }
515 Ok((y, row_data))
516 })
517 .collect();
518
519 for (y, row_data) in results? {
520 for (x, value) in row_data.into_iter().enumerate() {
521 dst.set_pixel(x as u64, y, value)
522 .map_err(AlgorithmError::Core)?;
523 }
524 }
525 }
526
527 #[cfg(not(feature = "parallel"))]
528 {
529 for y in 0..height {
530 for x in 0..width {
531 let value = compute_focal_value(src, x, y, &offsets, boundary, operation)?;
532 dst.set_pixel(x, y, value).map_err(AlgorithmError::Core)?;
533 }
534 }
535 }
536
537 Ok(dst)
538}
539
540fn compute_focal_value(
542 src: &RasterBuffer,
543 x: u64,
544 y: u64,
545 offsets: &[(i64, i64)],
546 boundary: &BoundaryMode,
547 operation: FocalOperation,
548) -> Result<f64> {
549 let width = src.width() as i64;
550 let height = src.height() as i64;
551 let x_i64 = x as i64;
552 let y_i64 = y as i64;
553
554 let mut values = Vec::with_capacity(offsets.len());
555
556 for &(dx, dy) in offsets {
557 let nx = x_i64 + dx;
558 let ny = y_i64 + dy;
559
560 let value = if nx >= 0 && nx < width && ny >= 0 && ny < height {
561 src.get_pixel(nx as u64, ny as u64)
562 .map_err(AlgorithmError::Core)?
563 } else {
564 match boundary {
565 BoundaryMode::Ignore => continue,
566 BoundaryMode::Constant(c) => *c as f64,
567 BoundaryMode::Reflect => {
568 let rx = if nx < 0 {
569 -nx - 1
570 } else if nx >= width {
571 2 * width - nx - 1
572 } else {
573 nx
574 };
575 let ry = if ny < 0 {
576 -ny - 1
577 } else if ny >= height {
578 2 * height - ny - 1
579 } else {
580 ny
581 };
582 src.get_pixel(rx as u64, ry as u64)
583 .map_err(AlgorithmError::Core)?
584 }
585 BoundaryMode::Wrap => {
586 let wx = ((nx % width) + width) % width;
587 let wy = ((ny % height) + height) % height;
588 src.get_pixel(wx as u64, wy as u64)
589 .map_err(AlgorithmError::Core)?
590 }
591 BoundaryMode::Edge => {
592 let ex = nx.clamp(0, width - 1);
593 let ey = ny.clamp(0, height - 1);
594 src.get_pixel(ex as u64, ey as u64)
595 .map_err(AlgorithmError::Core)?
596 }
597 }
598 };
599
600 values.push(value);
601 }
602
603 if values.is_empty() {
604 return Ok(0.0);
605 }
606
607 let result = match operation {
608 FocalOperation::Mean => {
609 let sum: f64 = values.iter().sum();
610 sum / values.len() as f64
611 }
612 FocalOperation::Median => {
613 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
614 let mid = values.len() / 2;
615 if values.len() % 2 == 0 {
616 (values[mid - 1] + values[mid]) / 2.0
617 } else {
618 values[mid]
619 }
620 }
621 FocalOperation::Range => {
622 let (min, max) = values.iter().copied().fold(
625 (f64::INFINITY, f64::NEG_INFINITY),
626 |(min_val, max_val), val| {
627 let new_min = if val < min_val { val } else { min_val };
628 let new_max = if val > max_val { val } else { max_val };
629 (new_min, new_max)
630 },
631 );
632 max - min
633 }
634 FocalOperation::Variety => {
635 let mut unique = values.clone();
636 unique.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
637 unique.dedup_by(|a, b| (*a - *b).abs() < 1e-10);
638 unique.len() as f64
639 }
640 FocalOperation::Min => {
641 values
644 .iter()
645 .copied()
646 .fold(f64::INFINITY, |acc, val| if val < acc { val } else { acc })
647 }
648 FocalOperation::Max => {
649 values.iter().copied().fold(
652 f64::NEG_INFINITY,
653 |acc, val| if val > acc { val } else { acc },
654 )
655 }
656 FocalOperation::Sum => values.iter().sum(),
657 FocalOperation::StdDev => {
658 let mean = values.iter().sum::<f64>() / values.len() as f64;
659 let variance = values
660 .iter()
661 .map(|v| {
662 let diff = v - mean;
663 diff * diff
664 })
665 .sum::<f64>()
666 / values.len() as f64;
667 variance.sqrt()
668 }
669 FocalOperation::Majority => {
670 let mut counts: Vec<(f64, usize)> = Vec::new();
672 for &val in &values {
673 if let Some(entry) = counts.iter_mut().find(|(v, _)| (v - val).abs() < 1e-10) {
674 entry.1 += 1;
675 } else {
676 counts.push((val, 1));
677 }
678 }
679 let (majority_val, _) =
682 counts
683 .into_iter()
684 .fold((0.0, 0), |(acc_val, acc_count), (val, count)| {
685 if count > acc_count {
686 (val, count)
687 } else {
688 (acc_val, acc_count)
689 }
690 });
691 majority_val
692 }
693 };
694
695 Ok(result)
696}
697
698pub fn focal_mean_separable(
712 src: &RasterBuffer,
713 width: usize,
714 height: usize,
715) -> Result<RasterBuffer> {
716 if width % 2 == 0 || height % 2 == 0 {
717 return Err(AlgorithmError::InvalidParameter {
718 parameter: "window_size",
719 message: "Window dimensions must be odd".to_string(),
720 });
721 }
722
723 let img_width = src.width();
724 let img_height = src.height();
725
726 let mut temp = RasterBuffer::zeros(img_width, img_height, RasterDataType::Float64);
728 let hw = (width / 2) as i64;
729
730 for y in 0..img_height {
731 for x in 0..img_width {
732 let mut sum = 0.0;
733 let mut count = 0;
734
735 for dx in -hw..=hw {
736 let nx = (x as i64 + dx).clamp(0, img_width as i64 - 1);
737 sum += src.get_pixel(nx as u64, y).map_err(AlgorithmError::Core)?;
738 count += 1;
739 }
740
741 temp.set_pixel(x, y, sum / count as f64)
742 .map_err(AlgorithmError::Core)?;
743 }
744 }
745
746 let mut dst = RasterBuffer::zeros(img_width, img_height, src.data_type());
748 let hh = (height / 2) as i64;
749
750 for y in 0..img_height {
751 for x in 0..img_width {
752 let mut sum = 0.0;
753 let mut count = 0;
754
755 for dy in -hh..=hh {
756 let ny = (y as i64 + dy).clamp(0, img_height as i64 - 1);
757 sum += temp.get_pixel(x, ny as u64).map_err(AlgorithmError::Core)?;
758 count += 1;
759 }
760
761 dst.set_pixel(x, y, sum / count as f64)
762 .map_err(AlgorithmError::Core)?;
763 }
764 }
765
766 Ok(dst)
767}
768
769pub fn focal_convolve(
783 src: &RasterBuffer,
784 kernel: &[f64],
785 width: usize,
786 height: usize,
787 normalize: bool,
788) -> Result<RasterBuffer> {
789 if kernel.len() != width * height {
790 return Err(AlgorithmError::InvalidParameter {
791 parameter: "kernel",
792 message: "Kernel size must match width * height".to_string(),
793 });
794 }
795
796 let img_width = src.width();
797 let img_height = src.height();
798 let mut dst = RasterBuffer::zeros(img_width, img_height, src.data_type());
799
800 let hw = (width / 2) as i64;
801 let hh = (height / 2) as i64;
802
803 let kernel_sum: f64 = if normalize {
804 let sum: f64 = kernel.iter().sum();
805 if sum.abs() < 1e-10 { 1.0 } else { sum }
806 } else {
807 1.0
808 };
809
810 for y in 0..img_height {
811 for x in 0..img_width {
812 let mut sum = 0.0;
813
814 for ky in 0..height {
815 for kx in 0..width {
816 let dx = kx as i64 - hw;
817 let dy = ky as i64 - hh;
818 let nx = (x as i64 + dx).clamp(0, img_width as i64 - 1);
819 let ny = (y as i64 + dy).clamp(0, img_height as i64 - 1);
820
821 let pixel_val = src
822 .get_pixel(nx as u64, ny as u64)
823 .map_err(AlgorithmError::Core)?;
824 sum += pixel_val * kernel[ky * width + kx];
825 }
826 }
827
828 dst.set_pixel(x, y, sum / kernel_sum)
829 .map_err(AlgorithmError::Core)?;
830 }
831 }
832
833 Ok(dst)
834}
835
836#[cfg(test)]
837mod tests {
838 use super::*;
839 use approx::assert_abs_diff_eq;
840
841 #[test]
842 fn test_window_shape_rectangular() {
843 let window = WindowShape::rectangular(3, 3)
844 .expect("3x3 rectangular window creation should succeed in test");
845 assert_eq!(window.dimensions(), (3, 3));
846 assert_eq!(window.cell_count(), 9);
847 assert!(window.includes(0, 0));
848 assert!(window.includes(1, 1));
849 assert!(!window.includes(2, 2));
850 }
851
852 #[test]
853 fn test_window_shape_circular() {
854 let window = WindowShape::circular(1.5)
855 .expect("circular window with radius 1.5 should succeed in test");
856 assert!(window.includes(0, 0));
857 assert!(window.includes(1, 0));
858 assert!(window.includes(0, 1));
859 assert!(!window.includes(2, 2));
860 }
861
862 #[test]
863 fn test_window_shape_custom() {
864 let mask = vec![false, true, false, true, true, true, false, true, false];
865 let window = WindowShape::custom(mask, 3, 3)
866 .expect("custom 3x3 window creation should succeed in test");
867 assert_eq!(window.cell_count(), 5);
868 assert!(window.includes(0, 0));
869 assert!(!window.includes(1, 1));
870 }
871
872 #[test]
873 fn test_focal_mean() {
874 let mut src = RasterBuffer::zeros(5, 5, RasterDataType::Float32);
875 src.set_pixel(2, 2, 5.0)
877 .expect("setting pixel (2,2) should succeed in test");
878
879 let window = WindowShape::rectangular(3, 3)
880 .expect("3x3 rectangular window creation should succeed in test");
881 let result = focal_mean(&src, &window, &BoundaryMode::Edge)
882 .expect("focal_mean operation should succeed in test");
883
884 let center = result
886 .get_pixel(2, 2)
887 .expect("getting pixel (2,2) from result should succeed in test");
888 assert_abs_diff_eq!(center, 5.0 / 9.0, epsilon = 0.01);
889 }
890
891 #[test]
892 fn test_focal_median() {
893 let mut src = RasterBuffer::zeros(5, 5, RasterDataType::Float32);
894 src.set_pixel(2, 2, 9.0)
895 .expect("setting pixel (2,2) should succeed in test");
896
897 let window = WindowShape::rectangular(3, 3)
898 .expect("3x3 rectangular window creation should succeed in test");
899 let result = focal_median(&src, &window, &BoundaryMode::Edge)
900 .expect("focal_median operation should succeed in test");
901
902 let center = result
904 .get_pixel(2, 2)
905 .expect("getting pixel (2,2) from result should succeed in test");
906 assert_abs_diff_eq!(center, 0.0, epsilon = 0.01);
907 }
908
909 #[test]
910 fn test_focal_range() {
911 let mut src = RasterBuffer::zeros(5, 5, RasterDataType::Float32);
912 src.set_pixel(2, 2, 10.0)
913 .expect("setting pixel (2,2) should succeed in test");
914
915 let window = WindowShape::rectangular(3, 3)
916 .expect("3x3 rectangular window creation should succeed in test");
917 let result = focal_range(&src, &window, &BoundaryMode::Edge)
918 .expect("focal_range operation should succeed in test");
919
920 let center = result
922 .get_pixel(2, 2)
923 .expect("getting pixel (2,2) from result should succeed in test");
924 assert_abs_diff_eq!(center, 10.0, epsilon = 0.01);
925 }
926
927 #[test]
928 fn test_focal_variety() {
929 let mut src = RasterBuffer::zeros(5, 5, RasterDataType::Float32);
930 src.set_pixel(2, 2, 1.0)
931 .expect("setting pixel (2,2) should succeed in test");
932 src.set_pixel(2, 3, 2.0)
933 .expect("setting pixel (2,3) should succeed in test");
934
935 let window = WindowShape::rectangular(3, 3)
936 .expect("3x3 rectangular window creation should succeed in test");
937 let result = focal_variety(&src, &window, &BoundaryMode::Edge)
938 .expect("focal_variety operation should succeed in test");
939
940 let center = result
942 .get_pixel(2, 2)
943 .expect("getting pixel (2,2) from result should succeed in test");
944 assert_abs_diff_eq!(center, 3.0, epsilon = 0.01);
945 }
946
947 #[test]
948 fn test_focal_stddev() {
949 let mut src = RasterBuffer::zeros(5, 5, RasterDataType::Float32);
950 for y in 0..5 {
951 for x in 0..5 {
952 src.set_pixel(x, y, (x + y) as f64)
953 .expect("setting pixel should succeed in test");
954 }
955 }
956
957 let window = WindowShape::rectangular(3, 3)
958 .expect("3x3 rectangular window creation should succeed in test");
959 let result = focal_stddev(&src, &window, &BoundaryMode::Edge)
960 .expect("focal_stddev operation should succeed in test");
961
962 let center = result
964 .get_pixel(2, 2)
965 .expect("getting pixel (2,2) from result should succeed in test");
966 assert!(center > 0.0);
967 }
968
969 #[test]
970 fn test_focal_mean_separable() {
971 let mut src = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
972 for y in 0..10 {
973 for x in 0..10 {
974 src.set_pixel(x, y, (x + y) as f64)
975 .expect("setting pixel should succeed in test");
976 }
977 }
978
979 let result = focal_mean_separable(&src, 3, 3)
980 .expect("focal_mean_separable operation should succeed in test");
981
982 let window = WindowShape::rectangular(3, 3)
984 .expect("3x3 rectangular window creation should succeed in test");
985 let expected = focal_mean(&src, &window, &BoundaryMode::Edge)
986 .expect("focal_mean operation should succeed in test");
987
988 for y in 1..9 {
989 for x in 1..9 {
990 let val = result
991 .get_pixel(x, y)
992 .expect("getting pixel from result should succeed in test");
993 let exp = expected
994 .get_pixel(x, y)
995 .expect("getting pixel from expected should succeed in test");
996 assert_abs_diff_eq!(val, exp, epsilon = 0.1);
997 }
998 }
999 }
1000
1001 #[test]
1002 fn test_boundary_modes() {
1003 let src = RasterBuffer::zeros(3, 3, RasterDataType::Float32);
1004 let window = WindowShape::rectangular(3, 3)
1005 .expect("3x3 rectangular window creation should succeed in test");
1006
1007 let _ = focal_mean(&src, &window, &BoundaryMode::Ignore);
1009 let _ = focal_mean(&src, &window, &BoundaryMode::Constant(0));
1010 let _ = focal_mean(&src, &window, &BoundaryMode::Reflect);
1011 let _ = focal_mean(&src, &window, &BoundaryMode::Wrap);
1012 let _ = focal_mean(&src, &window, &BoundaryMode::Edge);
1013 }
1014}