Skip to content

Reporting & Visualization

TI-Toolbox writes one self-contained HTML report per pipeline run. There are five kinds: SimNIBS's own charm report (copied from m2m_<id>/charm_report.html when charm finishes), and DTI QC, simulator, flex-search and ex-search reports, which the pipelines write themselves when they finish. There is no report object to build by hand: each generator reads what its run wrote, and rebuilds a report from the command line (inside the container):

simnibs_python -m tit.reporting.generators.dti_qc      /mnt/project 001                 [--out DIR]
simnibs_python -m tit.reporting.generators.simulation  /mnt/project 001 motor_cortex    [--out DIR]
simnibs_python -m tit.reporting.generators.flex_search /mnt/project 001 <run folder>    [--out DIR]
simnibs_python -m tit.reporting.generators.ex_search   /mnt/project 001 <run name>      [--out DIR]

or from Python:

from tit.reporting.generators.simulation import create_simulation_report

path = create_simulation_report("/mnt/project", "001", "motor_cortex")

A simulator report opens with the grey-matter envelope (99.9th percentile, median and where its maximum is), the montage on the EEG cap with its dose, the conductivity model and the envelope in three planes through the hot spot. Only the DTI QC report has checks; each shows its role (gate, software check, advisory, reported) and citation, and the rules are in tit.reporting.qc_rules.RULES.

Plotting Utilities

The tit.plotting module provides visualization functions used by the analysis and reporting pipelines:

from tit.analyzer.visualizer import save_histogram
from tit.plotting import (
    plot_permutation_null_distribution,
    plot_cluster_size_mass_correlation,
    plot_montage_distributions,
    plot_intensity_vs_focality,
)

Plotting Context

Most plotting functions are called internally by the Analyzer and report generators. You typically do not need to call them directly unless building custom visualizations.

Output Location

Reports are saved under the BIDS derivatives tree:

derivatives/ti-toolbox/reports/sub-001/
├── charm_report.html
├── dti_qc_20250101_120000.html
├── simulation_report_20250101_120000.html
├── flex_search_report_20250101_120000.html
└── ex_search_report_20250101_120000.html

API Reference

tit.reporting.generators.simulation.create_simulation_report

create_simulation_report(project_dir: str | Path, subject_id: str, simulation_name: str, out_dir: str | Path | None = None) -> Path

Write the report for one simulation folder; into the project's reports unless out_dir.

Source code in tit/reporting/generators/simulation.py
def create_simulation_report(
    project_dir: str | Path,
    subject_id: str,
    simulation_name: str,
    out_dir: str | Path | None = None,
) -> Path:
    """Write the report for one simulation folder; into the project's reports unless *out_dir*."""
    from tit.paths import get_path_manager

    t0 = time.time()
    pm = get_path_manager(str(project_dir))
    rec = collect(pm.simulation(subject_id, simulation_name))
    m2m = Path(pm.m2m(subject_id))
    if rec["envelope"]:
        try:
            rec["envelope"]["peak_mni"] = to_mni(rec["envelope"]["peak_world"], m2m)
        except (OSError, ValueError) as exc:
            logger.warning(f"Simulation report: peak left in subject space ({exc})")
    try:
        images = field_images(rec, m2m / "T1.nii.gz")
    except (OSError, ValueError) as exc:
        logger.warning(f"Simulation report: no envelope figure ({exc})")
        images = None
    html = build_html(rec, images, subject_id)
    return common.write_report(
        html,
        project_dir,
        subject_id,
        REPORT_PREFIX,
        SIZE_BUDGET,
        out_dir,
        t0,
        label="Simulation report",
    )

tit.reporting.generators.flex_search.create_flex_search_report

create_flex_search_report(project_dir: str | Path, subject_id: str, run_dir: str | Path, out_dir: str | Path | None = None) -> Path

Write the report for one flex-search run folder; into the project's reports unless out_dir.

Source code in tit/reporting/generators/flex_search.py
def create_flex_search_report(
    project_dir: str | Path,
    subject_id: str,
    run_dir: str | Path,
    out_dir: str | Path | None = None,
) -> Path:
    """Write the report for one flex-search run folder; into the project's reports unless *out_dir*."""
    t0 = time.time()
    rec = collect(run_dir)
    return common.write_report(
        build_html(rec, subject_id),
        project_dir,
        subject_id,
        REPORT_PREFIX,
        SIZE_BUDGET,
        out_dir,
        t0,
        label="Flex-search report",
    )

tit.reporting.generators.ex_search.create_ex_search_report

create_ex_search_report(project_dir: str | Path, subject_id: str, run_dir: str | Path, out_dir: str | Path | None = None) -> Path

Write the report for one ex-search run folder; into the project's reports unless out_dir.

Source code in tit/reporting/generators/ex_search.py
def create_ex_search_report(
    project_dir: str | Path,
    subject_id: str,
    run_dir: str | Path,
    out_dir: str | Path | None = None,
) -> Path:
    """Write the report for one ex-search run folder; into the project's reports unless *out_dir*."""
    from tit.paths import get_path_manager

    t0 = time.time()
    pm = get_path_manager(str(project_dir))
    rec = collect(run_dir, pm.leadfields(subject_id))
    if not rec["rows"]:
        raise ValueError(f"{run_dir}/final_output.csv has no montages")
    return common.write_report(
        build_html(rec, subject_id),
        project_dir,
        subject_id,
        REPORT_PREFIX,
        SIZE_BUDGET,
        out_dir,
        t0,
        label="Ex-search report",
    )

tit.reporting.generators.dti_qc.create_dti_qc_report

create_dti_qc_report(project_dir: str | Path, subject_id: str, qc: dict, vols, out_dir: str | Path | None = None) -> Path

Render the images from vols (:class:~tit.pre.qsi.dti_advisories.DtiVolumes) and write the report.

Written to derivatives/ti-toolbox/reports/sub-<id>/dti_qc_<timestamp>.html unless out_dir is given. Works for a failed gate too: vols holds the tensor that was computed but not written.

Source code in tit/reporting/generators/dti_qc.py
def create_dti_qc_report(
    project_dir: str | Path,
    subject_id: str,
    qc: dict,
    vols,
    out_dir: str | Path | None = None,
) -> Path:
    """Render the images from *vols* (:class:`~tit.pre.qsi.dti_advisories.DtiVolumes`) and write the report.

    Written to ``derivatives/ti-toolbox/reports/sub-<id>/dti_qc_<timestamp>.html`` unless *out_dir*
    is given. Works for a failed gate too: *vols* holds the tensor that was computed but not written.
    """
    from tit import constants as const
    from tit.paths import get_path_manager
    from tit.plotting.dti_qc import render_all

    t0 = time.time()
    written = None
    if not qc.get("failures"):
        m2m = Path(get_path_manager(str(project_dir)).m2m(subject_id))
        tensor = m2m / const.FILE_DTI_TENSOR
        if tensor.is_file():
            written = datetime.fromtimestamp(tensor.stat().st_mtime)
    html = build_html(qc, render_all(vols), subject_id, tensor_written=written)
    out = (
        Path(out_dir)
        if out_dir
        else Path(get_path_manager(str(project_dir)).reports()) / f"sub-{subject_id}"
    )
    out.mkdir(parents=True, exist_ok=True)
    path = out / f"{REPORT_PREFIX}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html"
    path.write_text(html, encoding="utf-8")
    size = path.stat().st_size
    logger.info(
        f"DTI QC report: {path} ({size / 1e6:.2f} MB, {time.time() - t0:.0f} s)"
    )
    if size > SIZE_BUDGET:
        logger.warning(
            f"DTI QC report is {size / 1e6:.2f} MB, over its {SIZE_BUDGET / 1e6:.1f} MB budget"
        )
    return path