scikit-image
328 строк · 11.7 Кб
1"""Utility functions used in the morphology subpackage."""
2
3import numpy as np4from scipy import ndimage as ndi5
6
7def _validate_connectivity(image_dim, connectivity, offset):8"""Convert any valid connectivity to a footprint and offset.9
10Parameters
11----------
12image_dim : int
13The number of dimensions of the input image.
14connectivity : int, array, or None
15The neighborhood connectivity. An integer is interpreted as in
16``scipy.ndimage.generate_binary_structure``, as the maximum number
17of orthogonal steps to reach a neighbor. An array is directly
18interpreted as a footprint and its shape is validated against
19the input image shape. ``None`` is interpreted as a connectivity of 1.
20offset : tuple of int, or None
21The coordinates of the center of the footprint.
22
23Returns
24-------
25c_connectivity : array of bool
26The footprint (structuring element) corresponding to the input
27`connectivity`.
28offset : array of int
29The offset corresponding to the center of the footprint.
30
31Raises
32------
33ValueError:
34If the image dimension and the connectivity or offset dimensions don't
35match.
36"""
37if connectivity is None:38connectivity = 139
40if np.isscalar(connectivity):41c_connectivity = ndi.generate_binary_structure(image_dim, connectivity)42else:43c_connectivity = np.array(connectivity, bool)44if c_connectivity.ndim != image_dim:45raise ValueError("Connectivity dimension must be same as image")46
47if offset is None:48if any([x % 2 == 0 for x in c_connectivity.shape]):49raise ValueError("Connectivity array must have an unambiguous " "center")50
51offset = np.array(c_connectivity.shape) // 252
53return c_connectivity, offset54
55
56def _raveled_offsets_and_distances(57image_shape,58*,59footprint=None,60connectivity=1,61center=None,62spacing=None,63order='C',64):65"""Compute offsets to neighboring pixels in raveled coordinate space.66
67This function also returns the corresponding distances from the center
68pixel given a spacing (assumed to be 1 along each axis by default).
69
70Parameters
71----------
72image_shape : tuple of int
73The shape of the image for which the offsets are being computed.
74footprint : array of bool
75The footprint of the neighborhood, expressed as an n-dimensional array
76of 1s and 0s. If provided, the connectivity argument is ignored.
77connectivity : {1, ..., ndim}
78The square connectivity of the neighborhood: the number of orthogonal
79steps allowed to consider a pixel a neighbor. See
80`scipy.ndimage.generate_binary_structure`. Ignored if footprint is
81provided.
82center : tuple of int
83Tuple of indices to the center of the footprint. If not provided, it
84is assumed to be the center of the footprint, either provided or
85generated by the connectivity argument.
86spacing : tuple of float
87The spacing between pixels/voxels along each axis.
88order : 'C' or 'F'
89The ordering of the array, either C or Fortran ordering.
90
91Returns
92-------
93raveled_offsets : ndarray
94Linear offsets to a samples neighbors in the raveled image, sorted by
95their distance from the center.
96distances : ndarray
97The pixel distances corresponding to each offset.
98
99Notes
100-----
101This function will return values even if `image_shape` contains a dimension
102length that is smaller than `footprint`.
103
104Examples
105--------
106>>> off, d = _raveled_offsets_and_distances(
107... (4, 5), footprint=np.ones((4, 3)), center=(1, 1)
108... )
109>>> off
110array([-5, -1, 1, 5, -6, -4, 4, 6, 10, 9, 11])
111>>> d[0]
1121.0
113>>> d[-1] # distance from (1, 1) to (3, 2)
1142.236...
115"""
116ndim = len(image_shape)117if footprint is None:118footprint = ndi.generate_binary_structure(rank=ndim, connectivity=connectivity)119if center is None:120center = tuple(s // 2 for s in footprint.shape)121
122if not footprint.ndim == ndim == len(center):123raise ValueError(124"number of dimensions in image shape, footprint and its"125"center index does not match"126)127
128offsets = np.stack(129[(idx - c) for idx, c in zip(np.nonzero(footprint), center)], axis=-1130)131
132if order == 'F':133offsets = offsets[:, ::-1]134image_shape = image_shape[::-1]135elif order != 'C':136raise ValueError("order must be 'C' or 'F'")137
138# Scale offsets in each dimension and sum139ravel_factors = image_shape[1:] + (1,)140ravel_factors = np.cumprod(ravel_factors[::-1])[::-1]141raveled_offsets = (offsets * ravel_factors).sum(axis=1)142
143# Sort by distance144if spacing is None:145spacing = np.ones(ndim)146weighted_offsets = offsets * spacing147distances = np.sqrt(np.sum(weighted_offsets**2, axis=1))148sorted_raveled_offsets = raveled_offsets[np.argsort(distances, kind="stable")]149sorted_distances = np.sort(distances, kind="stable")150
151# If any dimension in image_shape is smaller than footprint.shape152# duplicates might occur, remove them153if any(x < y for x, y in zip(image_shape, footprint.shape)):154# np.unique reorders, which we don't want155_, indices = np.unique(sorted_raveled_offsets, return_index=True)156indices = np.sort(indices, kind="stable")157sorted_raveled_offsets = sorted_raveled_offsets[indices]158sorted_distances = sorted_distances[indices]159
160# Remove "offset to center"161sorted_raveled_offsets = sorted_raveled_offsets[1:]162sorted_distances = sorted_distances[1:]163
164return sorted_raveled_offsets, sorted_distances165
166
167def _offsets_to_raveled_neighbors(image_shape, footprint, center, order='C'):168"""Compute offsets to a samples neighbors if the image would be raveled.169
170Parameters
171----------
172image_shape : tuple
173The shape of the image for which the offsets are computed.
174footprint : ndarray
175The footprint (structuring element) determining the neighborhood
176expressed as an n-D array of 1's and 0's.
177center : tuple
178Tuple of indices to the center of `footprint`.
179order : {"C", "F"}, optional
180Whether the image described by `image_shape` is in row-major (C-style)
181or column-major (Fortran-style) order.
182
183Returns
184-------
185raveled_offsets : ndarray
186Linear offsets to a samples neighbors in the raveled image, sorted by
187their distance from the center.
188
189Notes
190-----
191This function will return values even if `image_shape` contains a dimension
192length that is smaller than `footprint`.
193
194Examples
195--------
196>>> _offsets_to_raveled_neighbors((4, 5), np.ones((4, 3)), (1, 1))
197array([-5, -1, 1, 5, -6, -4, 4, 6, 10, 9, 11])
198>>> _offsets_to_raveled_neighbors((2, 3, 2), np.ones((3, 3, 3)), (1, 1, 1))
199array([-6, -2, -1, 1, 2, 6, -8, -7, -5, -4, -3, 3, 4, 5, 7, 8, -9,
2009])
201"""
202raveled_offsets = _raveled_offsets_and_distances(203image_shape, footprint=footprint, center=center, order=order204)[0]205
206return raveled_offsets207
208
209def _resolve_neighborhood(footprint, connectivity, ndim, enforce_adjacency=True):210"""Validate or create a footprint (structuring element).211
212Depending on the values of `connectivity` and `footprint` this function
213either creates a new footprint (`footprint` is None) using `connectivity`
214or validates the given footprint (`footprint` is not None).
215
216Parameters
217----------
218footprint : ndarray
219The footprint (structuring) element used to determine the neighborhood
220of each evaluated pixel (``True`` denotes a connected pixel). It must
221be a boolean array and have the same number of dimensions as `image`.
222If neither `footprint` nor `connectivity` are given, all adjacent
223pixels are considered as part of the neighborhood.
224connectivity : int
225A number used to determine the neighborhood of each evaluated pixel.
226Adjacent pixels whose squared distance from the center is less than or
227equal to `connectivity` are considered neighbors. Ignored if
228`footprint` is not None.
229ndim : int
230Number of dimensions `footprint` ought to have.
231enforce_adjacency : bool
232A boolean that determines whether footprint must only specify direct
233neighbors.
234
235Returns
236-------
237footprint : ndarray
238Validated or new footprint specifying the neighborhood.
239
240Examples
241--------
242>>> _resolve_neighborhood(None, 1, 2)
243array([[False, True, False],
244[ True, True, True],
245[False, True, False]])
246>>> _resolve_neighborhood(None, None, 3).shape
247(3, 3, 3)
248"""
249if footprint is None:250if connectivity is None:251connectivity = ndim252footprint = ndi.generate_binary_structure(ndim, connectivity)253else:254# Validate custom structured element255footprint = np.asarray(footprint, dtype=bool)256# Must specify neighbors for all dimensions257if footprint.ndim != ndim:258raise ValueError(259"number of dimensions in image and footprint do not" "match"260)261# Must only specify direct neighbors262if enforce_adjacency and any(s != 3 for s in footprint.shape):263raise ValueError("dimension size in footprint is not 3")264elif any((s % 2 != 1) for s in footprint.shape):265raise ValueError("footprint size must be odd along all dimensions")266
267return footprint268
269
270def _set_border_values(image, value, border_width=1):271"""Set edge values along all axes to a constant value.272
273Parameters
274----------
275image : ndarray
276The array to modify inplace.
277value : scalar
278The value to use. Should be compatible with `image`'s dtype.
279border_width : int or sequence of tuples
280A sequence with one 2-tuple per axis where the first and second values
281are the width of the border at the start and end of the axis,
282respectively. If an int is provided, a uniform border width along all
283axes is used.
284
285Examples
286--------
287>>> image = np.zeros((4, 5), dtype=int)
288>>> _set_border_values(image, 1)
289>>> image
290array([[1, 1, 1, 1, 1],
291[1, 0, 0, 0, 1],
292[1, 0, 0, 0, 1],
293[1, 1, 1, 1, 1]])
294>>> image = np.zeros((8, 8), dtype=int)
295>>> _set_border_values(image, 1, border_width=((1, 1), (2, 3)))
296>>> image
297array([[1, 1, 1, 1, 1, 1, 1, 1],
298[1, 1, 0, 0, 0, 1, 1, 1],
299[1, 1, 0, 0, 0, 1, 1, 1],
300[1, 1, 0, 0, 0, 1, 1, 1],
301[1, 1, 0, 0, 0, 1, 1, 1],
302[1, 1, 0, 0, 0, 1, 1, 1],
303[1, 1, 0, 0, 0, 1, 1, 1],
304[1, 1, 1, 1, 1, 1, 1, 1]])
305"""
306if np.isscalar(border_width):307border_width = ((border_width, border_width),) * image.ndim308elif len(border_width) != image.ndim:309raise ValueError('length of `border_width` must match image.ndim')310for axis, npad in enumerate(border_width):311if len(npad) != 2:312raise ValueError('each sequence in `border_width` must have ' 'length 2')313w_start, w_end = npad314if w_start == w_end == 0:315continue316elif w_start == w_end == 1:317# Index first and last element in the current dimension318sl = (slice(None),) * axis + ((0, -1),) + (...,)319image[sl] = value320continue321if w_start > 0:322# set first w_start entries along axis to value323sl = (slice(None),) * axis + (slice(0, w_start),) + (...,)324image[sl] = value325if w_end > 0:326# set last w_end entries along axis to value327sl = (slice(None),) * axis + (slice(-w_end, None),) + (...,)328image[sl] = value329