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
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-16 12:32 +0200
1"""Handle plotting of connectomes in html."""
3import json
5import numpy as np
6from matplotlib import cm as mpl_cm
7from scipy import sparse
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)
22class ConnectomeView(HTMLDocument): # noqa: D101
23 pass
26def _encode_coordinates(coords, prefix):
27 """Transform a 2D-array of 3D data (x, y, z) into a dict of base64 values.
29 Parameters
30 ----------
31 coords : :class:`numpy.ndarray` of shape=(n_nodes, 3)
32 The coordinates of the nodes in MNI space.
34 prefix : :obj:`str`
35 Prefix for the key value in the returned dict.
36 Schema is {prefix}{x|y|z}
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
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.
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.
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.
65 given edges = [eab, ecd, eef] and nodes = [(a, b), (c, d), (e, f)], this
66 function returns:
68 path_edges: eab eab 0 ecd ecd 0 eef eef 0
69 path_nodes: a b 0 c d 0 e f 0
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
85def _prepare_colors_for_markers(marker_color, number_of_nodes):
86 """Generate "color" and "colorscale" attributes \
87 based on `marker_color` mode.
89 Parameters
90 ----------
91 marker_color : color or sequence of colors, default='auto'
92 Color(s) of the nodes.
94 number_of_nodes : :obj:`int`
95 Number of nodes in the view
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
109 return to_color_strings(colors)
112def _prepare_lines_metadata(
113 adjacency_matrix, coords, threshold, cmap, symmetric_cmap
114):
115 """Generate metadata related to lines for _connectome_view plot.
117 Parameters
118 ----------
119 adjacency_matrix : :class:`np.ndarray`, shape=(n_nodes, n_nodes)
120 The weights of the edges.
122 coords : :class:`np.ndarray`, shape=(n_nodes, 3)
123 The coordinates of the nodes in MNI space.
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.
133 %(cmap)s
135 symmetric_cmap : :obj:`bool`, default=True
136 Make colormap symmetric (ranging from -vmax to vmax).
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 )
167 line_coords = coords[path_nodes]
169 lines_metadata = {
170 **lines_metadata,
171 **_encode_coordinates(line_coords, prefix="_con_"),
172 }
174 return lines_metadata
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}
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 )
191 return markers_metadata
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 )
211 markers_metadata = _prepare_markers_metadata(
212 coords,
213 marker_size,
214 marker_color,
215 marker_only=False,
216 )
218 return {
219 **lines_metadata,
220 **markers_metadata,
221 }
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)
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.
261 Parameters
262 ----------
263 adjacency_matrix : :class:`numpy.ndarray` of shape=(n_nodes, n_nodes)
264 The weights of the edges.
266 node_coords : :class:`numpy.ndarray` of shape=(n_nodes, 3)
267 The coordinates of the nodes in :term:`MNI` space.
269 node_color : color or sequence of colors, default='auto'
270 Color(s) of the nodes.
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.
280 edge_cmap : :obj:`str` or matplotlib colormap, default="RdBu_r"
281 Colormap to use.
283 symmetric_cmap : :obj:`bool`, default=True
284 Make colormap symmetric (ranging from -vmax to vmax).
286 linewidth : :obj:`float`, default=6.0
287 Width of the lines that show connections.
289 node_size : :obj:`float`, default=3.0
290 Size of the markers showing the seeds in pixels.
292 colorbar : :obj:`bool`, default=True
293 Add a colorbar.
295 colorbar_height : :obj:`float`, default=0.5
296 Height of the colorbar, relative to the figure height.
298 colorbar_fontsize : :obj:`int`, default=25
299 Fontsize of the colorbar tick labels.
301 title : :obj:`str` or None, default=None
302 Title for the plot.
304 title_fontsize : :obj:`int`, default=25
305 Fontsize of the title.
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 :
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.
317 See Also
318 --------
319 nilearn.plotting.plot_connectome:
320 projected views of a connectome in a glass brain.
322 nilearn.plotting.view_markers:
323 interactive plot of colored markers
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.
329 """
330 node_coords = np.asarray(node_coords)
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)
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.
360 Parameters
361 ----------
362 marker_coords : :class:`numpy.ndarray` of shape=(n_nodes, 3)
363 The coordinates of the nodes in :term:`MNI` space.
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>`)
371 marker_size : :obj:`float` or array-like, default=5.0
372 Size of the markers showing the seeds in pixels.
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
378 title : :obj:`str` or None, default=None
379 Title for the plot.
381 title_fontsize : :obj:`int`, default=25
382 Fontsize of the title.
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 :
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.
394 See Also
395 --------
396 nilearn.plotting.plot_connectome:
397 projected views of a connectome in a glass brain.
399 nilearn.plotting.view_connectome:
400 interactive plot of a connectome.
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.
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)