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

1"""N-dimensional image manipulation.""" 

2 

3from pathlib import Path 

4 

5import numpy as np 

6from scipy.ndimage import label, maximum_filter 

7 

8############################################################################### 

9# Operating on connected components 

10############################################################################### 

11 

12 

13def largest_connected_component(volume): 

14 """Return the largest connected component of a 3D array. 

15 

16 Parameters 

17 ---------- 

18 volume : numpy.ndarray 

19 3D boolean array indicating a volume. 

20 

21 Returns 

22 ------- 

23 volume : numpy.ndarray 

24 3D boolean array with only one connected component. 

25 

26 See Also 

27 -------- 

28 nilearn.image.largest_connected_component_img : To simply operate the 

29 same manipulation directly on Nifti images. 

30 

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. 

37 

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") 

48 

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() 

60 

61 

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 ) 

74 

75 

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. 

85 

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`). 

88 

89 Parameters 

90 ---------- 

91 image : ndarray of floats 

92 Input image. 

93 

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`. 

99 

100 threshold_abs : float, default=0 

101 Minimum intensity of peaks. 

102 

103 threshold_rel : float, default=0.1 

104 Minimum intensity of peaks calculated as `max(image) * threshold_rel`. 

105 

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. 

109 

110 Returns 

111 ------- 

112 output : ndarray or ndarray of bools 

113 Boolean array shaped like `image`, with peaks represented by True 

114 values. 

115 

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. 

120 

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. 

126 

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 

130 

131 """ 

132 out = np.zeros_like(image, dtype=bool) 

133 

134 if np.all(image == image.flat[0]): 

135 return out 

136 

137 image = image.copy() 

138 

139 size = 2 * min_distance + 1 

140 image_max = maximum_filter(image, size=size, mode="constant") 

141 

142 mask = image == image_max 

143 image *= mask 

144 

145 # find top peak candidates above a threshold 

146 peak_threshold = max(np.max(image.ravel()) * threshold_rel, threshold_abs) 

147 

148 # get coordinates of peaks 

149 coordinates = np.argwhere(image > peak_threshold) 

150 

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] 

157 

158 nd_indices = tuple(coordinates.T) 

159 out[nd_indices] = True 

160 return out