Coverage for nilearn/_utils/ndimage.py: 12%
37 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-20 10:58 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-20 10:58 +0200
1"""N-dimensional image manipulation."""
3from pathlib import Path
5import numpy as np
6from scipy.ndimage import label, maximum_filter
8###############################################################################
9# Operating on connected components
10###############################################################################
13def largest_connected_component(volume):
14 """Return the largest connected component of a 3D array.
16 Parameters
17 ----------
18 volume : numpy.ndarray
19 3D boolean array indicating a volume.
21 Returns
22 -------
23 volume : numpy.ndarray
24 3D boolean array with only one connected component.
26 See Also
27 --------
28 nilearn.image.largest_connected_component_img : To simply operate the
29 same manipulation directly on Nifti images.
31 Notes
32 -----
33 **Handling big-endian in given numpy.ndarray**
34 This function changes the existing byte-ordering information to new byte
35 order, if the given volume has non-native data type. This operation
36 is done inplace to avoid big-endian issues with scipy ndimage module.
38 """
39 if hasattr(volume, "get_fdata") or isinstance(volume, (str, Path)):
40 raise ValueError(
41 "Please enter a valid numpy array. For images use "
42 "largest_connected_component_img."
43 )
44 # Get the new byteorder to handle issues like "Big-endian buffer not
45 # supported on little-endian compiler" with scipy ndimage label.
46 if not volume.dtype.isnative:
47 volume.dtype = volume.dtype.newbyteorder("N")
49 # We use asarray to be able to work with masked arrays.
50 volume = np.asarray(volume)
51 labels, label_nb = label(volume)
52 if not label_nb:
53 raise ValueError("No non-zero values: no connected components")
54 if label_nb == 1:
55 return volume.astype(bool)
56 label_count = np.bincount(labels.ravel().astype(int))
57 # discard the 0 label
58 label_count[0] = 0
59 return labels == label_count.argmax()
62def get_border_data(data, border_size):
63 """Return the data at the border of an array."""
64 return np.concatenate(
65 [
66 data[:border_size, :, :].ravel(),
67 data[-border_size:, :, :].ravel(),
68 data[:, :border_size, :].ravel(),
69 data[:, -border_size:, :].ravel(),
70 data[:, :, :border_size].ravel(),
71 data[:, :, -border_size:].ravel(),
72 ]
73 )
76def peak_local_max(
77 image,
78 min_distance=10,
79 threshold_abs=0,
80 threshold_rel=0.1,
81 num_peaks=np.inf,
82):
83 """Find peaks in an image, and return them \
84 as coordinates or a boolean array.
86 Peaks are the local maxima in a region of `2 * min_distance + 1`
87 (i.e. peaks are separated by at least `min_distance`).
89 Parameters
90 ----------
91 image : ndarray of floats
92 Input image.
94 min_distance : int, default=10
95 Minimum number of pixels separating peaks in a region of `2 *
96 min_distance + 1` (i.e. peaks are separated by at least
97 `min_distance`). To find the maximum number of peaks, use
98 `min_distance=1`.
100 threshold_abs : float, default=0
101 Minimum intensity of peaks.
103 threshold_rel : float, default=0.1
104 Minimum intensity of peaks calculated as `max(image) * threshold_rel`.
106 num_peaks : int, default=np.inf
107 Maximum number of peaks. When the number of peaks exceeds `num_peaks`,
108 return `num_peaks` peaks based on highest peak intensity.
110 Returns
111 -------
112 output : ndarray or ndarray of bools
113 Boolean array shaped like `image`, with peaks represented by True
114 values.
116 Notes
117 -----
118 If peaks are flat (i.e. multiple adjacent pixels have identical
119 intensities), the coordinates of all such pixels are returned.
121 The peak local maximum function returns the coordinates of local peaks
122 (maxima) in a image. A maximum filter is used for finding local maxima.
123 This operation dilates the original image. After comparison between
124 dilated and original image, peak_local_max function returns the
125 coordinates of peaks where dilated image = original.
127 This code is mostly adapted from scikit image 0.11.3 release.
128 Location of file in scikit image: peak_local_max function in
129 skimage.feature.peak
131 """
132 out = np.zeros_like(image, dtype=bool)
134 if np.all(image == image.flat[0]):
135 return out
137 image = image.copy()
139 size = 2 * min_distance + 1
140 image_max = maximum_filter(image, size=size, mode="constant")
142 mask = image == image_max
143 image *= mask
145 # find top peak candidates above a threshold
146 peak_threshold = max(np.max(image.ravel()) * threshold_rel, threshold_abs)
148 # get coordinates of peaks
149 coordinates = np.argwhere(image > peak_threshold)
151 if coordinates.shape[0] > num_peaks:
152 intensities = image.flat[
153 np.ravel_multi_index(coordinates.transpose(), image.shape)
154 ]
155 idx_maxsort = np.argsort(intensities)[::-1]
156 coordinates = coordinates[idx_maxsort][:num_peaks]
158 nd_indices = tuple(coordinates.T)
159 out[nd_indices] = True
160 return out