Skip to content

Reporting & Visualization

TI-Toolbox generates interactive HTML reports for every stage of the pipeline. Reports are assembled from reusable building blocks called reportlets and saved to BIDS-compliant paths.

graph LR
    SIM_DATA[Simulation Data] --> GEN[Report Generators]
    FLEX_DATA[Flex-Search Results] --> GEN
    PRE_DATA[Preprocessing Info] --> GEN
    GEN --> HTML[HTML Report]
    GEN --> PLOTS[Plots & Figures]
    style HTML fill:#2d5a27,stroke:#4a8,color:#fff

Report Generators

Simulation Reports

from tit.reporting import SimulationReportGenerator

report = SimulationReportGenerator(
    project_dir="/data/my_project",
    simulation_session_id="motor_cortex",
    subject_id="001",
)
report.add_simulation_parameters(
    conductivity_type="scalar",
    simulation_mode="TI",
    eeg_net="GSN-HydroCel-185",
    intensity_ch1=1.0,
    intensity_ch2=1.0,
)
report.add_montage(
    montage_name="motor_cortex",
    electrode_pairs=[("C3", "C4"), ("F3", "F4")],
    montage_type="TI",
)
output_path = report.generate()

Flex-Search Reports

from tit.reporting import create_flex_search_report

output_path = create_flex_search_report(
    project_dir="/data/my_project",
    subject_id="001",
    data={"config": {...}, "results": [...], "best_solution": {...}},
    output_path="/data/my_project/derivatives/ti-toolbox/reports/flex_report.html",
)

For loading results from an output directory, use the class directly:

from tit.reporting import FlexSearchReportGenerator

generator = FlexSearchReportGenerator(
    project_dir="/data/my_project",
    subject_id="001",
)
generator.load_from_output_dir("/data/my_project/derivatives/ti-toolbox/flex_output")
output_path = generator.generate()

Preprocessing Reports

from tit.reporting import create_preprocessing_report

output_path = create_preprocessing_report(
    project_dir="/data/my_project",
    subject_id="001",
    processing_steps=[],  # list of step dicts passed to add_processing_step()
    output_path=None,     # auto-generates BIDS-compliant path
    auto_scan=True,       # auto-scan directories for input/output data
)

Custom Reports with Reportlets

Reports are assembled from reusable components called reportlets. Use this approach to build custom reports:

from tit.reporting import (
    ReportAssembler,
    ReportMetadata,
    MetadataReportlet,
    ImageReportlet,
    TableReportlet,
    SummaryCardsReportlet,
    MethodsBoilerplateReportlet,
)

# Create the assembler
metadata = ReportMetadata(title="My Analysis", subject_id="001")
assembler = ReportAssembler(metadata=metadata, title="Custom Report")

# Add sections with reportlets
section = assembler.add_section("results", "Results", description="Analysis output")

cards = SummaryCardsReportlet(title="Key Metrics", columns=4)
cards.add_card(label="ROI Mean", value="0.152 V/m", color="#4CAF50")
cards.add_card(label="Focality", value="0.83", color="#2196F3")
section.add_reportlet(cards)

assembler.save("/data/output/report.html")

Available Reportlets

Base reportlets (in tit.reporting.core.base):

Reportlet Description
MetadataReportlet Key-value pairs displayed as a table or card grid
ImageReportlet Embedded images (from path, bytes, or PIL) with captions
TableReportlet Data tables from lists of dicts, lists of lists, or DataFrames
TextReportlet Plain text, HTML, or code blocks with optional copy-to-clipboard
ErrorReportlet Error and warning messages with severity levels
ReferencesReportlet Formatted citation list with DOI/URL links

Specialized reportlets (in tit.reporting.reportlets):

Reportlet Module Description
SummaryCardsReportlet metadata Colored metric cards (mean, max, focality)
ConductivityTableReportlet metadata Tissue conductivity values used in simulation
ProcessingStepReportlet metadata Collapsible pipeline steps with status and duration
ParameterListReportlet metadata Categorized parameter display
MethodsBoilerplateReportlet text Publication-ready methods text with copy button
DescriptionReportlet text Formatted text paragraphs
CommandLogReportlet text Terminal-style command execution log
TIToolboxReferencesReportlet references Citation list filtered by pipeline components
SliceSeriesReportlet images Multi-slice brain views (axial, sagittal, coronal)
MontageImageReportlet images Electrode montage visualization with pair table
MultiViewBrainReportlet images Side-by-side axial/sagittal/coronal brain views

Plotting Utilities

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

from tit.plotting import (
    plot_whole_head_roi_histogram,
    generate_static_overlay_images,
    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/
│   │   ├── simulation_report_20250101_120000.html
│   │   ├── flex_search_report_20250101_120000.html
│   │   └── pre_processing_report_20250101_120000.html
│   └── dataset_description.json
└── analysis/
    └── ...

API Reference

Report Generators

tit.reporting.generators.base_generator.BaseReportGenerator

BaseReportGenerator(project_dir: str | Path, subject_id: str | None = None, session_id: str | None = None, report_type: str = 'general')

Bases: ABC

Abstract base class for all TI-Toolbox report generators.

Provides common functionality including: - BIDS-compliant output path management - Software version collection - Error and warning tracking - Dataset description generation

Initialize the base report generator.

Parameters:

Name Type Description Default
project_dir str | Path

Path to the project directory

required
subject_id str | None

BIDS subject ID (without 'sub-' prefix)

None
session_id str | None

Optional session/run identifier

None
report_type str

Type of report being generated

'general'
Source code in tit/reporting/generators/base_generator.py
def __init__(
    self,
    project_dir: str | Path,
    subject_id: str | None = None,
    session_id: str | None = None,
    report_type: str = "general",
):
    """
    Initialize the base report generator.

    Args:
        project_dir: Path to the project directory
        subject_id: BIDS subject ID (without 'sub-' prefix)
        session_id: Optional session/run identifier
        report_type: Type of report being generated
    """
    self.project_dir = Path(project_dir)
    self.subject_id = subject_id
    self.session_id = session_id or datetime.now().strftime("%Y%m%d_%H%M%S")
    self.report_type = report_type

    # Initialize report metadata
    self.metadata = ReportMetadata(
        title=self._get_default_title(),
        subject_id=subject_id,
        session_id=session_id,
        report_type=report_type,
        project_dir=str(project_dir),
    )

    # Initialize assembler
    self.assembler = ReportAssembler(metadata=self.metadata)

    # Tracking
    self.errors: list[dict[str, Any]] = []
    self.warnings: list[dict[str, Any]] = []
    self.software_versions: dict[str, str] = {}

    # Collect software versions
    self._collect_software_versions()

add_error

add_error(message: str, context: str | None = None, step: str | None = None) -> None

Add an error to the report.

Parameters:

Name Type Description Default
message str

Error message

required
context str | None

Context (e.g., subject ID, montage name)

None
step str | None

Processing step where error occurred

None
Source code in tit/reporting/generators/base_generator.py
def add_error(
    self,
    message: str,
    context: str | None = None,
    step: str | None = None,
) -> None:
    """
    Add an error to the report.

    Args:
        message: Error message
        context: Context (e.g., subject ID, montage name)
        step: Processing step where error occurred
    """
    self.errors.append(
        {
            "message": message,
            "context": context,
            "step": step,
            "severity": SeverityLevel.ERROR.value,
            "timestamp": datetime.now().isoformat(),
        }
    )

add_warning

add_warning(message: str, context: str | None = None, step: str | None = None) -> None

Add a warning to the report.

Parameters:

Name Type Description Default
message str

Warning message

required
context str | None

Context (e.g., subject ID, montage name)

None
step str | None

Processing step where warning occurred

None
Source code in tit/reporting/generators/base_generator.py
def add_warning(
    self,
    message: str,
    context: str | None = None,
    step: str | None = None,
) -> None:
    """
    Add a warning to the report.

    Args:
        message: Warning message
        context: Context (e.g., subject ID, montage name)
        step: Processing step where warning occurred
    """
    self.warnings.append(
        {
            "message": message,
            "context": context,
            "step": step,
            "severity": SeverityLevel.WARNING.value,
            "timestamp": datetime.now().isoformat(),
        }
    )

get_output_dir

get_output_dir() -> Path

Get the BIDS-compliant output directory for reports.

Returns:

Type Description
Path

Path to the reports directory

Source code in tit/reporting/generators/base_generator.py
def get_output_dir(self) -> Path:
    """
    Get the BIDS-compliant output directory for reports.

    Returns:
        Path to the reports directory
    """
    base_dir = self.project_dir / REPORTS_BASE_DIR

    if self.subject_id:
        return base_dir / f"sub-{self.subject_id}"
    return base_dir

get_output_path

get_output_path(timestamp: str | None = None) -> Path

Get the full output path for the report file.

Parameters:

Name Type Description Default
timestamp str | None

Optional timestamp string (uses current time if not provided)

None

Returns:

Type Description
Path

Full path to the report file

Source code in tit/reporting/generators/base_generator.py
def get_output_path(self, timestamp: str | None = None) -> Path:
    """
    Get the full output path for the report file.

    Args:
        timestamp: Optional timestamp string (uses current time if not provided)

    Returns:
        Full path to the report file
    """
    if timestamp is None:
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

    prefix = self._get_report_prefix()
    filename = f"{prefix}_{timestamp}.html"

    return self.get_output_dir() / filename

generate

generate(output_path: str | Path | None = None) -> Path

Generate the HTML report.

Parameters:

Name Type Description Default
output_path str | Path | None

Optional custom output path

None

Returns:

Type Description
Path

Path to the generated report file

Source code in tit/reporting/generators/base_generator.py
def generate(self, output_path: str | Path | None = None) -> Path:
    """
    Generate the HTML report.

    Args:
        output_path: Optional custom output path

    Returns:
        Path to the generated report file
    """
    # Build the report content
    self._build_report()

    # Add standard sections
    self._add_errors_section()
    self._add_methods_section(pipeline_components=[self.report_type])
    self._add_references_section(pipeline_components=[self.report_type])

    # Ensure output directory exists
    self._ensure_output_dir()

    # Create dataset description
    self._create_dataset_description()

    # Determine output path
    if output_path:
        final_path = Path(output_path)
    else:
        final_path = self.get_output_path()

    # Save the report
    self.assembler.save(final_path)

    return final_path

tit.reporting.generators.simulation.SimulationReportGenerator

SimulationReportGenerator(project_dir: str | Path, simulation_session_id: str | None = None, subject_id: str | None = None)

Bases: BaseReportGenerator

Report generator for TI/mTI simulation pipelines.

Creates comprehensive HTML reports including: - Simulation parameters and configuration - Electrode specifications - Conductivity values - Montage configurations - Simulation results with visualizations - Methods boilerplate and references

Initialize the simulation report generator.

Parameters:

Name Type Description Default
project_dir str | Path

Path to the project directory

required
simulation_session_id str | None

Unique session identifier

None
subject_id str | None

BIDS subject ID (for single-subject reports)

None
Source code in tit/reporting/generators/simulation.py
def __init__(
    self,
    project_dir: str | Path,
    simulation_session_id: str | None = None,
    subject_id: str | None = None,
):
    """
    Initialize the simulation report generator.

    Args:
        project_dir: Path to the project directory
        simulation_session_id: Unique session identifier
        subject_id: BIDS subject ID (for single-subject reports)
    """
    super().__init__(
        project_dir=project_dir,
        subject_id=subject_id,
        session_id=simulation_session_id,
        report_type="simulation",
    )

    # Simulation-specific data
    self.simulation_parameters: dict[str, Any] = {}
    self.electrode_parameters: dict[str, Any] = {}
    self.conductivities: dict[str, dict[str, Any]] = {}
    self.subjects: list[dict[str, Any]] = []
    self.montages: list[dict[str, Any]] = []
    self.simulation_results: dict[str, dict[str, Any]] = {}
    self.visualizations: list[dict[str, Any]] = []

add_simulation_parameters

add_simulation_parameters(conductivity_type: str = 'scalar', simulation_mode: str = 'TI', eeg_net: str | None = None, intensity_ch1: float = 1.0, intensity_ch2: float = 1.0, quiet_mode: bool = False, conductivities: dict[str, Any] | None = None, **kwargs) -> None

Add simulation configuration parameters.

Parameters:

Name Type Description Default
conductivity_type str

Type of conductivity (scalar, anisotropic)

'scalar'
simulation_mode str

Simulation mode (TI, mTI)

'TI'
eeg_net str | None

EEG electrode net used

None
intensity_ch1 float

Channel 1 intensity (mA)

1.0
intensity_ch2 float

Channel 2 intensity (mA)

1.0
quiet_mode bool

Whether running in quiet mode

False
conductivities dict[str, Any] | None

Optional custom conductivity values

None
**kwargs

Additional parameters

{}
Source code in tit/reporting/generators/simulation.py
def add_simulation_parameters(
    self,
    conductivity_type: str = "scalar",
    simulation_mode: str = "TI",
    eeg_net: str | None = None,
    intensity_ch1: float = 1.0,
    intensity_ch2: float = 1.0,
    quiet_mode: bool = False,
    conductivities: dict[str, Any] | None = None,
    **kwargs,
) -> None:
    """
    Add simulation configuration parameters.

    Args:
        conductivity_type: Type of conductivity (scalar, anisotropic)
        simulation_mode: Simulation mode (TI, mTI)
        eeg_net: EEG electrode net used
        intensity_ch1: Channel 1 intensity (mA)
        intensity_ch2: Channel 2 intensity (mA)
        quiet_mode: Whether running in quiet mode
        conductivities: Optional custom conductivity values
        **kwargs: Additional parameters
    """
    self.simulation_parameters = {
        "conductivity_type": conductivity_type,
        "simulation_mode": simulation_mode,
        "eeg_net": eeg_net,
        "intensity_ch1": intensity_ch1,
        "intensity_ch2": intensity_ch2,
        "quiet_mode": quiet_mode,
        **kwargs,
    }
    if conductivities:
        self.add_conductivities(conductivities, conductivity_type)

add_electrode_parameters

add_electrode_parameters(shape: str = 'circular', dimensions: str | list[float] | None = None, gel_thickness: float | None = None, **kwargs) -> None

Add electrode specifications.

Parameters:

Name Type Description Default
shape str

Electrode shape (circular, rectangular)

'circular'
dimensions str | list[float] | None

Electrode dimensions (string or list)

None
gel_thickness float | None

Saline gel layer thickness in mm

None
**kwargs

Additional parameters

{}
Source code in tit/reporting/generators/simulation.py
def add_electrode_parameters(
    self,
    shape: str = "circular",
    dimensions: str | list[float] | None = None,
    gel_thickness: float | None = None,
    **kwargs,
) -> None:
    """
    Add electrode specifications.

    Args:
        shape: Electrode shape (circular, rectangular)
        dimensions: Electrode dimensions (string or list)
        gel_thickness: Saline gel layer thickness in mm
        **kwargs: Additional parameters
    """
    # Convert list dimensions to string
    if isinstance(dimensions, list):
        dimensions = f"{dimensions[0]}x{dimensions[1]} mm"

    self.electrode_parameters = {
        "shape": shape,
        "dimensions": dimensions,
        "gel_thickness": gel_thickness,
        **kwargs,
    }

add_conductivities

add_conductivities(conductivities: dict[str, dict[str, Any]], conductivity_type: str = 'scalar') -> None

Add tissue conductivity values.

Parameters:

Name Type Description Default
conductivities dict[str, dict[str, Any]]

Dict mapping tissue names to conductivity info

required
conductivity_type str

Type of conductivity values

'scalar'
Source code in tit/reporting/generators/simulation.py
def add_conductivities(
    self,
    conductivities: dict[str, dict[str, Any]],
    conductivity_type: str = "scalar",
) -> None:
    """
    Add tissue conductivity values.

    Args:
        conductivities: Dict mapping tissue names to conductivity info
        conductivity_type: Type of conductivity values
    """
    self.conductivities = conductivities
    self.simulation_parameters["conductivity_type"] = conductivity_type

add_subject

add_subject(subject_id: str, m2m_path: str | None = None, status: str = 'pending') -> None

Add a subject to the simulation.

Parameters:

Name Type Description Default
subject_id str

BIDS subject ID

required
m2m_path str | None

Path to the m2m folder

None
status str

Subject processing status

'pending'
Source code in tit/reporting/generators/simulation.py
def add_subject(
    self,
    subject_id: str,
    m2m_path: str | None = None,
    status: str = "pending",
) -> None:
    """
    Add a subject to the simulation.

    Args:
        subject_id: BIDS subject ID
        m2m_path: Path to the m2m folder
        status: Subject processing status
    """
    self.subjects.append(
        {
            "subject_id": subject_id,
            "m2m_path": m2m_path,
            "status": status,
        }
    )

add_montage

add_montage(montage_name: str, electrode_pairs: list[Any], montage_type: str = 'TI') -> None

Add a montage configuration.

Parameters:

Name Type Description Default
montage_name str

Name of the montage

required
electrode_pairs list[Any]

List of electrode pair specifications

required
montage_type str

Type of montage (TI, mTI, unipolar, multipolar)

'TI'
Source code in tit/reporting/generators/simulation.py
def add_montage(
    self,
    montage_name: str,
    electrode_pairs: list[Any],
    montage_type: str = "TI",
) -> None:
    """
    Add a montage configuration.

    Args:
        montage_name: Name of the montage
        electrode_pairs: List of electrode pair specifications
        montage_type: Type of montage (TI, mTI, unipolar, multipolar)
    """
    self.montages.append(
        {
            "name": montage_name,
            "electrode_pairs": self._normalize_electrode_pairs(electrode_pairs),
            "type": montage_type,
        }
    )

add_simulation_result

add_simulation_result(subject_id: str, montage_name: str, output_files: list[str] | None = None, duration: float | None = None, status: str = 'completed', metrics: dict[str, Any] | None = None) -> None

Add simulation results for a subject/montage combination.

Parameters:

Name Type Description Default
subject_id str

Subject ID

required
montage_name str

Montage name

required
output_files list[str] | None

List of output file paths

None
duration float | None

Simulation duration in seconds

None
status str

Simulation status

'completed'
metrics dict[str, Any] | None

Optional computed metrics

None
Source code in tit/reporting/generators/simulation.py
def add_simulation_result(
    self,
    subject_id: str,
    montage_name: str,
    output_files: list[str] | None = None,
    duration: float | None = None,
    status: str = "completed",
    metrics: dict[str, Any] | None = None,
) -> None:
    """
    Add simulation results for a subject/montage combination.

    Args:
        subject_id: Subject ID
        montage_name: Montage name
        output_files: List of output file paths
        duration: Simulation duration in seconds
        status: Simulation status
        metrics: Optional computed metrics
    """
    key = f"{subject_id}_{montage_name}"
    self.simulation_results[key] = {
        "subject_id": subject_id,
        "montage_name": montage_name,
        "output_files": output_files or [],
        "duration": duration,
        "status": status,
        "metrics": metrics or {},
    }

add_visualization

add_visualization(subject_id: str, montage_name: str, image_type: str, base64_data: str, title: str | None = None, caption: str | None = None) -> None

Add a visualization image.

Parameters:

Name Type Description Default
subject_id str

Subject ID

required
montage_name str

Montage name

required
image_type str

Type of visualization

required
base64_data str

Base64-encoded image data

required
title str | None

Image title

None
caption str | None

Image caption

None
Source code in tit/reporting/generators/simulation.py
def add_visualization(
    self,
    subject_id: str,
    montage_name: str,
    image_type: str,
    base64_data: str,
    title: str | None = None,
    caption: str | None = None,
) -> None:
    """
    Add a visualization image.

    Args:
        subject_id: Subject ID
        montage_name: Montage name
        image_type: Type of visualization
        base64_data: Base64-encoded image data
        title: Image title
        caption: Image caption
    """
    self.visualizations.append(
        {
            "subject_id": subject_id,
            "montage_name": montage_name,
            "image_type": image_type,
            "base64_data": base64_data,
            "title": title,
            "caption": caption,
        }
    )

tit.reporting.generators.flex_search.FlexSearchReportGenerator

FlexSearchReportGenerator(project_dir: str | Path, subject_id: str, session_id: str | None = None)

Bases: BaseReportGenerator

Report generator for flex-search optimization results.

Creates comprehensive HTML reports including: - Optimization configuration - Target ROI specification - Search results and rankings - Best solution details - Visualization of optimal electrode placement

Initialize the flex-search report generator.

Parameters:

Name Type Description Default
project_dir str | Path

Path to the project directory

required
subject_id str

BIDS subject ID

required
session_id str | None

Optional session identifier

None
Source code in tit/reporting/generators/flex_search.py
def __init__(
    self,
    project_dir: str | Path,
    subject_id: str,
    session_id: str | None = None,
):
    """
    Initialize the flex-search report generator.

    Args:
        project_dir: Path to the project directory
        subject_id: BIDS subject ID
        session_id: Optional session identifier
    """
    super().__init__(
        project_dir=project_dir,
        subject_id=subject_id,
        session_id=session_id,
        report_type="flex-search",
    )

    # Flex-search specific data
    self.config: dict[str, Any] = {}
    self.roi_info: dict[str, Any] = {}
    self.search_results: list[dict[str, Any]] = []
    self.best_solution: dict[str, Any] | None = None
    self.optimization_metrics: dict[str, Any] = {}

set_configuration

set_configuration(electrode_net: str | None = None, optimization_target: str | None = None, n_candidates: int = 100, selection_method: str = 'best', intensity_ch1: float = 1.0, intensity_ch2: float = 1.0, **kwargs) -> None

Set the optimization configuration.

Parameters:

Name Type Description Default
electrode_net str | None

EEG net used

None
optimization_target str | None

Target metric to optimize

None
n_candidates int

Number of candidate solutions evaluated

100
selection_method str

Method for selecting best solution

'best'
intensity_ch1 float

Channel 1 intensity

1.0
intensity_ch2 float

Channel 2 intensity

1.0
**kwargs

Additional configuration

{}
Source code in tit/reporting/generators/flex_search.py
def set_configuration(
    self,
    electrode_net: str | None = None,
    optimization_target: str | None = None,
    n_candidates: int = 100,
    selection_method: str = "best",
    intensity_ch1: float = 1.0,
    intensity_ch2: float = 1.0,
    **kwargs,
) -> None:
    """
    Set the optimization configuration.

    Args:
        electrode_net: EEG net used
        optimization_target: Target metric to optimize
        n_candidates: Number of candidate solutions evaluated
        selection_method: Method for selecting best solution
        intensity_ch1: Channel 1 intensity
        intensity_ch2: Channel 2 intensity
        **kwargs: Additional configuration
    """
    self.config = {
        "electrode_net": electrode_net,
        "optimization_target": optimization_target,
        "n_candidates": n_candidates,
        "selection_method": selection_method,
        "intensity_ch1": intensity_ch1,
        "intensity_ch2": intensity_ch2,
        **kwargs,
    }

set_roi_info

set_roi_info(roi_name: str, roi_type: str = 'mask', coordinates: list[float] | None = None, radius: float | None = None, volume_mm3: float | None = None, n_voxels: int | None = None, **kwargs) -> None

Set the target ROI information.

Parameters:

Name Type Description Default
roi_name str

Name of the target ROI

required
roi_type str

Type of ROI (mask, sphere, coordinates)

'mask'
coordinates list[float] | None

Center coordinates (if applicable)

None
radius float | None

Radius in mm (if sphere)

None
volume_mm3 float | None

ROI volume in mm³

None
n_voxels int | None

Number of voxels in ROI

None
**kwargs

Additional ROI info

{}
Source code in tit/reporting/generators/flex_search.py
def set_roi_info(
    self,
    roi_name: str,
    roi_type: str = "mask",
    coordinates: list[float] | None = None,
    radius: float | None = None,
    volume_mm3: float | None = None,
    n_voxels: int | None = None,
    **kwargs,
) -> None:
    """
    Set the target ROI information.

    Args:
        roi_name: Name of the target ROI
        roi_type: Type of ROI (mask, sphere, coordinates)
        coordinates: Center coordinates (if applicable)
        radius: Radius in mm (if sphere)
        volume_mm3: ROI volume in mm³
        n_voxels: Number of voxels in ROI
        **kwargs: Additional ROI info
    """
    self.roi_info = {
        "name": roi_name,
        "type": roi_type,
        "coordinates": coordinates,
        "radius": radius,
        "volume_mm3": volume_mm3,
        "n_voxels": n_voxels,
        **kwargs,
    }

add_search_result

add_search_result(rank: int, electrode_1a: str, electrode_1b: str, electrode_2a: str, electrode_2b: str, score: float, mean_field_roi: float | None = None, max_field_roi: float | None = None, focality: float | None = None, **metrics) -> None

Add a search result entry.

Parameters:

Name Type Description Default
rank int

Ranking of this solution

required
electrode_1a str

First electrode of pair 1

required
electrode_1b str

Second electrode of pair 1

required
electrode_2a str

First electrode of pair 2

required
electrode_2b str

Second electrode of pair 2

required
score float

Optimization score

required
mean_field_roi float | None

Mean field in ROI (V/m)

None
max_field_roi float | None

Max field in ROI (V/m)

None
focality float | None

Focality metric

None
**metrics

Additional metrics

{}
Source code in tit/reporting/generators/flex_search.py
def add_search_result(
    self,
    rank: int,
    electrode_1a: str,
    electrode_1b: str,
    electrode_2a: str,
    electrode_2b: str,
    score: float,
    mean_field_roi: float | None = None,
    max_field_roi: float | None = None,
    focality: float | None = None,
    **metrics,
) -> None:
    """
    Add a search result entry.

    Args:
        rank: Ranking of this solution
        electrode_1a: First electrode of pair 1
        electrode_1b: Second electrode of pair 1
        electrode_2a: First electrode of pair 2
        electrode_2b: Second electrode of pair 2
        score: Optimization score
        mean_field_roi: Mean field in ROI (V/m)
        max_field_roi: Max field in ROI (V/m)
        focality: Focality metric
        **metrics: Additional metrics
    """
    self.search_results.append(
        {
            "rank": rank,
            "electrode_1a": electrode_1a,
            "electrode_1b": electrode_1b,
            "electrode_2a": electrode_2a,
            "electrode_2b": electrode_2b,
            "pair_1": f"{electrode_1a}-{electrode_1b}",
            "pair_2": f"{electrode_2a}-{electrode_2b}",
            "score": score,
            "mean_field_roi": mean_field_roi,
            "max_field_roi": max_field_roi,
            "focality": focality,
            **metrics,
        }
    )

set_best_solution

set_best_solution(electrode_pairs: list[dict[str, str]], score: float, metrics: dict[str, Any], montage_image_base64: str | None = None, field_map_base64: str | None = None, electrode_coordinates: list[list[float]] | None = None, channel_array_indices: list[list[int]] | None = None) -> None

Set the best (selected) solution.

Parameters:

Name Type Description Default
electrode_pairs list[dict[str, str]]

List of electrode pair specs

required
score float

Final optimization score

required
metrics dict[str, Any]

Solution metrics

required
montage_image_base64 str | None

Base64 montage visualization

None
field_map_base64 str | None

Base64 field map visualization

None
Source code in tit/reporting/generators/flex_search.py
def set_best_solution(
    self,
    electrode_pairs: list[dict[str, str]],
    score: float,
    metrics: dict[str, Any],
    montage_image_base64: str | None = None,
    field_map_base64: str | None = None,
    electrode_coordinates: list[list[float]] | None = None,
    channel_array_indices: list[list[int]] | None = None,
) -> None:
    """
    Set the best (selected) solution.

    Args:
        electrode_pairs: List of electrode pair specs
        score: Final optimization score
        metrics: Solution metrics
        montage_image_base64: Base64 montage visualization
        field_map_base64: Base64 field map visualization
    """
    self.best_solution = {
        "electrode_pairs": electrode_pairs,
        "score": score,
        "metrics": metrics,
        "montage_image_base64": montage_image_base64,
        "field_map_base64": field_map_base64,
        "electrode_coordinates": electrode_coordinates,
        "channel_array_indices": channel_array_indices,
    }

populate_from_data

populate_from_data(data: dict[str, Any]) -> None

Populate the report from a data dictionary.

Parameters:

Name Type Description Default
data dict[str, Any]

Dictionary containing all optimization data

required
Source code in tit/reporting/generators/flex_search.py
def populate_from_data(self, data: dict[str, Any]) -> None:
    """
    Populate the report from a data dictionary.

    Args:
        data: Dictionary containing all optimization data
    """
    # Configuration
    if "config" in data:
        self.config = data["config"]

    # ROI info
    if "roi" in data:
        self.roi_info = data["roi"]

    # Search results
    if "results" in data:
        for i, result in enumerate(data["results"]):
            self.add_search_result(rank=i + 1, **result)

    # Best solution
    if "best_solution" in data:
        self.best_solution = data["best_solution"]

    # Optimization metrics
    if "metrics" in data:
        self.optimization_metrics = data["metrics"]

load_from_output_dir

load_from_output_dir(output_dir: str | Path) -> None

Load optimization data from an output directory.

Parameters:

Name Type Description Default
output_dir str | Path

Path to the flex-search output directory

required
Source code in tit/reporting/generators/flex_search.py
def load_from_output_dir(self, output_dir: str | Path) -> None:
    """
    Load optimization data from an output directory.

    Args:
        output_dir: Path to the flex-search output directory
    """
    output_dir = Path(output_dir)

    # Try to load results JSON
    results_file = output_dir / "optimization_results.json"
    if results_file.exists():
        with open(results_file) as f:
            data = json.load(f)
            self.populate_from_data(data)

    # Try to load configuration
    config_file = output_dir / "config.json"
    if config_file.exists():
        with open(config_file) as f:
            self.config = json.load(f)

tit.reporting.generators.flex_search.create_flex_search_report

create_flex_search_report(project_dir: str | Path, subject_id: str, data: dict[str, Any], output_path: str | Path | None = None) -> Path

Convenience function to create a flex-search report.

Parameters:

Name Type Description Default
project_dir str | Path

Path to project directory

required
subject_id str

BIDS subject ID

required
data dict[str, Any]

Dictionary containing optimization data

required
output_path str | Path | None

Optional custom output path

None

Returns:

Type Description
Path

Path to the generated report

Source code in tit/reporting/generators/flex_search.py
def create_flex_search_report(
    project_dir: str | Path,
    subject_id: str,
    data: dict[str, Any],
    output_path: str | Path | None = None,
) -> Path:
    """
    Convenience function to create a flex-search report.

    Args:
        project_dir: Path to project directory
        subject_id: BIDS subject ID
        data: Dictionary containing optimization data
        output_path: Optional custom output path

    Returns:
        Path to the generated report
    """
    generator = FlexSearchReportGenerator(
        project_dir=project_dir,
        subject_id=subject_id,
    )
    generator.populate_from_data(data)
    return generator.generate(output_path)

tit.reporting.generators.preprocessing.PreprocessingReportGenerator

PreprocessingReportGenerator(project_dir: str | Path, subject_id: str, session_id: str | None = None)

Bases: BaseReportGenerator

Report generator for preprocessing pipelines.

Creates comprehensive HTML reports including: - Input data summary - Processing steps with status - Output data summary - Software versions - Quality control visualizations - Methods boilerplate and references

Initialize the preprocessing report generator.

Parameters:

Name Type Description Default
project_dir str | Path

Path to the project directory

required
subject_id str

BIDS subject ID

required
session_id str | None

Optional session identifier

None
Source code in tit/reporting/generators/preprocessing.py
def __init__(
    self,
    project_dir: str | Path,
    subject_id: str,
    session_id: str | None = None,
):
    """
    Initialize the preprocessing report generator.

    Args:
        project_dir: Path to the project directory
        subject_id: BIDS subject ID
        session_id: Optional session identifier
    """
    super().__init__(
        project_dir=project_dir,
        subject_id=subject_id,
        session_id=session_id,
        report_type="preprocessing",
    )

    # Preprocessing-specific data
    self.input_data: dict[str, dict[str, Any]] = {}
    self.output_data: dict[str, dict[str, Any]] = {}
    self.processing_steps: list[dict[str, Any]] = []
    self.qc_images: list[dict[str, Any]] = []
    self.pipeline_config: dict[str, Any] = {}

set_pipeline_config

set_pipeline_config(**config) -> None

Set the pipeline configuration.

Parameters:

Name Type Description Default
**config

Pipeline configuration parameters

{}
Source code in tit/reporting/generators/preprocessing.py
def set_pipeline_config(self, **config) -> None:
    """
    Set the pipeline configuration.

    Args:
        **config: Pipeline configuration parameters
    """
    self.pipeline_config = config

add_input_data

add_input_data(data_type: str, file_paths: list[str], metadata: dict[str, Any] | None = None) -> None

Add input data information.

Parameters:

Name Type Description Default
data_type str

Type of data (T1w, T2w, DWI, etc.)

required
file_paths list[str]

List of input file paths

required
metadata dict[str, Any] | None

Optional metadata about the data

None
Source code in tit/reporting/generators/preprocessing.py
def add_input_data(
    self,
    data_type: str,
    file_paths: list[str],
    metadata: dict[str, Any] | None = None,
) -> None:
    """
    Add input data information.

    Args:
        data_type: Type of data (T1w, T2w, DWI, etc.)
        file_paths: List of input file paths
        metadata: Optional metadata about the data
    """
    self.input_data[data_type] = {
        "file_paths": file_paths,
        "metadata": metadata or {},
        "n_files": len(file_paths),
    }

add_output_data

add_output_data(data_type: str, file_paths: list[str], metadata: dict[str, Any] | None = None) -> None

Add output data information.

Parameters:

Name Type Description Default
data_type str

Type of data (m2m, FreeSurfer, etc.)

required
file_paths list[str]

List of output file paths

required
metadata dict[str, Any] | None

Optional metadata about the data

None
Source code in tit/reporting/generators/preprocessing.py
def add_output_data(
    self,
    data_type: str,
    file_paths: list[str],
    metadata: dict[str, Any] | None = None,
) -> None:
    """
    Add output data information.

    Args:
        data_type: Type of data (m2m, FreeSurfer, etc.)
        file_paths: List of output file paths
        metadata: Optional metadata about the data
    """
    self.output_data[data_type] = {
        "file_paths": file_paths,
        "metadata": metadata or {},
        "n_files": len(file_paths),
    }

add_processing_step

add_processing_step(step_name: str, description: str | None = None, parameters: dict[str, Any] | None = None, status: StatusType | str = PENDING, duration: float | None = None, output_files: list[str] | None = None, figures: list[dict[str, Any]] | None = None, error_message: str | None = None) -> None

Add a processing step.

Parameters:

Name Type Description Default
step_name str

Name of the processing step

required
description str | None

Step description

None
parameters dict[str, Any] | None

Step parameters

None
status StatusType | str

Step status

PENDING
duration float | None

Duration in seconds

None
output_files list[str] | None

Output file paths

None
figures list[dict[str, Any]] | None

QC figures

None
error_message str | None

Error message if failed

None
Source code in tit/reporting/generators/preprocessing.py
def add_processing_step(
    self,
    step_name: str,
    description: str | None = None,
    parameters: dict[str, Any] | None = None,
    status: StatusType | str = StatusType.PENDING,
    duration: float | None = None,
    output_files: list[str] | None = None,
    figures: list[dict[str, Any]] | None = None,
    error_message: str | None = None,
) -> None:
    """
    Add a processing step.

    Args:
        step_name: Name of the processing step
        description: Step description
        parameters: Step parameters
        status: Step status
        duration: Duration in seconds
        output_files: Output file paths
        figures: QC figures
        error_message: Error message if failed
    """
    if isinstance(status, StatusType):
        status = status.value

    self.processing_steps.append(
        {
            "name": step_name,
            "description": description,
            "parameters": parameters or {},
            "status": status,
            "duration": duration,
            "output_files": output_files or [],
            "figures": figures or [],
            "error_message": error_message,
        }
    )

    # Track errors
    if status == "failed" and error_message:
        self.add_error(error_message, step=step_name)

add_qc_image

add_qc_image(title: str, base64_data: str, step_name: str | None = None, caption: str | None = None, image_type: str = 'qc') -> None

Add a quality control image.

Parameters:

Name Type Description Default
title str

Image title

required
base64_data str

Base64-encoded image data

required
step_name str | None

Associated processing step

None
caption str | None

Image caption

None
image_type str

Type of QC image

'qc'
Source code in tit/reporting/generators/preprocessing.py
def add_qc_image(
    self,
    title: str,
    base64_data: str,
    step_name: str | None = None,
    caption: str | None = None,
    image_type: str = "qc",
) -> None:
    """
    Add a quality control image.

    Args:
        title: Image title
        base64_data: Base64-encoded image data
        step_name: Associated processing step
        caption: Image caption
        image_type: Type of QC image
    """
    self.qc_images.append(
        {
            "title": title,
            "base64_data": base64_data,
            "step_name": step_name,
            "caption": caption,
            "image_type": image_type,
        }
    )

scan_for_data

scan_for_data() -> None

Automatically scan directories for input and output data.

Only scans for outputs that correspond to the processing steps that were added to this report.

Source code in tit/reporting/generators/preprocessing.py
def scan_for_data(self) -> None:
    """
    Automatically scan directories for input and output data.

    Only scans for outputs that correspond to the processing steps
    that were added to this report.
    """
    # Determine which steps were run based on added processing steps
    step_names = {s["name"].lower() for s in self.processing_steps}

    # Input data - look for raw data
    rawdata_dir = self.project_dir / "rawdata" / f"sub-{self.subject_id}"
    if rawdata_dir.exists():
        # Look for anatomical data
        anat_dir = rawdata_dir / "anat"
        if anat_dir.exists():
            t1_files = list(anat_dir.glob("*T1w*.nii*"))
            if t1_files:
                self.add_input_data("T1w", [str(f) for f in t1_files])

            t2_files = list(anat_dir.glob("*T2w*.nii*"))
            if t2_files:
                self.add_input_data("T2w", [str(f) for f in t2_files])

        # Look for diffusion data (only if QSI steps were run)
        if any("qsi" in s or "dti" in s or "diffusion" in s for s in step_names):
            dwi_dir = rawdata_dir / "dwi"
            if dwi_dir.exists():
                dwi_files = list(dwi_dir.glob("*.nii*"))
                if dwi_files:
                    self.add_input_data("DWI", [str(f) for f in dwi_files])

    # Output data - look for derivatives based on steps that were run
    derivatives_dir = self.project_dir / "derivatives"

    # DICOM conversion outputs (NIfTI files)
    if any("dicom" in s for s in step_names):
        nifti_dir = self.project_dir / "rawdata" / f"sub-{self.subject_id}"
        if nifti_dir.exists():
            nifti_files = list(nifti_dir.rglob("*.nii*"))
            if nifti_files:
                self.add_output_data("NIfTI (converted)", [str(nifti_dir)])

    # FreeSurfer outputs - only if recon step was run
    if any("freesurfer" in s or "recon" in s for s in step_names):
        fs_dir = derivatives_dir / "freesurfer" / f"sub-{self.subject_id}"
        if fs_dir.exists():
            self.add_output_data("FreeSurfer", [str(fs_dir)])

    # SimNIBS m2m outputs - only if charm/m2m step was run
    if any("simnibs" in s or "charm" in s or "m2m" in s for s in step_names):
        # Try multiple possible paths
        m2m_paths = [
            derivatives_dir
            / "SimNIBS"
            / f"sub-{self.subject_id}"
            / f"m2m_{self.subject_id}",
            derivatives_dir
            / "simnibs"
            / f"sub-{self.subject_id}"
            / f"m2m_{self.subject_id}",
            derivatives_dir / "simnibs" / f"m2m_sub-{self.subject_id}",
        ]
        for m2m_dir in m2m_paths:
            if m2m_dir.exists():
                self.add_output_data("SimNIBS m2m", [str(m2m_dir)])
                break

    # Tissue analysis outputs
    if any("tissue" in s for s in step_names):
        tissue_dir = derivatives_dir / "tissue_analysis" / f"sub-{self.subject_id}"
        if tissue_dir.exists():
            self.add_output_data("Tissue Analysis", [str(tissue_dir)])

    # QSIPrep outputs - only if qsiprep step was run
    if any("qsiprep" in s for s in step_names):
        qsiprep_dir = derivatives_dir / "qsiprep" / f"sub-{self.subject_id}"
        if qsiprep_dir.exists():
            self.add_output_data("QSIPrep", [str(qsiprep_dir)])

    # QSIRecon outputs - only if qsirecon step was run
    if any("qsirecon" in s for s in step_names):
        qsirecon_dir = derivatives_dir / "qsirecon" / f"sub-{self.subject_id}"
        if qsirecon_dir.exists():
            self.add_output_data("QSIRecon", [str(qsirecon_dir)])

    # DTI outputs - only if DTI step was run
    if any("dti" in s for s in step_names):
        dti_dir = derivatives_dir / "dti" / f"sub-{self.subject_id}"
        if dti_dir.exists():
            self.add_output_data("DTI Tensors", [str(dti_dir)])

tit.reporting.generators.preprocessing.create_preprocessing_report

create_preprocessing_report(project_dir: str | Path, subject_id: str, processing_steps: list[dict[str, Any]] | None = None, output_path: str | Path | None = None, auto_scan: bool = True) -> Path

Convenience function to create a preprocessing report.

Parameters:

Name Type Description Default
project_dir str | Path

Path to project directory

required
subject_id str

BIDS subject ID

required
processing_steps list[dict[str, Any]] | None

List of processing step dictionaries

None
output_path str | Path | None

Optional custom output path

None
auto_scan bool

Whether to auto-scan for data

True

Returns:

Type Description
Path

Path to the generated report

Source code in tit/reporting/generators/preprocessing.py
def create_preprocessing_report(
    project_dir: str | Path,
    subject_id: str,
    processing_steps: list[dict[str, Any]] | None = None,
    output_path: str | Path | None = None,
    auto_scan: bool = True,
) -> Path:
    """
    Convenience function to create a preprocessing report.

    Args:
        project_dir: Path to project directory
        subject_id: BIDS subject ID
        processing_steps: List of processing step dictionaries
        output_path: Optional custom output path
        auto_scan: Whether to auto-scan for data

    Returns:
        Path to the generated report
    """
    generator = PreprocessingReportGenerator(
        project_dir=project_dir,
        subject_id=subject_id,
    )

    if auto_scan:
        generator.scan_for_data()

    if processing_steps:
        for step in processing_steps:
            generator.add_processing_step(**step)

    return generator.generate(output_path)

Report Assembly

tit.reporting.core.assembler.ReportAssembler

ReportAssembler(metadata: ReportMetadata | None = None, title: str | None = None)

Assembles reportlets into a complete HTML report.

The assembler manages sections, handles ordering, generates the table of contents, and renders the final HTML document.

Initialize the report assembler.

Parameters:

Name Type Description Default
metadata ReportMetadata | None

Report metadata (title, subject, etc.)

None
title str | None

Report title (overrides metadata.title if provided)

None
Source code in tit/reporting/core/assembler.py
def __init__(
    self,
    metadata: ReportMetadata | None = None,
    title: str | None = None,
):
    """
    Initialize the report assembler.

    Args:
        metadata: Report metadata (title, subject, etc.)
        title: Report title (overrides metadata.title if provided)
    """
    self.metadata = metadata or ReportMetadata(title=title or "Report")
    if title:
        self.metadata.title = title

    self.sections: list[ReportSection] = []
    self._custom_css: str = ""
    self._custom_js: str = ""

add_section

add_section(section_id: str, title: str, description: str | None = None, collapsed: bool = False, order: int | None = None) -> ReportSection

Add a new section to the report.

Parameters:

Name Type Description Default
section_id str

Unique identifier for the section

required
title str

Section title

required
description str | None

Optional section description

None
collapsed bool

Whether section starts collapsed

False
order int | None

Sort order (lower = earlier in report)

None

Returns:

Type Description
ReportSection

The created ReportSection object

Source code in tit/reporting/core/assembler.py
def add_section(
    self,
    section_id: str,
    title: str,
    description: str | None = None,
    collapsed: bool = False,
    order: int | None = None,
) -> ReportSection:
    """
    Add a new section to the report.

    Args:
        section_id: Unique identifier for the section
        title: Section title
        description: Optional section description
        collapsed: Whether section starts collapsed
        order: Sort order (lower = earlier in report)

    Returns:
        The created ReportSection object
    """
    if order is None:
        order = len(self.sections)

    section = ReportSection(
        section_id=section_id,
        title=title,
        description=description,
        collapsed=collapsed,
        order=order,
    )
    self.sections.append(section)
    return section

get_section

get_section(section_id: str) -> ReportSection | None

Get a section by its ID.

Parameters:

Name Type Description Default
section_id str

The section identifier

required

Returns:

Type Description
ReportSection | None

The ReportSection or None if not found

Source code in tit/reporting/core/assembler.py
def get_section(self, section_id: str) -> ReportSection | None:
    """
    Get a section by its ID.

    Args:
        section_id: The section identifier

    Returns:
        The ReportSection or None if not found
    """
    for section in self.sections:
        if section.section_id == section_id:
            return section
    return None

add_reportlet_to_section

add_reportlet_to_section(section_id: str, reportlet: Any, create_if_missing: bool = True, section_title: str | None = None) -> None

Add a reportlet to a specific section.

Parameters:

Name Type Description Default
section_id str

The section identifier

required
reportlet Any

The reportlet to add

required
create_if_missing bool

Create section if it doesn't exist

True
section_title str | None

Title for new section (if created)

None
Source code in tit/reporting/core/assembler.py
def add_reportlet_to_section(
    self,
    section_id: str,
    reportlet: Any,
    create_if_missing: bool = True,
    section_title: str | None = None,
) -> None:
    """
    Add a reportlet to a specific section.

    Args:
        section_id: The section identifier
        reportlet: The reportlet to add
        create_if_missing: Create section if it doesn't exist
        section_title: Title for new section (if created)
    """
    section = self.get_section(section_id)

    if section is None:
        if create_if_missing:
            title = section_title or section_id.replace("_", " ").title()
            section = self.add_section(section_id, title)
        else:
            raise ValueError(f"Section '{section_id}' not found")

    section.add_reportlet(reportlet)

set_custom_css

set_custom_css(css: str) -> None

Add custom CSS styles to the report.

Source code in tit/reporting/core/assembler.py
def set_custom_css(self, css: str) -> None:
    """Add custom CSS styles to the report."""
    self._custom_css = css

set_custom_js

set_custom_js(js: str) -> None

Add custom JavaScript to the report.

Source code in tit/reporting/core/assembler.py
def set_custom_js(self, js: str) -> None:
    """Add custom JavaScript to the report."""
    self._custom_js = js

render_toc

render_toc() -> str

Render the table of contents as HTML.

Returns:

Type Description
str

HTML string for the table of contents

Source code in tit/reporting/core/assembler.py
def render_toc(self) -> str:
    """
    Render the table of contents as HTML.

    Returns:
        HTML string for the table of contents
    """
    sorted_sections = sorted(self.sections, key=lambda s: s.order)

    links = []
    for section in sorted_sections:
        links.append(
            f'<li><a href="#{section.section_id}">{section.title}</a></li>'
        )

    return f'<ul class="toc-list">{"".join(links)}</ul>'

render_metadata

render_metadata() -> str

Render the header metadata as HTML.

Returns:

Type Description
str

HTML string for the header metadata

Source code in tit/reporting/core/assembler.py
def render_metadata(self) -> str:
    """
    Render the header metadata as HTML.

    Returns:
        HTML string for the header metadata
    """
    parts = []

    if self.metadata.subject_id:
        parts.append(
            f"<span>Subject: <strong>{self.metadata.subject_id}</strong></span>"
        )

    if self.metadata.session_id:
        parts.append(
            f"<span>Session: <strong>{self.metadata.session_id}</strong></span>"
        )

    parts.append(
        f'<span>Generated: <strong>{self.metadata.generation_time.strftime("%Y-%m-%d %H:%M:%S")}</strong></span>'
    )

    return f'<div class="header-meta">{"".join(parts)}</div>'

render_sections

render_sections() -> str

Render all sections as HTML.

Returns:

Type Description
str

HTML string for all sections

Source code in tit/reporting/core/assembler.py
def render_sections(self) -> str:
    """
    Render all sections as HTML.

    Returns:
        HTML string for all sections
    """
    sorted_sections = sorted(self.sections, key=lambda s: s.order)
    return "\n".join(section.render_html() for section in sorted_sections)

render_html

render_html() -> str

Render the complete report as HTML.

Returns:

Type Description
str

Complete HTML document as a string

Source code in tit/reporting/core/assembler.py
def render_html(self) -> str:
    """
    Render the complete report as HTML.

    Returns:
        Complete HTML document as a string
    """
    content = self.render_sections()
    toc_html = self.render_toc()
    metadata_html = self.render_metadata()

    return get_html_template(
        title=self.metadata.title,
        content=content,
        toc_html=toc_html,
        metadata_html=metadata_html,
        custom_css=self._custom_css,
        custom_js=self._custom_js,
    )

save

save(output_path: str | Path, create_dirs: bool = True) -> Path

Save the report to a file.

Parameters:

Name Type Description Default
output_path str | Path

Path to save the HTML file

required
create_dirs bool

Create parent directories if needed

True

Returns:

Type Description
Path

Path to the saved file

Source code in tit/reporting/core/assembler.py
def save(
    self,
    output_path: str | Path,
    create_dirs: bool = True,
) -> Path:
    """
    Save the report to a file.

    Args:
        output_path: Path to save the HTML file
        create_dirs: Create parent directories if needed

    Returns:
        Path to the saved file
    """
    output_path = Path(output_path)

    if create_dirs:
        output_path.parent.mkdir(parents=True, exist_ok=True)

    html_content = self.render_html()
    output_path.write_text(html_content, encoding="utf-8")

    return output_path

to_dict

to_dict() -> dict[str, Any]

Convert the report to a dictionary representation.

Returns:

Type Description
dict[str, Any]

Dictionary containing all report data

Source code in tit/reporting/core/assembler.py
def to_dict(self) -> dict[str, Any]:
    """
    Convert the report to a dictionary representation.

    Returns:
        Dictionary containing all report data
    """
    return {
        "metadata": self.metadata.to_dict(),
        "sections": [s.to_dict() for s in self.sections],
    }

from_dict classmethod

from_dict(data: dict[str, Any]) -> Self

Create a ReportAssembler from a dictionary.

Parameters:

Name Type Description Default
data dict[str, Any]

Dictionary containing report data

required

Returns:

Type Description
Self

Reconstructed ReportAssembler instance

Note

This reconstructs the structure but not the reportlet instances. Use this for loading report metadata, not for full reconstruction.

Source code in tit/reporting/core/assembler.py
@classmethod
def from_dict(cls, data: dict[str, Any]) -> Self:
    """
    Create a ReportAssembler from a dictionary.

    Args:
        data: Dictionary containing report data

    Returns:
        Reconstructed ReportAssembler instance

    Note:
        This reconstructs the structure but not the reportlet instances.
        Use this for loading report metadata, not for full reconstruction.
    """
    metadata_dict = data.get("metadata", {})
    metadata = ReportMetadata(
        title=metadata_dict.get("title", "Report"),
        subject_id=metadata_dict.get("subject_id"),
        session_id=metadata_dict.get("session_id"),
        report_type=metadata_dict.get("report_type", "general"),
        project_dir=metadata_dict.get("project_dir"),
    )

    assembler = cls(metadata=metadata)

    for section_data in data.get("sections", []):
        assembler.add_section(
            section_id=section_data["section_id"],
            title=section_data["title"],
            description=section_data.get("description"),
            collapsed=section_data.get("collapsed", False),
            order=section_data.get("order", 0),
        )

    return assembler

tit.reporting.core.protocols.ReportMetadata dataclass

ReportMetadata(title: str, subject_id: str | None = None, session_id: str | None = None, report_type: str = 'general', generation_time: datetime = now(), software_versions: dict[str, str] = dict(), project_dir: str | None = None, bids_version: str = '1.8.0', dataset_type: str = 'derivative')

Metadata for a generated report.

to_dict

to_dict() -> dict[str, Any]

Convert metadata to dictionary.

Source code in tit/reporting/core/protocols.py
def to_dict(self) -> dict[str, Any]:
    """Convert metadata to dictionary."""
    return {
        "title": self.title,
        "subject_id": self.subject_id,
        "session_id": self.session_id,
        "report_type": self.report_type,
        "generation_time": self.generation_time.isoformat(),
        "software_versions": self.software_versions,
        "project_dir": self.project_dir,
        "bids_version": self.bids_version,
        "dataset_type": self.dataset_type,
    }

tit.reporting.core.protocols.ReportSection dataclass

ReportSection(section_id: str, title: str, reportlets: list[Any] = list(), description: str | None = None, collapsed: bool = False, order: int = 0)

A section within a report containing multiple reportlets.

add_reportlet

add_reportlet(reportlet: Any) -> None

Add a reportlet to this section.

Source code in tit/reporting/core/protocols.py
def add_reportlet(self, reportlet: Any) -> None:
    """Add a reportlet to this section."""
    self.reportlets.append(reportlet)

render_html

render_html() -> str

Render the section and all its reportlets as HTML.

Source code in tit/reporting/core/protocols.py
def render_html(self) -> str:
    """Render the section and all its reportlets as HTML."""
    collapse_class = "collapsible" if self.collapsed else ""
    content_parts = []

    for reportlet in self.reportlets:
        content_parts.append(reportlet.render_html())

    content = "\n".join(content_parts)

    description_html = ""
    if self.description:
        description_html = f'<p class="section-description">{self.description}</p>'

    return f"""
    <section id="{self.section_id}" class="report-section {collapse_class}">
        <h2 class="section-title">{self.title}</h2>
        {description_html}
        <div class="section-content">
            {content}
        </div>
    </section>
    """

to_dict

to_dict() -> dict[str, Any]

Convert section to dictionary.

Source code in tit/reporting/core/protocols.py
def to_dict(self) -> dict[str, Any]:
    """Convert section to dictionary."""
    return {
        "section_id": self.section_id,
        "title": self.title,
        "description": self.description,
        "collapsed": self.collapsed,
        "order": self.order,
        "reportlets": [r.to_dict() for r in self.reportlets],
    }

Base Reportlets

tit.reporting.core.base.MetadataReportlet

MetadataReportlet(data: dict[str, Any], title: str | None = None, display_mode: str = 'table', columns: int = 2)

Bases: BaseReportlet

Reportlet for displaying metadata as key-value pairs.

Supports two display modes: - 'table': Traditional table layout - 'cards': Modern card grid layout

Source code in tit/reporting/core/base.py
def __init__(
    self,
    data: dict[str, Any],
    title: str | None = None,
    display_mode: str = "table",
    columns: int = 2,
):
    super().__init__(title)
    self.data = data
    self.display_mode = display_mode
    self.columns = columns

render_html

render_html() -> str

Render metadata as HTML table or cards.

Source code in tit/reporting/core/base.py
def render_html(self) -> str:
    """Render metadata as HTML table or cards."""
    if self.display_mode == "cards":
        return self._render_cards()
    return self._render_table()

tit.reporting.core.base.ImageReportlet

ImageReportlet(image_source: str | Path | bytes | Any | None = None, title: str | None = None, caption: str | None = None, alt_text: str | None = None, width: str | None = None, height: str | None = None)

Bases: BaseReportlet

Reportlet for displaying images.

Supports embedding images as base64 or referencing external paths. Images can be loaded from file paths, PIL Images, or raw bytes.

Source code in tit/reporting/core/base.py
def __init__(
    self,
    image_source: str | Path | bytes | Any | None = None,
    title: str | None = None,
    caption: str | None = None,
    alt_text: str | None = None,
    width: str | None = None,
    height: str | None = None,
):
    super().__init__(title)
    self.caption = caption
    self.alt_text = alt_text or title or "Image"
    self.width = width
    self.height = height
    self._base64_data: str | None = None
    self._mime_type: str = "image/png"

    if image_source is not None:
        self._load_image(image_source)

set_base64_data

set_base64_data(data: str, mime_type: str = 'image/png') -> None

Directly set base64 encoded image data.

Source code in tit/reporting/core/base.py
def set_base64_data(self, data: str, mime_type: str = "image/png") -> None:
    """Directly set base64 encoded image data."""
    self._base64_data = data
    self._mime_type = mime_type

render_html

render_html() -> str

Render image as HTML.

Source code in tit/reporting/core/base.py
def render_html(self) -> str:
    """Render image as HTML."""
    if not self._base64_data:
        return f"""
        <div class="reportlet image-reportlet" id="{self.reportlet_id}">
            <div class="image-placeholder">
                <em>No image available</em>
            </div>
        </div>
        """

    style_parts = []
    if self.width:
        style_parts.append(f"max-width: {self.width}")
    if self.height:
        style_parts.append(f"max-height: {self.height}")
    style = "; ".join(style_parts) if style_parts else ""

    title_html = f"<h3>{self._title}</h3>" if self._title else ""
    caption_html = (
        f'<figcaption class="image-caption">{self.caption}</figcaption>'
        if self.caption
        else ""
    )

    return f"""
    <div class="reportlet image-reportlet" id="{self.reportlet_id}">
        {title_html}
        <figure class="image-figure">
            <img src="data:{self._mime_type};base64,{self._base64_data}"
                 alt="{self.alt_text}"
                 style="{style}"
                 class="report-image" />
            {caption_html}
        </figure>
    </div>
    """

tit.reporting.core.base.TableReportlet

TableReportlet(data: list[dict] | list[list] | Any, title: str | None = None, headers: list[str] | None = None, sortable: bool = False, striped: bool = True, compact: bool = False)

Bases: BaseReportlet

Reportlet for displaying tabular data.

Supports various input formats including lists of dicts, lists of lists, and pandas DataFrames.

Source code in tit/reporting/core/base.py
def __init__(
    self,
    data: list[dict] | list[list] | Any,
    title: str | None = None,
    headers: list[str] | None = None,
    sortable: bool = False,
    striped: bool = True,
    compact: bool = False,
):
    super().__init__(title)
    self.headers: list[str] = []
    self.rows: list[list[Any]] = []
    self.sortable = sortable
    self.striped = striped
    self.compact = compact

    self._process_data(data, headers)

render_html

render_html() -> str

Render table as HTML.

Source code in tit/reporting/core/base.py
def render_html(self) -> str:
    """Render table as HTML."""
    if not self.rows and not self.headers:
        return f"""
        <div class="reportlet table-reportlet" id="{self.reportlet_id}">
            <em>No data available</em>
        </div>
        """

    classes = ["data-table"]
    if self.striped:
        classes.append("striped")
    if self.compact:
        classes.append("compact")
    if self.sortable:
        classes.append("sortable")

    header_html = ""
    if self.headers:
        header_cells = "".join(f"<th>{h}</th>" for h in self.headers)
        header_html = f"<thead><tr>{header_cells}</tr></thead>"

    body_rows = []
    for row in self.rows:
        cells = "".join(f"<td>{self._format_cell(c)}</td>" for c in row)
        body_rows.append(f"<tr>{cells}</tr>")
    body_html = f"<tbody>{''.join(body_rows)}</tbody>"

    title_html = f"<h3>{self._title}</h3>" if self._title else ""

    return f"""
    <div class="reportlet table-reportlet" id="{self.reportlet_id}">
        {title_html}
        <div class="table-wrapper">
            <table class="{' '.join(classes)}">
                {header_html}
                {body_html}
            </table>
        </div>
    </div>
    """

tit.reporting.core.base.TextReportlet

TextReportlet(content: str, title: str | None = None, content_type: str = 'text', copyable: bool = False, monospace: bool = False)

Bases: BaseReportlet

Reportlet for displaying text content.

Supports plain text, HTML, and markdown-style formatting. Includes optional copy-to-clipboard functionality for boilerplate text.

Source code in tit/reporting/core/base.py
def __init__(
    self,
    content: str,
    title: str | None = None,
    content_type: str = "text",
    copyable: bool = False,
    monospace: bool = False,
):
    super().__init__(title)
    self.content = content
    self.content_type = content_type  # 'text', 'html', 'code'
    self.copyable = copyable
    self.monospace = monospace

render_html

render_html() -> str

Render text content as HTML.

Source code in tit/reporting/core/base.py
def render_html(self) -> str:
    """Render text content as HTML."""
    classes = ["text-content"]
    if self.monospace:
        classes.append("monospace")
    if self.copyable:
        classes.append("copyable")

    # Format content based on type
    if self.content_type == "html":
        formatted_content = self.content
    elif self.content_type == "code":
        formatted_content = (
            f"<pre><code>{self._escape_html(self.content)}</code></pre>"
        )
    else:
        # Plain text - convert newlines to paragraphs
        paragraphs = self.content.split("\n\n")
        formatted_content = "".join(f"<p>{p}</p>" for p in paragraphs if p.strip())

    title_html = f"<h3>{self._title}</h3>" if self._title else ""

    copy_button = ""
    if self.copyable:
        copy_button = f"""
        <button class="copy-btn" onclick="copyToClipboard('{self.reportlet_id}-content')">
            Copy to Clipboard
        </button>
        """

    return f"""
    <div class="reportlet text-reportlet" id="{self.reportlet_id}">
        {title_html}
        {copy_button}
        <div class="{' '.join(classes)}" id="{self.reportlet_id}-content">
            {formatted_content}
        </div>
    </div>
    """

tit.reporting.core.base.ErrorReportlet

ErrorReportlet(messages: list[dict[str, Any]] | None = None, title: str | None = None)

Bases: BaseReportlet

Reportlet for displaying errors and warnings.

Supports different severity levels with appropriate styling.

Source code in tit/reporting/core/base.py
def __init__(
    self,
    messages: list[dict[str, Any]] | None = None,
    title: str | None = None,
):
    super().__init__(title or "Errors and Warnings")
    self.messages: list[dict[str, Any]] = messages or []

add_message

add_message(message: str, severity: SeverityLevel = ERROR, context: str | None = None, step: str | None = None) -> None

Add an error or warning message.

Source code in tit/reporting/core/base.py
def add_message(
    self,
    message: str,
    severity: SeverityLevel = SeverityLevel.ERROR,
    context: str | None = None,
    step: str | None = None,
) -> None:
    """Add an error or warning message."""
    self.messages.append(
        {
            "message": message,
            "severity": (
                severity.value if isinstance(severity, SeverityLevel) else severity
            ),
            "context": context,
            "step": step,
        }
    )

add_error

add_error(message: str, context: str | None = None, step: str | None = None) -> None

Add an error message.

Source code in tit/reporting/core/base.py
def add_error(
    self, message: str, context: str | None = None, step: str | None = None
) -> None:
    """Add an error message."""
    self.add_message(message, SeverityLevel.ERROR, context, step)

add_warning

add_warning(message: str, context: str | None = None, step: str | None = None) -> None

Add a warning message.

Source code in tit/reporting/core/base.py
def add_warning(
    self, message: str, context: str | None = None, step: str | None = None
) -> None:
    """Add a warning message."""
    self.add_message(message, SeverityLevel.WARNING, context, step)

render_html

render_html() -> str

Render errors and warnings as HTML.

Source code in tit/reporting/core/base.py
def render_html(self) -> str:
    """Render errors and warnings as HTML."""
    if not self.messages:
        return f"""
        <div class="reportlet error-reportlet success" id="{self.reportlet_id}">
            <div class="success-message">
                <span class="status-icon">[OK]</span>
                No errors or warnings
            </div>
        </div>
        """

    message_items = []
    for msg in self.messages:
        severity = msg.get("severity", "error")
        icon = (
            "[!]"
            if severity == "warning"
            else "[X]" if severity in ("error", "critical") else "[i]"
        )
        context = msg.get("context", "")
        step = msg.get("step", "")

        context_html = (
            f'<span class="error-context">[{context}]</span>' if context else ""
        )
        step_html = f'<span class="error-step">Step: {step}</span>' if step else ""

        message_items.append(f"""
            <div class="message-item {severity}">
                <span class="severity-icon">{icon}</span>
                <div class="message-content">
                    {context_html}
                    <span class="message-text">{msg["message"]}</span>
                    {step_html}
                </div>
            </div>
            """)

    title_html = f"<h3>{self._title}</h3>" if self._title else ""

    return f"""
    <div class="reportlet error-reportlet" id="{self.reportlet_id}">
        {title_html}
        <div class="messages-list">
            {"".join(message_items)}
        </div>
    </div>
    """

tit.reporting.core.base.ReferencesReportlet

ReferencesReportlet(references: list[dict[str, str]] | None = None, title: str | None = None)

Bases: BaseReportlet

Reportlet for displaying citations and references.

Automatically formats references in a consistent style.

Source code in tit/reporting/core/base.py
def __init__(
    self,
    references: list[dict[str, str]] | None = None,
    title: str | None = None,
):
    super().__init__(title or "References")
    self.references: list[dict[str, str]] = references or []

add_reference

add_reference(key: str, citation: str, url: str | None = None, doi: str | None = None) -> None

Add a reference.

Source code in tit/reporting/core/base.py
def add_reference(
    self,
    key: str,
    citation: str,
    url: str | None = None,
    doi: str | None = None,
) -> None:
    """Add a reference."""
    self.references.append(
        {
            "key": key,
            "citation": citation,
            "url": url,
            "doi": doi,
        }
    )

render_html

render_html() -> str

Render references as HTML.

Source code in tit/reporting/core/base.py
def render_html(self) -> str:
    """Render references as HTML."""
    if not self.references:
        return ""

    ref_items = []
    for ref in self.references:
        citation = ref["citation"]
        key = ref.get("key", "")

        # Add DOI link if available
        if ref.get("doi"):
            doi_link = (
                f'<a href="https://doi.org/{ref["doi"]}" target="_blank">[DOI]</a>'
            )
            citation = f"{citation} {doi_link}"
        elif ref.get("url"):
            url_link = f'<a href="{ref["url"]}" target="_blank">[Link]</a>'
            citation = f"{citation} {url_link}"

        ref_items.append(f"""
            <li class="reference-item" id="ref-{key}">
                <span class="ref-key">[{key}]</span>
                <span class="ref-citation">{citation}</span>
            </li>
            """)

    title_html = f"<h3>{self._title}</h3>"

    return f"""
    <div class="reportlet references-reportlet" id="{self.reportlet_id}">
        {title_html}
        <ol class="references-list">
            {"".join(ref_items)}
        </ol>
    </div>
    """

Specialized Reportlets

tit.reporting.reportlets.metadata.SummaryCardsReportlet

SummaryCardsReportlet(title: str | None = None, cards: list[dict[str, Any]] | None = None, columns: int = 4)

Bases: BaseReportlet

Reportlet for displaying key summary metrics as cards.

Shows important values in a prominent card grid layout.

Initialize the summary cards reportlet.

Parameters:

Name Type Description Default
title str | None

Title for the summary section

None
cards list[dict[str, Any]] | None

List of card data dicts

None
columns int

Number of columns in grid

4
Source code in tit/reporting/reportlets/metadata.py
def __init__(
    self,
    title: str | None = None,
    cards: list[dict[str, Any]] | None = None,
    columns: int = 4,
):
    """
    Initialize the summary cards reportlet.

    Args:
        title: Title for the summary section
        cards: List of card data dicts
        columns: Number of columns in grid
    """
    super().__init__(title)
    self.cards: list[dict[str, Any]] = cards or []
    self.columns = columns

add_card

add_card(label: str, value: Any, icon: str | None = None, color: str | None = None, subtitle: str | None = None) -> None

Add a summary card.

Parameters:

Name Type Description Default
label str

Card label

required
value Any

Card value

required
icon str | None

Optional icon character

None
color str | None

Optional accent color

None
subtitle str | None

Optional subtitle text

None
Source code in tit/reporting/reportlets/metadata.py
def add_card(
    self,
    label: str,
    value: Any,
    icon: str | None = None,
    color: str | None = None,
    subtitle: str | None = None,
) -> None:
    """
    Add a summary card.

    Args:
        label: Card label
        value: Card value
        icon: Optional icon character
        color: Optional accent color
        subtitle: Optional subtitle text
    """
    self.cards.append(
        {
            "label": label,
            "value": value,
            "icon": icon,
            "color": color,
            "subtitle": subtitle,
        }
    )

render_html

render_html() -> str

Render the summary cards as HTML.

Source code in tit/reporting/reportlets/metadata.py
def render_html(self) -> str:
    """Render the summary cards as HTML."""
    if not self.cards:
        return ""

    card_items = []
    for card in self.cards:
        icon_html = (
            f'<span class="card-icon">{card["icon"]}</span>'
            if card.get("icon")
            else ""
        )
        subtitle_html = (
            f'<div class="card-subtitle">{card["subtitle"]}</div>'
            if card.get("subtitle")
            else ""
        )
        style = f'border-top-color: {card["color"]};' if card.get("color") else ""

        card_items.append(f"""
            <div class="summary-card" style="{style}">
                {icon_html}
                <div class="card-label">{card["label"]}</div>
                <div class="card-value">{card["value"]}</div>
                {subtitle_html}
            </div>
            """)

    title_html = f"<h3>{self._title}</h3>" if self._title else ""

    return f"""
    <div class="reportlet summary-cards-reportlet" id="{self.reportlet_id}">
        {title_html}
        <div class="card-grid columns-{self.columns}">
            {"".join(card_items)}
        </div>
    </div>
    """

tit.reporting.reportlets.metadata.ConductivityTableReportlet

ConductivityTableReportlet(conductivities: dict[str, dict[str, Any]] | None = None, title: str | None = None, show_sources: bool = True, conductivity_type: str = 'scalar')

Bases: BaseReportlet

Reportlet for displaying tissue conductivity values.

Shows conductivity values for different tissue types with their sources/references.

Initialize the conductivity table reportlet.

Parameters:

Name Type Description Default
conductivities dict[str, dict[str, Any]] | None

Dict mapping tissue names to conductivity info

None
title str | None

Title for the table

None
show_sources bool

Whether to show source references

True
conductivity_type str

Type of conductivity (scalar, anisotropic, etc.)

'scalar'
Source code in tit/reporting/reportlets/metadata.py
def __init__(
    self,
    conductivities: dict[str, dict[str, Any]] | None = None,
    title: str | None = None,
    show_sources: bool = True,
    conductivity_type: str = "scalar",
):
    """
    Initialize the conductivity table reportlet.

    Args:
        conductivities: Dict mapping tissue names to conductivity info
        title: Title for the table
        show_sources: Whether to show source references
        conductivity_type: Type of conductivity (scalar, anisotropic, etc.)
    """
    super().__init__(title or "Tissue Conductivities")
    self.conductivities = conductivities or DEFAULT_CONDUCTIVITIES.copy()
    self.show_sources = show_sources
    self.conductivity_type = conductivity_type

set_conductivity

set_conductivity(tissue: str, value: float, unit: str = 'S/m', source: str | None = None) -> None

Set conductivity for a tissue type.

Parameters:

Name Type Description Default
tissue str

Tissue name

required
value float

Conductivity value

required
unit str

Unit of measurement

'S/m'
source str | None

Source reference

None
Source code in tit/reporting/reportlets/metadata.py
def set_conductivity(
    self,
    tissue: str,
    value: float,
    unit: str = "S/m",
    source: str | None = None,
) -> None:
    """
    Set conductivity for a tissue type.

    Args:
        tissue: Tissue name
        value: Conductivity value
        unit: Unit of measurement
        source: Source reference
    """
    self.conductivities[tissue] = {
        "value": value,
        "unit": unit,
        "source": source or "User-defined",
    }

render_html

render_html() -> str

Render the conductivity table as HTML.

Source code in tit/reporting/reportlets/metadata.py
def render_html(self) -> str:
    """Render the conductivity table as HTML."""
    # Build table headers
    headers = ["Tissue", "Conductivity"]
    if self.show_sources:
        headers.append("Source")

    header_cells = "".join(f"<th>{h}</th>" for h in headers)

    # Build table rows
    rows = []
    for tissue, data in self.conductivities.items():
        # Handle both string keys ("white_matter") and integer keys (1)
        if isinstance(tissue, int):
            # Integer key - use the 'name' field from data if available
            tissue_name = data.get("name", f"Tissue {tissue}")
        else:
            tissue_name = str(tissue).replace("_", " ").title()
        # Handle both 'value' and 'conductivity' field names
        value = data.get("value", data.get("conductivity", 0))
        unit = data.get("unit", "S/m")

        cells = [
            f"<td>{tissue_name}</td>",
            f"<td>{value:.4f} {unit}</td>",
        ]

        if self.show_sources:
            # Handle both 'source' and 'reference' field names
            source = data.get("source", data.get("reference", "—"))
            cells.append(f"<td class='source-cell'>{source}</td>")

        rows.append(f"<tr>{''.join(cells)}</tr>")

    title_html = f"<h3>{self._title}</h3>" if self._title else ""
    type_badge = (
        f'<span class="conductivity-type-badge">{self.conductivity_type}</span>'
    )

    return f"""
    <div class="reportlet conductivity-reportlet" id="{self.reportlet_id}">
        {title_html}
        {type_badge}
        <div class="table-wrapper">
            <table class="data-table conductivity-table striped">
                <thead>
                    <tr>{header_cells}</tr>
                </thead>
                <tbody>
                    {"".join(rows)}
                </tbody>
            </table>
        </div>
    </div>
    """

tit.reporting.reportlets.metadata.ProcessingStepReportlet

ProcessingStepReportlet(title: str | None = None, steps: list[dict[str, Any]] | None = None)

Bases: BaseReportlet

Reportlet for displaying processing pipeline steps.

Shows collapsible processing steps with status, duration, and optional details.

Initialize the processing step reportlet.

Parameters:

Name Type Description Default
title str | None

Title for the processing steps section

None
steps list[dict[str, Any]] | None

List of step dictionaries

None
Source code in tit/reporting/reportlets/metadata.py
def __init__(
    self,
    title: str | None = None,
    steps: list[dict[str, Any]] | None = None,
):
    """
    Initialize the processing step reportlet.

    Args:
        title: Title for the processing steps section
        steps: List of step dictionaries
    """
    super().__init__(title or "Processing Steps")
    self.steps: list[dict[str, Any]] = steps or []

add_step

add_step(name: str, description: str | None = None, status: StatusType | str = PENDING, duration: float | None = None, parameters: dict[str, Any] | None = None, output_files: list[str] | None = None, error_message: str | None = None) -> None

Add a processing step.

Parameters:

Name Type Description Default
name str

Step name

required
description str | None

Step description

None
status StatusType | str

Step status (pending, running, completed, failed, skipped)

PENDING
duration float | None

Duration in seconds

None
parameters dict[str, Any] | None

Step parameters

None
output_files list[str] | None

List of output file paths

None
error_message str | None

Error message if failed

None
Source code in tit/reporting/reportlets/metadata.py
def add_step(
    self,
    name: str,
    description: str | None = None,
    status: StatusType | str = StatusType.PENDING,
    duration: float | None = None,
    parameters: dict[str, Any] | None = None,
    output_files: list[str] | None = None,
    error_message: str | None = None,
) -> None:
    """
    Add a processing step.

    Args:
        name: Step name
        description: Step description
        status: Step status (pending, running, completed, failed, skipped)
        duration: Duration in seconds
        parameters: Step parameters
        output_files: List of output file paths
        error_message: Error message if failed
    """
    if isinstance(status, StatusType):
        status = status.value

    self.steps.append(
        {
            "name": name,
            "description": description,
            "status": status,
            "duration": duration,
            "parameters": parameters or {},
            "output_files": output_files or [],
            "error_message": error_message,
        }
    )

render_html

render_html() -> str

Render the processing steps as HTML.

Source code in tit/reporting/reportlets/metadata.py
def render_html(self) -> str:
    """Render the processing steps as HTML."""
    if not self.steps:
        return f"""
        <div class="reportlet processing-steps-reportlet" id="{self.reportlet_id}">
            <em>No processing steps recorded</em>
        </div>
        """

    step_items = []
    for i, step in enumerate(self.steps):
        step_id = f"{self.reportlet_id}-step-{i}"
        status = step.get("status", "pending")
        icon = self._get_status_icon(status)
        s = step.get("duration")
        if s is None:
            duration = "—"
        elif s < 60:
            duration = f"{s:.1f}s"
        elif s < 3600:
            duration = f"{s / 60:.1f}m"
        else:
            duration = f"{s / 3600:.1f}h"

        # Build parameters section
        params_html = ""
        if step.get("parameters"):
            param_rows = "".join(
                f"<tr><td>{k}</td><td>{v}</td></tr>"
                for k, v in step["parameters"].items()
            )
            params_html = f"""
            <div class="step-parameters">
                <strong>Parameters:</strong>
                <table class="data-table compact">
                    <tbody>{param_rows}</tbody>
                </table>
            </div>
            """

        # Build output files section
        outputs_html = ""
        if step.get("output_files"):
            file_list = "".join(f"<li>{f}</li>" for f in step["output_files"])
            outputs_html = f"""
            <div class="step-outputs">
                <strong>Output Files:</strong>
                <ul>{file_list}</ul>
            </div>
            """

        # Build error section
        error_html = ""
        if step.get("error_message"):
            error_html = f"""
            <div class="step-error">
                <strong>Error:</strong>
                <span class="error-text">{step["error_message"]}</span>
            </div>
            """

        description_html = ""
        if step.get("description"):
            description_html = (
                f'<p class="step-description">{step["description"]}</p>'
            )

        step_items.append(f"""
            <div class="processing-step" id="{step_id}">
                <div class="step-header" onclick="toggleStep('{step_id}')">
                    <span class="step-status {status}">{icon}</span>
                    <span class="step-name">{step["name"]}</span>
                    <span class="step-duration">{duration}</span>
                </div>
                <div class="step-content" id="{step_id}-content">
                    {description_html}
                    {params_html}
                    {outputs_html}
                    {error_html}
                </div>
            </div>
            """)

    title_html = f"<h3>{self._title}</h3>" if self._title else ""

    # Summary counts
    completed = sum(1 for s in self.steps if s.get("status") == "completed")
    failed = sum(1 for s in self.steps if s.get("status") == "failed")
    total = len(self.steps)

    summary_html = f"""
    <div class="steps-summary">
        <span class="summary-item completed">{completed}/{total} completed</span>
        {f'<span class="summary-item failed">{failed} failed</span>' if failed > 0 else ''}
    </div>
    """

    return f"""
    <div class="reportlet processing-steps-reportlet" id="{self.reportlet_id}">
        {title_html}
        {summary_html}
        <div class="steps-list">
            {"".join(step_items)}
        </div>
    </div>
    """

tit.reporting.reportlets.metadata.ParameterListReportlet

ParameterListReportlet(title: str | None = None, parameters: dict[str, dict[str, Any]] | None = None)

Bases: BaseReportlet

Reportlet for displaying a categorized list of parameters.

Organizes parameters into groups with clear visual hierarchy.

Initialize the parameter list reportlet.

Parameters:

Name Type Description Default
title str | None

Title for the parameters section

None
parameters dict[str, dict[str, Any]] | None

Dict of category -> {param_name: param_value}

None
Source code in tit/reporting/reportlets/metadata.py
def __init__(
    self,
    title: str | None = None,
    parameters: dict[str, dict[str, Any]] | None = None,
):
    """
    Initialize the parameter list reportlet.

    Args:
        title: Title for the parameters section
        parameters: Dict of category -> {param_name: param_value}
    """
    super().__init__(title or "Parameters")
    self.parameters: dict[str, dict[str, Any]] = parameters or {}

add_category

add_category(category: str, params: dict[str, Any]) -> None

Add a parameter category.

Parameters:

Name Type Description Default
category str

Category name

required
params dict[str, Any]

Dict of parameter name to value

required
Source code in tit/reporting/reportlets/metadata.py
def add_category(self, category: str, params: dict[str, Any]) -> None:
    """
    Add a parameter category.

    Args:
        category: Category name
        params: Dict of parameter name to value
    """
    self.parameters[category] = params

add_parameter

add_parameter(category: str, name: str, value: Any) -> None

Add a single parameter to a category.

Parameters:

Name Type Description Default
category str

Category name

required
name str

Parameter name

required
value Any

Parameter value

required
Source code in tit/reporting/reportlets/metadata.py
def add_parameter(self, category: str, name: str, value: Any) -> None:
    """
    Add a single parameter to a category.

    Args:
        category: Category name
        name: Parameter name
        value: Parameter value
    """
    if category not in self.parameters:
        self.parameters[category] = {}
    self.parameters[category][name] = value

render_html

render_html() -> str

Render the parameter list as HTML.

Source code in tit/reporting/reportlets/metadata.py
def render_html(self) -> str:
    """Render the parameter list as HTML."""
    if not self.parameters:
        return ""

    category_sections = []
    for category, params in self.parameters.items():
        rows = []
        for name, value in params.items():
            formatted_name = name.replace("_", " ").title()
            formatted_value = self._format_value(value)
            rows.append(f"""<tr>
                    <td class="param-name">{formatted_name}</td>
                    <td class="param-value">{formatted_value}</td>
                </tr>""")

        category_sections.append(f"""
            <div class="parameter-category">
                <h4>{category}</h4>
                <table class="data-table compact">
                    <tbody>{"".join(rows)}</tbody>
                </table>
            </div>
            """)

    title_html = f"<h3>{self._title}</h3>" if self._title else ""

    return f"""
    <div class="reportlet parameter-list-reportlet" id="{self.reportlet_id}">
        {title_html}
        {"".join(category_sections)}
    </div>
    """

tit.reporting.reportlets.text.MethodsBoilerplateReportlet

MethodsBoilerplateReportlet(title: str | None = None, boilerplate_text: str | None = None, pipeline_type: str = 'simulation', parameters: dict[str, Any] | None = None)

Bases: BaseReportlet

Reportlet for displaying methods section boilerplate text.

Generates publication-ready text describing the methods used in the analysis, with a copy-to-clipboard button.

Initialize the methods boilerplate reportlet.

Parameters:

Name Type Description Default
title str | None

Title for the section

None
boilerplate_text str | None

Pre-written boilerplate text

None
pipeline_type str

Type of pipeline (simulation, preprocessing, optimization)

'simulation'
parameters dict[str, Any] | None

Parameters to include in generated text

None
Source code in tit/reporting/reportlets/text.py
def __init__(
    self,
    title: str | None = None,
    boilerplate_text: str | None = None,
    pipeline_type: str = "simulation",
    parameters: dict[str, Any] | None = None,
):
    """
    Initialize the methods boilerplate reportlet.

    Args:
        title: Title for the section
        boilerplate_text: Pre-written boilerplate text
        pipeline_type: Type of pipeline (simulation, preprocessing, optimization)
        parameters: Parameters to include in generated text
    """
    super().__init__(title or "Methods Boilerplate")
    self._boilerplate_text = boilerplate_text
    self.pipeline_type = pipeline_type
    self.parameters = parameters or {}

set_boilerplate

set_boilerplate(text: str) -> None

Set the boilerplate text directly.

Source code in tit/reporting/reportlets/text.py
def set_boilerplate(self, text: str) -> None:
    """Set the boilerplate text directly."""
    self._boilerplate_text = text

generate_boilerplate

generate_boilerplate() -> str

Generate boilerplate text based on pipeline type and parameters.

Returns:

Type Description
str

Generated boilerplate text

Source code in tit/reporting/reportlets/text.py
def generate_boilerplate(self) -> str:
    """
    Generate boilerplate text based on pipeline type and parameters.

    Returns:
        Generated boilerplate text
    """
    if self._boilerplate_text:
        return self._boilerplate_text

    match self.pipeline_type:
        case "simulation":
            return self._generate_simulation_boilerplate()
        case "preprocessing":
            return self._generate_preprocessing_boilerplate()
        case "optimization":
            return self._generate_optimization_boilerplate()
        case _:
            return self._generate_generic_boilerplate()

render_html

render_html() -> str

Render the boilerplate text as HTML.

Source code in tit/reporting/reportlets/text.py
def render_html(self) -> str:
    """Render the boilerplate text as HTML."""
    boilerplate = self.generate_boilerplate()

    title_html = f"<h3>{self._title}</h3>" if self._title else ""

    return f"""
    <div class="reportlet methods-boilerplate-reportlet" id="{self.reportlet_id}">
        {title_html}
        <p class="boilerplate-intro">
            The following text can be used as a starting point for the methods
            section of a publication. Please verify and adapt as needed.
        </p>
        <button class="copy-btn" onclick="copyToClipboard('{self.reportlet_id}-content')">
            Copy to Clipboard
        </button>
        <div class="text-content monospace copyable" id="{self.reportlet_id}-content">
            {boilerplate}
        </div>
    </div>
    """

tit.reporting.reportlets.text.DescriptionReportlet

DescriptionReportlet(content: str, title: str | None = None, format_type: str = 'paragraphs')

Bases: BaseReportlet

Reportlet for displaying descriptive text content.

Renders paragraphs of text with optional formatting.

Initialize the description reportlet.

Parameters:

Name Type Description Default
content str

Text content to display

required
title str | None

Optional section title

None
format_type str

How to format content (paragraphs, html, preformatted)

'paragraphs'
Source code in tit/reporting/reportlets/text.py
def __init__(
    self,
    content: str,
    title: str | None = None,
    format_type: str = "paragraphs",
):
    """
    Initialize the description reportlet.

    Args:
        content: Text content to display
        title: Optional section title
        format_type: How to format content (paragraphs, html, preformatted)
    """
    super().__init__(title)
    self.content = content
    self.format_type = format_type

render_html

render_html() -> str

Render the description text as HTML.

Source code in tit/reporting/reportlets/text.py
def render_html(self) -> str:
    """Render the description text as HTML."""
    title_html = f"<h3>{self._title}</h3>" if self._title else ""

    if self.format_type == "html":
        formatted_content = self.content
    elif self.format_type == "preformatted":
        formatted_content = f"<pre>{self._escape_html(self.content)}</pre>"
    else:
        # Split into paragraphs
        paragraphs = self.content.split("\n\n")
        formatted_content = "".join(
            f"<p>{p.strip()}</p>" for p in paragraphs if p.strip()
        )

    return f"""
    <div class="reportlet description-reportlet" id="{self.reportlet_id}">
        {title_html}
        <div class="text-content">
            {formatted_content}
        </div>
    </div>
    """

tit.reporting.reportlets.text.CommandLogReportlet

CommandLogReportlet(title: str | None = None, commands: list[dict[str, str]] | None = None)

Bases: BaseReportlet

Reportlet for displaying command execution logs.

Shows commands that were run with their outputs in a terminal-like display.

Initialize the command log reportlet.

Parameters:

Name Type Description Default
title str | None

Optional section title

None
commands list[dict[str, str]] | None

List of command dicts with 'command' and optional 'output'

None
Source code in tit/reporting/reportlets/text.py
def __init__(
    self,
    title: str | None = None,
    commands: list[dict[str, str]] | None = None,
):
    """
    Initialize the command log reportlet.

    Args:
        title: Optional section title
        commands: List of command dicts with 'command' and optional 'output'
    """
    super().__init__(title or "Command Log")
    self.commands: list[dict[str, str]] = commands or []

add_command

add_command(command: str, output: str | None = None, status: str = 'success') -> None

Add a command to the log.

Parameters:

Name Type Description Default
command str

The command that was executed

required
output str | None

Command output (if any)

None
status str

Execution status (success, error)

'success'
Source code in tit/reporting/reportlets/text.py
def add_command(
    self,
    command: str,
    output: str | None = None,
    status: str = "success",
) -> None:
    """
    Add a command to the log.

    Args:
        command: The command that was executed
        output: Command output (if any)
        status: Execution status (success, error)
    """
    self.commands.append(
        {
            "command": command,
            "output": output or "",
            "status": status,
        }
    )

render_html

render_html() -> str

Render the command log as HTML.

Source code in tit/reporting/reportlets/text.py
def render_html(self) -> str:
    """Render the command log as HTML."""
    if not self.commands:
        return ""

    command_items = []
    for cmd in self.commands:
        status_class = "success" if cmd.get("status") == "success" else "error"
        output_html = ""
        if cmd.get("output"):
            output_html = f'<div class="command-output">{self._escape_html(cmd["output"])}</div>'

        command_items.append(f"""
            <div class="command-item {status_class}">
                <div class="command-prompt">$ {self._escape_html(cmd["command"])}</div>
                {output_html}
            </div>
            """)

    title_html = f"<h3>{self._title}</h3>" if self._title else ""

    return f"""
    <div class="reportlet command-log-reportlet" id="{self.reportlet_id}">
        {title_html}
        <div class="command-log">
            {"".join(command_items)}
        </div>
    </div>
    """

tit.reporting.reportlets.references.TIToolboxReferencesReportlet

TIToolboxReferencesReportlet(title: str | None = None, include_defaults: bool = True, pipeline_components: list[str] | None = None)

Bases: ReferencesReportlet

Specialized references reportlet with TI-Toolbox default citations.

Automatically includes relevant citations based on the pipeline components used.

Initialize the TI-Toolbox references reportlet.

Parameters:

Name Type Description Default
title str | None

Section title

None
include_defaults bool

Whether to include default TI-Toolbox refs

True
pipeline_components list[str] | None

List of components used (to filter refs)

None
Source code in tit/reporting/reportlets/references.py
def __init__(
    self,
    title: str | None = None,
    include_defaults: bool = True,
    pipeline_components: list[str] | None = None,
):
    """
    Initialize the TI-Toolbox references reportlet.

    Args:
        title: Section title
        include_defaults: Whether to include default TI-Toolbox refs
        pipeline_components: List of components used (to filter refs)
    """
    super().__init__(title=title or "References")

    self.pipeline_components = pipeline_components or []

    if include_defaults:
        self._add_default_references()

add_default_reference

add_default_reference(key: str) -> bool

Add a default reference by key.

Parameters:

Name Type Description Default
key str

The reference key (e.g., 'freesurfer', 'qsiprep')

required

Returns:

Type Description
bool

True if reference was found and added, False otherwise

Source code in tit/reporting/reportlets/references.py
def add_default_reference(self, key: str) -> bool:
    """
    Add a default reference by key.

    Args:
        key: The reference key (e.g., 'freesurfer', 'qsiprep')

    Returns:
        True if reference was found and added, False otherwise
    """
    for ref_data in DEFAULT_REFERENCES:
        if ref_data["key"] == key:
            # Check if already added
            if not any(r["key"] == key for r in self.references):
                self.add_reference(
                    key=ref_data["key"],
                    citation=ref_data["citation"],
                    doi=ref_data.get("doi"),
                    url=ref_data.get("url"),
                )
            return True
    return False

tit.reporting.reportlets.images.SliceSeriesReportlet

SliceSeriesReportlet(title: str | None = None, slices: list[dict[str, Any]] | None = None, orientation: str = 'axial', caption: str | None = None)

Bases: BaseReportlet

Reportlet for displaying a series of brain slices.

Displays multiple slices (typically 7) across axial, sagittal, or coronal views, commonly used for QC visualizations.

Initialize the slice series reportlet.

Parameters:

Name Type Description Default
title str | None

Title for the slice series

None
slices list[dict[str, Any]] | None

List of slice data dicts with 'base64' and optional 'label'

None
orientation str

View orientation (axial, sagittal, coronal)

'axial'
caption str | None

Optional caption text

None
Source code in tit/reporting/reportlets/images.py
def __init__(
    self,
    title: str | None = None,
    slices: list[dict[str, Any]] | None = None,
    orientation: str = "axial",
    caption: str | None = None,
):
    """
    Initialize the slice series reportlet.

    Args:
        title: Title for the slice series
        slices: List of slice data dicts with 'base64' and optional 'label'
        orientation: View orientation (axial, sagittal, coronal)
        caption: Optional caption text
    """
    super().__init__(title)
    self.slices: list[dict[str, Any]] = slices or []
    self.orientation = orientation
    self.caption = caption

add_slice

add_slice(image_data: str | bytes | Path | Any, label: str | None = None, mime_type: str = 'image/png') -> None

Add a slice to the series.

Parameters:

Name Type Description Default
image_data str | bytes | Path | Any

Base64 string, bytes, path, or PIL Image

required
label str | None

Optional label for this slice

None
mime_type str

MIME type of the image

'image/png'
Source code in tit/reporting/reportlets/images.py
def add_slice(
    self,
    image_data: str | bytes | Path | Any,
    label: str | None = None,
    mime_type: str = "image/png",
) -> None:
    """
    Add a slice to the series.

    Args:
        image_data: Base64 string, bytes, path, or PIL Image
        label: Optional label for this slice
        mime_type: MIME type of the image
    """
    base64_data = self._process_image(image_data)
    self.slices.append(
        {
            "base64": base64_data,
            "label": label,
            "mime_type": mime_type,
        }
    )

load_from_files

load_from_files(file_paths: list[str | Path]) -> None

Load slices from a list of image files.

Parameters:

Name Type Description Default
file_paths list[str | Path]

List of paths to slice images

required
Source code in tit/reporting/reportlets/images.py
def load_from_files(self, file_paths: list[str | Path]) -> None:
    """
    Load slices from a list of image files.

    Args:
        file_paths: List of paths to slice images
    """
    for i, path in enumerate(file_paths):
        path = Path(path)
        if path.exists():
            self.add_slice(path, label=f"Slice {i + 1}")

render_html

render_html() -> str

Render the slice series as HTML.

Source code in tit/reporting/reportlets/images.py
def render_html(self) -> str:
    """Render the slice series as HTML."""
    if not self.slices:
        return f"""
        <div class="reportlet slice-series-reportlet" id="{self.reportlet_id}">
            <div class="image-placeholder">
                <em>No slices available</em>
            </div>
        </div>
        """

    slice_images = []
    for slice_data in self.slices:
        mime_type = slice_data.get("mime_type", "image/png")
        label = slice_data.get("label", "")
        label_html = f'<span class="slice-label">{label}</span>' if label else ""

        slice_images.append(f"""
            <div class="slice-image">
                <img src="data:{mime_type};base64,{slice_data["base64"]}"
                     alt="{label or 'Brain slice'}" />
                {label_html}
            </div>
            """)

    title_html = f"<h3>{self._title}</h3>" if self._title else ""
    caption_html = (
        f'<p class="series-caption">{self.caption}</p>' if self.caption else ""
    )

    return f"""
    <div class="reportlet slice-series-reportlet {self.orientation}" id="{self.reportlet_id}">
        {title_html}
        <div class="slice-series">
            {"".join(slice_images)}
        </div>
        {caption_html}
    </div>
    """

tit.reporting.reportlets.images.MontageImageReportlet

MontageImageReportlet(title: str | None = None, image_source: str | Path | bytes | Any | None = None, electrode_pairs: list[dict[str, Any]] | None = None, montage_name: str | None = None)

Bases: BaseReportlet

Reportlet for displaying electrode montage visualizations.

Shows electrode placement with labeled pairs and optional intensity annotations.

Initialize the montage image reportlet.

Parameters:

Name Type Description Default
title str | None

Title for the montage

None
image_source str | Path | bytes | Any | None

Montage image (path, bytes, or PIL Image)

None
electrode_pairs list[dict[str, Any]] | None

List of electrode pair configurations

None
montage_name str | None

Name of the montage

None
Source code in tit/reporting/reportlets/images.py
def __init__(
    self,
    title: str | None = None,
    image_source: str | Path | bytes | Any | None = None,
    electrode_pairs: list[dict[str, Any]] | None = None,
    montage_name: str | None = None,
):
    """
    Initialize the montage image reportlet.

    Args:
        title: Title for the montage
        image_source: Montage image (path, bytes, or PIL Image)
        electrode_pairs: List of electrode pair configurations
        montage_name: Name of the montage
    """
    super().__init__(title)
    self._base64_data: str | None = None
    self._mime_type: str = "image/png"
    self.electrode_pairs = electrode_pairs or []
    self.montage_name = montage_name

    if image_source is not None:
        self._load_image(image_source)

set_base64_data

set_base64_data(data: str, mime_type: str = 'image/png') -> None

Directly set base64 encoded image data.

Source code in tit/reporting/reportlets/images.py
def set_base64_data(self, data: str, mime_type: str = "image/png") -> None:
    """Directly set base64 encoded image data."""
    self._base64_data = data
    self._mime_type = mime_type

add_electrode_pair

add_electrode_pair(name: str, electrode1: str, electrode2: str, intensity: float | None = None) -> None

Add an electrode pair to the montage info.

Parameters:

Name Type Description Default
name str

Name of the pair (e.g., "Pair 1")

required
electrode1 str

First electrode position

required
electrode2 str

Second electrode position

required
intensity float | None

Optional current intensity

None
Source code in tit/reporting/reportlets/images.py
def add_electrode_pair(
    self,
    name: str,
    electrode1: str,
    electrode2: str,
    intensity: float | None = None,
) -> None:
    """
    Add an electrode pair to the montage info.

    Args:
        name: Name of the pair (e.g., "Pair 1")
        electrode1: First electrode position
        electrode2: Second electrode position
        intensity: Optional current intensity
    """
    self.electrode_pairs.append(
        {
            "name": name,
            "electrode1": electrode1,
            "electrode2": electrode2,
            "intensity": intensity,
        }
    )

render_html

render_html() -> str

Render the montage image as HTML.

Source code in tit/reporting/reportlets/images.py
def render_html(self) -> str:
    """Render the montage image as HTML."""
    title_html = (
        f"<h3>{self._title or self.montage_name or 'Electrode Montage'}</h3>"
    )

    # Electrode pairs table
    pairs_html = ""
    if self.electrode_pairs:
        rows = []
        for pair in self.electrode_pairs:
            intensity_value = pair.get("intensity")
            if intensity_value is None or intensity_value == "":
                intensity_str = "—"
            else:
                try:
                    intensity_str = f"{float(intensity_value):.2f} mA"
                except (TypeError, ValueError):
                    intensity_str = str(intensity_value)
            rows.append(f"""
                <tr>
                    <td>{pair.get("name", "")}</td>
                    <td>{pair.get("electrode1", "")}</td>
                    <td>{pair.get("electrode2", "")}</td>
                    <td>{intensity_str}</td>
                </tr>
                """)

        pairs_html = f"""
        <div class="electrode-pairs">
            <table class="data-table compact">
                <thead>
                    <tr>
                        <th>Pair</th>
                        <th>Electrode 1</th>
                        <th>Electrode 2</th>
                        <th>Intensity</th>
                    </tr>
                </thead>
                <tbody>
                    {"".join(rows)}
                </tbody>
            </table>
        </div>
        """

    # Image display
    image_html = ""
    if self._base64_data:
        image_html = f"""
        <figure class="montage-figure">
            <img src="data:{self._mime_type};base64,{self._base64_data}"
                 alt="{self.montage_name or 'Electrode montage'}"
                 class="report-image montage-image" />
        </figure>
        """
    else:
        image_html = """
        <div class="image-placeholder">
            <em>No montage image available</em>
        </div>
        """

    return f"""
    <div class="reportlet montage-reportlet" id="{self.reportlet_id}">
        {title_html}
        <div class="montage-content">
            {image_html}
            {pairs_html}
        </div>
    </div>
    """

tit.reporting.reportlets.images.MultiViewBrainReportlet

MultiViewBrainReportlet(title: str | None = None, axial_image: str | Path | bytes | None = None, sagittal_image: str | Path | bytes | None = None, coronal_image: str | Path | bytes | None = None, caption: str | None = None)

Bases: BaseReportlet

Reportlet for displaying brain images in multiple views.

Shows the same brain data in axial, sagittal, and coronal orientations side by side.

Initialize the multi-view brain reportlet.

Parameters:

Name Type Description Default
title str | None

Title for the visualization

None
axial_image str | Path | bytes | None

Axial view image

None
sagittal_image str | Path | bytes | None

Sagittal view image

None
coronal_image str | Path | bytes | None

Coronal view image

None
caption str | None

Optional caption

None
Source code in tit/reporting/reportlets/images.py
def __init__(
    self,
    title: str | None = None,
    axial_image: str | Path | bytes | None = None,
    sagittal_image: str | Path | bytes | None = None,
    coronal_image: str | Path | bytes | None = None,
    caption: str | None = None,
):
    """
    Initialize the multi-view brain reportlet.

    Args:
        title: Title for the visualization
        axial_image: Axial view image
        sagittal_image: Sagittal view image
        coronal_image: Coronal view image
        caption: Optional caption
    """
    super().__init__(title)
    self.views: dict[str, str | None] = {
        "axial": None,
        "sagittal": None,
        "coronal": None,
    }
    self.caption = caption

    if axial_image:
        self.set_view("axial", axial_image)
    if sagittal_image:
        self.set_view("sagittal", sagittal_image)
    if coronal_image:
        self.set_view("coronal", coronal_image)

set_view

set_view(view_name: str, image_data: str | Path | bytes | Any) -> None

Set an image for a specific view.

Parameters:

Name Type Description Default
view_name str

One of 'axial', 'sagittal', 'coronal'

required
image_data str | Path | bytes | Any

Image data (path, bytes, base64, or PIL Image)

required
Source code in tit/reporting/reportlets/images.py
def set_view(self, view_name: str, image_data: str | Path | bytes | Any) -> None:
    """
    Set an image for a specific view.

    Args:
        view_name: One of 'axial', 'sagittal', 'coronal'
        image_data: Image data (path, bytes, base64, or PIL Image)
    """
    if view_name not in self.views:
        raise ValueError(f"Invalid view name: {view_name}")

    if isinstance(image_data, (str, Path)):
        path = Path(image_data)
        if path.exists():
            with open(path, "rb") as f:
                self.views[view_name] = base64.b64encode(f.read()).decode("utf-8")
        else:
            # Assume already base64
            self.views[view_name] = str(image_data)
    elif isinstance(image_data, bytes):
        self.views[view_name] = base64.b64encode(image_data).decode("utf-8")
    else:
        # Assume PIL Image
        try:
            buffer = io.BytesIO()
            image_data.save(buffer, format="PNG")
            self.views[view_name] = base64.b64encode(buffer.getvalue()).decode(
                "utf-8"
            )
        except (AttributeError, ValueError):
            pass

render_html

render_html() -> str

Render the multi-view visualization as HTML.

Source code in tit/reporting/reportlets/images.py
def render_html(self) -> str:
    """Render the multi-view visualization as HTML."""
    title_html = f"<h3>{self._title}</h3>" if self._title else ""

    view_panels = []
    for view_name, base64_data in self.views.items():
        if base64_data:
            view_panels.append(f"""
                <div class="view-panel {view_name}">
                    <div class="view-label">{view_name.capitalize()}</div>
                    <img src="data:image/png;base64,{base64_data}"
                         alt="{view_name} view"
                         class="view-image" />
                </div>
                """)
        else:
            view_panels.append(f"""
                <div class="view-panel {view_name}">
                    <div class="view-label">{view_name.capitalize()}</div>
                    <div class="view-placeholder">Not available</div>
                </div>
                """)

    caption_html = (
        f'<p class="multiview-caption">{self.caption}</p>' if self.caption else ""
    )

    return f"""
    <div class="reportlet multiview-reportlet" id="{self.reportlet_id}">
        {title_html}
        <div class="multiview-grid">
            {"".join(view_panels)}
        </div>
        {caption_html}
    </div>
    """