scikit-image
310 строк · 10.5 Кб
1"""flood_fill.py - in place flood fill algorithm
2
3This module provides a function to fill all equal (or within tolerance) values
4connected to a given seed point with a different value.
5"""
6
7import numpy as np
8
9from ..util import crop
10from ._flood_fill_cy import _flood_fill_equal, _flood_fill_tolerance
11from ._util import (
12_offsets_to_raveled_neighbors,
13_resolve_neighborhood,
14_set_border_values,
15)
16from .._shared.dtype import numeric_dtype_min_max
17
18
19def flood_fill(
20image,
21seed_point,
22new_value,
23*,
24footprint=None,
25connectivity=None,
26tolerance=None,
27in_place=False,
28):
29"""Perform flood filling on an image.
30
31Starting at a specific `seed_point`, connected points equal or within
32`tolerance` of the seed value are found, then set to `new_value`.
33
34Parameters
35----------
36image : ndarray
37An n-dimensional array.
38seed_point : tuple or int
39The point in `image` used as the starting point for the flood fill. If
40the image is 1D, this point may be given as an integer.
41new_value : `image` type
42New value to set the entire fill. This must be chosen in agreement
43with the dtype of `image`.
44footprint : ndarray, optional
45The footprint (structuring element) used to determine the neighborhood
46of each evaluated pixel. It must contain only 1's and 0's, have the
47same number of dimensions as `image`. If not given, all adjacent pixels
48are considered as part of the neighborhood (fully connected).
49connectivity : int, optional
50A number used to determine the neighborhood of each evaluated pixel.
51Adjacent pixels whose squared distance from the center is less than or
52equal to `connectivity` are considered neighbors. Ignored if
53`footprint` is not None.
54tolerance : float or int, optional
55If None (default), adjacent values must be strictly equal to the
56value of `image` at `seed_point` to be filled. This is fastest.
57If a tolerance is provided, adjacent points with values within plus or
58minus tolerance from the seed point are filled (inclusive).
59in_place : bool, optional
60If True, flood filling is applied to `image` in place. If False, the
61flood filled result is returned without modifying the input `image`
62(default).
63
64Returns
65-------
66filled : ndarray
67An array with the same shape as `image` is returned, with values in
68areas connected to and equal (or within tolerance of) the seed point
69replaced with `new_value`.
70
71Notes
72-----
73The conceptual analogy of this operation is the 'paint bucket' tool in many
74raster graphics programs.
75
76Examples
77--------
78>>> from skimage.morphology import flood_fill
79>>> image = np.zeros((4, 7), dtype=int)
80>>> image[1:3, 1:3] = 1
81>>> image[3, 0] = 1
82>>> image[1:3, 4:6] = 2
83>>> image[3, 6] = 3
84>>> image
85array([[0, 0, 0, 0, 0, 0, 0],
86[0, 1, 1, 0, 2, 2, 0],
87[0, 1, 1, 0, 2, 2, 0],
88[1, 0, 0, 0, 0, 0, 3]])
89
90Fill connected ones with 5, with full connectivity (diagonals included):
91
92>>> flood_fill(image, (1, 1), 5)
93array([[0, 0, 0, 0, 0, 0, 0],
94[0, 5, 5, 0, 2, 2, 0],
95[0, 5, 5, 0, 2, 2, 0],
96[5, 0, 0, 0, 0, 0, 3]])
97
98Fill connected ones with 5, excluding diagonal points (connectivity 1):
99
100>>> flood_fill(image, (1, 1), 5, connectivity=1)
101array([[0, 0, 0, 0, 0, 0, 0],
102[0, 5, 5, 0, 2, 2, 0],
103[0, 5, 5, 0, 2, 2, 0],
104[1, 0, 0, 0, 0, 0, 3]])
105
106Fill with a tolerance:
107
108>>> flood_fill(image, (0, 0), 5, tolerance=1)
109array([[5, 5, 5, 5, 5, 5, 5],
110[5, 5, 5, 5, 2, 2, 5],
111[5, 5, 5, 5, 2, 2, 5],
112[5, 5, 5, 5, 5, 5, 3]])
113"""
114mask = flood(
115image,
116seed_point,
117footprint=footprint,
118connectivity=connectivity,
119tolerance=tolerance,
120)
121
122if not in_place:
123image = image.copy()
124
125image[mask] = new_value
126return image
127
128
129def flood(image, seed_point, *, footprint=None, connectivity=None, tolerance=None):
130"""Mask corresponding to a flood fill.
131
132Starting at a specific `seed_point`, connected points equal or within
133`tolerance` of the seed value are found.
134
135Parameters
136----------
137image : ndarray
138An n-dimensional array.
139seed_point : tuple or int
140The point in `image` used as the starting point for the flood fill. If
141the image is 1D, this point may be given as an integer.
142footprint : ndarray, optional
143The footprint (structuring element) used to determine the neighborhood
144of each evaluated pixel. It must contain only 1's and 0's, have the
145same number of dimensions as `image`. If not given, all adjacent pixels
146are considered as part of the neighborhood (fully connected).
147connectivity : int, optional
148A number used to determine the neighborhood of each evaluated pixel.
149Adjacent pixels whose squared distance from the center is less than or
150equal to `connectivity` are considered neighbors. Ignored if
151`footprint` is not None.
152tolerance : float or int, optional
153If None (default), adjacent values must be strictly equal to the
154initial value of `image` at `seed_point`. This is fastest. If a value
155is given, a comparison will be done at every point and if within
156tolerance of the initial value will also be filled (inclusive).
157
158Returns
159-------
160mask : ndarray
161A Boolean array with the same shape as `image` is returned, with True
162values for areas connected to and equal (or within tolerance of) the
163seed point. All other values are False.
164
165Notes
166-----
167The conceptual analogy of this operation is the 'paint bucket' tool in many
168raster graphics programs. This function returns just the mask
169representing the fill.
170
171If indices are desired rather than masks for memory reasons, the user can
172simply run `numpy.nonzero` on the result, save the indices, and discard
173this mask.
174
175Examples
176--------
177>>> from skimage.morphology import flood
178>>> image = np.zeros((4, 7), dtype=int)
179>>> image[1:3, 1:3] = 1
180>>> image[3, 0] = 1
181>>> image[1:3, 4:6] = 2
182>>> image[3, 6] = 3
183>>> image
184array([[0, 0, 0, 0, 0, 0, 0],
185[0, 1, 1, 0, 2, 2, 0],
186[0, 1, 1, 0, 2, 2, 0],
187[1, 0, 0, 0, 0, 0, 3]])
188
189Fill connected ones with 5, with full connectivity (diagonals included):
190
191>>> mask = flood(image, (1, 1))
192>>> image_flooded = image.copy()
193>>> image_flooded[mask] = 5
194>>> image_flooded
195array([[0, 0, 0, 0, 0, 0, 0],
196[0, 5, 5, 0, 2, 2, 0],
197[0, 5, 5, 0, 2, 2, 0],
198[5, 0, 0, 0, 0, 0, 3]])
199
200Fill connected ones with 5, excluding diagonal points (connectivity 1):
201
202>>> mask = flood(image, (1, 1), connectivity=1)
203>>> image_flooded = image.copy()
204>>> image_flooded[mask] = 5
205>>> image_flooded
206array([[0, 0, 0, 0, 0, 0, 0],
207[0, 5, 5, 0, 2, 2, 0],
208[0, 5, 5, 0, 2, 2, 0],
209[1, 0, 0, 0, 0, 0, 3]])
210
211Fill with a tolerance:
212
213>>> mask = flood(image, (0, 0), tolerance=1)
214>>> image_flooded = image.copy()
215>>> image_flooded[mask] = 5
216>>> image_flooded
217array([[5, 5, 5, 5, 5, 5, 5],
218[5, 5, 5, 5, 2, 2, 5],
219[5, 5, 5, 5, 2, 2, 5],
220[5, 5, 5, 5, 5, 5, 3]])
221"""
222# Correct start point in ravelled image - only copy if non-contiguous
223image = np.asarray(image)
224if image.flags.f_contiguous is True:
225order = 'F'
226elif image.flags.c_contiguous is True:
227order = 'C'
228else:
229image = np.ascontiguousarray(image)
230order = 'C'
231
232# Shortcut for rank zero
233if 0 in image.shape:
234return np.zeros(image.shape, dtype=bool)
235
236# Convenience for 1d input
237try:
238iter(seed_point)
239except TypeError:
240seed_point = (seed_point,)
241
242seed_value = image[seed_point]
243seed_point = tuple(np.asarray(seed_point) % image.shape)
244
245footprint = _resolve_neighborhood(
246footprint, connectivity, image.ndim, enforce_adjacency=False
247)
248center = tuple(s // 2 for s in footprint.shape)
249# Compute padding width as the maximum offset to neighbors on each axis.
250# Generates a 2-tuple of (pad_start, pad_end) for each axis.
251pad_width = [
252(np.max(np.abs(idx - c)),) * 2 for idx, c in zip(np.nonzero(footprint), center)
253]
254
255# Must annotate borders
256working_image = np.pad(
257image, pad_width, mode='constant', constant_values=image.min()
258)
259# Stride-aware neighbors - works for both C- and Fortran-contiguity
260ravelled_seed_idx = np.ravel_multi_index(
261[i + pad_start for i, (pad_start, pad_end) in zip(seed_point, pad_width)],
262working_image.shape,
263order=order,
264)
265neighbor_offsets = _offsets_to_raveled_neighbors(
266working_image.shape, footprint, center=center, order=order
267)
268
269# Use a set of flags; see _flood_fill_cy.pyx for meanings
270flags = np.zeros(working_image.shape, dtype=np.uint8, order=order)
271_set_border_values(flags, value=2, border_width=pad_width)
272
273try:
274if tolerance is not None:
275tolerance = abs(tolerance)
276# Account for over- & underflow problems with seed_value ± tolerance
277# in a way that works with NumPy 1 & 2
278min_value, max_value = numeric_dtype_min_max(seed_value.dtype)
279low_tol = max(min_value.item(), seed_value.item() - tolerance)
280high_tol = min(max_value.item(), seed_value.item() + tolerance)
281
282_flood_fill_tolerance(
283working_image.ravel(order),
284flags.ravel(order),
285neighbor_offsets,
286ravelled_seed_idx,
287seed_value,
288low_tol,
289high_tol,
290)
291else:
292_flood_fill_equal(
293working_image.ravel(order),
294flags.ravel(order),
295neighbor_offsets,
296ravelled_seed_idx,
297seed_value,
298)
299except TypeError:
300if working_image.dtype == np.float16:
301# Provide the user with clearer error message
302raise TypeError(
303"dtype of `image` is float16 which is not "
304"supported, try upcasting to float32"
305)
306else:
307raise
308
309# Output what the user requested; view does not create a new copy.
310return crop(flags, pad_width, copy=False).view(bool)
311