oxigdal_algorithms/raster/
morphology_compound.rs1#[cfg(not(feature = "std"))]
33use alloc::vec::Vec;
34
35#[derive(Debug, Clone, Copy, PartialEq)]
41pub enum BorderMode {
42 Replicate,
44 Reflect,
46 Constant(f32),
48}
49
50impl Default for BorderMode {
51 fn default() -> Self {
52 Self::Replicate
53 }
54}
55
56#[derive(Debug, Clone, Copy)]
66pub struct MorphologyOptions {
67 pub kernel_size: usize,
69 pub iterations: u32,
71 pub border_mode: BorderMode,
73}
74
75impl Default for MorphologyOptions {
76 fn default() -> Self {
77 Self {
78 kernel_size: 3,
79 iterations: 1,
80 border_mode: BorderMode::default(),
81 }
82 }
83}
84
85impl MorphologyOptions {
86 pub fn new(kernel_size: usize) -> Self {
89 Self {
90 kernel_size,
91 ..Self::default()
92 }
93 }
94
95 pub fn with_iterations(mut self, n: u32) -> Self {
97 self.iterations = n;
98 self
99 }
100
101 pub fn with_border_mode(mut self, m: BorderMode) -> Self {
103 self.border_mode = m;
104 self
105 }
106}
107
108#[inline]
112fn sample(
113 raster: &[f32],
114 width: usize,
115 height: usize,
116 x: i64,
117 y: i64,
118 mode: BorderMode,
119) -> Option<f32> {
120 let w = width as i64;
121 let h = height as i64;
122
123 let (sx, sy) = match mode {
124 BorderMode::Replicate => (x.clamp(0, w - 1), y.clamp(0, h - 1)),
125 BorderMode::Reflect => {
126 let rx = reflect_index(x, w);
128 let ry = reflect_index(y, h);
129 (rx, ry)
130 }
131 BorderMode::Constant(c) => {
132 if x < 0 || x >= w || y < 0 || y >= h {
133 return Some(c);
134 }
135 (x, y)
136 }
137 };
138
139 let ux = sx as usize;
140 let uy = sy as usize;
141 Some(raster[uy * width + ux])
142}
143
144#[inline]
145fn reflect_index(i: i64, n: i64) -> i64 {
146 if n <= 1 {
147 return 0;
148 }
149 let period = 2 * (n - 1);
150 let mut m = i % period;
151 if m < 0 {
152 m += period;
153 }
154 if m >= n { period - m } else { m }
155}
156
157pub fn dilate_slice(
169 raster: &[f32],
170 width: usize,
171 height: usize,
172 kernel_size: usize,
173 mode: BorderMode,
174) -> Vec<f32> {
175 let ks = kernel_size.max(1);
176 let half = (ks / 2) as i64;
177 let mut out = vec![0.0f32; width * height];
178
179 for y in 0..height {
180 for x in 0..width {
181 let mut max_val = f32::NEG_INFINITY;
182 for dy in -half..=half {
183 for dx in -half..=half {
184 if let Some(v) =
185 sample(raster, width, height, x as i64 + dx, y as i64 + dy, mode)
186 {
187 if v.is_finite() && v > max_val {
188 max_val = v;
189 }
190 }
191 }
192 }
193 out[y * width + x] = if max_val.is_finite() {
194 max_val
195 } else {
196 raster[y * width + x]
197 };
198 }
199 }
200
201 out
202}
203
204pub fn erode_slice(
210 raster: &[f32],
211 width: usize,
212 height: usize,
213 kernel_size: usize,
214 mode: BorderMode,
215) -> Vec<f32> {
216 let ks = kernel_size.max(1);
217 let half = (ks / 2) as i64;
218 let mut out = vec![0.0f32; width * height];
219
220 for y in 0..height {
221 for x in 0..width {
222 let mut min_val = f32::INFINITY;
223 for dy in -half..=half {
224 for dx in -half..=half {
225 if let Some(v) =
226 sample(raster, width, height, x as i64 + dx, y as i64 + dy, mode)
227 {
228 if v.is_finite() && v < min_val {
229 min_val = v;
230 }
231 }
232 }
233 }
234 out[y * width + x] = if min_val.is_finite() {
235 min_val
236 } else {
237 raster[y * width + x]
238 };
239 }
240 }
241
242 out
243}
244
245pub fn opening(raster: &[f32], width: usize, height: usize, kernel_size: usize) -> Vec<f32> {
250 let mode = BorderMode::default();
251 let eroded = erode_slice(raster, width, height, kernel_size, mode);
252 dilate_slice(&eroded, width, height, kernel_size, mode)
253}
254
255pub fn closing(raster: &[f32], width: usize, height: usize, kernel_size: usize) -> Vec<f32> {
260 let mode = BorderMode::default();
261 let dilated = dilate_slice(raster, width, height, kernel_size, mode);
262 erode_slice(&dilated, width, height, kernel_size, mode)
263}
264
265pub fn top_hat(raster: &[f32], width: usize, height: usize, kernel_size: usize) -> Vec<f32> {
270 let opened = opening(raster, width, height, kernel_size);
271 raster
272 .iter()
273 .zip(opened.iter())
274 .map(|(a, b)| a - b)
275 .collect()
276}
277
278pub fn black_hat(raster: &[f32], width: usize, height: usize, kernel_size: usize) -> Vec<f32> {
283 let closed = closing(raster, width, height, kernel_size);
284 closed
285 .iter()
286 .zip(raster.iter())
287 .map(|(a, b)| a - b)
288 .collect()
289}
290
291pub fn opening_with(
295 raster: &[f32],
296 width: usize,
297 height: usize,
298 opts: &MorphologyOptions,
299) -> Vec<f32> {
300 let mut current = raster.to_vec();
301 for _ in 0..opts.iterations {
302 let eroded = erode_slice(¤t, width, height, opts.kernel_size, opts.border_mode);
303 current = dilate_slice(&eroded, width, height, opts.kernel_size, opts.border_mode);
304 }
305 current
306}
307
308pub fn closing_with(
312 raster: &[f32],
313 width: usize,
314 height: usize,
315 opts: &MorphologyOptions,
316) -> Vec<f32> {
317 let mut current = raster.to_vec();
318 for _ in 0..opts.iterations {
319 let dilated = dilate_slice(¤t, width, height, opts.kernel_size, opts.border_mode);
320 current = erode_slice(&dilated, width, height, opts.kernel_size, opts.border_mode);
321 }
322 current
323}
324
325pub fn top_hat_with(
327 raster: &[f32],
328 width: usize,
329 height: usize,
330 opts: &MorphologyOptions,
331) -> Vec<f32> {
332 let opened = opening_with(raster, width, height, opts);
333 raster
334 .iter()
335 .zip(opened.iter())
336 .map(|(a, b)| a - b)
337 .collect()
338}
339
340pub fn black_hat_with(
342 raster: &[f32],
343 width: usize,
344 height: usize,
345 opts: &MorphologyOptions,
346) -> Vec<f32> {
347 let closed = closing_with(raster, width, height, opts);
348 closed
349 .iter()
350 .zip(raster.iter())
351 .map(|(a, b)| a - b)
352 .collect()
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 #[test]
360 fn test_reflect_index_basic() {
361 assert_eq!(reflect_index(-1, 5), 1);
362 assert_eq!(reflect_index(-2, 5), 2);
363 assert_eq!(reflect_index(5, 5), 3);
364 assert_eq!(reflect_index(6, 5), 2);
365 }
366
367 #[test]
368 fn test_default_morphology_options() {
369 let opts = MorphologyOptions::default();
370 assert_eq!(opts.kernel_size, 3);
371 assert_eq!(opts.iterations, 1);
372 matches!(opts.border_mode, BorderMode::Replicate);
373 }
374
375 #[test]
376 fn test_morphology_options_builder() {
377 let opts = MorphologyOptions::new(5)
378 .with_iterations(2)
379 .with_border_mode(BorderMode::Constant(0.0));
380 assert_eq!(opts.kernel_size, 5);
381 assert_eq!(opts.iterations, 2);
382 matches!(opts.border_mode, BorderMode::Constant(_));
383 }
384
385 #[test]
386 fn test_dilate_slice_constant_raster() {
387 let r = vec![5.0f32; 16];
388 let out = dilate_slice(&r, 4, 4, 3, BorderMode::Replicate);
389 assert!(out.iter().all(|&v| (v - 5.0).abs() < 1e-6));
390 }
391
392 #[test]
393 fn test_erode_slice_constant_raster() {
394 let r = vec![5.0f32; 16];
395 let out = erode_slice(&r, 4, 4, 3, BorderMode::Replicate);
396 assert!(out.iter().all(|&v| (v - 5.0).abs() < 1e-6));
397 }
398}