Coverage for nilearn/plotting/surface/html_surface.py: 0%

99 statements  

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

1"""Handle plotting of surfaces for html rendering.""" 

2 

3import json 

4from warnings import warn 

5 

6import numpy as np 

7 

8from nilearn import DEFAULT_DIVERGING_CMAP 

9from nilearn._utils import check_niimg_3d, fill_doc 

10from nilearn._utils.html_document import HTMLDocument 

11from nilearn._utils.logger import find_stack_level 

12from nilearn._utils.param_validation import check_params 

13from nilearn.plotting import cm 

14from nilearn.plotting.js_plotting_utils import ( 

15 add_js_lib, 

16 colorscale, 

17 get_html_template, 

18 mesh_to_plotly, 

19) 

20from nilearn.plotting.surface._utils import ( 

21 DEFAULT_ENGINE, 

22 DEFAULT_HEMI, 

23 check_surface_plotting_inputs, 

24 get_surface_backend, 

25) 

26from nilearn.surface import ( 

27 PolyMesh, 

28 SurfaceImage, 

29 load_surf_data, 

30 load_surf_mesh, 

31) 

32from nilearn.surface.surface import ( 

33 check_mesh_and_data, 

34 check_mesh_is_fsaverage, 

35 combine_hemispheres_meshes, 

36 get_data, 

37) 

38 

39 

40class SurfaceView(HTMLDocument): # noqa: D101 

41 pass 

42 

43 

44def _one_mesh_info( 

45 surf_map, 

46 surf_mesh, 

47 threshold=None, 

48 cmap=cm.cold_hot, 

49 black_bg=False, 

50 bg_map=None, 

51 symmetric_cmap=True, 

52 bg_on_data=False, 

53 darkness=0.7, 

54 vmax=None, 

55 vmin=None, 

56): 

57 """Prepare info for plotting one surface map on a single mesh. 

58 

59 This computes the dictionary that gets inserted in the web page, 

60 which contains the encoded mesh, colors, min and max values, and 

61 background color. 

62 

63 """ 

64 colors = colorscale( 

65 cmap, 

66 surf_map, 

67 threshold, 

68 symmetric_cmap=symmetric_cmap, 

69 vmax=vmax, 

70 vmin=vmin, 

71 ) 

72 info = {"inflated_both": mesh_to_plotly(surf_mesh)} 

73 backend = get_surface_backend(DEFAULT_ENGINE) 

74 info["vertexcolor_both"] = backend._get_vertexcolor( 

75 surf_map, 

76 colors["cmap"], 

77 colors["norm"], 

78 absolute_threshold=colors["abs_threshold"], 

79 bg_map=bg_map, 

80 bg_on_data=bg_on_data, 

81 darkness=darkness, 

82 ) 

83 info["cmin"], info["cmax"] = float(colors["vmin"]), float(colors["vmax"]) 

84 info["black_bg"] = black_bg 

85 info["full_brain_mesh"] = False 

86 info["colorscale"] = colors["colors"] 

87 return info 

88 

89 

90def one_mesh_info( 

91 surf_map, 

92 surf_mesh, 

93 threshold=None, 

94 cmap=DEFAULT_DIVERGING_CMAP, 

95 black_bg=False, 

96 bg_map=None, 

97 symmetric_cmap=True, 

98 bg_on_data=False, 

99 darkness=0.7, 

100 vmax=None, 

101 vmin=None, 

102): 

103 """Deprecate public function. See _one_mesh_info.""" 

104 warn( 

105 category=DeprecationWarning, 

106 message="one_mesh_info is a private function and is renamed " 

107 "to _one_mesh_info. Using the deprecated name will " 

108 "raise an error in release 0.13", 

109 stacklevel=find_stack_level(), 

110 ) 

111 

112 return _one_mesh_info( 

113 surf_map, 

114 surf_mesh, 

115 threshold=threshold, 

116 cmap=cmap, 

117 black_bg=black_bg, 

118 bg_map=bg_map, 

119 symmetric_cmap=symmetric_cmap, 

120 bg_on_data=bg_on_data, 

121 darkness=darkness, 

122 vmax=vmax, 

123 vmin=vmin, 

124 ) 

125 

126 

127def _get_combined_curvature_map(mesh_left, mesh_right): 

128 """Get combined curvature map from left and right hemisphere maps. 

129 Only used in _full_brain_info. 

130 """ 

131 curv_left = load_surf_data(mesh_left) 

132 curv_right = load_surf_data(mesh_right) 

133 curv_left_sign = np.sign(curv_left) 

134 curv_right_sign = np.sign(curv_right) 

135 curv_left_sign[np.isnan(curv_left)] = 0 

136 curv_right_sign[np.isnan(curv_right)] = 0 

137 curv_combined = np.concatenate([curv_left_sign, curv_right_sign]) 

138 return curv_combined 

139 

140 

141def _full_brain_info( 

142 volume_img, 

143 mesh="fsaverage5", 

144 threshold=None, 

145 cmap=DEFAULT_DIVERGING_CMAP, 

146 black_bg=False, 

147 symmetric_cmap=True, 

148 bg_on_data=False, 

149 darkness=0.7, 

150 vmax=None, 

151 vmin=None, 

152 vol_to_surf_kwargs=None, 

153): 

154 """Project 3D map on cortex; prepare info to plot both hemispheres. 

155 

156 This computes the dictionary that gets inserted in the web page, 

157 which contains encoded meshes, colors, min and max values, and 

158 background color. 

159 

160 """ 

161 if vol_to_surf_kwargs is None: 

162 vol_to_surf_kwargs = {} 

163 info = {} 

164 mesh = check_mesh_is_fsaverage(mesh) 

165 surface_maps = SurfaceImage.from_volume( 

166 mesh=PolyMesh( 

167 left=mesh["pial_left"], 

168 right=mesh["pial_right"], 

169 ), 

170 volume_img=volume_img, 

171 inner_mesh=PolyMesh( 

172 left=mesh.get("white_left", None), 

173 right=mesh.get("white_right", None), 

174 ), 

175 **vol_to_surf_kwargs, 

176 ) 

177 colors = colorscale( 

178 cmap, 

179 get_data(surface_maps).ravel(), 

180 threshold, 

181 symmetric_cmap=symmetric_cmap, 

182 vmax=vmax, 

183 vmin=vmin, 

184 ) 

185 

186 for hemi, surf_map in surface_maps.data.parts.items(): 

187 curv_map = load_surf_data(mesh[f"curv_{hemi}"]) 

188 bg_map = np.sign(curv_map) 

189 

190 info[f"pial_{hemi}"] = mesh_to_plotly(mesh[f"pial_{hemi}"]) 

191 info[f"inflated_{hemi}"] = mesh_to_plotly(mesh[f"infl_{hemi}"]) 

192 

193 backend = get_surface_backend(DEFAULT_ENGINE) 

194 info[f"vertexcolor_{hemi}"] = backend._get_vertexcolor( 

195 surf_map, 

196 colors["cmap"], 

197 colors["norm"], 

198 absolute_threshold=colors["abs_threshold"], 

199 bg_map=bg_map, 

200 bg_on_data=bg_on_data, 

201 darkness=darkness, 

202 ) 

203 

204 # also add info for both hemispheres 

205 for mesh_type in ["infl", "pial"]: 

206 if mesh_type == "infl": 

207 info["inflated_both"] = mesh_to_plotly( 

208 combine_hemispheres_meshes( 

209 PolyMesh( 

210 left=mesh[f"{mesh_type}_left"], 

211 right=mesh[f"{mesh_type}_right"], 

212 ) 

213 ) 

214 ) 

215 else: 

216 info[f"{mesh_type}_both"] = mesh_to_plotly( 

217 combine_hemispheres_meshes( 

218 PolyMesh( 

219 left=mesh[f"{mesh_type}_left"], 

220 right=mesh[f"{mesh_type}_right"], 

221 ) 

222 ) 

223 ) 

224 backend = get_surface_backend(DEFAULT_ENGINE) 

225 info["vertexcolor_both"] = backend._get_vertexcolor( 

226 get_data(surface_maps), 

227 colors["cmap"], 

228 colors["norm"], 

229 absolute_threshold=colors["abs_threshold"], 

230 bg_map=_get_combined_curvature_map( 

231 mesh["curv_left"], mesh["curv_right"] 

232 ), 

233 bg_on_data=bg_on_data, 

234 darkness=darkness, 

235 ) 

236 info["cmin"], info["cmax"] = float(colors["vmin"]), float(colors["vmax"]) 

237 info["black_bg"] = black_bg 

238 info["full_brain_mesh"] = True 

239 info["colorscale"] = colors["colors"] 

240 return info 

241 

242 

243def full_brain_info( 

244 volume_img, 

245 mesh="fsaverage5", 

246 threshold=None, 

247 cmap=DEFAULT_DIVERGING_CMAP, 

248 black_bg=False, 

249 symmetric_cmap=True, 

250 bg_on_data=False, 

251 darkness=0.7, 

252 vmax=None, 

253 vmin=None, 

254 vol_to_surf_kwargs=None, 

255): 

256 """Deprecate public function. See _full_brain_info.""" 

257 warn( 

258 category=DeprecationWarning, 

259 message="full_brain_info is a private function and is renamed to " 

260 "_full_brain_info. Using the deprecated name will raise an error " 

261 "in release 0.13", 

262 stacklevel=find_stack_level(), 

263 ) 

264 

265 return _full_brain_info( 

266 volume_img, 

267 mesh=mesh, 

268 threshold=threshold, 

269 cmap=cmap, 

270 black_bg=black_bg, 

271 symmetric_cmap=symmetric_cmap, 

272 bg_on_data=bg_on_data, 

273 darkness=darkness, 

274 vmax=vmax, 

275 vmin=vmin, 

276 vol_to_surf_kwargs=vol_to_surf_kwargs, 

277 ) 

278 

279 

280def _fill_html_template(info, embed_js=True): 

281 as_json = json.dumps(info) 

282 as_html = get_html_template("surface_plot_template.html").safe_substitute( 

283 { 

284 "INSERT_STAT_MAP_JSON_HERE": as_json, 

285 "INSERT_PAGE_TITLE_HERE": info["title"] or "Surface plot", 

286 } 

287 ) 

288 as_html = add_js_lib(as_html, embed_js=embed_js) 

289 return SurfaceView(as_html) 

290 

291 

292@fill_doc 

293def view_img_on_surf( 

294 stat_map_img, 

295 surf_mesh="fsaverage5", 

296 threshold=None, 

297 cmap=DEFAULT_DIVERGING_CMAP, 

298 black_bg=False, 

299 vmax=None, 

300 vmin=None, 

301 symmetric_cmap=True, 

302 bg_on_data=False, 

303 darkness=0.7, 

304 colorbar=True, 

305 colorbar_height=0.5, 

306 colorbar_fontsize=25, 

307 title=None, 

308 title_fontsize=25, 

309 vol_to_surf_kwargs=None, 

310): 

311 """Insert a surface plot of a statistical map into an HTML page. 

312 

313 Parameters 

314 ---------- 

315 stat_map_img : Niimg-like object, 3D 

316 See :ref:`extracting_data`. 

317 

318 surf_mesh : :obj:`str` or :obj:`dict`, default='fsaverage5' 

319 If a string, it should be one of the following values: 

320 %(fsaverage_options)s 

321 If a dictionary, it should have the same structure as those returned by 

322 nilearn.datasets.fetch_surf_fsaverage, i.e. keys should be 'infl_left', 

323 'pial_left', 'sulc_left', 'infl_right', 'pial_right', and 'sulc_right', 

324 containing inflated and pial meshes, and sulcal depth values for left 

325 and right hemispheres. 

326 

327 threshold : :obj:`str`, number or None, default=None 

328 If None, no thresholding. 

329 If it is a number only values of amplitude greater 

330 than threshold will be shown. 

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

332 e.g. "25.3%%", and only values of amplitude above the 

333 given percentile will be shown. 

334 

335 %(cmap)s 

336 default="RdBu_r" 

337 

338 black_bg : :obj:`bool`, default=False 

339 If True, image is plotted on a black background. Otherwise on a 

340 white background. 

341 

342 %(bg_on_data)s 

343 

344 %(darkness)s 

345 Default=1. 

346 

347 vmax : :obj:`float` or None, default=None 

348 upper bound for the colorbar. if None, use the absolute max of the 

349 brain map. 

350 

351 vmin : :obj:`float` or None, default=None 

352 min value for mapping colors. 

353 If `symmetric_cmap` is `True`, `vmin` is always equal to `-vmax` and 

354 cannot be chosen. 

355 If `symmetric_cmap` is `False`, `vmin` is equal to the min of the 

356 image, or 0 when a threshold is used. 

357 

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

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

360 You can set it to False if you are plotting only positive values. 

361 

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

363 Add a colorbar or not. 

364 

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

366 Height of the colorbar, relative to the figure height 

367 

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

369 Fontsize of the colorbar tick labels. 

370 

371 title : :obj:`str`, default=None 

372 Title for the plot. 

373 

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

375 Fontsize of the title. 

376 

377 vol_to_surf_kwargs : :obj:`dict`, default=None 

378 Dictionary of keyword arguments that are passed on to 

379 :func:`nilearn.surface.vol_to_surf` when extracting a surface from 

380 the input image. See the function documentation for details.This 

381 parameter is especially useful when plotting an atlas. See 

382 https://nilearn.github.io/stable/auto_examples/01_plotting/plot_3d_map_to_surface_projection.html 

383 Will default to ``{}`` if ``None`` is passed. 

384 

385 Returns 

386 ------- 

387 SurfaceView : plot of the stat map. 

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

389 Jupyter notebook. Useful methods are : 

390 

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

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

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

394 

395 See Also 

396 -------- 

397 nilearn.plotting.view_surf: plot from a surface map on a cortical mesh. 

398 

399 """ 

400 if vol_to_surf_kwargs is None: 

401 vol_to_surf_kwargs = {} 

402 stat_map_img = check_niimg_3d(stat_map_img) 

403 info = _full_brain_info( 

404 volume_img=stat_map_img, 

405 mesh=surf_mesh, 

406 threshold=threshold, 

407 cmap=cmap, 

408 black_bg=black_bg, 

409 vmax=vmax, 

410 vmin=vmin, 

411 bg_on_data=bg_on_data, 

412 darkness=darkness, 

413 symmetric_cmap=symmetric_cmap, 

414 vol_to_surf_kwargs=vol_to_surf_kwargs, 

415 ) 

416 info["colorbar"] = colorbar 

417 info["cbar_height"] = colorbar_height 

418 info["cbar_fontsize"] = colorbar_fontsize 

419 info["title"] = title 

420 info["title_fontsize"] = title_fontsize 

421 return _fill_html_template(info, embed_js=True) 

422 

423 

424@fill_doc 

425def view_surf( 

426 surf_mesh=None, 

427 surf_map=None, 

428 bg_map=None, 

429 hemi=DEFAULT_HEMI, 

430 threshold=None, 

431 cmap=DEFAULT_DIVERGING_CMAP, 

432 black_bg=False, 

433 vmax=None, 

434 vmin=None, 

435 bg_on_data=False, 

436 darkness=0.7, 

437 symmetric_cmap=True, 

438 colorbar=True, 

439 colorbar_height=0.5, 

440 colorbar_fontsize=25, 

441 title=None, 

442 title_fontsize=25, 

443): 

444 """Insert a surface plot of a surface map into an HTML page. 

445 

446 Parameters 

447 ---------- 

448 %(surf_mesh)s 

449 If None is passed, then ``surf_map`` must be a 

450 :obj:`~nilearn.surface.SurfaceImage` instance and the mesh from that 

451 :obj:`~nilearn.surface.SurfaceImage` instance will be used. 

452 

453 surf_map : :obj:`str` or :class:`numpy.ndarray`, \ 

454 or :obj:`~nilearn.surface.SurfaceImage` or None, \ 

455 default=None 

456 Data to be displayed on the surface :term:`mesh`. 

457 Can be a file (valid formats are .gii, .mgz, .nii, .nii.gz, 

458 or Freesurfer specific files such as 

459 .thickness, .area, .curv, .sulc, .annot, .label) or 

460 a Numpy array. 

461 If None is passed for ``surf_mesh`` 

462 then ``surf_map`` 

463 must be a :obj:`~nilearn.surface.SurfaceImage` instance 

464 and its the mesh will be used for plotting. 

465 

466 %(bg_map)s 

467 

468 %(hemi)s 

469 It is only used if ``surf_map`` is :obj:`~nilearn.surface.SurfaceImage` 

470 and / or ``surf_mesh`` is :obj:`~nilearn.surface.PolyMesh`. 

471 Otherwise a warning will be displayed. 

472 

473 .. versionadded:: 0.11.0 

474 

475 %(bg_on_data)s 

476 

477 %(darkness)s 

478 Default=1. 

479 

480 threshold : :obj:`str`, number or None, default=None 

481 If None, no thresholding. 

482 If it is a number only values of amplitude greater 

483 than threshold will be shown. 

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

485 e.g. "25.3%%", and only values of amplitude above the 

486 given percentile will be shown. 

487 

488 %(cmap)s 

489 default="RdBu_r" 

490 

491 black_bg : :obj:`bool`, default=False 

492 If True, image is plotted on a black background. Otherwise on a 

493 white background. 

494 

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

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

497 Set it to False if you are plotting a surface atlas. 

498 

499 vmax : :obj:`float` or None, default=None 

500 upper bound for the colorbar. if None, use the absolute max of the 

501 brain map. 

502 

503 vmin : :obj:`float` or None, default=None 

504 min value for mapping colors. 

505 If `symmetric_cmap` is `True`, `vmin` is always equal to `-vmax` and 

506 cannot be chosen. 

507 If `symmetric_cmap` is `False`, `vmin` defaults to the min of the 

508 image, or 0 when a threshold is used. 

509 

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

511 Add a colorbar or not. 

512 

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

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

515 

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

517 Fontsize of the colorbar tick labels. 

518 

519 title : :obj:`str`, default=None 

520 Title for the plot. 

521 

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

523 Fontsize of the title. 

524 

525 Returns 

526 ------- 

527 SurfaceView : plot of the stat map. 

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

529 Jupyter notebook. Useful methods are : 

530 

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

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

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

534 

535 See Also 

536 -------- 

537 nilearn.plotting.view_img_on_surf: Surface plot from a 3D statistical map. 

538 """ 

539 check_params(locals()) 

540 surf_map, surf_mesh, bg_map = check_surface_plotting_inputs( 

541 surf_map, surf_mesh, hemi, bg_map, map_var_name="surf_map" 

542 ) 

543 

544 surf_mesh = load_surf_mesh(surf_mesh) 

545 if surf_map is None: 

546 surf_map = np.ones(len(surf_mesh[0])) 

547 else: 

548 surf_mesh, surf_map = check_mesh_and_data(surf_mesh, surf_map) 

549 if bg_map is not None: 

550 _, bg_map = check_mesh_and_data(surf_mesh, bg_map) 

551 info = _one_mesh_info( 

552 surf_map=surf_map, 

553 surf_mesh=surf_mesh, 

554 threshold=threshold, 

555 cmap=cmap, 

556 black_bg=black_bg, 

557 bg_map=bg_map, 

558 bg_on_data=bg_on_data, 

559 darkness=darkness, 

560 symmetric_cmap=symmetric_cmap, 

561 vmax=vmax, 

562 vmin=vmin, 

563 ) 

564 info["colorbar"] = colorbar 

565 info["cbar_height"] = colorbar_height 

566 info["cbar_fontsize"] = colorbar_fontsize 

567 info["title"] = title 

568 info["title_fontsize"] = title_fontsize 

569 return _fill_html_template(info, embed_js=True)