Coverage for nilearn/reporting/glm_reporter.py: 0%

215 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-20 10:58 +0200

1""" 

2Functionality to create an HTML report using a fitted GLM & contrasts. 

3 

4Functions 

5--------- 

6 

7make_glm_report(model, contrasts): 

8 Creates an HTMLReport Object which can be viewed or saved as a report. 

9 

10""" 

11 

12import datetime 

13import uuid 

14import warnings 

15from html import escape 

16from string import Template 

17 

18import numpy as np 

19import pandas as pd 

20 

21from nilearn import DEFAULT_DIVERGING_CMAP 

22from nilearn._utils import check_niimg, fill_doc, logger 

23from nilearn._utils.glm import coerce_to_dict, make_stat_maps 

24from nilearn._utils.helpers import is_matplotlib_installed 

25from nilearn._utils.html_document import HEIGHT_DEFAULT, WIDTH_DEFAULT 

26from nilearn._utils.logger import find_stack_level 

27from nilearn._utils.niimg import safe_get_data 

28from nilearn._version import __version__ 

29from nilearn.externals import tempita 

30from nilearn.glm import threshold_stats_img 

31from nilearn.maskers import NiftiMasker 

32from nilearn.reporting._utils import ( 

33 dataframe_to_html, 

34) 

35from nilearn.reporting.get_clusters_table import ( 

36 clustering_params_to_dataframe, 

37 get_clusters_table, 

38) 

39from nilearn.reporting.html_report import ( 

40 HTMLReport, 

41 _render_warnings_partial, 

42 is_notebook, 

43) 

44from nilearn.reporting.utils import ( 

45 CSS_PATH, 

46 HTML_TEMPLATE_PATH, 

47 JS_PATH, 

48 TEMPLATE_ROOT_PATH, 

49 figure_to_png_base64, 

50) 

51from nilearn.surface.surface import SurfaceImage 

52from nilearn.surface.surface import get_data as get_surface_data 

53 

54MNI152TEMPLATE = None 

55if is_matplotlib_installed(): 

56 from matplotlib import pyplot as plt 

57 

58 from nilearn._utils.plotting import ( 

59 generate_contrast_matrices_figures, 

60 generate_design_matrices_figures, 

61 resize_plot_inches, 

62 ) 

63 from nilearn.plotting import ( 

64 plot_glass_brain, 

65 plot_roi, 

66 plot_stat_map, 

67 plot_surf_stat_map, 

68 ) 

69 from nilearn.plotting.cm import _cmap_d as nilearn_cmaps 

70 from nilearn.plotting.img_plotting import ( # type: ignore[assignment] 

71 MNI152TEMPLATE, 

72 ) 

73 

74 

75@fill_doc 

76def make_glm_report( 

77 model, 

78 contrasts=None, 

79 first_level_contrast=None, 

80 title=None, 

81 bg_img="MNI152TEMPLATE", 

82 threshold=3.09, 

83 alpha=0.001, 

84 cluster_threshold=0, 

85 height_control="fpr", 

86 two_sided=False, 

87 min_distance=8.0, 

88 plot_type="slice", 

89 cut_coords=None, 

90 display_mode=None, 

91 report_dims=(WIDTH_DEFAULT, HEIGHT_DEFAULT), 

92): 

93 """Return HTMLReport object \ 

94 for a report which shows all important aspects of a fitted GLM. 

95 

96 The object can be opened in a browser, displayed in a notebook, 

97 or saved to disk as a standalone HTML file. 

98 

99 Examples 

100 -------- 

101 report = make_glm_report(model, contrasts) 

102 report.open_in_browser() 

103 report.save_as_html(destination_path) 

104 

105 Parameters 

106 ---------- 

107 model : FirstLevelModel or SecondLevelModel object 

108 A fitted first or second level model object. 

109 Must have the computed design matrix(ces). 

110 

111 contrasts : :obj:`dict` with :obj:`str` - ndarray key-value pairs \ 

112 or :obj:`str` \ 

113 or :obj:`list` of :obj:`str` \ 

114 or ndarray or \ 

115 :obj:`list` of ndarray, Default=None 

116 

117 Contrasts information for a first or second level model. 

118 

119 Example: 

120 

121 Dict of :term:`contrast` names and coefficients, 

122 or list of :term:`contrast` names 

123 or list of :term:`contrast` coefficients 

124 or :term:`contrast` name 

125 or :term:`contrast` coefficient 

126 

127 Each :term:`contrast` name must be a string. 

128 Each :term:`contrast` coefficient must be a list 

129 or numpy array of ints. 

130 

131 Contrasts are passed to ``contrast_def`` for FirstLevelModel 

132 (:func:`nilearn.glm.first_level.FirstLevelModel.compute_contrast`) 

133 & second_level_contrast for SecondLevelModel 

134 (:func:`nilearn.glm.second_level.SecondLevelModel.compute_contrast`) 

135 

136 %(first_level_contrast)s 

137 

138 .. versionadded:: 0.11.2dev 

139 

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

141 If string, represents the web page's title and primary heading, 

142 model type is sub-heading. 

143 If None, page titles and headings are autogenerated 

144 using :term:`contrast` names. 

145 

146 bg_img : Niimg-like object, default='MNI152TEMPLATE' 

147 See :ref:`extracting_data`. 

148 The background image for mask and stat maps to be plotted on upon. 

149 To turn off background image, just pass "bg_img=None". 

150 

151 threshold : :obj:`float`, default=3.09 

152 Cluster forming threshold in same scale as `stat_img` (either a 

153 t-scale or z-scale value). Used only if height_control is None. 

154 

155 alpha : :obj:`float`, default=0.001 

156 Number controlling the thresholding (either a p-value or q-value). 

157 Its actual meaning depends on the height_control parameter. 

158 This function translates alpha to a z-scale threshold. 

159 

160 cluster_threshold : :obj:`int`, default=0 

161 Cluster size threshold, in voxels. 

162 

163 height_control : :obj:`str`, default='fpr' 

164 false positive control meaning of cluster forming 

165 threshold: 'fpr' or 'fdr' or 'bonferroni' or None. 

166 

167 two_sided : :obj:`bool`, default=False 

168 Whether to employ two-sided thresholding or to evaluate positive values 

169 only. 

170 

171 min_distance : :obj:`float`, default=8.0 

172 For display purposes only. 

173 Minimum distance between subpeaks in mm. 

174 

175 plot_type : :obj:`str`, {'slice', 'glass'}, default='slice' 

176 Specifies the type of plot to be drawn for the statistical maps. 

177 

178 %(cut_coords)s 

179 

180 display_mode : :obj:`str`, default=None 

181 Default is 'z' if plot_type is 'slice'; ' 

182 ortho' if plot_type is 'glass'. 

183 

184 Choose the direction of the cuts: 

185 'x' - sagittal, 'y' - coronal, 'z' - axial, 

186 'l' - sagittal left hemisphere only, 

187 'r' - sagittal right hemisphere only, 

188 'ortho' - three cuts are performed in orthogonal directions. 

189 

190 Possible values are: 

191 'ortho', 'x', 'y', 'z', 'xz', 'yx', 'yz', 

192 'l', 'r', 'lr', 'lzr', 'lyr', 'lzry', 'lyrz'. 

193 

194 report_dims : Sequence[:obj:`int`, :obj:`int`], default=(1600, 800) 

195 Specifies width, height (in pixels) of report window within a notebook. 

196 Only applicable when inserting the report into a Jupyter notebook. 

197 Can be set after report creation using report.width, report.height. 

198 

199 Returns 

200 ------- 

201 report_text : HTMLReport Object 

202 Contains the HTML code for the :term:`GLM` Report. 

203 

204 """ 

205 if not is_matplotlib_installed(): 

206 warnings.warn( 

207 ("No plotting back-end detected. Output will be missing figures."), 

208 UserWarning, 

209 stacklevel=find_stack_level(), 

210 ) 

211 

212 unique_id = str(uuid.uuid4()).replace("-", "") 

213 

214 title = f"<br>{title}" if title else "" 

215 title = f"Statistical Report - {model.__str__()}{title}" 

216 

217 docstring = model.__doc__ 

218 snippet = docstring.partition("Parameters\n ----------\n")[0] 

219 

220 date = datetime.datetime.now().replace(microsecond=0).isoformat() 

221 

222 smoothing_fwhm = getattr(model, "smoothing_fwhm", 0) 

223 if smoothing_fwhm == 0: 

224 smoothing_fwhm = None 

225 

226 model_attributes = _glm_model_attributes_to_dataframe(model) 

227 with pd.option_context("display.max_colwidth", 100): 

228 model_attributes_html = dataframe_to_html( 

229 model_attributes, 

230 precision=2, 

231 header=True, 

232 sparsify=False, 

233 ) 

234 

235 contrasts = coerce_to_dict(contrasts) 

236 

237 # If some contrasts are passed 

238 # we do not rely on filenames stored in the model. 

239 output = None 

240 if contrasts is None: 

241 output = model._reporting_data.get("filenames", None) 

242 

243 design_matrices = None 

244 mask_plot = None 

245 mask_info = {"n_elements": 0, "coverage": 0} 

246 results = None 

247 warning_messages = ["The model has not been fit yet."] 

248 if model.__sklearn_is_fitted__(): 

249 warning_messages = [] 

250 

251 if model.__str__() == "Second Level Model": 

252 design_matrices = [model.design_matrix_] 

253 else: 

254 design_matrices = model.design_matrices_ 

255 

256 if bg_img == "MNI152TEMPLATE": 

257 bg_img = MNI152TEMPLATE if model._is_volume_glm() else None 

258 if ( 

259 not model._is_volume_glm() 

260 and bg_img 

261 and not isinstance(bg_img, SurfaceImage) 

262 ): 

263 raise TypeError( 

264 f"'bg_img' must a SurfaceImage instance. Got {type(bg_img)=}" 

265 ) 

266 

267 mask_plot = _mask_to_plot(model, bg_img, cut_coords) 

268 

269 mask_info = { 

270 k: v 

271 for k, v in model.masker_._report_content.items() 

272 if k in ["n_elements", "coverage"] 

273 } 

274 if "coverage" in mask_info: 

275 mask_info["coverage"] = f"{mask_info['coverage']:0.1f}" 

276 

277 statistical_maps = {} 

278 if output is not None: 

279 # we try to rely on the content of glm object only 

280 try: 

281 statistical_maps = { 

282 contrast_name: output["dir"] 

283 / output["statistical_maps"][contrast_name]["z_score"] 

284 for contrast_name in output["statistical_maps"] 

285 } 

286 except KeyError: # pragma: no cover 

287 if contrasts is not None: 

288 statistical_maps = make_stat_maps( 

289 model, 

290 contrasts, 

291 output_type="z_score", 

292 first_level_contrast=first_level_contrast, 

293 ) 

294 elif contrasts is not None: 

295 statistical_maps = make_stat_maps( 

296 model, 

297 contrasts, 

298 output_type="z_score", 

299 first_level_contrast=first_level_contrast, 

300 ) 

301 

302 logger.log( 

303 "Generating contrast-level figures...", verbose=model.verbose 

304 ) 

305 results = _make_stat_maps_contrast_clusters( 

306 stat_img=statistical_maps, 

307 threshold=threshold, 

308 alpha=alpha, 

309 cluster_threshold=cluster_threshold, 

310 height_control=height_control, 

311 two_sided=two_sided, 

312 min_distance=min_distance, 

313 bg_img=bg_img, 

314 cut_coords=cut_coords, 

315 display_mode=display_mode, 

316 plot_type=plot_type, 

317 ) 

318 

319 design_matrices_dict = tempita.bunch() 

320 contrasts_dict = tempita.bunch() 

321 if output is not None: 

322 design_matrices_dict = output["design_matrices_dict"] 

323 contrasts_dict = output["contrasts_dict"] 

324 

325 if is_matplotlib_installed(): 

326 logger.log( 

327 "Generating design matrices figures...", verbose=model.verbose 

328 ) 

329 design_matrices_dict = generate_design_matrices_figures( 

330 design_matrices, 

331 design_matrices_dict=design_matrices_dict, 

332 output=output, 

333 ) 

334 

335 logger.log( 

336 "Generating contrast matrices figures...", verbose=model.verbose 

337 ) 

338 contrasts_dict = generate_contrast_matrices_figures( 

339 design_matrices, 

340 contrasts, 

341 contrasts_dict=contrasts_dict, 

342 output=output, 

343 ) 

344 

345 run_wise_dict = tempita.bunch() 

346 for i_run in design_matrices_dict: 

347 tmp = tempita.bunch() 

348 tmp["design_matrix_png"] = design_matrices_dict[i_run][ 

349 "design_matrix_png" 

350 ] 

351 tmp["correlation_matrix_png"] = design_matrices_dict[i_run][ 

352 "correlation_matrix_png" 

353 ] 

354 tmp["all_contrasts"] = None 

355 if i_run in contrasts_dict: 

356 tmp["all_contrasts"] = contrasts_dict[i_run] 

357 run_wise_dict[i_run] = tmp 

358 

359 # for methods writing, only keep the contrast expressed as strings 

360 if contrasts is not None: 

361 contrasts = [x for x in contrasts.values() if isinstance(x, str)] 

362 method_section_template_path = HTML_TEMPLATE_PATH / "method_section.html" 

363 method_tpl = tempita.HTMLTemplate.from_filename( 

364 str(method_section_template_path), 

365 encoding="utf-8", 

366 ) 

367 method_section = method_tpl.substitute( 

368 version=__version__, 

369 model_type=model.__str__(), 

370 reporting_data=tempita.bunch(**model._reporting_data), 

371 smoothing_fwhm=smoothing_fwhm, 

372 contrasts=contrasts, 

373 ) 

374 

375 body_template_path = HTML_TEMPLATE_PATH / "glm_report.html" 

376 tpl = tempita.HTMLTemplate.from_filename( 

377 str(body_template_path), 

378 encoding="utf-8", 

379 ) 

380 

381 css_file_path = CSS_PATH / "masker_report.css" 

382 with css_file_path.open(encoding="utf-8") as css_file: 

383 css = css_file.read() 

384 

385 with (JS_PATH / "carousel.js").open(encoding="utf-8") as js_file: 

386 js_carousel = js_file.read() 

387 

388 body = tpl.substitute( 

389 css=css, 

390 title=title, 

391 docstring=snippet, 

392 warning_messages=_render_warnings_partial(warning_messages), 

393 parameters=model_attributes_html, 

394 mask_plot=mask_plot, 

395 results=results, 

396 run_wise_dict=run_wise_dict, 

397 unique_id=unique_id, 

398 date=date, 

399 show_navbar="style='display: none;'" if is_notebook() else "", 

400 method_section=method_section, 

401 js_carousel=js_carousel, 

402 displayed_runs=list(range(len(run_wise_dict))), 

403 **mask_info, 

404 ) 

405 

406 # revert HTML safe substitutions in CSS sections 

407 body = body.replace(".pure-g &gt; div", ".pure-g > div") 

408 

409 head_template_path = ( 

410 TEMPLATE_ROOT_PATH / "html" / "report_head_template.html" 

411 ) 

412 with head_template_path.open() as head_file: 

413 head_tpl = Template(head_file.read()) 

414 

415 head_css_file_path = CSS_PATH / "head.css" 

416 with head_css_file_path.open(encoding="utf-8") as head_css_file: 

417 head_css = head_css_file.read() 

418 

419 report = HTMLReport( 

420 body=body, 

421 head_tpl=head_tpl, 

422 head_values={ 

423 "head_css": head_css, 

424 "version": __version__, 

425 "page_title": title, 

426 "display_footer": "style='display: none'" if is_notebook() else "", 

427 }, 

428 ) 

429 

430 report.resize(*report_dims) 

431 

432 return report 

433 

434 

435def _glm_model_attributes_to_dataframe(model): 

436 """Return a pandas dataframe with pertinent model attributes & information. 

437 

438 Parameters 

439 ---------- 

440 model : FirstLevelModel or SecondLevelModel object. 

441 

442 Returns 

443 ------- 

444 pandas.DataFrame 

445 DataFrame with the pertinent attributes of the model. 

446 """ 

447 model_attributes = pd.DataFrame.from_dict( 

448 model._attributes_to_dict(), 

449 orient="index", 

450 ) 

451 

452 if len(model_attributes) == 0: 

453 return model_attributes 

454 

455 attribute_units = { 

456 "t_r": "seconds", 

457 "high_pass": "Hertz", 

458 "smoothing_fwhm": "mm", 

459 } 

460 attribute_names_with_units = { 

461 attribute_name_: attribute_name_ + f" ({attribute_unit_})" 

462 for attribute_name_, attribute_unit_ in attribute_units.items() 

463 } 

464 model_attributes = model_attributes.rename( 

465 index=attribute_names_with_units 

466 ) 

467 model_attributes.index.names = ["Parameter"] 

468 model_attributes.columns = ["Value"] 

469 

470 return model_attributes 

471 

472 

473def _mask_to_plot(model, bg_img, cut_coords): 

474 """Plot a mask image and creates PNG code of it. 

475 

476 Parameters 

477 ---------- 

478 model 

479 

480 bg_img : Niimg-like object 

481 See :ref:`extracting_data`. 

482 The background image that the mask will be plotted on top of. 

483 To turn off background image, just pass "bg_img=None". 

484 

485 cut_coords 

486 

487 

488 Returns 

489 ------- 

490 mask_plot : str 

491 PNG Image for the mask plot. 

492 

493 """ 

494 if not is_matplotlib_installed(): 

495 return None 

496 # Select mask_img to use for plotting 

497 if not model._is_volume_glm(): 

498 model.masker_._create_figure_for_report() 

499 fig = plt.gcf() 

500 mask_plot = figure_to_png_base64(fig) 

501 # prevents sphinx-gallery & jupyter from scraping & inserting plots 

502 plt.close() 

503 return mask_plot 

504 

505 if isinstance(model.mask_img, NiftiMasker): 

506 mask_img = model.masker_.mask_img_ 

507 else: 

508 try: 

509 # check that mask_img is a niiimg-like object 

510 check_niimg(model.mask_img) 

511 mask_img = model.mask_img 

512 except Exception: 

513 mask_img = model.masker_.mask_img_ 

514 

515 plot_roi( 

516 roi_img=mask_img, 

517 bg_img=bg_img, 

518 display_mode="z", 

519 cmap="Set1", 

520 cut_coords=cut_coords, 

521 colorbar=False, 

522 ) 

523 mask_plot = figure_to_png_base64(plt.gcf()) 

524 # prevents sphinx-gallery & jupyter from scraping & inserting plots 

525 plt.close() 

526 

527 return mask_plot 

528 

529 

530@fill_doc 

531def _make_stat_maps_contrast_clusters( 

532 stat_img, 

533 threshold, 

534 alpha, 

535 cluster_threshold, 

536 height_control, 

537 two_sided, 

538 min_distance, 

539 bg_img, 

540 cut_coords, 

541 display_mode, 

542 plot_type, 

543 # clusters_tsvs, 

544): 

545 """Populate a smaller HTML sub-template with the proper values, \ 

546 make a list containing one or more of such components \ 

547 & return the list to be inserted into the HTML Report Template. 

548 

549 Each component contains the HTML code for 

550 a contrast & its corresponding statistical maps & cluster table; 

551 

552 Parameters 

553 ---------- 

554 stat_img : dictionary of Niimg-like object or None 

555 Statistical image (presumably in z scale) 

556 whenever height_control is 'fpr' or None, 

557 stat_img=None is acceptable. 

558 If it is 'fdr' or 'bonferroni', 

559 an error is raised if stat_img is None. 

560 

561 contrasts_plots : Dict[str, str] 

562 Contains contrast names & HTML code of the contrast's PNG plot. 

563 

564 threshold : float 

565 Desired threshold in z-scale. 

566 This is used only if height_control is None 

567 

568 alpha : float 

569 Number controlling the thresholding (either a p-value or q-value). 

570 Its actual meaning depends on the height_control parameter. 

571 This function translates alpha to a z-scale threshold. 

572 

573 cluster_threshold : float 

574 Cluster size threshold. In the returned thresholded map, 

575 sets of connected voxels (`clusters`) with size smaller 

576 than this number will be removed. 

577 

578 height_control : string 

579 False positive control meaning of cluster forming 

580 threshold: 'fpr' or 'fdr' or 'bonferroni' or None. 

581 

582 two_sided : `bool`, default=False 

583 Whether to employ two-sided thresholding or to evaluate positive values 

584 only. 

585 

586 min_distance : float, default=8 

587 For display purposes only. 

588 Minimum distance between subpeaks in mm. 

589 

590 bg_img : Niimg-like object 

591 Only used when plot_type is 'slice'. 

592 See :ref:`extracting_data`. 

593 The background image for stat maps to be plotted on upon. 

594 If nothing is specified, the MNI152 template will be used. 

595 To turn off background image, just pass "bg_img=False". 

596 

597 %(cut_coords)s 

598 

599 display_mode : string 

600 Choose the direction of the cuts: 

601 'x' - sagittal, 'y' - coronal, 'z' - axial, 

602 'l' - sagittal left hemisphere only, 

603 'r' - sagittal right hemisphere only, 

604 'ortho' - three cuts are performed in orthogonal directions. 

605 

606 Possible values are: 

607 'ortho', 'x', 'y', 'z', 'xz', 'yx', 'yz', 

608 'l', 'r', 'lr', 'lzr', 'lyr', 'lzry', 'lyrz'. 

609 

610 plot_type : string {'slice', 'glass'} 

611 The type of plot to be drawn. 

612 

613 clusters_tsvs : dictionary of path of to tsv files 

614 

615 Returns 

616 ------- 

617 results : dict 

618 Each key contains 

619 contrast name, contrast plot, statistical map, cluster table. 

620 

621 """ 

622 if not display_mode: 

623 display_mode_selector = {"slice": "z", "glass": "lzry"} 

624 display_mode = display_mode_selector[plot_type] 

625 

626 results = {} 

627 for contrast_name, stat_map_img in stat_img.items(): 

628 # Only use threshold_stats_img to adjust the threshold 

629 # that we will pass to clustering_params_to_dataframe 

630 # and _stat_map_to_png 

631 # Necessary to avoid : 

632 # https://github.com/nilearn/nilearn/issues/4192 

633 thresholded_img, threshold = threshold_stats_img( 

634 stat_img=stat_map_img, 

635 threshold=threshold, 

636 alpha=alpha, 

637 cluster_threshold=cluster_threshold, 

638 height_control=height_control, 

639 ) 

640 

641 table_details = clustering_params_to_dataframe( 

642 threshold, 

643 cluster_threshold, 

644 min_distance, 

645 height_control, 

646 alpha, 

647 is_volume_glm=not isinstance(stat_map_img, SurfaceImage), 

648 ) 

649 table_details_html = dataframe_to_html( 

650 table_details, 

651 precision=3, 

652 header=False, 

653 ) 

654 

655 cluster_table_html = """ 

656 <p style="text-align: center; font-size: 200%; color: grey" 

657 > 

658 Results table not available for surface data. 

659 </p> 

660 """ 

661 if not isinstance(thresholded_img, SurfaceImage): 

662 # FIXME 

663 # The commented code below was there to reuse 

664 # cluster tables generated by save_glm_to_bids 

665 # to save time. 

666 # However cluster tables may have been computed 

667 # with different threshold, cluster_threshol... 

668 # by save_glm_to_bids than those requested in 

669 # generate_report. 

670 # So we are skipping this for now. 

671 

672 # if clusters_tsvs: 

673 # # try to reuse results saved to disk by 

674 # # save_glm_to_bids 

675 # try: 

676 # cluster_table = pd.read_csv( 

677 # clusters_tsvs[contrast_name], sep="\t" 

678 # ) 

679 # except Exception: 

680 # cluster_table = get_clusters_table( 

681 # thresholded_img, 

682 # stat_threshold=threshold, 

683 # cluster_threshold=cluster_threshold, 

684 # min_distance=min_distance, 

685 # two_sided=two_sided, 

686 # ) 

687 # else: 

688 

689 cluster_table = get_clusters_table( 

690 thresholded_img, 

691 stat_threshold=threshold, 

692 cluster_threshold=cluster_threshold, 

693 min_distance=min_distance, 

694 two_sided=two_sided, 

695 ) 

696 cluster_table_html = dataframe_to_html( 

697 cluster_table, 

698 precision=2, 

699 index=False, 

700 ) 

701 

702 stat_map_png = _stat_map_to_png( 

703 stat_img=thresholded_img, 

704 threshold=threshold, 

705 bg_img=bg_img, 

706 cut_coords=cut_coords, 

707 display_mode=display_mode, 

708 plot_type=plot_type, 

709 table_details=table_details, 

710 ) 

711 if ( 

712 not isinstance(thresholded_img, SurfaceImage) 

713 and len(cluster_table) < 2 

714 ): 

715 cluster_table_html = None 

716 stat_map_png = None 

717 

718 results[escape(contrast_name)] = tempita.bunch( 

719 stat_map_img=stat_map_png, 

720 cluster_table_details=table_details_html, 

721 cluster_table=cluster_table_html, 

722 ) 

723 

724 return results 

725 

726 

727@fill_doc 

728def _stat_map_to_png( 

729 stat_img, 

730 threshold, 

731 bg_img, 

732 cut_coords, 

733 display_mode, 

734 plot_type, 

735 table_details, 

736): 

737 """Generate PNG code for a statistical map, \ 

738 including its clustering parameters. 

739 

740 Parameters 

741 ---------- 

742 stat_img : Niimg-like object or None 

743 Statistical image (presumably in z scale), 

744 to be plotted as slices or glass brain. 

745 Does not perform any thresholding. 

746 

747 threshold : float 

748 Desired threshold in z-scale. 

749 

750 bg_img : Niimg-like object 

751 Only used when plot_type is 'slice'. 

752 See :ref:`extracting_data`. 

753 The background image for stat maps to be plotted on upon. 

754 If nothing is specified, the MNI152 template will be used. 

755 To turn off background image, just pass "bg_img=False". 

756 

757 %(cut_coords)s 

758 

759 display_mode : string 

760 Choose the direction of the cuts: 

761 'x' - sagittal, 'y' - coronal, 'z' - axial, 

762 'l' - sagittal left hemisphere only, 

763 'r' - sagittal right hemisphere only, 

764 'ortho' - three cuts are performed in orthogonal directions. 

765 

766 Possible values are: 

767 'ortho', 'x', 'y', 'z', 'xz', 'yx', 'yz', 

768 'l', 'r', 'lr', 'lzr', 'lyr', 'lzry', 'lyrz'. 

769 

770 plot_type : string {'slice', 'glass'} 

771 The type of plot to be drawn. 

772 

773 table_details : pandas.Dataframe 

774 Dataframe listing the parameters used for clustering, 

775 to be included in the plot. 

776 

777 Returns 

778 ------- 

779 stat_map_png : string 

780 PNG Image Data representing a statistical map. 

781 

782 """ 

783 if not is_matplotlib_installed(): 

784 return None 

785 

786 cmap = DEFAULT_DIVERGING_CMAP 

787 

788 if isinstance(stat_img, SurfaceImage): 

789 data = get_surface_data(stat_img) 

790 else: 

791 data = safe_get_data(stat_img, ensure_finite=True) 

792 

793 stat_map_min = np.nanmin(data) 

794 stat_map_max = np.nanmax(data) 

795 symmetric_cbar = True 

796 if stat_map_min >= 0.0: 

797 symmetric_cbar = False 

798 cmap = "red_transparent_full_alpha_range" 

799 elif stat_map_max <= 0.0: 

800 symmetric_cbar = False 

801 cmap = "blue_transparent_full_alpha_range" 

802 cmap = nilearn_cmaps[cmap].reversed() 

803 

804 if isinstance(stat_img, SurfaceImage): 

805 surf_mesh = bg_img.mesh if bg_img else None 

806 stat_map_plot = plot_surf_stat_map( 

807 stat_map=stat_img, 

808 hemi="left", 

809 threshold=threshold, 

810 bg_map=bg_img, 

811 surf_mesh=surf_mesh, 

812 cmap=cmap, 

813 ) 

814 

815 x_label_color = "black" 

816 

817 else: 

818 if plot_type == "slice": 

819 stat_map_plot = plot_stat_map( 

820 stat_img, 

821 bg_img=bg_img, 

822 cut_coords=cut_coords, 

823 display_mode=display_mode, 

824 cmap=cmap, 

825 symmetric_cbar=symmetric_cbar, 

826 threshold=threshold, 

827 ) 

828 elif plot_type == "glass": 

829 stat_map_plot = plot_glass_brain( 

830 stat_img, 

831 display_mode=display_mode, 

832 plot_abs=False, 

833 symmetric_cbar=symmetric_cbar, 

834 cmap=cmap, 

835 threshold=threshold, 

836 ) 

837 else: 

838 raise ValueError( 

839 "Invalid plot type provided. " 

840 "Acceptable options are 'slice' or 'glass'." 

841 ) 

842 

843 x_label_color = "white" if plot_type == "slice" else "black" 

844 

845 if hasattr(stat_map_plot, "_cbar"): 

846 cbar_ax = stat_map_plot._cbar.ax 

847 cbar_ax.set_xlabel( 

848 "Z score", 

849 labelpad=5, 

850 fontweight="bold", 

851 loc="right", 

852 color=x_label_color, 

853 ) 

854 

855 with pd.option_context("display.precision", 2): 

856 _add_params_to_plot(table_details, stat_map_plot) 

857 

858 fig = plt.gcf() 

859 stat_map_png = figure_to_png_base64(fig) 

860 # prevents sphinx-gallery & jupyter from scraping & inserting plots 

861 plt.close() 

862 

863 return stat_map_png 

864 

865 

866def _add_params_to_plot(table_details, stat_map_plot): 

867 """Insert thresholding parameters into the stat map plot \ 

868 as figure suptitle. 

869 

870 Parameters 

871 ---------- 

872 table_details : Dict[String, Any] 

873 Dict of parameters and values used in thresholding. 

874 

875 stat_map_plot : matplotlib.Axes 

876 Axes object of the stat map plot. 

877 

878 Returns 

879 ------- 

880 stat_map_plot : matplotlib.Axes 

881 Axes object of the stat map plot, with the added suptitle. 

882 

883 """ 

884 thresholding_params = [ 

885 ":".join([name, str(val)]) for name, val in table_details[0].items() 

886 ] 

887 thresholding_params = " ".join(thresholding_params) 

888 suptitle_text = plt.suptitle( 

889 thresholding_params, 

890 fontsize=11, 

891 x=0.45, 

892 wrap=True, 

893 ) 

894 fig = plt.gcf() 

895 resize_plot_inches( 

896 plot=fig, 

897 width_change=0.2, 

898 height_change=1, 

899 ) 

900 

901 if hasattr(stat_map_plot, "_black_bg") and stat_map_plot._black_bg: 

902 suptitle_text.set_color("w") 

903 

904 return stat_map_plot