Coverage for nilearn/plotting/html_connectome.py: 0%

88 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-16 12:32 +0200

1"""Handle plotting of connectomes in html.""" 

2 

3import json 

4 

5import numpy as np 

6from matplotlib import cm as mpl_cm 

7from scipy import sparse 

8 

9from nilearn import DEFAULT_DIVERGING_CMAP 

10from nilearn._utils.html_document import HTMLDocument 

11from nilearn.datasets import fetch_surf_fsaverage 

12from nilearn.plotting.js_plotting_utils import ( 

13 add_js_lib, 

14 colorscale, 

15 encode, 

16 get_html_template, 

17 mesh_to_plotly, 

18 to_color_strings, 

19) 

20 

21 

22class ConnectomeView(HTMLDocument): # noqa: D101 

23 pass 

24 

25 

26def _encode_coordinates(coords, prefix): 

27 """Transform a 2D-array of 3D data (x, y, z) into a dict of base64 values. 

28 

29 Parameters 

30 ---------- 

31 coords : :class:`numpy.ndarray` of shape=(n_nodes, 3) 

32 The coordinates of the nodes in MNI space. 

33 

34 prefix : :obj:`str` 

35 Prefix for the key value in the returned dict. 

36 Schema is {prefix}{x|y|z} 

37 

38 Returns 

39 ------- 

40 coordinates : :obj:`dict` 

41 Dictionary containing base64 values for each axis 

42 """ 

43 coords = np.asarray(coords, dtype="<f4") 

44 marker_x, marker_y, marker_z = coords.T 

45 coordinates = { 

46 f"{prefix}{cname}": encode(np.asarray(coord, dtype="<f4")) 

47 for coord, cname in [(marker_x, "x"), (marker_y, "y"), (marker_z, "z")] 

48 } 

49 return coordinates 

50 

51 

52def _prepare_line(edges, nodes): 

53 """Prepare a plotly scatter3d line plot \ 

54 so that a set of disconnected edges \ 

55 can be drawn as a single line. 

56 

57 `edges` are values associated with each edge (that get mapped to colors 

58 through a colorscale). `nodes` are pairs of (source, target) node indices 

59 for each edge. 

60 

61 the color of a line segment in plotly is a mixture of the colors associated 

62 with the points it connects. Moreover, segments that begin or end at a 

63 point whose value is `null` are not drawn. 

64 

65 given edges = [eab, ecd, eef] and nodes = [(a, b), (c, d), (e, f)], this 

66 function returns: 

67 

68 path_edges: eab eab 0 ecd ecd 0 eef eef 0 

69 path_nodes: a b 0 c d 0 e f 0 

70 

71 moreover the javascript code replaces every third element (the '0' in the 

72 lists above) with `null`, so only the a-b, c-d, and e-f segments will get 

73 plotted, and their colors are correct because both their start and end 

74 points are associated with the same value. 

75 """ 

76 path_edges = np.zeros(len(edges) * 3, dtype=int) 

77 path_edges[::3] = edges 

78 path_edges[1::3] = edges 

79 path_nodes = np.zeros(len(nodes) * 3, dtype=int) 

80 path_nodes[::3] = nodes[:, 0] 

81 path_nodes[1::3] = nodes[:, 1] 

82 return path_edges, path_nodes 

83 

84 

85def _prepare_colors_for_markers(marker_color, number_of_nodes): 

86 """Generate "color" and "colorscale" attributes \ 

87 based on `marker_color` mode. 

88 

89 Parameters 

90 ---------- 

91 marker_color : color or sequence of colors, default='auto' 

92 Color(s) of the nodes. 

93 

94 number_of_nodes : :obj:`int` 

95 Number of nodes in the view 

96 

97 Returns 

98 ------- 

99 markers_colors : :obj:`list` 

100 List of `number_of_nodes` colors as hexadecimal values 

101 """ 

102 if isinstance(marker_color, str) and marker_color == "auto": 

103 colors = mpl_cm.viridis(np.linspace(0, 1, number_of_nodes)) 

104 elif isinstance(marker_color, str): 

105 colors = [marker_color] * number_of_nodes 

106 else: 

107 colors = marker_color 

108 

109 return to_color_strings(colors) 

110 

111 

112def _prepare_lines_metadata( 

113 adjacency_matrix, coords, threshold, cmap, symmetric_cmap 

114): 

115 """Generate metadata related to lines for _connectome_view plot. 

116 

117 Parameters 

118 ---------- 

119 adjacency_matrix : :class:`np.ndarray`, shape=(n_nodes, n_nodes) 

120 The weights of the edges. 

121 

122 coords : :class:`np.ndarray`, shape=(n_nodes, 3) 

123 The coordinates of the nodes in MNI space. 

124 

125 threshold : :obj:`str`, number or None, optional 

126 If None, no thresholding. 

127 If it is a number only connections of amplitude greater 

128 than threshold will be shown. 

129 If it is a string it must finish with a percent sign, 

130 e.g. "25.3%", and only connections of amplitude above the 

131 given percentile will be shown. 

132 

133 %(cmap)s 

134 

135 symmetric_cmap : :obj:`bool`, default=True 

136 Make colormap symmetric (ranging from -vmax to vmax). 

137 

138 Returns 

139 ------- 

140 coordinates : :obj:`dict` 

141 Dictionary containing base64 values for each axis 

142 """ 

143 adjacency_matrix = np.nan_to_num(adjacency_matrix, copy=True) 

144 colors = colorscale( 

145 cmap, 

146 adjacency_matrix.ravel(), 

147 threshold=threshold, 

148 symmetric_cmap=symmetric_cmap, 

149 ) 

150 lines_metadata = { 

151 "line_colorscale": colors["colors"], 

152 "line_cmin": float(colors["vmin"]), 

153 "line_cmax": float(colors["vmax"]), 

154 } 

155 if threshold is not None: 

156 adjacency_matrix[ 

157 np.abs(adjacency_matrix) <= colors["abs_threshold"] 

158 ] = 0 

159 s = sparse.coo_matrix(adjacency_matrix) 

160 nodes = np.asarray([s.row, s.col], dtype=int).T 

161 edges = np.arange(len(nodes)) 

162 path_edges, path_nodes = _prepare_line(edges, nodes) 

163 lines_metadata["_con_w"] = encode( 

164 np.asarray(s.data, dtype="<f4")[path_edges] 

165 ) 

166 

167 line_coords = coords[path_nodes] 

168 

169 lines_metadata = { 

170 **lines_metadata, 

171 **_encode_coordinates(line_coords, prefix="_con_"), 

172 } 

173 

174 return lines_metadata 

175 

176 

177def _prepare_markers_metadata(coords, marker_size, marker_color, marker_only): 

178 markers_coordinates = _encode_coordinates(coords, prefix="_marker_") 

179 markers_metadata = {"markers_only": marker_only, **markers_coordinates} 

180 

181 if np.ndim(marker_size) > 0: 

182 marker_size = np.asarray(marker_size) 

183 if hasattr(marker_size, "tolist"): 

184 marker_size = marker_size.tolist() 

185 markers_metadata["marker_size"] = marker_size 

186 markers_metadata["marker_color"] = _prepare_colors_for_markers( 

187 marker_color, 

188 len(coords), 

189 ) 

190 

191 return markers_metadata 

192 

193 

194def _get_connectome( 

195 adjacency_matrix, 

196 coords, 

197 threshold=None, 

198 marker_size=None, 

199 marker_color="auto", 

200 cmap=DEFAULT_DIVERGING_CMAP, 

201 symmetric_cmap=True, 

202): 

203 lines_metadata = _prepare_lines_metadata( 

204 adjacency_matrix, 

205 coords, 

206 threshold, 

207 cmap, 

208 symmetric_cmap, 

209 ) 

210 

211 markers_metadata = _prepare_markers_metadata( 

212 coords, 

213 marker_size, 

214 marker_color, 

215 marker_only=False, 

216 ) 

217 

218 return { 

219 **lines_metadata, 

220 **markers_metadata, 

221 } 

222 

223 

224def _make_connectome_html(connectome_info, embed_js=True): 

225 plot_info = {"connectome": connectome_info} 

226 mesh = fetch_surf_fsaverage() 

227 for hemi in ["pial_left", "pial_right"]: 

228 plot_info[hemi] = mesh_to_plotly(mesh[hemi]) 

229 as_json = json.dumps(plot_info) 

230 as_html = get_html_template( 

231 "connectome_plot_template.html" 

232 ).safe_substitute( 

233 { 

234 "INSERT_CONNECTOME_JSON_HERE": as_json, 

235 "INSERT_PAGE_TITLE_HERE": ( 

236 connectome_info["title"] or "Connectome plot" 

237 ), 

238 } 

239 ) 

240 as_html = add_js_lib(as_html, embed_js=embed_js) 

241 return ConnectomeView(as_html) 

242 

243 

244def view_connectome( 

245 adjacency_matrix, 

246 node_coords, 

247 edge_threshold=None, 

248 edge_cmap=DEFAULT_DIVERGING_CMAP, 

249 symmetric_cmap=True, 

250 linewidth=6.0, 

251 node_color="auto", 

252 node_size=3.0, 

253 colorbar=True, 

254 colorbar_height=0.5, 

255 colorbar_fontsize=25, 

256 title=None, 

257 title_fontsize=25, 

258): 

259 """Insert a 3d plot of a connectome into an HTML page. 

260 

261 Parameters 

262 ---------- 

263 adjacency_matrix : :class:`numpy.ndarray` of shape=(n_nodes, n_nodes) 

264 The weights of the edges. 

265 

266 node_coords : :class:`numpy.ndarray` of shape=(n_nodes, 3) 

267 The coordinates of the nodes in :term:`MNI` space. 

268 

269 node_color : color or sequence of colors, default='auto' 

270 Color(s) of the nodes. 

271 

272 edge_threshold : :obj:`str`, number or None, default=None 

273 If None, no thresholding. 

274 If it is a number only connections of amplitude greater 

275 than threshold will be shown. 

276 If it is a string it must finish with a percent sign, 

277 e.g. "25.3%", and only connections of amplitude above the 

278 given percentile will be shown. 

279 

280 edge_cmap : :obj:`str` or matplotlib colormap, default="RdBu_r" 

281 Colormap to use. 

282 

283 symmetric_cmap : :obj:`bool`, default=True 

284 Make colormap symmetric (ranging from -vmax to vmax). 

285 

286 linewidth : :obj:`float`, default=6.0 

287 Width of the lines that show connections. 

288 

289 node_size : :obj:`float`, default=3.0 

290 Size of the markers showing the seeds in pixels. 

291 

292 colorbar : :obj:`bool`, default=True 

293 Add a colorbar. 

294 

295 colorbar_height : :obj:`float`, default=0.5 

296 Height of the colorbar, relative to the figure height. 

297 

298 colorbar_fontsize : :obj:`int`, default=25 

299 Fontsize of the colorbar tick labels. 

300 

301 title : :obj:`str` or None, default=None 

302 Title for the plot. 

303 

304 title_fontsize : :obj:`int`, default=25 

305 Fontsize of the title. 

306 

307 Returns 

308 ------- 

309 ConnectomeView : plot of the connectome. 

310 It can be saved as an html page or rendered (transparently) by the 

311 Jupyter notebook. Useful methods are : 

312 

313 - 'resize' to resize the plot displayed in a Jupyter notebook 

314 - 'save_as_html' to save the plot to a file 

315 - 'open_in_browser' to save the plot and open it in a web browser. 

316 

317 See Also 

318 -------- 

319 nilearn.plotting.plot_connectome: 

320 projected views of a connectome in a glass brain. 

321 

322 nilearn.plotting.view_markers: 

323 interactive plot of colored markers 

324 

325 nilearn.plotting.view_surf, nilearn.plotting.view_img_on_surf: 

326 interactive view of statistical maps or surface atlases on the cortical 

327 surface. 

328 

329 """ 

330 node_coords = np.asarray(node_coords) 

331 

332 connectome_info = _get_connectome( 

333 adjacency_matrix, 

334 node_coords, 

335 threshold=edge_threshold, 

336 cmap=edge_cmap, 

337 symmetric_cmap=symmetric_cmap, 

338 marker_size=node_size, 

339 marker_color=node_color, 

340 ) 

341 connectome_info["line_width"] = linewidth 

342 connectome_info["colorbar"] = colorbar 

343 connectome_info["cbar_height"] = colorbar_height 

344 connectome_info["cbar_fontsize"] = colorbar_fontsize 

345 connectome_info["title"] = title 

346 connectome_info["title_fontsize"] = title_fontsize 

347 return _make_connectome_html(connectome_info) 

348 

349 

350def view_markers( 

351 marker_coords, 

352 marker_color="auto", 

353 marker_size=5.0, 

354 marker_labels=None, 

355 title=None, 

356 title_fontsize=25, 

357): 

358 """Insert a 3d plot of markers in a brain into an HTML page. 

359 

360 Parameters 

361 ---------- 

362 marker_coords : :class:`numpy.ndarray` of shape=(n_nodes, 3) 

363 The coordinates of the nodes in :term:`MNI` space. 

364 

365 marker_color : :class:`numpy.ndarray` of shape=(n_nodes,) or \ 

366 'auto', default='auto' 

367 colors of the markers: list of strings, hex rgb or rgba strings, rgb 

368 triplets, or rgba triplets (see `formats accepted by matplotlib \ 

369 <https://matplotlib.org/stable/users/explain/colors/colors.html>`) 

370 

371 marker_size : :obj:`float` or array-like, default=5.0 

372 Size of the markers showing the seeds in pixels. 

373 

374 marker_labels : :obj:`list` of :obj:`str` of shape=(n_nodes)\ 

375 or None, default=None 

376 Labels for the markers: list of strings 

377 

378 title : :obj:`str` or None, default=None 

379 Title for the plot. 

380 

381 title_fontsize : :obj:`int`, default=25 

382 Fontsize of the title. 

383 

384 Returns 

385 ------- 

386 ConnectomeView : plot of the markers. 

387 It can be saved as an html page or rendered (transparently) by the 

388 Jupyter notebook. Useful methods are : 

389 

390 - 'resize' to resize the plot displayed in a Jupyter notebook 

391 - 'save_as_html' to save the plot to a file 

392 - 'open_in_browser' to save the plot and open it in a web browser. 

393 

394 See Also 

395 -------- 

396 nilearn.plotting.plot_connectome: 

397 projected views of a connectome in a glass brain. 

398 

399 nilearn.plotting.view_connectome: 

400 interactive plot of a connectome. 

401 

402 nilearn.plotting.view_surf, nilearn.plotting.view_img_on_surf: 

403 interactive view of statistical maps or surface atlases on the cortical 

404 surface. 

405 

406 """ 

407 marker_coords = np.asarray(marker_coords) 

408 if marker_color is None: 

409 marker_color = ["red" for _ in range(len(marker_coords))] 

410 connectome_info = _prepare_markers_metadata( 

411 marker_coords, 

412 marker_size, 

413 marker_color, 

414 marker_only=True, 

415 ) 

416 if marker_labels is None: 

417 marker_labels = ["" for _ in range(marker_coords.shape[0])] 

418 connectome_info["marker_labels"] = marker_labels 

419 connectome_info["title"] = title 

420 connectome_info["title_fontsize"] = title_fontsize 

421 return _make_connectome_html(connectome_info)