ndarray_ndimage/morphology/mod.rs
1mod offsets;
2
3use ndarray::{Array3, ArrayRef3, ArrayView3, ArrayViewMut3};
4
5use crate::Mask;
6use offsets::Offsets;
7
8/// Binary erosion of a 3D binary image.
9///
10/// * `mask` - Binary image to be eroded.
11/// * `kernel` - Structuring element used for the erosion. Must be of odd length. The center must
12/// be `true`.
13/// * `iterations` - The erosion is repeated iterations times.
14pub fn binary_erosion(
15 mask: &ArrayRef3<bool>,
16 kernel: &ArrayRef3<bool>,
17 iterations: usize,
18) -> Mask
19{
20 mask.as_slice_memory_order()
21 .expect("Morphological operations can only be called on arrays with contiguous memory.");
22
23 // We can't really reserve a good number of elements here. It could be anything.
24 let mut last_indices = (iterations > 1).then_some(vec![]);
25 let mut eroded = mask.to_owned();
26 let mut offsets = Offsets::new(mask, kernel.view(), false);
27 erode(mask.view(), &mut eroded.view_mut(), &mut offsets, &mut last_indices);
28
29 if let Some(mut last_indices) = last_indices {
30 for it in 1..iterations {
31 if last_indices.is_empty() {
32 break;
33 }
34
35 let save_next_indices = it < iterations - 1;
36 next_it(&mut eroded, &mut offsets, &mut last_indices, save_next_indices, true, false);
37 }
38 }
39 eroded
40}
41
42/// Binary dilation of a 3D binary image.
43///
44/// * `mask` - Binary image to be dilated.
45/// * `kernel` - Structuring element used for the erosion. Must be of odd length. The center must
46/// be `true`.
47/// * `iterations` - The dilation is repeated iterations times.
48pub fn binary_dilation(
49 mask: &ArrayRef3<bool>,
50 kernel: &ArrayRef3<bool>,
51 iterations: usize,
52) -> Mask
53{
54 mask.as_slice_memory_order()
55 .expect("Morphological operations can only be called on arrays with contiguous memory.");
56
57 // We can't really reserve a good number of elements here. It could be anything.
58 let mut last_indices = (iterations > 1).then_some(vec![]);
59 let mut dilated = mask.to_owned();
60 let mut offsets = Offsets::new(mask, kernel.view(), true);
61 dilate(mask.view(), &mut dilated, &mut offsets, &mut last_indices);
62
63 if let Some(mut last_indices) = last_indices {
64 for it in 1..iterations {
65 if last_indices.is_empty() {
66 break;
67 }
68
69 let save_next_indices = it < iterations - 1;
70 next_it(&mut dilated, &mut offsets, &mut last_indices, save_next_indices, false, true);
71 }
72 }
73 dilated
74}
75
76/// Binary opening of a 3D binary image.
77///
78/// The opening of an input image by a structuring element is the dilation of the erosion of the
79/// image by the structuring element.
80///
81/// Unlike other libraries, the **border values** of the:
82/// - dilation is always `false`, to avoid dilating the borders
83/// - erosion is always `true`, to avoid *border effects*
84///
85/// * `mask` - Binary image to be opened.
86/// * `kernel` - Structuring element used for the opening.
87/// * `iterations` - The erosion step of the opening, then the dilation step are each repeated
88/// iterations times.
89pub fn binary_opening(
90 mask: &ArrayRef3<bool>,
91 kernel: &ArrayRef3<bool>,
92 iterations: usize,
93) -> Mask
94{
95 let eroded = binary_erosion(mask, kernel, iterations);
96 binary_dilation(&eroded, kernel, iterations)
97}
98
99/// Binary closing of a 3D binary image.
100///
101/// The closing of an input image by a structuring element is the erosion of the dilation of the
102/// image by the structuring element.
103///
104/// Unlike other libraries, the **border values** of the:
105/// - dilation is always `false`, to avoid dilating the borders
106/// - erosion is always `true`, to avoid *border effects*
107///
108/// * `mask` - Binary image to be closed.
109/// * `kernel` - Structuring element used for the closing.
110/// * `iterations` - The dilation step of the closing, then the erosion step are each repeated
111/// iterations times.
112pub fn binary_closing(
113 mask: &ArrayRef3<bool>,
114 kernel: &ArrayRef3<bool>,
115 iterations: usize,
116) -> Mask
117{
118 let dilated = binary_dilation(mask, kernel, iterations);
119 binary_erosion(&dilated, kernel, iterations)
120}
121
122/// Actual erosion work.
123///
124/// `out` MUST be a clone of `mask`, otherwise this won't work. We're not setting any values
125/// uselessly, to be as fast as possible.
126fn erode(
127 mask: ArrayView3<bool>,
128 out: &mut ArrayViewMut3<bool>,
129 offsets: &mut Offsets,
130 last_indices: &mut Option<Vec<isize>>,
131) {
132 let mask = mask.as_slice_memory_order().unwrap();
133 let out = out.as_slice_memory_order_mut().unwrap();
134 let ooi_offset = mask.len() as isize;
135
136 let mut i = 0;
137 for (&m, o) in mask.iter().zip(out) {
138 if m {
139 for &offset in offsets.range() {
140 // Is offset the special value "Out Of Image"?
141 if offset == ooi_offset {
142 // The offsets are sorted so we can quit as soon as we see the `ooi_offset`
143 break;
144 } else {
145 // unsafe { !*mask.get_unchecked((i + offset) as usize) }
146 if !mask[(i + offset) as usize] {
147 *o = false;
148 // If we have more than one iteration, note all modified indices
149 if let Some(last_indices) = last_indices {
150 // Remember that `i` IS the neighbor, not the "center", so we're adding
151 // `i` here, not `i + offset`.
152 last_indices.push(i);
153 }
154 break;
155 }
156 }
157 }
158 }
159 offsets.next();
160 i += 1;
161 }
162}
163
164/// Actual dilation work.
165///
166/// `out` MUST be a clone of `mask`, otherwise this won't work. We're not setting any values
167/// uselessly, to be as fast as possible.
168fn dilate(
169 mask: ArrayView3<bool>,
170 out: &mut Array3<bool>,
171 offsets: &mut Offsets,
172 last_indices: &mut Option<Vec<isize>>,
173) {
174 // Even if `erode` and `dilate` could share the same code (as SciPy does), it produces much
175 // slower code in practice. See previous function for some documentation.
176 let mask = mask.as_slice_memory_order().unwrap();
177 let out = out.as_slice_memory_order_mut().unwrap();
178 let ooi_offset = mask.len() as isize;
179
180 let mut i = 0;
181 for (&m, o) in mask.iter().zip(out) {
182 if !m {
183 for &offset in offsets.range() {
184 if offset == ooi_offset {
185 break;
186 } else {
187 // unsafe { *mask.get_unchecked((i + offset) as usize) }
188 if mask[(i + offset) as usize] {
189 *o = true;
190 if let Some(last_indices) = last_indices {
191 last_indices.push(i);
192 }
193 break;
194 }
195 }
196 }
197 }
198 offsets.next();
199 i += 1;
200 }
201}
202
203/// Common function do compute another iteration of dilate or erode.
204///
205/// Use only when `dilate` or `erode` have been called and the `last_indices` collected.
206///
207/// - Use `false` and `true` for dilate.
208/// - Use `true` and `false` for erode.
209fn next_it(
210 out: &mut Array3<bool>,
211 offsets: &mut Offsets,
212 last_indices: &mut Vec<isize>,
213 save_next_indices: bool,
214 b1: bool,
215 b2: bool,
216) {
217 let out = out.as_slice_memory_order_mut().unwrap();
218 let ooi_offset = out.len() as isize;
219
220 // Again, it's complex to guess the right number of elements. I think the same number as last
221 // time + ?% makes sense, but it could also be empty.
222 let mut new_indices = vec![];
223 for &i in &*last_indices {
224 offsets.move_to(i);
225 for &offset in offsets.range() {
226 if offset == ooi_offset {
227 break;
228 } else {
229 let out = &mut out[(i + offset) as usize];
230 if save_next_indices && *out == b1 {
231 // This time, `i` is the center and `i + offset` is the neighbor
232 new_indices.push(i + offset);
233 }
234 *out = b2;
235 }
236 }
237 }
238 *last_indices = new_indices;
239}