Skip to main content

scirs2_ndimage/morphology/binary/
dilation.rs

1//! Binary dilation operations
2//!
3//! This module implements binary dilation for 1D, 2D, and n-dimensional arrays.
4//! Dilation adds pixels to the boundaries of regions of positive pixels,
5//! effectively expanding these regions.
6
7use scirs2_core::ndarray::{Array, Array1, Array2, Dimension, Ix1, Ix2, IxDyn};
8
9use crate::error::{NdimageError, NdimageResult};
10use crate::morphology::structuring::generate_binary_structure_dyn;
11use crate::morphology::utils::get_structure_center_dyn;
12
13/// Dilate a binary array using a structuring element
14///
15/// Binary dilation adds pixels to the boundaries of regions of positive pixels,
16/// effectively expanding these regions.
17///
18/// # Arguments
19///
20/// * `input` - Input binary array
21/// * `structure` - Structuring element (if None, uses a box with connectivity 1)
22/// * `iterations` - Number of times to apply the dilation (default: 1)
23/// * `mask` - Mask array that limits the operation (if None, no mask is applied)
24/// * `border_value` - Border value (default: false)
25/// * `origin` - Origin of the structuring element (if None, uses the center)
26/// * `brute_force` - Whether to use brute force algorithm (default: false)
27///
28/// # Returns
29///
30/// * `Result<Array<bool, D>>` - Dilated array
31#[allow(dead_code)]
32pub fn binary_dilation<D>(
33    input: &Array<bool, D>,
34    structure: Option<&Array<bool, D>>,
35    iterations: Option<usize>,
36    mask: Option<&Array<bool, D>>,
37    border_value: Option<bool>,
38    origin: Option<&[isize]>,
39    brute_force: Option<bool>,
40) -> NdimageResult<Array<bool, D>>
41where
42    D: Dimension + 'static,
43{
44    // Validate inputs
45    if input.ndim() == 0 {
46        return Err(NdimageError::InvalidInput(
47            "Input array cannot be 0-dimensional".into(),
48        ));
49    }
50
51    // Handle based on dimensionality
52    match input.ndim() {
53        1 => {
54            if let Ok(input_1d) = input.clone().into_dimensionality::<Ix1>() {
55                // Convert structure to 1D if provided
56                let structure_1d = match structure {
57                    Some(s) => {
58                        if let Ok(s1d) = s.clone().into_dimensionality::<Ix1>() {
59                            Some(s1d)
60                        } else {
61                            return Err(NdimageError::DimensionError(
62                                "Failed to convert structure to 1D".to_string(),
63                            ));
64                        }
65                    }
66                    None => None,
67                };
68
69                // Convert mask to 1D if provided
70                let mask_1d = match mask {
71                    Some(m) => {
72                        if let Ok(m1d) = m.clone().into_dimensionality::<Ix1>() {
73                            Some(m1d)
74                        } else {
75                            return Err(NdimageError::DimensionError(
76                                "Failed to convert mask to 1D".to_string(),
77                            ));
78                        }
79                    }
80                    None => None,
81                };
82
83                // Call 1D implementation
84                let result_1d = binary_dilation1d(
85                    &input_1d,
86                    structure_1d.as_ref(),
87                    iterations,
88                    mask_1d.as_ref(),
89                    border_value,
90                    origin,
91                    brute_force,
92                )?;
93
94                // Convert back to original dimensionality
95                return result_1d.into_dimensionality().map_err(|_| {
96                    NdimageError::DimensionError(
97                        "Failed to convert result back to original dimensionality".to_string(),
98                    )
99                });
100            }
101        }
102        2 => {
103            if let Ok(input_2d) = input.clone().into_dimensionality::<Ix2>() {
104                // Convert structure to 2D if provided
105                let structure_2d = match structure {
106                    Some(s) => {
107                        if let Ok(s2d) = s.clone().into_dimensionality::<Ix2>() {
108                            Some(s2d)
109                        } else {
110                            return Err(NdimageError::DimensionError(
111                                "Failed to convert structure to 2D".to_string(),
112                            ));
113                        }
114                    }
115                    None => None,
116                };
117
118                // Convert mask to 2D if provided
119                let mask_2d = match mask {
120                    Some(m) => {
121                        if let Ok(m2d) = m.clone().into_dimensionality::<Ix2>() {
122                            Some(m2d)
123                        } else {
124                            return Err(NdimageError::DimensionError(
125                                "Failed to convert mask to 2D".to_string(),
126                            ));
127                        }
128                    }
129                    None => None,
130                };
131
132                // Call 2D implementation
133                let result_2d = binary_dilation2d(
134                    &input_2d,
135                    structure_2d.as_ref(),
136                    iterations,
137                    mask_2d.as_ref(),
138                    border_value,
139                    origin,
140                    brute_force,
141                )?;
142
143                // Convert back to original dimensionality
144                return result_2d.into_dimensionality().map_err(|_| {
145                    NdimageError::DimensionError(
146                        "Failed to convert result back to original dimensionality".to_string(),
147                    )
148                });
149            }
150        }
151        _ => {
152            // For higher dimensions, convert to dynamic dimension
153            if let Ok(input_dyn) = input.clone().into_dimensionality::<IxDyn>() {
154                // Convert structure to dyn if provided
155                let structure_dyn = match structure {
156                    Some(s) => {
157                        if let Ok(sdyn) = s.clone().into_dimensionality::<IxDyn>() {
158                            Some(sdyn)
159                        } else {
160                            return Err(NdimageError::DimensionError(
161                                "Failed to convert structure to dynamic dimension".to_string(),
162                            ));
163                        }
164                    }
165                    None => None,
166                };
167
168                // Convert mask to dyn if provided
169                let mask_dyn = match mask {
170                    Some(m) => {
171                        if let Ok(mdyn) = m.clone().into_dimensionality::<IxDyn>() {
172                            Some(mdyn)
173                        } else {
174                            return Err(NdimageError::DimensionError(
175                                "Failed to convert mask to dynamic dimension".to_string(),
176                            ));
177                        }
178                    }
179                    None => None,
180                };
181
182                // Call dynamic implementation
183                let result_dyn = binary_dilation_dyn(
184                    &input_dyn,
185                    structure_dyn.as_ref(),
186                    iterations,
187                    mask_dyn.as_ref(),
188                    border_value,
189                    origin,
190                    brute_force,
191                )?;
192
193                // Convert back to original dimensionality
194                return result_dyn.into_dimensionality().map_err(|_| {
195                    NdimageError::DimensionError(
196                        "Failed to convert result back to original dimensionality".to_string(),
197                    )
198                });
199            }
200        }
201    }
202
203    // Fallback case (should not be reached, but needed for type checking)
204    Err(NdimageError::DimensionError(
205        "Unsupported array dimensions for dilation".to_string(),
206    ))
207}
208
209/// Implementation of binary dilation for 1D arrays
210#[allow(dead_code)]
211fn binary_dilation1d(
212    input: &Array1<bool>,
213    structure: Option<&Array1<bool>>,
214    iterations: Option<usize>,
215    mask: Option<&Array1<bool>>,
216    border_value: Option<bool>,
217    origin: Option<&[isize]>,
218    brute_force: Option<bool>,
219) -> NdimageResult<Array1<bool>> {
220    // Default parameter values
221    let iters = iterations.unwrap_or(1);
222    let border_val = border_value.unwrap_or(false);
223    let brute_force_algo = brute_force.unwrap_or(false);
224
225    // Create a default structure if none is provided
226    let owned_structure;
227    let struct_elem = if let Some(s) = structure {
228        s
229    } else {
230        // Create a default structure with face connectivity
231        owned_structure = Array1::from_elem(3, true);
232        &owned_structure
233    };
234
235    // Calculate the origin if not provided
236    let origin_vec: Vec<isize> = if let Some(o) = origin {
237        if o.len() != 1 {
238            return Err(NdimageError::DimensionError(format!(
239                "Origin must have same length as input dimensions (got {} expected {})",
240                o.len(),
241                1
242            )));
243        }
244        o.to_vec()
245    } else {
246        // Default origin is at the center of the structure
247        vec![(struct_elem.len() as isize) / 2]
248    };
249
250    // Implementation for 1D dilation
251    let mut result = input.to_owned();
252
253    // Apply dilation the specified number of times
254    for _ in 0..iters {
255        // Create a temporary array for this iteration's result
256        let mut temp = Array1::from_elem(input.len(), false);
257        let prev = result.clone();
258
259        // Iterate over each position in the array
260        for (i, val) in temp.indexed_iter_mut() {
261            // Skip if masked
262            if let Some(m) = mask {
263                if !m[i] {
264                    *val = prev[i];
265                    continue;
266                }
267            }
268
269            // Initialize current position _value
270            *val = prev[i];
271
272            // If position is already true, no need to check neighbors
273            if *val {
274                continue;
275            }
276
277            // Check for neighboring true values using the structuring element
278            for (s_i, &s_val) in struct_elem.indexed_iter() {
279                if !s_val {
280                    continue; // Only consider true values in the structure
281                }
282
283                // Calculate corresponding position in input (reflected)
284                let offset = origin_vec[0] - s_i as isize;
285                let pos = i as isize + offset;
286
287                // Check if position is within bounds
288                if pos < 0 || pos >= prev.len() as isize {
289                    // Outside bounds - use border _value
290                    if border_val {
291                        *val = true;
292                        break;
293                    }
294                } else if prev[pos as usize] {
295                    // Position has a true _value in input
296                    *val = true;
297                    break;
298                }
299            }
300        }
301
302        result = temp;
303
304        // Check if we've reached a fixed point (no change)
305        if !brute_force_algo && result == prev {
306            break;
307        }
308    }
309
310    Ok(result)
311}
312
313/// Implementation of binary dilation for 2D arrays
314#[allow(dead_code)]
315fn binary_dilation2d(
316    input: &Array2<bool>,
317    structure: Option<&Array2<bool>>,
318    iterations: Option<usize>,
319    mask: Option<&Array2<bool>>,
320    border_value: Option<bool>,
321    origin: Option<&[isize]>,
322    brute_force: Option<bool>,
323) -> NdimageResult<Array2<bool>> {
324    // Default parameter values
325    let iters = iterations.unwrap_or(1);
326    let border_val = border_value.unwrap_or(false);
327    let brute_force_algo = brute_force.unwrap_or(false);
328
329    // Create a default structure if none is provided
330    let owned_structure;
331    let struct_elem = if let Some(s) = structure {
332        s
333    } else {
334        // Create a box structure with face connectivity
335        let size = [3, 3];
336        owned_structure = Array2::from_elem((size[0], size[1]), true);
337        &owned_structure
338    };
339
340    // Calculate the origin if not provided
341    let origin_vec: Vec<isize> = if let Some(o) = origin {
342        if o.len() != 2 {
343            return Err(NdimageError::DimensionError(format!(
344                "Origin must have same length as input dimensions (got {} expected {})",
345                o.len(),
346                2
347            )));
348        }
349        o.to_vec()
350    } else {
351        // Default origin is at the center of the structure
352        struct_elem
353            .shape()
354            .iter()
355            .map(|&s| (s as isize) / 2)
356            .collect()
357    };
358
359    let shape = input.shape();
360    let mut result = input.to_owned();
361
362    // Apply dilation for the specified number of iterations
363    for iter in 0..iters {
364        let prev = result.clone();
365        let mut temp = Array2::from_elem((shape[0], shape[1]), false);
366
367        // Get structure dimensions
368        let s_rows = struct_elem.shape()[0];
369        let s_cols = struct_elem.shape()[1];
370
371        // Calculate half sizes for the structure
372        let half_height = origin_vec[0];
373        let half_width = origin_vec[1];
374
375        // For each position in the array
376        for i in 0..shape[0] {
377            for j in 0..shape[1] {
378                // Skip masked positions
379                if let Some(m) = mask {
380                    if !m[[i, j]] {
381                        temp[[i, j]] = prev[[i, j]];
382                        continue;
383                    }
384                }
385
386                // Copy current _value first
387                temp[[i, j]] = prev[[i, j]];
388
389                // If already true, skip checking neighbors
390                if temp[[i, j]] {
391                    continue;
392                }
393
394                // Check for neighboring true values
395                let mut found_true = false;
396
397                // Iterate over the structure
398                'outer: for si in 0..s_rows {
399                    for sj in 0..s_cols {
400                        if !struct_elem[[si, sj]] {
401                            continue; // Skip false values in structure
402                        }
403
404                        // Calculate corresponding position in input (reverse direction from erosion)
405                        let ni = i as isize - (si as isize - half_height);
406                        let nj = j as isize - (sj as isize - half_width);
407
408                        // Check if neighbor position is within bounds
409                        if ni < 0 || ni >= shape[0] as isize || nj < 0 || nj >= shape[1] as isize {
410                            // Outside bounds - use border _value
411                            if border_val {
412                                found_true = true;
413                                break 'outer;
414                            }
415                        } else if prev[[ni as usize, nj as usize]] {
416                            // Position is within bounds and _value is true
417                            found_true = true;
418                            break 'outer;
419                        }
420                    }
421                }
422
423                if found_true {
424                    temp[[i, j]] = true;
425                }
426            }
427        }
428
429        result = temp;
430
431        // Check if we've reached a fixed point (no change)
432        if !brute_force_algo && iter > 0 && result == prev {
433            break;
434        }
435    }
436
437    Ok(result)
438}
439
440/// Implementation of binary dilation for n-dimensional arrays (using dynamic dimensions)
441#[allow(dead_code)]
442fn binary_dilation_dyn(
443    input: &Array<bool, IxDyn>,
444    structure: Option<&Array<bool, IxDyn>>,
445    iterations: Option<usize>,
446    mask: Option<&Array<bool, IxDyn>>,
447    border_value: Option<bool>,
448    origin: Option<&[isize]>,
449    _brute_force: Option<bool>,
450) -> NdimageResult<Array<bool, IxDyn>> {
451    let iterations = iterations.unwrap_or(1);
452    let border = border_value.unwrap_or(false);
453
454    // Get or generate structure
455    let default_structure = if let Some(s) = structure {
456        s.to_owned()
457    } else {
458        generate_binary_structure_dyn(input.ndim())?
459    };
460
461    // Validate input dimensions
462    if input.ndim() != default_structure.ndim() {
463        return Err(NdimageError::DimensionError(
464            "Input and structure must have the same number of dimensions".into(),
465        ));
466    }
467
468    // Validate mask dimensions if provided
469    if let Some(m) = mask {
470        if m.ndim() != input.ndim() || m.shape() != input.shape() {
471            return Err(NdimageError::InvalidInput(
472                "Mask must have the same shape as input".into(),
473            ));
474        }
475    }
476
477    // Get structure center
478    let center = get_structure_center_dyn(&default_structure, origin)?;
479
480    // Create result array
481    let mut result = input.to_owned();
482
483    // Apply dilation iterations
484    for _ in 0..iterations {
485        let temp = result.clone();
486
487        // Iterate through all positions in the input array
488        for idx in scirs2_core::ndarray::indices(input.shape()) {
489            let idx_vec: Vec<_> = idx.slice().to_vec();
490
491            // Skip if masked out
492            if let Some(m) = mask {
493                if !m[idx_vec.as_slice()] {
494                    continue;
495                }
496            }
497
498            // Check if any structure element touches a true _value
499            let mut any_fit = false;
500
501            // Check each structure element
502            for str_idx in scirs2_core::ndarray::indices(default_structure.shape()) {
503                let str_idx_vec: Vec<_> = str_idx.slice().to_vec();
504
505                // Skip if structure element is false
506                if !default_structure[str_idx_vec.as_slice()] {
507                    continue;
508                }
509
510                // Calculate corresponding input position
511                let mut input_pos = vec![0isize; input.ndim()];
512                for d in 0..input.ndim() {
513                    input_pos[d] = idx_vec[d] as isize + str_idx_vec[d] as isize - center[d];
514                }
515
516                // Check if position is within bounds
517                let mut within_bounds = true;
518                for (d, &pos) in input_pos.iter().enumerate().take(input.ndim()) {
519                    if pos < 0 || pos >= input.shape()[d] as isize {
520                        within_bounds = false;
521                        break;
522                    }
523                }
524
525                // Get the value, using border _value if out of bounds
526                let val = if within_bounds {
527                    let input_idx: Vec<_> = input_pos.iter().map(|&x| x as usize).collect();
528                    temp[input_idx.as_slice()]
529                } else {
530                    border
531                };
532
533                // Dilation requires at least one _value to be true
534                if val {
535                    any_fit = true;
536                    break;
537                }
538            }
539
540            result[idx_vec.as_slice()] = any_fit;
541        }
542    }
543
544    Ok(result)
545}