Skip to content

reporting

tit.reporting

TI-Toolbox Reportlet-Based Reporting System.

A modular, NiPreps-inspired reporting system for TI-Toolbox that generates self-contained HTML reports across preprocessing, simulation, and flex-search modules.

ReportletType

Bases: Enum

Enumeration of reportlet types.

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

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

SeverityLevel

Bases: Enum

Severity levels for errors and warnings.

StatusType

Bases: Enum

Status types for processing steps.

BaseReportlet

BaseReportlet(title: str | None = None)

Bases: ABC

Abstract base class for all reportlets.

Source code in tit/reporting/core/base.py
def __init__(self, title: str | None = None):
    self._title = title
    self._id = str(uuid.uuid4())[:8]

reportlet_type abstractmethod property

reportlet_type: ReportletType

Return the type of this reportlet.

reportlet_id property

reportlet_id: str

Return a unique identifier for this reportlet.

title property

title: str | None

Return the title of this reportlet.

render_html abstractmethod

render_html() -> str

Render the reportlet as HTML.

Source code in tit/reporting/core/base.py
@abstractmethod
def render_html(self) -> str:
    """Render the reportlet as HTML."""
    pass

to_dict abstractmethod

to_dict() -> dict[str, Any]

Convert the reportlet to a dictionary representation.

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

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

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>
    """

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>
    """

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>
    """

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>
    """

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>
    """

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

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>
    """

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>
    """

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>
    """

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>
    """

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>
    """

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>
    """

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>
    """

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>
    """

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>
    """

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>
    """

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

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

get_default_references

get_default_references() -> list[dict[str, str]]

Get the list of default TI-Toolbox references.

Returns:

Type Description
list[dict[str, str]]

List of reference dictionaries

Source code in tit/reporting/reportlets/references.py
def get_default_references() -> list[dict[str, str]]:
    """
    Get the list of default TI-Toolbox references.

    Returns:
        List of reference dictionaries
    """
    return DEFAULT_REFERENCES.copy()

get_reference_by_key

get_reference_by_key(key: str) -> dict[str, str] | None

Get a specific reference by its key.

Parameters:

Name Type Description Default
key str

The reference key

required

Returns:

Type Description
dict[str, str] | None

Reference dictionary or None if not found

Source code in tit/reporting/reportlets/references.py
def get_reference_by_key(key: str) -> dict[str, str] | None:
    """
    Get a specific reference by its key.

    Args:
        key: The reference key

    Returns:
        Reference dictionary or None if not found
    """
    for ref in DEFAULT_REFERENCES:
        if ref["key"] == key:
            return ref.copy()
    return None

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)