Coverage for nilearn/plotting/surface/_backend.py: 84%

46 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-04 12:51 +0200

1"""Functions that are common to all possible backend implementations for 

2surface visualization functions in 

3:obj:`~nilearn.plotting.surface.surf_plotting`. 

4 

5"Matplotlib" is default engine for surface visualization functions. For some 

6functions, there is also a "plotly" implementation. 

7 

8All dependencies and functions related to "matplotlib" implementation is in 

9:obj:`~nilearn.plotting.surface._matplotlib_backend` module. 

10 

11All dependencies and functions related to "plotly" implementation is in 

12:obj:`~nilearn.plotting.surface._plotly_backend` module. 

13 

14Each backend engine implementation should be a self contained module. Any 

15imports on the engine package, or engine specific utility functions should not 

16appear elsewhere. 

17""" 

18 

19from collections.abc import Sequence 

20from warnings import warn 

21 

22import numpy as np 

23 

24from nilearn._utils.param_validation import check_params 

25from nilearn.plotting.surface._utils import check_surface_plotting_inputs 

26from nilearn.surface import load_surf_data, load_surf_mesh 

27from nilearn.surface.surface import ( 

28 FREESURFER_DATA_EXTENSIONS, 

29 check_extensions, 

30) 

31 

32# subset of data format extensions supported 

33DATA_EXTENSIONS = ( 

34 "gii", 

35 "gii.gz", 

36 "mgz", 

37) 

38 

39VALID_VIEWS = ( 

40 "anterior", 

41 "posterior", 

42 "medial", 

43 "lateral", 

44 "dorsal", 

45 "ventral", 

46 "left", 

47 "right", 

48) 

49 

50VALID_HEMISPHERES = "left", "right", "both" 

51 

52 

53class BaseSurfaceBackend: 

54 """A base class that behaves as an interface for Surface plotting 

55 backend. 

56 

57 The methods of class should be implemented by each engine used as backend. 

58 """ 

59 

60 def _check_engine_params(self, params): 

61 """Check default values of the parameters that are not implemented for 

62 current engine and warn the user if the parameter has other value then 

63 None. 

64 

65 Parameters 

66 ---------- 

67 params: :obj:`dict` 

68 A dictionary where keys are the unimplemented parameter names for a 

69 specific engine and values are the assigned value for corresponding 

70 parameter. 

71 """ 

72 for parameter, value in params.items(): 

73 if value is not None: 

74 warn( 

75 f"'{parameter}' is not implemented " 

76 f"for the {self.name} engine.\n" 

77 f"Got '{parameter} = {value}'.\n" 

78 f"Use '{parameter} = None' to silence this warning." 

79 ) 

80 

81 def plot_surf( 

82 self, 

83 surf_mesh=None, 

84 surf_map=None, 

85 bg_map=None, 

86 hemi="left", 

87 view=None, 

88 cmap=None, 

89 symmetric_cmap=None, 

90 colorbar=True, 

91 avg_method=None, 

92 threshold=None, 

93 alpha=None, 

94 bg_on_data=False, 

95 darkness=0.7, 

96 vmin=None, 

97 vmax=None, 

98 cbar_vmin=None, 

99 cbar_vmax=None, 

100 cbar_tick_format="auto", 

101 title=None, 

102 title_font_size=None, 

103 output_file=None, 

104 axes=None, 

105 figure=None, 

106 ): 

107 check_params(locals()) 

108 if view is None: 

109 view = "dorsal" if hemi == "both" else "lateral" 

110 

111 surf_map, surf_mesh, bg_map = check_surface_plotting_inputs( 

112 surf_map, surf_mesh, hemi, bg_map 

113 ) 

114 

115 check_extensions(surf_map, DATA_EXTENSIONS, FREESURFER_DATA_EXTENSIONS) 

116 

117 coords, faces = load_surf_mesh(surf_mesh) 

118 

119 return self._plot_surf( 

120 coords, 

121 faces, 

122 surf_map=surf_map, 

123 bg_map=bg_map, 

124 hemi=hemi, 

125 view=view, 

126 cmap=cmap, 

127 symmetric_cmap=symmetric_cmap, 

128 colorbar=colorbar, 

129 avg_method=avg_method, 

130 threshold=threshold, 

131 alpha=alpha, 

132 bg_on_data=bg_on_data, 

133 darkness=darkness, 

134 vmin=vmin, 

135 vmax=vmax, 

136 cbar_vmin=cbar_vmin, 

137 cbar_vmax=cbar_vmax, 

138 cbar_tick_format=cbar_tick_format, 

139 title=title, 

140 title_font_size=title_font_size, 

141 output_file=output_file, 

142 axes=axes, 

143 figure=figure, 

144 ) 

145 

146 

147def _check_hemisphere_is_valid(hemi): 

148 return hemi in VALID_HEMISPHERES 

149 

150 

151def check_hemispheres(hemispheres): 

152 """Check whether the hemispheres passed to in plot_img_on_surf are \ 

153 correct. 

154 

155 hemispheres : :obj:`list` 

156 Any combination of 'left' and 'right'. 

157 

158 """ 

159 invalid_hemis = [ 

160 not _check_hemisphere_is_valid(hemi) for hemi in hemispheres 

161 ] 

162 if any(invalid_hemis): 162 ↛ 163line 162 didn't jump to line 163 because the condition on line 162 was never true

163 raise ValueError( 

164 "Invalid hemispheres definition!\n" 

165 f"Got: {np.array(hemispheres)[invalid_hemis]!s}\n" 

166 f"Supported values are: {VALID_HEMISPHERES!s}" 

167 ) 

168 return hemispheres 

169 

170 

171def _check_view_is_valid(view) -> bool: 

172 """Check whether a single view is one of two valid input types. 

173 

174 Parameters 

175 ---------- 

176 view : :obj:`str` in {"anterior", "posterior", "medial", "lateral", 

177 "dorsal", "ventral" or pair of floats (elev, azim). 

178 

179 Returns 

180 ------- 

181 valid : True if view is valid, False otherwise. 

182 """ 

183 if isinstance(view, str) and (view in VALID_VIEWS): 183 ↛ 185line 183 didn't jump to line 185 because the condition on line 183 was always true

184 return True 

185 return ( 

186 isinstance(view, Sequence) 

187 and len(view) == 2 

188 and all(isinstance(x, (int, float)) for x in view) 

189 ) 

190 

191 

192def check_views(views) -> list: 

193 """Check whether the views passed to in plot_img_on_surf are correct. 

194 

195 Parameters 

196 ---------- 

197 views : :obj:`list` 

198 Any combination of strings in {"anterior", "posterior", "medial", 

199 "lateral", "dorsal", "ventral"} and / or pair of floats (elev, azim). 

200 

201 Returns 

202 ------- 

203 views : :obj:`list` 

204 Views given as inputs. 

205 """ 

206 invalid_views = [not _check_view_is_valid(view) for view in views] 

207 

208 if any(invalid_views): 208 ↛ 209line 208 didn't jump to line 209 because the condition on line 208 was never true

209 raise ValueError( 

210 "Invalid view definition!\n" 

211 f"Got: {np.array(views)[invalid_views]!s}\n" 

212 f"Supported values are: {VALID_VIEWS!s}" 

213 " or a sequence of length 2" 

214 " setting the elevation and azimut of the camera." 

215 ) 

216 

217 return views 

218 

219 

220def check_surf_map(surf_map, n_vertices): 

221 """Help for plot_surf. 

222 

223 This function checks the dimensions of provided surf_map. 

224 """ 

225 surf_map_data = load_surf_data(surf_map) 

226 if surf_map_data.ndim != 1: 226 ↛ 227line 226 didn't jump to line 227 because the condition on line 226 was never true

227 raise ValueError( 

228 "'surf_map' can only have one dimension " 

229 f"but has '{surf_map_data.ndim}' dimensions" 

230 ) 

231 if surf_map_data.shape[0] != n_vertices: 231 ↛ 232line 231 didn't jump to line 232 because the condition on line 231 was never true

232 raise ValueError( 

233 "The surf_map does not have the same number " 

234 "of vertices as the mesh." 

235 ) 

236 return surf_map_data