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