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
« 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.
4Functions
5---------
7make_glm_report(model, contrasts):
8 Creates an HTMLReport Object which can be viewed or saved as a report.
10"""
12import datetime
13import uuid
14import warnings
15from html import escape
16from string import Template
18import numpy as np
19import pandas as pd
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
54MNI152TEMPLATE = None
55if is_matplotlib_installed():
56 from matplotlib import pyplot as plt
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 )
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.
96 The object can be opened in a browser, displayed in a notebook,
97 or saved to disk as a standalone HTML file.
99 Examples
100 --------
101 report = make_glm_report(model, contrasts)
102 report.open_in_browser()
103 report.save_as_html(destination_path)
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).
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
117 Contrasts information for a first or second level model.
119 Example:
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
127 Each :term:`contrast` name must be a string.
128 Each :term:`contrast` coefficient must be a list
129 or numpy array of ints.
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`)
136 %(first_level_contrast)s
138 .. versionadded:: 0.11.2dev
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.
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".
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.
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.
160 cluster_threshold : :obj:`int`, default=0
161 Cluster size threshold, in voxels.
163 height_control : :obj:`str`, default='fpr'
164 false positive control meaning of cluster forming
165 threshold: 'fpr' or 'fdr' or 'bonferroni' or None.
167 two_sided : :obj:`bool`, default=False
168 Whether to employ two-sided thresholding or to evaluate positive values
169 only.
171 min_distance : :obj:`float`, default=8.0
172 For display purposes only.
173 Minimum distance between subpeaks in mm.
175 plot_type : :obj:`str`, {'slice', 'glass'}, default='slice'
176 Specifies the type of plot to be drawn for the statistical maps.
178 %(cut_coords)s
180 display_mode : :obj:`str`, default=None
181 Default is 'z' if plot_type is 'slice'; '
182 ortho' if plot_type is 'glass'.
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.
190 Possible values are:
191 'ortho', 'x', 'y', 'z', 'xz', 'yx', 'yz',
192 'l', 'r', 'lr', 'lzr', 'lyr', 'lzry', 'lyrz'.
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.
199 Returns
200 -------
201 report_text : HTMLReport Object
202 Contains the HTML code for the :term:`GLM` Report.
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 )
212 unique_id = str(uuid.uuid4()).replace("-", "")
214 title = f"<br>{title}" if title else ""
215 title = f"Statistical Report - {model.__str__()}{title}"
217 docstring = model.__doc__
218 snippet = docstring.partition("Parameters\n ----------\n")[0]
220 date = datetime.datetime.now().replace(microsecond=0).isoformat()
222 smoothing_fwhm = getattr(model, "smoothing_fwhm", 0)
223 if smoothing_fwhm == 0:
224 smoothing_fwhm = None
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 )
235 contrasts = coerce_to_dict(contrasts)
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)
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 = []
251 if model.__str__() == "Second Level Model":
252 design_matrices = [model.design_matrix_]
253 else:
254 design_matrices = model.design_matrices_
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 )
267 mask_plot = _mask_to_plot(model, bg_img, cut_coords)
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}"
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 )
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 )
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"]
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 )
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 )
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
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 )
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 )
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()
385 with (JS_PATH / "carousel.js").open(encoding="utf-8") as js_file:
386 js_carousel = js_file.read()
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 )
406 # revert HTML safe substitutions in CSS sections
407 body = body.replace(".pure-g > div", ".pure-g > div")
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())
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()
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 )
430 report.resize(*report_dims)
432 return report
435def _glm_model_attributes_to_dataframe(model):
436 """Return a pandas dataframe with pertinent model attributes & information.
438 Parameters
439 ----------
440 model : FirstLevelModel or SecondLevelModel object.
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 )
452 if len(model_attributes) == 0:
453 return model_attributes
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"]
470 return model_attributes
473def _mask_to_plot(model, bg_img, cut_coords):
474 """Plot a mask image and creates PNG code of it.
476 Parameters
477 ----------
478 model
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".
485 cut_coords
488 Returns
489 -------
490 mask_plot : str
491 PNG Image for the mask plot.
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
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_
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()
527 return mask_plot
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.
549 Each component contains the HTML code for
550 a contrast & its corresponding statistical maps & cluster table;
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.
561 contrasts_plots : Dict[str, str]
562 Contains contrast names & HTML code of the contrast's PNG plot.
564 threshold : float
565 Desired threshold in z-scale.
566 This is used only if height_control is None
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.
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.
578 height_control : string
579 False positive control meaning of cluster forming
580 threshold: 'fpr' or 'fdr' or 'bonferroni' or None.
582 two_sided : `bool`, default=False
583 Whether to employ two-sided thresholding or to evaluate positive values
584 only.
586 min_distance : float, default=8
587 For display purposes only.
588 Minimum distance between subpeaks in mm.
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".
597 %(cut_coords)s
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.
606 Possible values are:
607 'ortho', 'x', 'y', 'z', 'xz', 'yx', 'yz',
608 'l', 'r', 'lr', 'lzr', 'lyr', 'lzry', 'lyrz'.
610 plot_type : string {'slice', 'glass'}
611 The type of plot to be drawn.
613 clusters_tsvs : dictionary of path of to tsv files
615 Returns
616 -------
617 results : dict
618 Each key contains
619 contrast name, contrast plot, statistical map, cluster table.
621 """
622 if not display_mode:
623 display_mode_selector = {"slice": "z", "glass": "lzry"}
624 display_mode = display_mode_selector[plot_type]
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 )
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 )
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.
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:
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 )
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
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 )
724 return results
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.
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.
747 threshold : float
748 Desired threshold in z-scale.
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".
757 %(cut_coords)s
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.
766 Possible values are:
767 'ortho', 'x', 'y', 'z', 'xz', 'yx', 'yz',
768 'l', 'r', 'lr', 'lzr', 'lyr', 'lzry', 'lyrz'.
770 plot_type : string {'slice', 'glass'}
771 The type of plot to be drawn.
773 table_details : pandas.Dataframe
774 Dataframe listing the parameters used for clustering,
775 to be included in the plot.
777 Returns
778 -------
779 stat_map_png : string
780 PNG Image Data representing a statistical map.
782 """
783 if not is_matplotlib_installed():
784 return None
786 cmap = DEFAULT_DIVERGING_CMAP
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)
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()
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 )
815 x_label_color = "black"
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 )
843 x_label_color = "white" if plot_type == "slice" else "black"
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 )
855 with pd.option_context("display.precision", 2):
856 _add_params_to_plot(table_details, stat_map_plot)
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()
863 return stat_map_png
866def _add_params_to_plot(table_details, stat_map_plot):
867 """Insert thresholding parameters into the stat map plot \
868 as figure suptitle.
870 Parameters
871 ----------
872 table_details : Dict[String, Any]
873 Dict of parameters and values used in thresholding.
875 stat_map_plot : matplotlib.Axes
876 Axes object of the stat map plot.
878 Returns
879 -------
880 stat_map_plot : matplotlib.Axes
881 Axes object of the stat map plot, with the added suptitle.
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 )
901 if hasattr(stat_map_plot, "_black_bg") and stat_map_plot._black_bg:
902 suptitle_text.set_color("w")
904 return stat_map_plot