Skip to content

generators

tit.reporting.generators

Report generators for TI-Toolbox modules.

This module provides specialized report generators for different TI-Toolbox pipelines.

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

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,
        }
    )

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)

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)])

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)

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)