Skip to content

nilearn

tit.plotting.nilearn

Nilearn-based plotting helpers for TI-Toolbox.

The figures behind the desktop's Nilearn Visuals panel (run as simnibs_python -m tit.plotting.nilearn config.json) and the fsaverage surface maps of a cluster-permutation result.

Public API

NilearnVisualizer Multi-slice PDF of a group-averaged field with atlas outlines. create_pdf_entry_point_group The runner's entry point to that PDF. create_glass_brain_entry_point_group Glass-brain PDF of a group-averaged field. render_fsaverage_map / render_surface_stats_result fsaverage surface maps (a per-vertex array, or a surface_stats.msh).

See Also

tit.plotting.ti_metrics : TI-specific metric plots (histograms, scatter). tit.blender : Blender-based 3-D visualizations.

NilearnVisualizer

Draws a group-averaged field as a multi-slice PDF with atlas outlines.

See Also

tit.plotting.nilearn.img_slices : The runner's entry point to this class. tit.plotting.nilearn.img_glass : The glass-brain PDF of the same field.

create_pdf_visualization_group

create_pdf_visualization_group(averaged_img, base_filename: str, output_dir: str, min_cutoff: float | None = None, max_cutoff: float | None = None, atlas_name: str = 'harvard_oxford_sub', selected_regions: list[int] | None = None) -> str | None

Create a multi-slice PDF from a pre-averaged group NIfTI image.

Sagittal, coronal and axial rows of seven slices each, the field on a hot colour map over the MNI template, with optional atlas outlines.

Parameters

averaged_img : nibabel.Nifti1Image Pre-averaged electric-field image. base_filename : str Base filename for output (without extension). output_dir : str Directory where the PDF will be saved. min_cutoff : float or None, optional Lower display threshold in V/m. When None the 95th percentile of non-zero voxels is used. max_cutoff : float or None, optional Upper display threshold in V/m. When None the 99.9th percentile of non-zero voxels is used. atlas_name : str, optional Nilearn atlas key. selected_regions : list of int or None, optional 0-indexed region indices for the atlas overlay.

Returns

str or None Path to the saved PDF, or None on failure.

Source code in tit/plotting/nilearn/visualizer.py
def create_pdf_visualization_group(
    self,
    averaged_img,
    base_filename: str,
    output_dir: str,
    min_cutoff: float | None = None,
    max_cutoff: float | None = None,
    atlas_name: str = "harvard_oxford_sub",
    selected_regions: list[int] | None = None,
) -> str | None:
    """Create a multi-slice PDF from a pre-averaged group NIfTI image.

    Sagittal, coronal and axial rows of seven slices each, the field on
    a hot colour map over the MNI template, with optional atlas outlines.

    Parameters
    ----------
    averaged_img : nibabel.Nifti1Image
        Pre-averaged electric-field image.
    base_filename : str
        Base filename for output (without extension).
    output_dir : str
        Directory where the PDF will be saved.
    min_cutoff : float or None, optional
        Lower display threshold in V/m.  When *None* the 95th
        percentile of non-zero voxels is used.
    max_cutoff : float or None, optional
        Upper display threshold in V/m.  When *None* the 99.9th
        percentile of non-zero voxels is used.
    atlas_name : str, optional
        Nilearn atlas key.
    selected_regions : list of int or None, optional
        0-indexed region indices for the atlas overlay.

    Returns
    -------
    str or None
        Path to the saved PDF, or *None* on failure.
    """
    print(f"=== Creating PDF Visualization for Group Averaged Data ===")

    # Load and analyze data
    data = averaged_img.get_fdata()
    data_nonzero = data[data > 0]
    if len(data_nonzero) == 0:
        print("Warning: No non-zero field values found")
        return None

    max_value = np.max(data)
    percentile_999 = np.percentile(data_nonzero, 99.9)
    min_value = np.min(data_nonzero)
    min_cutoff, max_cutoff = resolve_cutoffs(data_nonzero, min_cutoff, max_cutoff)

    print(f"Electric field statistics (averaged data):")
    print(f"  Absolute maximum: {format_vm(max_value)} V/m")
    print(f"  99.9th percentile: {format_vm(percentile_999)} V/m")
    print(f"  Minimum (non-zero): {format_vm(min_value)} V/m")
    print(
        f"  Visualization range: {format_vm(min_cutoff)} - {format_vm(max_cutoff)} V/m"
    )

    # Create output filename
    pdf_filename = f"{base_filename}_multiple_views.pdf"
    pdf_filepath = os.path.join(output_dir, pdf_filename)

    # Load atlas for contours
    atlas_img, atlas_display_name = self._load_atlas(atlas_name, selected_regions)

    # Create multi-slice plot with atlas contours
    self._create_multi_slice_plot_with_atlas(
        averaged_img,
        atlas_img,
        atlas_display_name,
        pdf_filepath,
        min_cutoff,
        max_cutoff,
    )

    print(f"✓ Saved: {pdf_filepath}")
    print(
        f"  Colorbar range: {format_vm(min_cutoff)} - {format_vm(max_cutoff)} V/m"
    )

    return pdf_filepath

create_pdf_entry_point_group

create_pdf_entry_point_group(averaged_img, base_filename: str, output_dir: str, min_cutoff: float | None = None, max_cutoff: float | None = None, atlas_name: str = 'harvard_oxford_sub', selected_regions: list = None, output_callback=None)

Entry point for PDF visualization creation with pre-averaged nifti data.

Parameters:

Name Type Description Default
averaged_img

Pre-averaged nibabel Nifti1Image

required
base_filename str

Base filename for output (without extension)

required
output_dir str

Output directory path

required
min_cutoff float | None

Minimum cutoff for visualization (V/m)

None
max_cutoff float | None

Maximum cutoff for visualization (V/m), if None uses 99.9th percentile

None
atlas_name str

Atlas name for contours

'harvard_oxford_sub'
selected_regions list

List of region indices to include (0-indexed), if None includes all

None
output_callback

Optional callback function for output (for GUI integration)

None
Source code in tit/plotting/nilearn/img_slices.py
def create_pdf_entry_point_group(
    averaged_img,
    base_filename: str,
    output_dir: str,
    min_cutoff: float | None = None,
    max_cutoff: float | None = None,
    atlas_name: str = "harvard_oxford_sub",
    selected_regions: list = None,
    output_callback=None,
):
    """
    Entry point for PDF visualization creation with pre-averaged nifti data.

    Args:
        averaged_img: Pre-averaged nibabel Nifti1Image
        base_filename: Base filename for output (without extension)
        output_dir: Output directory path
        min_cutoff: Minimum cutoff for visualization (V/m)
        max_cutoff: Maximum cutoff for visualization (V/m), if None uses 99.9th percentile
        atlas_name: Atlas name for contours
        selected_regions: List of region indices to include (0-indexed), if None includes all
        output_callback: Optional callback function for output (for GUI integration)
    """
    # Redirect stdout if callback provided (for GUI integration)
    import sys
    from contextlib import redirect_stdout
    import io

    if output_callback:
        # Capture stdout and send to callback
        captured_output = io.StringIO()
        with redirect_stdout(captured_output):
            visualizer = NilearnVisualizer()
            result = visualizer.create_pdf_visualization_group(
                averaged_img,
                base_filename,
                output_dir,
                min_cutoff,
                max_cutoff,
                atlas_name,
                selected_regions,
            )

        # Send captured output to callback line by line
        output_text = captured_output.getvalue()
        for line in output_text.split("\n"):
            if line.strip():
                output_callback(line)

        if result:
            output_callback(f"✓ PDF visualization completed: {result}")
            return result
        else:
            output_callback("✗ PDF visualization failed")
            return None
    else:
        # Normal operation - print to stdout
        visualizer = NilearnVisualizer()
        result = visualizer.create_pdf_visualization_group(
            averaged_img,
            base_filename,
            output_dir,
            min_cutoff,
            max_cutoff,
            atlas_name,
            selected_regions,
        )
        if result:
            print(f"\n✓ PDF visualization completed: {result}")
            return result
        else:
            print("\n✗ PDF visualization failed")
            return None

create_glass_brain_entry_point_group

create_glass_brain_entry_point_group(averaged_img, base_filename: str, output_dir: str, min_cutoff: float | None = None, max_cutoff: float | None = None, cmap: str = 'hot', output_callback=None)

Entry point for glass brain visualization creation with pre-averaged nifti data.

Parameters:

Name Type Description Default
averaged_img

Pre-averaged nibabel Nifti1Image

required
base_filename str

Base filename for output (without extension)

required
output_dir str

Output directory path

required
min_cutoff float | None

Minimum cutoff for visualization (V/m)

None
max_cutoff float | None

Maximum cutoff for visualization (V/m), if None uses 99.9th percentile

None
cmap str

Colormap name for visualization

'hot'
output_callback

Optional callback function for output (for GUI integration)

None
Source code in tit/plotting/nilearn/img_glass.py
def create_glass_brain_entry_point_group(
    averaged_img,
    base_filename: str,
    output_dir: str,
    min_cutoff: float | None = None,
    max_cutoff: float | None = None,
    cmap: str = "hot",
    output_callback=None,
):
    """
    Entry point for glass brain visualization creation with pre-averaged nifti data.

    Args:
        averaged_img: Pre-averaged nibabel Nifti1Image
        base_filename: Base filename for output (without extension)
        output_dir: Output directory path
        min_cutoff: Minimum cutoff for visualization (V/m)
        max_cutoff: Maximum cutoff for visualization (V/m), if None uses 99.9th percentile
        cmap: Colormap name for visualization
        output_callback: Optional callback function for output (for GUI integration)
    """
    # Redirect stdout if callback provided (for GUI integration)
    import sys
    from contextlib import redirect_stdout
    import io
    import os
    import numpy as np
    import matplotlib.pyplot as plt
    from nilearn import plotting

    if output_callback:
        # Capture stdout and send to callback
        captured_output = io.StringIO()
        with redirect_stdout(captured_output):
            # Load and analyze data
            data = averaged_img.get_fdata()
            data_nonzero = data[data > 0]
            if len(data_nonzero) == 0:
                print("Warning: No non-zero field values found")
                return None

            max_value = np.max(data)
            percentile_999 = np.percentile(data_nonzero, 99.9)
            min_value = np.min(data_nonzero)

            min_cutoff, max_cutoff = resolve_cutoffs(
                data_nonzero, min_cutoff, max_cutoff
            )

            print(f"Electric field statistics (averaged data):")
            print(f"  Absolute maximum: {format_vm(max_value)} V/m")
            print(f"  99.9th percentile: {format_vm(percentile_999)} V/m")
            print(f"  Minimum (non-zero): {format_vm(min_value)} V/m")
            print(
                f"  Visualization range: {format_vm(min_cutoff)} - {format_vm(max_cutoff)} V/m"
            )

            # Create output filename
            pdf_filename = f"{base_filename}_glass_brain.pdf"
            pdf_filepath = os.path.join(output_dir, pdf_filename)

            # Create glass brain visualization
            plotting.plot_glass_brain(
                stat_map_img=averaged_img,
                threshold=min_cutoff,
                vmax=max_cutoff,
                cmap=cmap,
                colorbar=True,
                plot_abs=False,
                symmetric_cbar=False,
                display_mode="lyrz",
                title=f"Electric Field - Group Average\n{format_vm(min_cutoff)}-{format_vm(max_cutoff)} V/m",
                output_file=pdf_filepath,
            )

            print(f"✓ Saved glass brain visualization: {pdf_filepath}")
            result = pdf_filepath

        # Send captured output to callback line by line
        output_text = captured_output.getvalue()
        for line in output_text.split("\n"):
            if line.strip():
                output_callback(line)

        if result:
            output_callback(f"✓ Glass brain visualization completed: {result}")
            return result
        else:
            output_callback("✗ Glass brain visualization failed")
            return None
    else:
        # Normal operation - print to stdout
        # Load and analyze data
        data = averaged_img.get_fdata()
        data_nonzero = data[data > 0]
        if len(data_nonzero) == 0:
            print("Warning: No non-zero field values found")
            return None

        max_value = np.max(data)
        percentile_999 = np.percentile(data_nonzero, 99.9)
        min_value = np.min(data_nonzero)

        min_cutoff, max_cutoff = resolve_cutoffs(data_nonzero, min_cutoff, max_cutoff)

        print(f"Electric field statistics (averaged data):")
        print(f"  Absolute maximum: {format_vm(max_value)} V/m")
        print(f"  99.9th percentile: {format_vm(percentile_999)} V/m")
        print(f"  Minimum (non-zero): {format_vm(min_value)} V/m")
        print(
            f"  Visualization range: {format_vm(min_cutoff)} - {format_vm(max_cutoff)} V/m"
        )

        # Create output filename
        pdf_filename = f"{base_filename}_glass_brain.pdf"
        pdf_filepath = os.path.join(output_dir, pdf_filename)

        # Create glass brain visualization
        plotting.plot_glass_brain(
            stat_map_img=averaged_img,
            threshold=min_cutoff,
            vmax=max_cutoff,
            cmap=cmap,
            colorbar=True,
            plot_abs=False,
            symmetric_cbar=False,
            display_mode="lyrz",
            title=f"Electric Field - Group Average\n{format_vm(min_cutoff)}-{format_vm(max_cutoff)} V/m",
            output_file=pdf_filepath,
        )

        print(f"✓ Saved glass brain visualization: {pdf_filepath}")
        result = pdf_filepath

        if result:
            print(f"\n✓ Glass brain visualization completed: {result}")
            return result
        else:
            print("\n✗ Glass brain visualization failed")
            return None

render_fsaverage_map

render_fsaverage_map(values, spacing: int = 5, *, title: str | None = None, threshold: float | None = None, cmap: str = 'cold_hot', symmetric_cbar: bool | str = 'auto', out_path: str | None = None)

Paint a (n_vertices,) fsaverage map on the inflated cortex.

Parameters

values : array-like Per-vertex values in [lh; rh] order, length _FSAVG_NODES[spacing]. spacing : int fsaverage subdivision factor (5, 6, or 7). title : str, optional Figure suptitle. threshold : float, optional Hide |value| < threshold (e.g. to show only significant vertices). cmap : str Matplotlib/nilearn colormap. cold_hot suits signed t/r maps; use a sequential map (e.g. "inferno") for magnitude fields. symmetric_cbar : bool or "auto" Passed to nilearn; keep signed maps symmetric about zero. out_path : str, optional If given, save the figure there and return the path; else return the Matplotlib figure.

Returns

str or matplotlib.figure.Figure The saved path (when out_path is given) or the figure.

Source code in tit/plotting/nilearn/surface.py
def render_fsaverage_map(
    values,
    spacing: int = 5,
    *,
    title: str | None = None,
    threshold: float | None = None,
    cmap: str = "cold_hot",
    symmetric_cbar: bool | str = "auto",
    out_path: str | None = None,
):
    """Paint a ``(n_vertices,)`` fsaverage map on the inflated cortex.

    Parameters
    ----------
    values : array-like
        Per-vertex values in ``[lh; rh]`` order, length ``_FSAVG_NODES[spacing]``.
    spacing : int
        fsaverage subdivision factor (5, 6, or 7).
    title : str, optional
        Figure suptitle.
    threshold : float, optional
        Hide ``|value| < threshold`` (e.g. to show only significant vertices).
    cmap : str
        Matplotlib/nilearn colormap.  ``cold_hot`` suits signed t/r maps;
        use a sequential map (e.g. ``"inferno"``) for magnitude fields.
    symmetric_cbar : bool or "auto"
        Passed to nilearn; keep signed maps symmetric about zero.
    out_path : str, optional
        If given, save the figure there and return the path; else return the
        Matplotlib figure.

    Returns
    -------
    str or matplotlib.figure.Figure
        The saved path (when *out_path* is given) or the figure.
    """
    ensure_headless_matplotlib_backend()
    import matplotlib.pyplot as plt
    from nilearn import datasets, plotting

    n_total = _FSAVG_NODES[spacing]
    values = np.asarray(values, dtype=float).reshape(-1)
    if values.shape[0] != n_total:
        raise ValueError(
            f"expected {n_total} fsaverage{spacing} vertices, got {values.shape[0]}"
        )
    n_lh = n_total // 2

    fs = datasets.fetch_surf_fsaverage(f"fsaverage{spacing}")
    hemi_data = {
        "left": (values[:n_lh], fs["infl_left"], fs["sulc_left"]),
        "right": (values[n_lh:], fs["infl_right"], fs["sulc_right"]),
    }
    # Shared color scale so the single colorbar is valid across all four panels
    # (nilearn otherwise auto-scales vmax per hemisphere).
    vmax = float(np.nanmax(np.abs(values))) or None

    fig, axes = plt.subplots(2, 2, figsize=(11, 9), subplot_kw={"projection": "3d"})
    for col, hemi in enumerate(_HEMIS):
        stat_map, mesh, bg_map = hemi_data[hemi]
        for row, view in enumerate(_VIEWS):
            plotting.plot_surf_stat_map(
                mesh,
                stat_map,
                bg_map=bg_map,
                hemi=hemi,
                view=view,
                threshold=threshold,
                cmap=cmap,
                vmax=vmax,
                colorbar=(row == 0 and col == 1),
                symmetric_cbar=symmetric_cbar,
                axes=axes[row, col],
                figure=fig,
            )
            axes[row, col].set_title(f"{hemi} {view}")

    if title:
        fig.suptitle(title)
    if out_path is None:
        return fig
    fig.savefig(out_path, dpi=150, bbox_inches="tight")
    plt.close(fig)
    return out_path

render_surface_stats_result

render_surface_stats_result(msh_path: str, out_dir: str, spacing: int | None = None)

Render a :mod:tit.stats.surface surface_stats.msh to PDFs.

Paints the effect map (signed r if present, else t) and the thresholded significant-cluster mask onto the inflated cortex. spacing is inferred from the array length when not given.

Returns

list of str Paths of the figures written into out_dir.

Source code in tit/plotting/nilearn/surface.py
def render_surface_stats_result(
    msh_path: str, out_dir: str, spacing: int | None = None
):
    """Render a :mod:`tit.stats.surface` ``surface_stats.msh`` to PDFs.

    Paints the effect map (signed ``r`` if present, else ``t``) and the
    thresholded significant-cluster mask onto the inflated cortex.  *spacing* is
    inferred from the array length when not given.

    Returns
    -------
    list of str
        Paths of the figures written into *out_dir*.
    """
    os.makedirs(out_dir, exist_ok=True)
    written: list[str] = []
    from tit.source.fsaverage import read_fsaverage_fields

    data = read_fsaverage_fields(msh_path)
    effect_key = "r" if "r" in data else "t"
    effect = data[effect_key]
    sig_mask = data.get("sig_mask")

    if spacing is None:
        by_nodes = {n: s for s, n in _FSAVG_NODES.items()}
        spacing = by_nodes.get(effect.shape[0])
        if spacing is None:
            raise ValueError(
                f"cannot infer fsaverage spacing from {effect.shape[0]} vertices"
            )

    effect_pdf = os.path.join(out_dir, f"surface_{effect_key}_map.pdf")
    render_fsaverage_map(
        effect, spacing, title=f"{effect_key} map", out_path=effect_pdf
    )
    written.append(effect_pdf)

    if sig_mask is not None and float(sig_mask.sum()) > 0:
        sig_pdf = os.path.join(out_dir, "surface_significant_clusters.pdf")
        render_fsaverage_map(
            effect * sig_mask,
            spacing,
            title="significant clusters",
            threshold=1e-9,
            out_path=sig_pdf,
        )
        written.append(sig_pdf)
    return written