Skip to content

aimbat._cli

Cyclopts command groups for the AIMBAT CLI, re-exported for aimbat.app.

Modules:

Name Description
align

Align seismograms using ICCS or MCCC.

common

Shared decorators, parameters, and table formatting for CLI command modules.

data

Manage data sources in an AIMBAT project.

db

Manage the AIMBAT project database schema.

event

View and manage events in the AIMBAT project.

plot

Create plots for seismograms and ICCS results.

project

Manage AIMBAT projects.

seismogram

View and manage seismograms in the AIMBAT project.

shell

Interactive AIMBAT shell with tab-completion and command history.

snapshot

View and manage snapshots of processing parameters.

station

View and manage stations.

tool

Launch interactive tools for picking phase arrival times and processing parameters.

tui

Launch the AIMBAT terminal user interface.

utils

Utilities for AIMBAT.

align

Align seismograms using ICCS or MCCC.

This command aligns seismograms using either the ICCS or MCCC algorithm. Both commands update the pick stored in t1. If t1 is None, t0 is used as starting point instead, with the resulting pick stored in t1.

Functions:

Name Description
cli_iccs_run

Run the ICCS algorithm to align seismograms for an event.

cli_mccc_run

Run the MCCC algorithm to refine arrival time picks for an event.

cli_iccs_run

cli_iccs_run(
    event_id: Annotated[UUID, event_parameter()],
    *,
    autoselect: Annotated[
        bool,
        Parameter(
            name=autoselect,
            help="Whether to automatically de-select seismograms whose "
            + "cross-correlation with the stack falls below `min_cc`, and re-select"
            + " them if the cross-correlation later exceeds `min_cc`.",
        ),
    ] = False,
    autoflip: Annotated[
        bool,
        Parameter(
            name=autoflip,
            help="Whether to automatically flip seismograms (multiply data by -1) when"
            + " the cross-correlation is negative.",
        ),
    ] = False,
    _: DebugParameter = DebugParameter(),
) -> None

Run the ICCS algorithm to align seismograms for an event.

Iteratively cross-correlates seismograms against a running stack to refine arrival time picks (t1). If t1 is not yet set, t0 is used as the starting point.

Source code in src/aimbat/_cli/align.py
@app.command(name="iccs")
@handle_issues
def cli_iccs_run(
    event_id: Annotated[UUID, event_parameter()],
    *,
    autoselect: Annotated[
        bool,
        Parameter(
            name="autoselect",
            help=(
                "Whether to automatically de-select seismograms whose "
                + "cross-correlation with the stack falls below `min_cc`, and re-select"
                + " them if the cross-correlation later exceeds `min_cc`."
            ),
        ),
    ] = False,
    autoflip: Annotated[
        bool,
        Parameter(
            name="autoflip",
            help=(
                "Whether to automatically flip seismograms (multiply data by -1) when"
                + " the cross-correlation is negative."
            ),
        ),
    ] = False,
    _: DebugParameter = DebugParameter(),
) -> None:
    """Run the ICCS algorithm to align seismograms for an event.

    Iteratively cross-correlates seismograms against a running stack to refine
    arrival time picks (`t1`). If `t1` is not yet set, `t0` is used as the
    starting point.
    """
    from sqlmodel import Session

    from aimbat.core import create_iccs_instance, resolve_event, run_iccs
    from aimbat.db import engine

    with Session(engine) as session:
        event = resolve_event(session, event_id)
        iccs = create_iccs_instance(session, event).iccs
        run_iccs(session, event, iccs, autoflip, autoselect)

cli_mccc_run

cli_mccc_run(
    event_id: Annotated[UUID, event_parameter()],
    *,
    all_seismograms: Annotated[
        bool,
        Parameter(
            name=all,
            help="Include all seismograms of an event in MCCC processing, not just the"
            + " currently selected ones.",
        ),
    ] = False,
    _: DebugParameter = DebugParameter(),
) -> None

Run the MCCC algorithm to refine arrival time picks for an event.

Multi-channel cross-correlation simultaneously determines the optimal time shifts for all seismograms. Results are stored in t1.

Source code in src/aimbat/_cli/align.py
@app.command(name="mccc")
@handle_issues
def cli_mccc_run(
    event_id: Annotated[UUID, event_parameter()],
    *,
    all_seismograms: Annotated[
        bool,
        Parameter(
            name="all",
            help=(
                "Include all seismograms of an event in MCCC processing, not just the"
                + " currently selected ones."
            ),
        ),
    ] = False,
    _: DebugParameter = DebugParameter(),
) -> None:
    """Run the MCCC algorithm to refine arrival time picks for an event.

    Multi-channel cross-correlation simultaneously determines the optimal time
    shifts for all seismograms. Results are stored in `t1`.
    """
    from sqlmodel import Session

    from aimbat.core import create_iccs_instance, resolve_event, run_mccc
    from aimbat.db import engine

    with Session(engine) as session:
        event = resolve_event(session, event_id)
        iccs = create_iccs_instance(session, event).iccs
        run_mccc(session, event, iccs, all_seismograms)

common

Shared decorators, parameters, and table formatting for CLI command modules.

Classes:

Name Description
ConfirmParameters

Shared parameters for destructive commands (--yes, --debug).

DebugParameter

Shared parameter that adds --debug to any CLI command.

EventDebugParameters

Parameters for commands that operate on individual events, with optional debug mode.

IccsPlotParameters

Shared parameters for ICCS plot commands (--context, --all).

JsonDumpParameters

Shared parameters for JSON dump commands (--alias, --debug).

TableParameters

Shared parameters for table display commands (--raw, --debug).

Functions:

Name Description
causal

Return a cyclopts Parameter for switching between causal and zero-phase filtering.

confirm_or_abort

Prompt for confirmation before a destructive action, then abort if declined.

event_parameter

Return a cyclopts Parameter for selecting a single event by UUID or prefix.

event_parameter_is_all

Return True if event_id is the literal string "all" (case-insensitive).

event_parameter_with_all

Return a cyclopts Parameter for selecting an event or the literal "all".

event_table_title

Title for a table of records belonging to one event.

handle_issues

Decorator that reports exceptions to the console and exits cleanly.

id_label

Render a record's ID the way a table title shows it.

id_parameter

Return a cyclopts Parameter for selecting a record by UUID or unique prefix.

json_to_table

Print a JSON dict or list of dicts as a rich table driven by a Pydantic model.

make_note_app

Build the note sub-app (read and edit) for one kind of record.

open_in_editor

Write initial_content to a temporary Markdown file, open it in $EDITOR.

print_error_panel

Print an exception to the console in a red panel.

print_json_dump

Print a table dump as JSON, in its own database session.

print_quality_table

Print one of the aggregated quality tables.

print_warning

Print a non-fatal warning message to the console, styled yellow.

run_reporting_issues

Run func, reporting exceptions to the console the same way handle_issues does.

station_parameter_is_all

Return True if station_id is the literal string "all" (case-insensitive).

station_parameter_with_all

Return a cyclopts Parameter for selecting a station or the literal "all".

use_event_parameter

Return a cyclopts Parameter for linking data to an existing event record.

use_matrix_image

Return a cyclopts Parameter for switching from stack to matrix image plots.

use_station_parameter

Return a cyclopts Parameter for linking data to an existing station record.

Attributes:

Name Type Description
CAUSAL_DEFAULTS dict[str, bool]

Default causal/zero-phase setting per tool subcommand and TUI tool id.

CAUSAL_DEFAULTS module-attribute

CAUSAL_DEFAULTS: dict[str, bool] = {
    "phase": True,
    "window": False,
    "cc": False,
}

Default causal/zero-phase setting per tool subcommand and TUI tool id.

Single source of truth for the causal() parameter defaults in _cli/tool.py and the equivalent per-tool defaults in the TUI's InteractiveToolsModal (_tui/modals.py).

ConfirmParameters dataclass

Bases: _YesTrait, _DebugTrait

Shared parameters for destructive commands (--yes, --debug).

Parameters:

Name Type Description Default
debug bool

Enable verbose logging for troubleshooting.

False
yes bool
False

Attributes:

Name Type Description
debug bool

Enable verbose logging for troubleshooting.

Source code in src/aimbat/_cli/common/_parameters.py
@Parameter(name="*")
@dataclass
class ConfirmParameters(_YesTrait, _DebugTrait):
    """Shared parameters for destructive commands (`--yes`, `--debug`)."""

    pass

debug class-attribute instance-attribute

debug: bool = False

Enable verbose logging for troubleshooting.

DebugParameter dataclass

Bases: _DebugTrait

Shared parameter that adds --debug to any CLI command.

Parameters:

Name Type Description Default
debug bool

Enable verbose logging for troubleshooting.

False

Attributes:

Name Type Description
debug bool

Enable verbose logging for troubleshooting.

Source code in src/aimbat/_cli/common/_parameters.py
@Parameter(name="*")
@dataclass
class DebugParameter(_DebugTrait):
    """Shared parameter that adds `--debug` to any CLI command."""

    pass

debug class-attribute instance-attribute

debug: bool = False

Enable verbose logging for troubleshooting.

EventDebugParameters dataclass

Bases: _DebugTrait, _EventContextTrait

Parameters for commands that operate on individual events, with optional debug mode.

Parameters:

Name Type Description Default
event_id UUID
required
debug bool

Enable verbose logging for troubleshooting.

False

Attributes:

Name Type Description
debug bool

Enable verbose logging for troubleshooting.

Source code in src/aimbat/_cli/common/_parameters.py
@Parameter(name="*")
@dataclass
class EventDebugParameters(_DebugTrait, _EventContextTrait):
    """Parameters for commands that operate on individual events, with optional debug mode."""

    pass

debug class-attribute instance-attribute

debug: bool = False

Enable verbose logging for troubleshooting.

IccsPlotParameters dataclass

Shared parameters for ICCS plot commands (--context, --all).

Parameters:

Name Type Description Default
context bool
True
all_seismograms bool
False
Source code in src/aimbat/_cli/common/_parameters.py
@Parameter(name="*")
@dataclass
class IccsPlotParameters:
    """Shared parameters for ICCS plot commands (`--context`, `--all`)."""

    context: Annotated[
        bool,
        Parameter(
            help=(
                "Plot seismograms with extra context instead of the short tapered "
                + "ones used for cross-correlation"
            )
        ),
    ] = True
    all_seismograms: Annotated[
        bool,
        Parameter(
            name="all",
            help="Include all seismograms in the plot, even if not used in stack.",
        ),
    ] = False

JsonDumpParameters dataclass

Bases: _ByAliasTrait, _DebugTrait

Shared parameters for JSON dump commands (--alias, --debug).

Parameters:

Name Type Description Default
debug bool

Enable verbose logging for troubleshooting.

False
by_alias bool
False

Attributes:

Name Type Description
debug bool

Enable verbose logging for troubleshooting.

Source code in src/aimbat/_cli/common/_parameters.py
@Parameter(name="*")
@dataclass
class JsonDumpParameters(_ByAliasTrait, _DebugTrait):
    """Shared parameters for JSON dump commands (`--alias`, `--debug`)."""

    pass

debug class-attribute instance-attribute

debug: bool = False

Enable verbose logging for troubleshooting.

TableParameters dataclass

Bases: _TableParametersTrait, _DebugTrait

Shared parameters for table display commands (--raw, --debug).

Parameters:

Name Type Description Default
debug bool

Enable verbose logging for troubleshooting.

False
raw bool
False

Attributes:

Name Type Description
debug bool

Enable verbose logging for troubleshooting.

Source code in src/aimbat/_cli/common/_parameters.py
@Parameter(name="*")
@dataclass
class TableParameters(_TableParametersTrait, _DebugTrait):
    """Shared parameters for table display commands (`--raw`, `--debug`)."""

    pass

debug class-attribute instance-attribute

debug: bool = False

Enable verbose logging for troubleshooting.

causal

causal() -> Parameter

Return a cyclopts Parameter for switching between causal and zero-phase filtering.

Source code in src/aimbat/_cli/common/_parameters.py
def causal() -> Parameter:
    """Return a cyclopts `Parameter` for switching between causal and zero-phase filtering."""
    return Parameter(
        name="causal",
        help="Use causal (single-pass) instead of zero-phase filtering.",
        negative="--zero-phase",
    )

confirm_or_abort

confirm_or_abort(message: str, *, yes: bool) -> None

Prompt for confirmation before a destructive action, then abort if declined.

Parameters:

Name Type Description Default
message str

The Yes/No question to display.

required
yes bool

If True, skip the prompt (for scripting/non-interactive use).

required

Raises:

Type Description
SystemExit

With status 0, if the user declines or the prompt cannot be read (e.g. no interactive terminal is attached).

Source code in src/aimbat/_cli/common/_decorators.py
def confirm_or_abort(message: str, *, yes: bool) -> None:
    """Prompt for confirmation before a destructive action, then abort if declined.

    Args:
        message: The Yes/No question to display.
        yes: If True, skip the prompt (for scripting/non-interactive use).

    Raises:
        SystemExit: With status 0, if the user declines or the prompt cannot
            be read (e.g. no interactive terminal is attached).
    """
    if yes:
        return

    import sys

    from rich.console import Console
    from rich.prompt import Confirm

    console = Console(stderr=True)
    try:
        confirmed = Confirm.ask(message, console=console, default=False)
    except (EOFError, KeyboardInterrupt):
        confirmed = False

    if not confirmed:
        console.print("Aborted.")
        sys.exit(0)

event_parameter

event_parameter(help: str | None = None) -> Parameter

Return a cyclopts Parameter for selecting a single event by UUID or prefix.

Parameters:

Name Type Description Default
help str | None

Custom help string; falls back to a generic event UUID prompt.

None
Source code in src/aimbat/_cli/common/_parameters.py
def event_parameter(help: str | None = None) -> Parameter:
    """Return a cyclopts `Parameter` for selecting a single event by UUID or prefix.

    Args:
        help: Custom help string; falls back to a generic event UUID prompt.
    """
    from aimbat.models import AimbatEvent

    return Parameter(
        name=["event", "event-id"],
        help=help or "UUID (or unique prefix) of event to process.",
        env_var="DEFAULT_EVENT_ID",
        converter=_make_uuid_converter(AimbatEvent),
    )

event_parameter_is_all

event_parameter_is_all(
    event_id: UUID | Literal["all"],
) -> TypeIs[Literal["all"]]

Return True if event_id is the literal string "all" (case-insensitive).

Source code in src/aimbat/_cli/common/_parameters.py
def event_parameter_is_all(event_id: UUID | Literal["all"]) -> TypeIs[Literal["all"]]:
    """Return `True` if `event_id` is the literal string `"all"` (case-insensitive)."""
    if isinstance(event_id, str) and event_id.lower() == "all":
        return True
    return False

event_parameter_with_all

event_parameter_with_all(
    help: str | None = None,
) -> Parameter

Return a cyclopts Parameter for selecting an event or the literal "all".

Parameters:

Name Type Description Default
help str | None

Custom help string; falls back to a generic prompt.

None
Source code in src/aimbat/_cli/common/_parameters.py
def event_parameter_with_all(help: str | None = None) -> Parameter:
    """Return a cyclopts `Parameter` for selecting an event or the literal `"all"`.

    Args:
        help: Custom help string; falls back to a generic prompt.
    """
    from aimbat.models import AimbatEvent

    return Parameter(
        name=["event", "event-id"],
        help=help
        or '"all" for all events, or UUID (or unique prefix) of event to process.',
        env_var="DEFAULT_EVENT_ID",
        converter=_make_uuid_converter(AimbatEvent, allow_all=True),
        show_choices=False,
    )

event_table_title

event_table_title(
    session: Session,
    event: AimbatEvent,
    *,
    subject: str,
    raw: bool,
) -> str

Title for a table of records belonging to one event.

Parameters:

Name Type Description Default
session Session

Database session.

required
event AimbatEvent

The event the records belong to.

required
subject str

Plural noun for what the table lists, e.g. "seismograms".

required
raw bool

If True, show the full event time and UUID instead of the rounded time and short ID.

required
Source code in src/aimbat/_cli/common/_table.py
def event_table_title(
    session: "Session", event: "AimbatEvent", *, subject: str, raw: bool
) -> str:
    """Title for a table of records belonging to one event.

    Args:
        session: Database session.
        event: The event the records belong to.
        subject: Plural noun for what the table lists, e.g. `"seismograms"`.
        raw: If `True`, show the full event time and UUID instead of the
            rounded time and short ID.
    """
    if raw:
        return f"AIMBAT {subject} for event {event.time} (ID={event.id})"
    from aimbat.utils import uuid_shortener

    time = event.time.strftime("%Y-%m-%d %H:%M:%S")
    return f"AIMBAT {subject} for event {time} (ID={uuid_shortener(session, event)})"

handle_issues

handle_issues(func: F) -> F

Decorator that reports exceptions to the console and exits cleanly.

Exceptions raised by the wrapped command are printed (without traceback) in a red panel, then the process exits with status 1. Any aimbat.core.SchemaStaleWarning raised during the call is always promoted to an error first, so a stale database schema is reported through the same red-panel path as any other failure, regardless of AIMBAT_STRICT_SCHEMA_CHECK.

In debugging mode (AIMBAT_LOG_LEVEL=DEBUG or TRACE), the schema staleness promotion still applies, but exceptions are no longer caught and rendered as a panel; they propagate as a normal Python traceback instead.

Source code in src/aimbat/_cli/common/_decorators.py
def handle_issues[F: Callable[..., Any]](func: F) -> F:
    """Decorator that reports exceptions to the console and exits cleanly.

    Exceptions raised by the wrapped command are printed (without traceback)
    in a red panel, then the process exits with status 1. Any
    `aimbat.core.SchemaStaleWarning` raised during the call is always
    promoted to an error first, so a stale database schema is reported
    through the same red-panel path as any other failure, regardless of
    `AIMBAT_STRICT_SCHEMA_CHECK`.

    In debugging mode (`AIMBAT_LOG_LEVEL=DEBUG` or `TRACE`), the schema
    staleness promotion still applies, but exceptions are no longer caught
    and rendered as a panel; they propagate as a normal Python traceback
    instead.
    """
    from functools import wraps

    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        return run_reporting_issues(lambda: func(*args, **kwargs))

    return wrapper  # type: ignore[return-value]

id_label

id_label(
    session: Session,
    model: type[AimbatTypes],
    record_id: UUID | str,
    *,
    raw: bool,
) -> str

Render a record's ID the way a table title shows it.

Parameters:

Name Type Description Default
session Session

Database session.

required
model type[AimbatTypes]

Model the ID belongs to, used to shorten it unambiguously.

required
record_id UUID | str

The ID to render.

required
raw bool

If True, show the full UUID instead of its short prefix.

required
Source code in src/aimbat/_cli/common/_table.py
def id_label(
    session: "Session", model: type["AimbatTypes"], record_id: UUID | str, *, raw: bool
) -> str:
    """Render a record's ID the way a table title shows it.

    Args:
        session: Database session.
        model: Model the ID belongs to, used to shorten it unambiguously.
        record_id: The ID to render.
        raw: If `True`, show the full UUID instead of its short prefix.
    """
    from aimbat.utils import uuid_shortener

    if raw:
        return str(record_id)
    return uuid_shortener(session, model, str_uuid=str(record_id))

id_parameter

id_parameter(
    model_class: type, help: str = ""
) -> Parameter

Return a cyclopts Parameter for selecting a record by UUID or unique prefix.

Parameters:

Name Type Description Default
model_class type

AIMBAT model class used to resolve short UUID prefixes.

required
help str

Custom help string; falls back to a generic UUID prompt if empty.

''
Source code in src/aimbat/_cli/common/_parameters.py
def id_parameter(model_class: type, help: str = "") -> Parameter:
    """Return a cyclopts `Parameter` for selecting a record by UUID or unique prefix.

    Args:
        model_class: AIMBAT model class used to resolve short UUID prefixes.
        help: Custom help string; falls back to a generic UUID prompt if empty.
    """
    return Parameter(
        name="id",
        help=help or "UUID (or any unique prefix).",
        converter=_make_uuid_converter(model_class),
    )

json_to_table

json_to_table(
    data: dict[str, Any] | list[dict[str, Any]],
    model: type[BaseModel],
    title: str | None = None,
    raw: bool = False,
    col_specs: dict[str, RichColSpec] | None = None,
    column_order: list[str] | None = None,
    key_header: str = "Property",
    value_header: str = "Value",
) -> None

Print a JSON dict or list of dicts as a rich table driven by a Pydantic model.

Parameters:

Name Type Description Default
data dict[str, Any] | list[dict[str, Any]]

A single row (dict) or list of rows to display.

required
model type[BaseModel]

Pydantic model whose field metadata drives column configuration.

required
title str | None

Optional table title.

None
raw bool

If True, ignore RichColSpec metadata and render using only type-based heuristics. Useful for a quick unformatted view.

False
col_specs dict[str, RichColSpec] | None

Optional per-field overrides. Each entry is merged on top of the spec derived from the model field, so only the attributes that differ need to be set. Ignored when raw=True.

None
column_order list[str] | None

Optional list of field names that should appear first, in that order. Fields not listed appear after in model-declaration order.

None
key_header str

Header for the property-name column in vertical (dict) tables.

'Property'
value_header str

Header for the value column in vertical (dict) tables.

'Value'
Source code in src/aimbat/_cli/common/_table.py
def json_to_table(
    data: dict[str, Any] | list[dict[str, Any]],
    model: type[BaseModel],
    title: str | None = None,
    raw: bool = False,
    col_specs: dict[str, RichColSpec] | None = None,
    column_order: list[str] | None = None,
    key_header: str = "Property",
    value_header: str = "Value",
) -> None:
    """Print a JSON dict or list of dicts as a rich table driven by a Pydantic model.

    Args:
        data: A single row (dict) or list of rows to display.
        model: Pydantic model whose field metadata drives column configuration.
        title: Optional table title.
        raw: If `True`, ignore `RichColSpec` metadata and render using only
            type-based heuristics. Useful for a quick unformatted view.
        col_specs: Optional per-field overrides. Each entry is merged on top of
            the spec derived from the model field, so only the attributes that
            differ need to be set. Ignored when `raw=True`.
        column_order: Optional list of field names that should appear first, in
            that order. Fields not listed appear after in model-declaration order.
        key_header: Header for the property-name column in vertical (dict) tables.
        value_header: Header for the value column in vertical (dict) tables.
    """
    console = Console()
    table = Table(title=title)

    data_list = [data] if isinstance(data, dict) else data
    if not data_list:
        console.print(table)
        return

    # Build specs from model field metadata (skipped in raw mode).
    specs: dict[str, RichColSpec] = {}
    if not raw:
        for name, field in model.model_fields.items():
            spec: RichColSpec | None = None
            # Pydantic Field uses json_schema_extra; SQLModel Field uses schema_extra
            # which spreads its keys directly into _attributes_set on the FieldInfo.
            for source in (
                field.json_schema_extra,
                getattr(field, "_attributes_set", None),
            ):
                if isinstance(source, dict):
                    candidate = source.get("rich")
                    if isinstance(candidate, RichColSpec):
                        spec = candidate
                        break
            specs[name] = spec if spec is not None else RichColSpec()

        # Apply caller-supplied overrides. model_fields_set tracks which fields
        # were explicitly provided, so only those replace the model-derived spec.
        if col_specs:
            for name, override in col_specs.items():
                base = specs.get(name, RichColSpec())
                specs[name] = base.model_copy(
                    update={k: getattr(override, k) for k in override.model_fields_set}
                )

    def _ordered(names: list[str]) -> list[str]:
        if not column_order:
            return names
        ordered = [n for n in column_order if n in names]
        rest = [n for n in names if n not in set(column_order)]
        return ordered + rest

    def _fmt_val(name: str, val: Any) -> str:
        if raw:
            return "" if val is None else str(val)
        if val is None or val is NaT:
            return _MISSING_MARKER
        spec = specs.get(name)
        if spec and spec.formatter:
            return spec.formatter(val)
        if isinstance(val, bool):
            return fmt_bool(val)
        if isinstance(val, float):
            return fmt_float(val)
        low_key = name.lower()
        if (
            isinstance(val, str)
            and val.strip()
            and any(k in low_key for k in ("time", "date", "modified"))
        ):
            try:
                dt = to_datetime(val)
                return fmt_timestamp(dt)
            except (ValueError, TypeError):
                return str(val)
        return str(val)

    field_names = _ordered(list(model.model_fields.keys()))

    if isinstance(data, dict):
        # Vertical table
        table.add_column(key_header, style="cyan")
        table.add_column(value_header)
        for name in field_names:
            if name not in data:
                continue
            spec = specs.get(name)
            if spec and spec.display_title:
                label = spec.display_title
            else:
                field = model.model_fields[name]
                label = field.title if field.title else name
            table.add_row(label, _fmt_val(name, data[name]))
    else:
        # Horizontal table: restrict to fields present in data.
        data_keys: set[str] = set()
        for item in data_list:
            data_keys.update(item.keys())
        visible_fields = [n for n in field_names if n in data_keys]

        for name in visible_fields:
            spec = specs.get(name)
            field = model.model_fields[name]
            header = (
                spec.display_title
                if spec and spec.display_title
                else field.title or name
            )
            kwargs: dict[str, Any] = {}
            default_justify = _justify_for_annotation(field.annotation)
            if default_justify:
                kwargs["justify"] = default_justify
            if spec:
                if spec.justify:
                    kwargs["justify"] = spec.justify
                if spec.style:
                    kwargs["style"] = spec.style
                if spec.no_wrap is not None:
                    kwargs["no_wrap"] = spec.no_wrap
                if spec.highlight is not None:
                    kwargs["highlight"] = spec.highlight
            table.add_column(header, **kwargs)

        for item in data:
            table.add_row(*[_fmt_val(n, item.get(n)) for n in visible_fields])

    console.print(table)

make_note_app

make_note_app(
    target: NoteTarget,
    model: type[AimbatTypes],
    parameter: Parameter,
) -> App

Build the note sub-app (read and edit) for one kind of record.

Parameters:

Name Type Description Default
target NoteTarget

The kind of record the notes belong to, also the noun used in the commands' help text.

required
model type[AimbatTypes]

Model class the note is attached to.

required
parameter Parameter

cyclopts parameter that selects the record to act on.

required
Source code in src/aimbat/_cli/common/_notes.py
def make_note_app(
    target: "NoteTarget", model: type["AimbatTypes"], parameter: Parameter
) -> App:
    """Build the `note` sub-app (`read` and `edit`) for one kind of record.

    Args:
        target: The kind of record the notes belong to, also the noun used in
            the commands' help text.
        model: Model class the note is attached to.
        parameter: cyclopts parameter that selects the record to act on.
    """
    note_app = App(
        name="note", help=f"Read and edit {target} notes.", help_format="markdown"
    )
    article = "an" if target.startswith(("a", "e", "i", "o", "u")) else "a"

    def read(
        record_id: Annotated[UUID, parameter],
        *,
        _: DebugParameter = DebugParameter(),
    ) -> None:
        from rich.console import Console
        from rich.markdown import Markdown
        from sqlmodel import Session

        from aimbat.core import get_note_content
        from aimbat.db import engine

        with Session(engine) as session:
            content = get_note_content(
                session, target, _resolve_record(session, model, record_id)
            )

        Console().print(Markdown(content) if content else "(no note)")

    def edit(
        record_id: Annotated[UUID, parameter],
        *,
        _: DebugParameter = DebugParameter(),
    ) -> None:
        from sqlmodel import Session

        from aimbat.core import get_note_content, save_note
        from aimbat.db import engine

        with Session(engine) as session:
            original = get_note_content(
                session, target, _resolve_record(session, model, record_id)
            )

        updated = open_in_editor(original)

        if updated != original:
            with Session(engine) as session:
                raced = save_note(
                    session, target, record_id, updated, expected_previous=original
                )
            if raced:
                print_warning(
                    "Note changed elsewhere while the editor was open; your edit has"
                    + " overwritten that change."
                )

    read.__doc__ = (
        f"Display the note attached to {article} {target}, rendered as Markdown."
    )
    edit.__doc__ = _EDIT_HELP.format(target=target)

    note_app.command(handle_issues(read), name="read")
    note_app.command(handle_issues(edit), name="edit")

    return note_app

open_in_editor

open_in_editor(initial_content: str) -> str

Write initial_content to a temporary Markdown file, open it in $EDITOR.

Returns the (possibly updated) content after the editor exits. The temporary file is not removed until then, since Windows does not allow a second process to open a file that is already open.

The editor command is taken from $EDITOR or $VISUAL. If neither is set, notepad is used on Windows and vi elsewhere. To use a GUI editor that does not block by default (e.g. VS Code), set EDITOR="code --wait".

Source code in src/aimbat/_cli/common/_parameters.py
def open_in_editor(initial_content: str) -> str:
    """Write `initial_content` to a temporary Markdown file, open it in `$EDITOR`.

    Returns the (possibly updated) content after the editor exits. The
    temporary file is not removed until then, since Windows does not allow a
    second process to open a file that is already open.

    The editor command is taken from `$EDITOR` or `$VISUAL`. If neither is
    set, `notepad` is used on Windows and `vi` elsewhere. To use a GUI editor
    that does not block by default (e.g. VS Code), set `EDITOR="code --wait"`.
    """
    import os
    import shlex
    import subprocess
    import tempfile
    import time

    editor = os.environ.get("EDITOR") or os.environ.get("VISUAL")
    if not editor:
        editor = "notepad" if sys.platform == "win32" else "vi"

    with tempfile.NamedTemporaryFile(
        mode="w",
        suffix=".md",
        delete=False,
        encoding="utf-8",
    ) as tmp:
        tmp.write(initial_content)
        tmp_path = tmp.name

    try:
        started = time.monotonic()
        result = subprocess.run([*shlex.split(editor), tmp_path], check=False)
        elapsed = time.monotonic() - started
        if result.returncode != 0:
            from aimbat.logger import logger

            message = (
                f"Editor '{editor}' exited with code {result.returncode}; discarding "
                + "changes."
            )
            logger.warning(message)
            print_warning(message)
            return initial_content
        with open(tmp_path, encoding="utf-8") as f:
            content = f.read()
        if content == initial_content and elapsed < 1:
            from aimbat.logger import logger

            message = (
                f"Editor '{editor}' returned in under a second with no changes - if "
                + "it launched a separate window that is still open (a non-blocking "
                + "GUI editor), any edits made there will be lost once that window "
                + "closes, since this temporary file is already gone. Use a "
                + 'wait-for-close flag, e.g. EDITOR="code --wait".'
            )
            logger.warning(message)
            print_warning(message)
        return content
    finally:
        os.unlink(tmp_path)

print_error_panel

print_error_panel(e: Exception) -> None

Print an exception to the console in a red panel.

Source code in src/aimbat/_cli/common/_decorators.py
def print_error_panel(e: Exception) -> None:
    """Print an exception to the console in a red panel."""
    from rich.console import Console
    from rich.panel import Panel

    from aimbat.utils import exception_message

    console = Console(stderr=True)
    panel = Panel(
        exception_message(e)
        + "\n\n(run with --debug or AIMBAT_LOG_LEVEL=DEBUG for a full traceback)",
        title="Error",
        title_align="left",
        border_style="red",
        expand=True,
    )
    console.print(panel)

print_json_dump

print_json_dump(dump: TableDump, *, by_alias: bool) -> None

Print a table dump as JSON, in its own database session.

Parameters:

Name Type Description Default
dump TableDump

The core.dump_*_table function to call.

required
by_alias bool

Whether to key the records by field alias.

required
Source code in src/aimbat/_cli/common/_table.py
def print_json_dump(dump: TableDump, *, by_alias: bool) -> None:
    """Print a table dump as JSON, in its own database session.

    Args:
        dump: The `core.dump_*_table` function to call.
        by_alias: Whether to key the records by field alias.
    """
    from rich import print_json
    from sqlmodel import Session

    from aimbat.db import engine

    with Session(engine) as session:
        print_json(data=dump(session, by_alias=by_alias))

print_quality_table

print_quality_table(
    session: Session,
    dump: TableDump,
    *,
    title: str,
    filter_id: UUID | None,
    id_field: str,
    id_columns: dict[str, type[AimbatTypes]],
    raw: bool,
) -> None

Print one of the aggregated quality tables.

Parameters:

Name Type Description Default
session Session

Database session.

required
dump TableDump

The core.dump_*_quality_table function to call.

required
title str

Table title.

required
filter_id UUID | None

Restrict the table to this record, or None for all of them.

required
id_field str

Name of dump's filter argument, e.g. "event_id". Also the column left out when the table covers a single record, since every row would repeat it.

required
id_columns dict[str, type[AimbatTypes]]

The ID columns to shorten, mapped to the model each belongs to.

required
raw bool

If True, render the table unformatted.

required
Source code in src/aimbat/_cli/common/_table.py
def print_quality_table(
    session: "Session",
    dump: TableDump,
    *,
    title: str,
    filter_id: UUID | None,
    id_field: str,
    id_columns: dict[str, type["AimbatTypes"]],
    raw: bool,
) -> None:
    """Print one of the aggregated quality tables.

    Args:
        session: Database session.
        dump: The `core.dump_*_quality_table` function to call.
        title: Table title.
        filter_id: Restrict the table to this record, or `None` for all of them.
        id_field: Name of `dump`'s filter argument, e.g. `"event_id"`. Also the
            column left out when the table covers a single record, since every
            row would repeat it.
        id_columns: The ID columns to shorten, mapped to the model each belongs to.
        raw: If `True`, render the table unformatted.
    """
    from aimbat.models import SeismogramQualityStats

    def shorten(model: type["AimbatTypes"]) -> Callable[[Any], str]:
        return lambda value: id_label(session, model, value, raw=False)

    col_specs = {
        column: RichColSpec(formatter=shorten(model))
        for column, model in id_columns.items()
    }

    json_to_table(
        data=dump(
            session,
            **{id_field: filter_id},
            exclude=None if filter_id is None else {id_field},
        ),
        model=SeismogramQualityStats,
        title=title,
        raw=raw,
        col_specs=col_specs,
    )

print_warning

print_warning(message: object) -> None

Print a non-fatal warning message to the console, styled yellow.

Source code in src/aimbat/_cli/common/_decorators.py
def print_warning(message: object) -> None:
    """Print a non-fatal warning message to the console, styled yellow."""
    from rich.console import Console
    from rich.text import Text

    # A `Text` object applies styling structurally rather than via markup
    # tags, so a `[`/`]` in the message (e.g. a future revision id, built
    # from database content) can't be misinterpreted as Rich markup.
    Console(stderr=True).print(Text(str(message), style="yellow"))

run_reporting_issues

run_reporting_issues(func: Callable[[], T]) -> T

Run func, reporting exceptions to the console the same way handle_issues does.

Exists so code that runs outside a handle_issues-wrapped command body - currently the UUID-prefix converters in common/_parameters.py, which cyclopts calls during argument parsing, before the command function itself is invoked - gets the same styled-panel / debug-mode-passthrough behaviour instead of a raw, unstyled traceback.

Any aimbat.core.SchemaStaleWarning raised during the call is always promoted to an error first, so a stale database schema is reported through the same red-panel path as any other failure, regardless of AIMBAT_STRICT_SCHEMA_CHECK.

In debugging mode (AIMBAT_LOG_LEVEL=DEBUG/TRACE, or a --debug flag on the command line), the schema staleness promotion still applies, but exceptions are no longer caught and rendered as a panel; they propagate as a normal Python traceback instead.

A converter runs while cyclopts is still parsing arguments, before it constructs the _DebugTrait-derived dataclass whose __post_init__ would otherwise set settings.log_level from --debug - that dataclass is only built once every field, including this one, has already converted successfully. So sys.argv is checked directly for --debug here as well, rather than relying solely on settings.log_level.

Source code in src/aimbat/_cli/common/_decorators.py
def run_reporting_issues[T](func: Callable[[], T]) -> T:
    """Run `func`, reporting exceptions to the console the same way `handle_issues` does.

    Exists so code that runs outside a `handle_issues`-wrapped command body -
    currently the UUID-prefix converters in `common/_parameters.py`, which
    cyclopts calls during argument parsing, before the command function
    itself is invoked - gets the same styled-panel / debug-mode-passthrough
    behaviour instead of a raw, unstyled traceback.

    Any `aimbat.core.SchemaStaleWarning` raised during the call is always
    promoted to an error first, so a stale database schema is reported
    through the same red-panel path as any other failure, regardless of
    `AIMBAT_STRICT_SCHEMA_CHECK`.

    In debugging mode (`AIMBAT_LOG_LEVEL=DEBUG`/`TRACE`, or a `--debug` flag
    on the command line), the schema staleness promotion still applies, but
    exceptions are no longer caught and rendered as a panel; they propagate
    as a normal Python traceback instead.

    A converter runs while cyclopts is still parsing arguments, before it
    constructs the `_DebugTrait`-derived dataclass whose `__post_init__`
    would otherwise set `settings.log_level` from `--debug` - that dataclass
    is only built once every field, including this one, has already
    converted successfully. So `sys.argv` is checked directly for `--debug`
    here as well, rather than relying solely on `settings.log_level`.
    """
    import sys
    import warnings

    from aimbat.core._migrations import SchemaStaleWarning

    with warnings.catch_warnings():
        warnings.filterwarnings("error", category=SchemaStaleWarning)

        if settings.log_level in ("TRACE", "DEBUG") or "--debug" in sys.argv:
            return func()

        try:
            return func()
        except Exception as e:
            print_error_panel(e)
            sys.exit(1)

station_parameter_is_all

station_parameter_is_all(
    station_id: UUID | Literal["all"],
) -> TypeIs[Literal["all"]]

Return True if station_id is the literal string "all" (case-insensitive).

Source code in src/aimbat/_cli/common/_parameters.py
def station_parameter_is_all(
    station_id: UUID | Literal["all"],
) -> TypeIs[Literal["all"]]:
    """Return `True` if `station_id` is the literal string `"all"` (case-insensitive)."""
    if isinstance(station_id, str) and station_id.lower() == "all":
        return True
    return False

station_parameter_with_all

station_parameter_with_all(
    help: str | None = None,
) -> Parameter

Return a cyclopts Parameter for selecting a station or the literal "all".

Parameters:

Name Type Description Default
help str | None

Custom help string; falls back to a generic prompt.

None
Source code in src/aimbat/_cli/common/_parameters.py
def station_parameter_with_all(help: str | None = None) -> Parameter:
    """Return a cyclopts `Parameter` for selecting a station or the literal `"all"`.

    Args:
        help: Custom help string; falls back to a generic prompt.
    """
    from aimbat.models import AimbatStation

    return Parameter(
        name=["station", "station-id"],
        help=help
        or '"all" for all stations, or UUID (or unique prefix) of station to process.',
        converter=_make_uuid_converter(AimbatStation, allow_all=True),
        show_choices=False,
    )

use_event_parameter

use_event_parameter() -> Parameter

Return a cyclopts Parameter for linking data to an existing event record.

Source code in src/aimbat/_cli/common/_parameters.py
def use_event_parameter() -> Parameter:
    """Return a cyclopts `Parameter` for linking data to an existing event record."""
    from aimbat.models import AimbatEvent

    return Parameter(
        name="use-event",
        help=(
            "UUID (or unique prefix) of an existing event to link to instead of "
            + "extracting one from each data source."
        ),
        converter=_make_uuid_converter(AimbatEvent),
    )

use_matrix_image

use_matrix_image() -> Parameter

Return a cyclopts Parameter for switching from stack to matrix image plots.

Source code in src/aimbat/_cli/common/_parameters.py
def use_matrix_image() -> Parameter:
    """Return a cyclopts `Parameter` for switching from stack to matrix image plots."""
    return Parameter(
        name="matrix",
        help="Use matrix image instead of stack plot.",
    )

use_station_parameter

use_station_parameter() -> Parameter

Return a cyclopts Parameter for linking data to an existing station record.

Source code in src/aimbat/_cli/common/_parameters.py
def use_station_parameter() -> Parameter:
    """Return a cyclopts `Parameter` for linking data to an existing station record."""
    from aimbat.models import AimbatStation

    return Parameter(
        name="use-station",
        help=(
            "UUID (or unique prefix) of an existing station to link to instead of"
            + " extracting one from each data source."
        ),
        converter=_make_uuid_converter(AimbatStation),
    )

data

Manage data sources in an AIMBAT project.

A data source is where AIMBAT reads seismogram waveforms and metadata from. Every current data type is a file, but a data source need not be one; it is identified by an opaque sourcename. When a data source is added, AIMBAT extracts and stores the associated station, event, and seismogram records in the project database, provided the data type supports it.

Supported data types (--type):

  • sac (default): SAC waveform file. Extracts station, event, and seismogram data automatically.
  • json_station: JSON file containing station metadata only. No seismogram is created.
  • json_event: JSON file containing event metadata only. No seismogram is created.

Typical workflow:

aimbat project create
aimbat data add *.sac
aimbat event list          # list events created from SAC headers

Re-adding a data source that is already in the project is safe: existing records are reused rather than duplicated.

Near-duplicate events (two files whose origin times differ by only a sliver, most often from precision loss upstream such as SAC's o-header 32-bit float, rather than genuinely distinct events) are merged onto the pre-existing event rather than turned into a second one. Merging is always first-wins: the new data links to the pre-existing event's stored time and location exactly as they already are, never merging, averaging, or recomputing from the new file. Detection is controlled by event_duplicate_tolerance (below this, a gap is assumed to be ordinary precision noise and the existing event is reused with a warning), event_duplicate_raise_tolerance (above event_duplicate_tolerance but below this, a gap is treated as a likely data problem and always raises, even during --dry-run; resolve it with --use-event <uuid> to link to the pre-existing event explicitly), and event_duplicate_strict (skips both checks entirely; with it set, events are only ever merged on an exact origin-time match, which is itself only accurate to the microsecond AIMBAT stores timestamps at, so any gap of a microsecond or more silently creates a second event).

data add automatically creates a snapshot for each event that received new seismogram data, so there is no need to run snapshot create right after ingestion (pass --no-snapshot to opt out for a given invocation). Use snapshot create later for deliberate checkpoints, e.g. before trying an experimental parameter change.

Functions:

Name Description
cli_data_add

Add or update data sources in the AIMBAT project.

cli_data_dump

Dump AIMBAT datasources table as a JSON string.

cli_data_list

Print a table of data sources registered in the AIMBAT project.

cli_data_prune

Delete seismograms whose data source has vanished.

cli_data_add

cli_data_add(
    data_sources: Annotated[
        list[Path],
        Parameter(
            name="sources",
            help="One or more data source paths to add.",
            consume_multiple=1,
            negative_iterable=(),
            validator=Path(exists=True, dir_okay=False),
        ),
    ],
    *,
    data_type: Annotated[
        DataType,
        Parameter(
            name="type",
            help="Format of the data sources. Determines which metadata (station, "
            + "event, seismogram) can be extracted automatically.",
        ),
    ] = SAC,
    station_id: Annotated[
        UUID | None, use_station_parameter()
    ] = None,
    event_id: Annotated[
        UUID | None, use_event_parameter()
    ] = None,
    dry_run: Annotated[
        bool,
        Parameter(
            name="dry-run",
            help="Preview which records would be added without modifying the database.",
        ),
    ] = False,
    show_progress_bar: Annotated[
        bool,
        Parameter(
            name="progress",
            help="Display a progress bar while ingesting sources.",
        ),
    ] = True,
    auto_snapshot: Annotated[
        bool,
        Parameter(
            name="snapshot",
            help="Automatically create a snapshot for each event that received new "
            + "seismogram data.",
        ),
    ] = True,
    _: DebugParameter = DebugParameter(),
) -> None

Add or update data sources in the AIMBAT project.

Each data source is processed according to --type. For sac (the default), AIMBAT extracts station, event, and seismogram metadata directly from the file. For types that cannot extract a station or event (e.g. a format that only carries waveform data), supply --use-station and/or --use-event to link to records that already exist in the project.

Station and event deduplication is automatic: if a matching record already exists it is reused. Re-running data add on the same files is safe.

Use --dry-run to preview what would be added without touching the database. Use --no-snapshot to skip the automatic post-ingestion snapshot for this invocation.

Note --dry-run can still raise rather than produce a clean preview: an "ambiguous gap" near-duplicate event (see the module help above) is flagged as an error unconditionally, since it usually signals a data problem worth stopping for even during a preview.

Source code in src/aimbat/_cli/data.py
@app.command(name="add")
@handle_issues
def cli_data_add(
    data_sources: Annotated[
        list[Path],
        Parameter(
            name="sources",
            help="One or more data source paths to add.",
            consume_multiple=1,
            negative_iterable=(),
            validator=validators.Path(exists=True, dir_okay=False),
        ),
    ],
    *,
    data_type: Annotated[
        DataType,
        Parameter(
            name="type",
            help=(
                "Format of the data sources. Determines which metadata (station, "
                + "event, seismogram) can be extracted automatically."
            ),
        ),
    ] = DataType.SAC,
    station_id: Annotated[uuid.UUID | None, use_station_parameter()] = None,
    event_id: Annotated[uuid.UUID | None, use_event_parameter()] = None,
    dry_run: Annotated[
        bool,
        Parameter(
            name="dry-run",
            help="Preview which records would be added without modifying the database.",
        ),
    ] = False,
    show_progress_bar: Annotated[
        bool,
        Parameter(
            name="progress", help="Display a progress bar while ingesting sources."
        ),
    ] = True,
    auto_snapshot: Annotated[
        bool,
        Parameter(
            name="snapshot",
            help=(
                "Automatically create a snapshot for each event that received new "
                + "seismogram data."
            ),
        ),
    ] = True,
    _: DebugParameter = DebugParameter(),
) -> None:
    """Add or update data sources in the AIMBAT project.

    Each data source is processed according to `--type`. For `sac` (the
    default), AIMBAT extracts station, event, and seismogram metadata directly
    from the file. For types that cannot extract a station or event (e.g. a
    format that only carries waveform data), supply `--use-station` and/or
    `--use-event` to link to records that already exist in the project.

    Station and event deduplication is automatic: if a matching record already
    exists it is reused. Re-running `data add` on the same files is safe.

    Use `--dry-run` to preview what would be added without touching the
    database. Use `--no-snapshot` to skip the automatic post-ingestion
    snapshot for this invocation.

    Note `--dry-run` can still raise rather than produce a clean preview: an
    "ambiguous gap" near-duplicate event (see the module help above) is
    flagged as an error unconditionally, since it usually signals a data
    problem worth stopping for even during a preview.
    """
    from rich.progress import Progress

    from aimbat.core import add_data_to_project
    from aimbat.db import engine

    with Session(engine) as session:
        with Progress(disable=not show_progress_bar) as progress:
            task = progress.add_task("Adding data ...", total=len(data_sources))

            def on_progress(done: int, _total: int) -> None:
                progress.update(task, completed=done)

            (
                added_datasources,
                existing_station_ids,
                existing_event_ids,
                existing_seismogram_ids,
                duplicate_warnings,
            ) = add_data_to_project(
                session,
                data_sources,
                data_type,
                station_id=station_id,
                event_id=event_id,
                dry_run=dry_run,
                on_progress=on_progress,
            )

        if dry_run:
            _print_dry_run_results(
                added_datasources,
                existing_station_ids,
                existing_event_ids,
                existing_seismogram_ids,
                duplicate_warnings,
            )
        elif auto_snapshot:
            from aimbat.core import create_snapshots_for_added_data

            _snapshotted, failures = create_snapshots_for_added_data(
                session, added_datasources, existing_seismogram_ids
            )
            for event_id, error in failures:
                print_warning(
                    f"Could not create an automatic snapshot for event {event_id}: {error}"
                )

cli_data_dump

cli_data_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None

Dump AIMBAT datasources table as a JSON string.

Output can be piped or redirected for use in external tools or scripts.

Source code in src/aimbat/_cli/data.py
@app.command(name="dump")
@handle_issues
def cli_data_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None:
    """Dump AIMBAT datasources table as a JSON string.

    Output can be piped or redirected for use in external tools or scripts.
    """
    from aimbat.core import dump_data_table

    print_json_dump(dump_data_table, by_alias=dump_parameters.by_alias)

cli_data_list

cli_data_list(
    event_id: Annotated[
        UUID | Literal["all"], event_parameter_with_all()
    ],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None

Print a table of data sources registered in the AIMBAT project.

Source code in src/aimbat/_cli/data.py
@app.command(name="list")
@handle_issues
def cli_data_list(
    event_id: Annotated[uuid.UUID | Literal["all"], event_parameter_with_all()],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None:
    """Print a table of data sources registered in the AIMBAT project."""
    from aimbat.core import dump_data_table, resolve_event
    from aimbat.db import engine
    from aimbat.logger import logger
    from aimbat.models import AimbatDataSource, AimbatSeismogram
    from aimbat.models._format import RichColSpec
    from aimbat.utils import uuid_shortener

    from .common import json_to_table

    raw = table_parameters.raw

    with Session(engine) as session:
        logger.debug("Printing data sources table.")

        if event_parameter_is_all(event_id):
            data = dump_data_table(session)
            title = "Data sources for all events"
        else:
            event = resolve_event(session, event_id)
            data = dump_data_table(session, event.id)
            _time = event.time.strftime("%Y-%m-%d %H:%M:%S") if not raw else event.time
            _id = uuid_shortener(session, event) if not raw else event.id
            title = f"Data sources for event {_time} (ID={_id})"

        col_specs = {
            "id": RichColSpec(
                formatter=lambda x: uuid_shortener(
                    session, AimbatDataSource, str_uuid=x
                )
            ),
            "seismogram_id": RichColSpec(
                formatter=lambda x: uuid_shortener(
                    session, AimbatSeismogram, str_uuid=x
                )
            ),
        }

        json_to_table(
            model=AimbatDataSource,
            data=data,
            title=title,
            raw=raw,
            col_specs=col_specs,
        )

cli_data_prune

cli_data_prune(
    event_id: Annotated[
        UUID | Literal["all"], event_parameter_with_all()
    ],
    *,
    dry_run: Annotated[
        bool,
        Parameter(
            name="dry-run",
            help="Show what would be pruned without deleting anything.",
        ),
    ] = False,
    force: Annotated[
        bool,
        Parameter(
            help="Treat a source whose presence cannot be determined (an "
            + "unreadable file, a missing parent directory, an unreachable "
            + "mount) as vanished instead of stopping."
        ),
    ] = False,
    prune_empty_stations: Annotated[
        bool,
        Parameter(
            name="prune-empty-stations",
            help="Also delete stations left with no seismograms.",
        ),
    ] = False,
    prune_empty_events: Annotated[
        bool,
        Parameter(
            name="prune-empty-events",
            help="Also delete events left with no seismograms. This permanently "
            + "destroys those events' snapshot history.",
        ),
    ] = False,
    auto_snapshot: Annotated[
        bool,
        Parameter(
            name="snapshot",
            help="Snapshot every affected event before pruning.",
        ),
    ] = True,
    confirm: ConfirmParameters = ConfirmParameters(),
) -> None

Delete seismograms whose data source has vanished.

Reconciles the project with reality after a waveform source is removed (a SAC file deleted, a directory moved). Each seismogram whose source can no longer be read is deleted, along with its data source, parameters, and quality records. Frozen snapshot history is preserved.

prune never deletes anything the source still provides. To remove a seismogram whose data still exist, remove it from the source directory first; to exclude one from analysis without deleting it, set select = False instead.

Always run with --dry-run first: prune cannot tell a relocated file from a deleted one. Pass --prune-empty-stations / --prune-empty-events to also clean up stations or events left with no seismograms.

Source code in src/aimbat/_cli/data.py
@app.command(name="prune")
@handle_issues
def cli_data_prune(
    event_id: Annotated[uuid.UUID | Literal["all"], event_parameter_with_all()],
    *,
    dry_run: Annotated[
        bool,
        Parameter(
            name="dry-run",
            help="Show what would be pruned without deleting anything.",
        ),
    ] = False,
    force: Annotated[
        bool,
        Parameter(
            help=(
                "Treat a source whose presence cannot be determined (an "
                + "unreadable file, a missing parent directory, an unreachable "
                + "mount) as vanished instead of stopping."
            ),
        ),
    ] = False,
    prune_empty_stations: Annotated[
        bool,
        Parameter(
            name="prune-empty-stations",
            help="Also delete stations left with no seismograms.",
        ),
    ] = False,
    prune_empty_events: Annotated[
        bool,
        Parameter(
            name="prune-empty-events",
            help=(
                "Also delete events left with no seismograms. This permanently "
                + "destroys those events' snapshot history."
            ),
        ),
    ] = False,
    auto_snapshot: Annotated[
        bool,
        Parameter(
            name="snapshot",
            help="Snapshot every affected event before pruning.",
        ),
    ] = True,
    confirm: ConfirmParameters = ConfirmParameters(),
) -> None:
    """Delete seismograms whose data source has vanished.

    Reconciles the project with reality after a waveform source is removed (a
    SAC file deleted, a directory moved). Each seismogram whose source can no
    longer be read is deleted, along with its data source, parameters, and
    quality records. Frozen snapshot history is preserved.

    `prune` never deletes anything the source still provides. To remove a
    seismogram whose data still exist, remove it from the source directory
    first; to exclude one from analysis without deleting it, set `select =
    False` instead.

    Always run with `--dry-run` first: `prune` cannot tell a relocated file
    from a deleted one. Pass `--prune-empty-stations` / `--prune-empty-events`
    to also clean up stations or events left with no seismograms.
    """
    from aimbat.core import prune_project
    from aimbat.db import engine

    def confirm_prune(report: PruneReport) -> bool:
        _print_prune_report(
            report,
            will_prune_stations=prune_empty_stations,
            will_prune_events=prune_empty_events,
        )
        stations = len(report.emptied_stations) if prune_empty_stations else 0
        events = len(report.emptied_events) if prune_empty_events else 0
        confirm_or_abort(
            f"Delete {len(report.orphan_seismograms)} seismogram(s) "
            + f"(+ {stations} station(s), {events} event(s), "
            + f"{report.total_snapshots} snapshot(s), {report.total_notes} note(s))?",
            yes=confirm.yes,
        )
        return True

    with Session(engine) as session:
        report = prune_project(
            session,
            event_id,
            dry_run=dry_run,
            prune_empty_stations=prune_empty_stations,
            prune_empty_events=prune_empty_events,
            auto_snapshot=auto_snapshot,
            force=force,
            confirm=None if dry_run else confirm_prune,
        )
        # A dry run, or a live run with nothing to prune, never reaches the
        # confirm callback, so the report is still unprinted here.
        if dry_run or not report.orphan_seismograms:
            _print_prune_report(
                report,
                will_prune_stations=prune_empty_stations,
                will_prune_events=prune_empty_events,
            )

db

Manage the AIMBAT project database schema.

Schema changes are applied via Alembic. Most users never need this command group: aimbat project create already creates a database with the current schema. It exists for bringing an existing project database up to date after an AIMBAT upgrade that changed the schema, including databases created before this command group existed.

Functions:

Name Description
cli_db_current

Show the schema version the project database is currently at.

cli_db_upgrade

Upgrade the project database to the latest schema version.

cli_db_current

cli_db_current(
    *, _: DebugParameter = DebugParameter()
) -> None

Show the schema version the project database is currently at.

Source code in src/aimbat/_cli/db.py
@app.command(name="current")
@handle_issues
def cli_db_current(*, _: DebugParameter = DebugParameter()) -> None:
    """Show the schema version the project database is currently at."""
    from aimbat.core import get_current_revision
    from aimbat.core._project import _project_exists
    from aimbat.db import engine

    if not _project_exists(engine):
        raise RuntimeError(
            'No AIMBAT project found. Try running "aimbat project create" first.'
        )

    revision = get_current_revision(engine)
    if revision is None:
        print("No Alembic version history (pre-dates schema versioning).")
    else:
        print(revision)

cli_db_upgrade

cli_db_upgrade(
    *, _: DebugParameter = DebugParameter()
) -> None

Upgrade the project database to the latest schema version.

Safe to run on a database that is already up to date (does nothing) or on one that predates Alembic (verifies its schema matches before stamping it, then upgrades).

Source code in src/aimbat/_cli/db.py
@app.command(name="upgrade")
@handle_issues
def cli_db_upgrade(*, _: DebugParameter = DebugParameter()) -> None:
    """Upgrade the project database to the latest schema version.

    Safe to run on a database that is already up to date (does nothing) or
    on one that predates Alembic (verifies its schema matches before
    stamping it, then upgrades).
    """
    import warnings

    from aimbat.core import upgrade_project
    from aimbat.core._migrations import SchemaStaleWarning
    from aimbat.core._project import _project_exists
    from aimbat.db import engine

    # The staleness warning's whole message is "run `aimbat db upgrade`" -
    # showing it while that command is already running is just noise.
    # Scoped to this call only; restored automatically on exit.
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", SchemaStaleWarning)

        if not _project_exists(engine):
            raise RuntimeError(
                'No AIMBAT project found. Try running "aimbat project create" first.'
            )

        upgrade_project(engine)

event

View and manage events in the AIMBAT project.

Functions:

Name Description
cli_event_delete

Delete an existing event.

cli_event_dump

Dump the contents of the AIMBAT event table to JSON.

cli_event_list

Print a table of events stored in the AIMBAT project.

cli_event_parameter_dump

Dump event parameter table to json.

cli_event_parameter_get

Get parameter value for an event.

cli_event_parameter_list

List processing parameter for an event or all events.

cli_event_parameter_set

Set parameter value for an event.

cli_event_quality_dump

Dump event quality statistics to JSON.

cli_event_quality_list

Show aggregated quality statistics for an event or all events.

cli_event_delete

cli_event_delete(
    event_id: Annotated[UUID, event_parameter()],
    *,
    confirm: ConfirmParameters = ConfirmParameters(),
) -> None

Delete an existing event.

Cascades to all of the event's seismograms (with their data sources, parameters, and quality records) and to every snapshot taken for it.

Source code in src/aimbat/_cli/event.py
@app.command(name="delete")
@handle_issues
def cli_event_delete(
    event_id: Annotated[uuid.UUID, event_parameter()],
    *,
    confirm: ConfirmParameters = ConfirmParameters(),
) -> None:
    """Delete an existing event.

    Cascades to all of the event's seismograms (with their data sources,
    parameters, and quality records) and to every snapshot taken for it.
    """
    from aimbat.core import delete_event, resolve_event
    from aimbat.db import engine

    with Session(engine) as session:
        event = resolve_event(session, event_id)
        confirm_or_abort(
            f"Delete event at {event.time} and all of its seismograms?",
            yes=confirm.yes,
        )
        delete_event(session, event.id)

cli_event_dump

cli_event_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None

Dump the contents of the AIMBAT event table to JSON.

Output can be piped or redirected for use in external tools or scripts.

Source code in src/aimbat/_cli/event.py
@app.command(name="dump")
@handle_issues
def cli_event_dump(
    *, dump_parameters: JsonDumpParameters = JsonDumpParameters()
) -> None:
    """Dump the contents of the AIMBAT event table to JSON.

    Output can be piped or redirected for use in external tools or scripts.
    """
    from aimbat.core import dump_event_table

    print_json_dump(dump_event_table, by_alias=dump_parameters.by_alias)

cli_event_list

cli_event_list(
    *, table_parameters: TableParameters = TableParameters()
) -> None

Print a table of events stored in the AIMBAT project.

Source code in src/aimbat/_cli/event.py
@app.command(name="list")
@handle_issues
def cli_event_list(
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None:
    """Print a table of events stored in the AIMBAT project."""

    from aimbat.core import dump_event_table
    from aimbat.db import engine
    from aimbat.logger import logger
    from aimbat.models import AimbatEventRead

    from .common import json_to_table

    if raw := table_parameters.raw:
        exclude = {"short_id"}
    else:
        exclude = {"id"}

    with Session(engine) as session:
        logger.info("Printing AIMBAT events table.")

        json_to_table(
            data=dump_event_table(
                session, from_read_model=True, by_title=False, exclude=exclude
            ),
            model=AimbatEventRead,
            title="AIMBAT Events",
            raw=raw,
        )

cli_event_parameter_dump

cli_event_parameter_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None

Dump event parameter table to json.

Source code in src/aimbat/_cli/event.py
@_parameter.command(name="dump")
@handle_issues
def cli_event_parameter_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None:
    """Dump event parameter table to json."""
    from aimbat.core import dump_event_parameter_table

    print_json_dump(dump_event_parameter_table, by_alias=dump_parameters.by_alias)

cli_event_parameter_get

cli_event_parameter_get(
    name: EventParameter,
    *,
    event_debug_parameters: EventDebugParameters,
) -> None

Get parameter value for an event.

Parameters:

Name Type Description Default
name EventParameter

Event parameter name.

required
event_debug_parameters EventDebugParameters

Event context and debug mode.

required
Source code in src/aimbat/_cli/event.py
@_parameter.command(name="get")
@handle_issues
def cli_event_parameter_get(
    name: EventParameter, *, event_debug_parameters: EventDebugParameters
) -> None:
    """Get parameter value for an event.

    Args:
        name: Event parameter name.
        event_debug_parameters: Event context and debug mode.
    """

    from sqlmodel import Session

    from aimbat.core import resolve_event
    from aimbat.db import engine

    event_id = event_debug_parameters.event_id

    with Session(engine) as session:
        event = resolve_event(session, event_id)
        value = event.parameters.model_dump(mode="json").get(name)
        print(value)

cli_event_parameter_list

cli_event_parameter_list(
    event_id: Annotated[
        UUID | Literal["all"], event_parameter_with_all()
    ],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None

List processing parameter for an event or all events.

Displays all event-level parameters (e.g. time window, bandpass filter settings, minimum cc) in a table.

Source code in src/aimbat/_cli/event.py
@_parameter.command(name="list")
@handle_issues
def cli_event_parameter_list(
    event_id: Annotated[uuid.UUID | Literal["all"], event_parameter_with_all()],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None:
    """List processing parameter for an event or all events.

    Displays all event-level parameters (e.g. time window, bandpass filter
    settings, minimum cc) in a table.
    """

    from aimbat.core import dump_event_parameter_table, resolve_event
    from aimbat.db import engine
    from aimbat.models import AimbatEventParameters
    from aimbat.models._format import RichColSpec
    from aimbat.utils import uuid_shortener

    from .common import json_to_table

    raw = table_parameters.raw

    with Session(engine) as session:
        if event_parameter_is_all(event_id):
            title = "Event parameters for all events"
            data = dump_event_parameter_table(session)
        else:
            event = resolve_event(session, event_id)
            label = id_label(session, AimbatEvent, event.id, raw=raw)
            title = f"Event parameters for event: {label}"
            data = dump_event_parameter_table(
                session, event_id=event.id, exclude={"event_id"}
            )

        column_order = ["id"]
        col_specs = {
            "id": RichColSpec(
                formatter=lambda x: uuid_shortener(
                    session, AimbatEventParameters, str_uuid=x
                )
            ),
            "event_id": RichColSpec(
                formatter=lambda x: uuid_shortener(session, AimbatEvent, str_uuid=x)
            ),
        }

        json_to_table(
            model=AimbatEventParameters,
            title=title,
            data=data,
            raw=raw,
            col_specs=col_specs,
            column_order=column_order,
        )

cli_event_parameter_set

cli_event_parameter_set(
    name: EventParameter,
    value: str,
    *,
    event_debug_parameters: EventDebugParameters,
) -> None

Set parameter value for an event.

Parameters:

Name Type Description Default
name EventParameter

Event parameter name.

required
value str

New parameter value.

required
event_debug_parameters EventDebugParameters

Event context and debug mode.

required
Source code in src/aimbat/_cli/event.py
@_parameter.command(name="set")
@handle_issues
def cli_event_parameter_set(
    name: EventParameter, value: str, *, event_debug_parameters: EventDebugParameters
) -> None:
    """Set parameter value for an event.

    Args:
        name: Event parameter name.
        value: New parameter value.
        event_debug_parameters: Event context and debug mode.
    """
    from sqlmodel import Session

    from aimbat.core import resolve_event, set_event_parameter
    from aimbat.db import engine

    event_id = event_debug_parameters.event_id

    with Session(engine) as session:
        event = resolve_event(session, event_id)
        set_event_parameter(session, event.id, name, value, validate_iccs=True)

cli_event_quality_dump

cli_event_quality_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None

Dump event quality statistics to JSON.

Output can be piped or redirected for use in external tools or scripts.

Source code in src/aimbat/_cli/event.py
@_quality.command(name="dump")
@handle_issues
def cli_event_quality_dump(
    *, dump_parameters: JsonDumpParameters = JsonDumpParameters()
) -> None:
    """Dump event quality statistics to JSON.

    Output can be piped or redirected for use in external tools or scripts.
    """
    from aimbat.core import dump_event_quality_table

    print_json_dump(dump_event_quality_table, by_alias=dump_parameters.by_alias)

cli_event_quality_list

cli_event_quality_list(
    event_id: Annotated[
        UUID | Literal["all"], event_parameter_with_all()
    ],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None

Show aggregated quality statistics for an event or all events.

Displays ICCS and MCCC quality metrics (means, SEMs, RMSE) aggregated across the seismograms of each event.

Source code in src/aimbat/_cli/event.py
@_quality.command(name="list")
@handle_issues
def cli_event_quality_list(
    event_id: Annotated[uuid.UUID | Literal["all"], event_parameter_with_all()],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None:
    """Show aggregated quality statistics for an event or all events.

    Displays ICCS and MCCC quality metrics (means, SEMs, RMSE) aggregated
    across the seismograms of each event.
    """
    from aimbat.core import dump_event_quality_table
    from aimbat.db import engine

    raw = table_parameters.raw

    with Session(engine) as session:
        if event_parameter_is_all(event_id):
            title, filter_id = "Quality statistics for all events", None
        else:
            label = id_label(session, AimbatEvent, event_id, raw=raw)
            title, filter_id = f"Quality statistics for event: {label}", event_id

        print_quality_table(
            session,
            dump_event_quality_table,
            title=title,
            filter_id=filter_id,
            id_field="event_id",
            id_columns={"event_id": AimbatEvent},
            raw=raw,
        )

plot

Create plots for seismograms and ICCS results.

Available plots:

  • data: raw seismograms sorted by epicentral distance.
  • stack: the ICCS cross-correlation stack for an event.
  • matrix: seismograms displayed as a matrix image.

Most plot commands support --context / --no-context to toggle extra waveform context, and --all to include deselected seismograms.

stack and matrix build an ICCS instance to plot from, which writes the resulting cross-correlation values to the project database (the same live-quality write every ICCS-consuming command makes) - despite being read-only from the user's point of view, they do write to the database.

Each command closes its database session before the plot window opens, so an open plot holds no database connection.

Functions:

Name Description
cli_plot_matrix_image

Plot the ICCS seismograms of an event as a matrix image.

cli_plot_stack

Plot the ICCS stack of an event.

cli_seismogram_plot

Plot input seismograms in an event sorted by epicentral distance.

cli_plot_matrix_image

cli_plot_matrix_image(
    event_id: Annotated[UUID, event_parameter()],
    *,
    iccs_plot_parameters: IccsPlotParameters = IccsPlotParameters(),
    _: DebugParameter = DebugParameter(),
) -> None

Plot the ICCS seismograms of an event as a matrix image.

The matrix is assembled from individual waveforms, with each row representing a different seismogram. Building the ICCS instance writes each seismogram's cross-correlation value to the project database, so this command writes to the database despite being read-only from the user's point of view.

Source code in src/aimbat/_cli/plot.py
@app.command(name="matrix")
@handle_issues
def cli_plot_matrix_image(
    event_id: Annotated[UUID, event_parameter()],
    *,
    iccs_plot_parameters: IccsPlotParameters = IccsPlotParameters(),
    _: DebugParameter = DebugParameter(),
) -> None:
    """Plot the ICCS seismograms of an event as a matrix image.

    The matrix is assembled from individual waveforms, with each row
    representing a different seismogram. Building the ICCS instance writes
    each seismogram's cross-correlation value to the project database, so
    this command writes to the database despite being read-only from the
    user's point of view.
    """
    from sqlmodel import Session

    from aimbat.core import (
        create_iccs_instance,
        resolve_event,
    )
    from aimbat.db import engine
    from aimbat.plot import plot_matrix_image

    with Session(engine) as session:
        event = resolve_event(session, event_id)
        iccs = create_iccs_instance(session, event).iccs
        session.commit()

    plot_matrix_image(
        iccs,
        iccs_plot_parameters.context,
        all_seismograms=iccs_plot_parameters.all_seismograms,
        return_fig=False,
    )

cli_plot_stack

cli_plot_stack(
    event_id: Annotated[UUID, event_parameter()],
    *,
    iccs_plot_parameters: IccsPlotParameters = IccsPlotParameters(),
    _: DebugParameter = DebugParameter(),
) -> None

Plot the ICCS stack of an event.

Building the ICCS instance writes each seismogram's cross-correlation value to the project database, so this command writes to the database despite being read-only from the user's point of view.

Source code in src/aimbat/_cli/plot.py
@app.command(name="stack")
@handle_issues
def cli_plot_stack(
    event_id: Annotated[UUID, event_parameter()],
    *,
    iccs_plot_parameters: IccsPlotParameters = IccsPlotParameters(),
    _: DebugParameter = DebugParameter(),
) -> None:
    """Plot the ICCS stack of an event.

    Building the ICCS instance writes each seismogram's cross-correlation
    value to the project database, so this command writes to the database
    despite being read-only from the user's point of view.
    """
    from sqlmodel import Session

    from aimbat.core import create_iccs_instance, resolve_event
    from aimbat.db import engine
    from aimbat.plot import plot_stack

    with Session(engine) as session:
        event = resolve_event(session, event_id)
        iccs = create_iccs_instance(session, event).iccs
        session.commit()

    plot_stack(
        iccs,
        iccs_plot_parameters.context,
        all_seismograms=iccs_plot_parameters.all_seismograms,
        return_fig=False,
    )

cli_seismogram_plot

cli_seismogram_plot(
    event_id: Annotated[UUID, event_parameter()],
    *,
    _: DebugParameter = DebugParameter(),
) -> None

Plot input seismograms in an event sorted by epicentral distance.

Source code in src/aimbat/_cli/plot.py
@app.command(name="seismograms")
@handle_issues
def cli_seismogram_plot(
    event_id: Annotated[UUID, event_parameter()],
    *,
    _: DebugParameter = DebugParameter(),
) -> None:
    """Plot input seismograms in an event sorted by epicentral distance."""
    from sqlmodel import Session

    from aimbat.core import resolve_event
    from aimbat.db import engine
    from aimbat.plot import plot_seismograms, show_figure

    with Session(engine) as session:
        event = resolve_event(session, event_id)
        fig, _axes = plot_seismograms(event, return_fig=True)

    show_figure(fig)

project

Manage AIMBAT projects.

This command manages projects. By default, the project consists of a file called aimbat.db in the current working directory. All aimbat commands must be executed from the same directory. The location (and name) of the project file may also be specified by setting the AIMBAT_PROJECT environment variable to the desired filename. Alternatively, aimbat can be executed with a database url directly.

Functions:

Name Description
cli_project_create

Create a new AIMBAT project in the current directory.

cli_project_delete

Delete the AIMBAT project database.

cli_project_info

Show information on an existing project.

cli_project_create

cli_project_create(
    *, _: DebugParameter = DebugParameter()
) -> None

Create a new AIMBAT project in the current directory.

Initialises a new project database (aimbat.db by default). Run this once before adding data with aimbat data add.

Source code in src/aimbat/_cli/project.py
@app.command(name="create")
@handle_issues
def cli_project_create(*, _: DebugParameter = DebugParameter()) -> None:
    """Create a new AIMBAT project in the current directory.

    Initialises a new project database (`aimbat.db` by default). Run this
    once before adding data with `aimbat data add`.
    """
    from aimbat.core import create_project
    from aimbat.db import engine

    create_project(engine)

cli_project_delete

cli_project_delete(
    *, confirm: ConfirmParameters = ConfirmParameters()
) -> None

Delete the AIMBAT project database.

Does not delete the underlying seismogram data source files.

Source code in src/aimbat/_cli/project.py
@app.command(name="delete")
@handle_issues
def cli_project_delete(*, confirm: ConfirmParameters = ConfirmParameters()) -> None:
    """Delete the AIMBAT project database.

    Does not delete the underlying seismogram data source files.
    """
    from aimbat.core import delete_project
    from aimbat.db import engine

    confirm_or_abort("Delete the AIMBAT project database?", yes=confirm.yes)
    delete_project(engine)

cli_project_info

cli_project_info(
    event_id: Annotated[
        UUID | None, event_parameter()
    ] = None,
    *,
    _: DebugParameter = DebugParameter(),
) -> None

Show information on an existing project.

Source code in src/aimbat/_cli/project.py
@app.command(name="info")
@handle_issues
def cli_project_info(
    event_id: Annotated[UUID | None, event_parameter()] = None,
    *,
    _: DebugParameter = DebugParameter(),
) -> None:
    """Show information on an existing project."""

    from rich.console import Console
    from rich.panel import Panel
    from rich.table import Table
    from sqlalchemy.exc import NoResultFound
    from sqlmodel import Session, select

    import aimbat.core._event as event
    import aimbat.core._seismogram as seismogram
    import aimbat.core._station as station
    from aimbat.core import resolve_event
    from aimbat.core._project import _project_exists
    from aimbat.db import engine
    from aimbat.models import AimbatEvent, AimbatSeismogram, AimbatStation

    if not _project_exists(engine):
        raise RuntimeError(
            'No AIMBAT project found. Try running "aimbat project create" first.'
        )

    with Session(engine) as session:
        grid = Table.grid(expand=False)
        grid.add_column()
        grid.add_column(justify="left")
        if engine.driver == "pysqlite":
            if engine.url.database == ":memory:":
                grid.add_row("AIMBAT Project: ", "in-memory database")
            else:
                grid.add_row("AIMBAT Project File: ", str(engine.url.database))

        events = len(session.exec(select(AimbatEvent)).all())
        completed_events = len(event.get_completed_events(session))
        stations = len(session.exec(select(AimbatStation)).all())
        seismograms = len(session.exec(select(AimbatSeismogram)).all())
        selected_seismograms = len(
            seismogram.get_selected_seismograms(session, all_events=True)
        )

        grid.add_row(
            "Number of Events (total/completed): ",
            f"({events}/{completed_events})",
        )

        try:
            target_event = resolve_event(session, event_id)
            target_event_id = target_event.id
            active_stations = len(
                station.get_stations_in_event(session, target_event.id)
            )
            seismograms_in_event = len(target_event.seismograms)
            selected_seismograms_in_event = len(
                seismogram.get_selected_seismograms(session, event_id=target_event_id)
            )
        except (NoResultFound, ValueError, RuntimeError):
            target_event_id = None
            active_stations = None
            seismograms_in_event = None
            selected_seismograms_in_event = None

        def _fmt(value: int | UUID | None) -> str:
            return "—" if value is None else str(value)

        event_label = "Selected Event ID: "
        grid.add_row(event_label, _fmt(target_event_id))
        grid.add_row(
            "Number of Stations in Project (total/selected event): ",
            f"({stations}/{_fmt(active_stations)})",
        )

        grid.add_row(
            "Number of Seismograms in Project (total/selected): ",
            f"({seismograms}/{selected_seismograms})",
        )
        grid.add_row(
            "Number of Seismograms in Selected Event (total/selected): ",
            f"({_fmt(seismograms_in_event)}/{_fmt(selected_seismograms_in_event)})",
        )

        console = Console()
        console.print(
            Panel(grid, title="Project Info", title_align="left", border_style="dim")
        )

seismogram

View and manage seismograms in the AIMBAT project.

Functions:

Name Description
cli_seismogram_delete

Delete an existing seismogram.

cli_seismogram_dump

Dump the contents of the AIMBAT seismogram table to JSON.

cli_seismogram_list

Print information on the seismograms in an event.

cli_seismogram_parameter_dump

Dump seismogram parameter table to json.

cli_seismogram_parameter_get

Get the value of a processing parameter.

cli_seismogram_parameter_list

List processing parameter values for seismograms in an event.

cli_seismogram_parameter_reset

Reset all processing parameters to their default values.

cli_seismogram_parameter_set

Set value of a processing parameter.

cli_seismogram_delete

cli_seismogram_delete(
    seismogram_id: Annotated[
        UUID,
        id_parameter(
            AimbatSeismogram,
            help="UUID (or unique prefix) of seismogram to delete.",
        ),
    ],
    *,
    confirm: ConfirmParameters = ConfirmParameters(),
) -> None

Delete an existing seismogram.

Cascades to its data source, processing parameters, and quality record. Also removes this seismogram's parameter and quality data from every snapshot that recorded it, including snapshots taken before deletion.

Source code in src/aimbat/_cli/seismogram.py
@app.command(name="delete")
@handle_issues
def cli_seismogram_delete(
    seismogram_id: Annotated[
        UUID,
        id_parameter(
            AimbatSeismogram, help="UUID (or unique prefix) of seismogram to delete."
        ),
    ],
    *,
    confirm: ConfirmParameters = ConfirmParameters(),
) -> None:
    """Delete an existing seismogram.

    Cascades to its data source, processing parameters, and quality record.
    Also removes this seismogram's parameter and quality data from every
    snapshot that recorded it, including snapshots taken before deletion.
    """
    from sqlmodel import Session

    from aimbat.core import delete_seismogram
    from aimbat.db import engine

    confirm_or_abort(f"Delete seismogram {seismogram_id}?", yes=confirm.yes)
    with Session(engine) as session:
        delete_seismogram(session, seismogram_id)

cli_seismogram_dump

cli_seismogram_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None

Dump the contents of the AIMBAT seismogram table to JSON.

Output can be piped or redirected for use in external tools or scripts.

Source code in src/aimbat/_cli/seismogram.py
@app.command(name="dump")
@handle_issues
def cli_seismogram_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None:
    """Dump the contents of the AIMBAT seismogram table to JSON.

    Output can be piped or redirected for use in external tools or scripts.
    """
    from aimbat.core import dump_seismogram_table

    print_json_dump(dump_seismogram_table, by_alias=dump_parameters.by_alias)

cli_seismogram_list

cli_seismogram_list(
    event_id: Annotated[
        UUID | Literal["all"], event_parameter_with_all()
    ],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None

Print information on the seismograms in an event.

Source code in src/aimbat/_cli/seismogram.py
@app.command(name="list")
@handle_issues
def cli_seismogram_list(
    event_id: Annotated[UUID | Literal["all"], event_parameter_with_all()],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None:
    """Print information on the seismograms in an event."""
    from sqlmodel import Session

    from aimbat.core import dump_seismogram_table, resolve_event
    from aimbat.db import engine
    from aimbat.models import AimbatSeismogramRead

    from .common import json_to_table

    if raw := table_parameters.raw:
        exclude = {"short_id", "short_event_id"}
    else:
        exclude = {"id", "event_id"}

    with Session(engine) as session:
        if event_parameter_is_all(event_id):
            title = "AIMBAT seismograms for all events"
            data = dump_seismogram_table(session, from_read_model=True, exclude=exclude)
        else:
            event = resolve_event(session, event_id)
            title = event_table_title(session, event, subject="seismograms", raw=raw)
            exclude.add("event_id" if raw else "short_event_id")
            data = dump_seismogram_table(
                session,
                from_read_model=True,
                event_id=event.id,
                exclude=exclude,
            )

        json_to_table(data, model=AimbatSeismogramRead, title=title, raw=raw)

cli_seismogram_parameter_dump

cli_seismogram_parameter_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None

Dump seismogram parameter table to json.

Source code in src/aimbat/_cli/seismogram.py
@parameter.command(name="dump")
@handle_issues
def cli_seismogram_parameter_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None:
    """Dump seismogram parameter table to json."""
    from aimbat.core import dump_seismogram_parameter_table

    print_json_dump(dump_seismogram_parameter_table, by_alias=dump_parameters.by_alias)

cli_seismogram_parameter_get

cli_seismogram_parameter_get(
    name: SeismogramParameter,
    *,
    seismogram_id: Annotated[
        UUID,
        id_parameter(
            AimbatSeismogram,
            help="UUID (or unique prefix) of seismogram to query.",
        ),
    ],
    _: DebugParameter = DebugParameter(),
) -> None

Get the value of a processing parameter.

Parameters:

Name Type Description Default
name SeismogramParameter

Name of the seismogram parameter.

required
seismogram_id Annotated[UUID, id_parameter(AimbatSeismogram, help='UUID (or unique prefix) of seismogram to query.')]

UUID (or unique prefix) of the seismogram to query.

required
Source code in src/aimbat/_cli/seismogram.py
@parameter.command(name="get")
@handle_issues
def cli_seismogram_parameter_get(
    name: SeismogramParameter,
    *,
    seismogram_id: Annotated[
        UUID,
        id_parameter(
            AimbatSeismogram, help="UUID (or unique prefix) of seismogram to query."
        ),
    ],
    _: DebugParameter = DebugParameter(),
) -> None:
    """Get the value of a processing parameter.

    Args:
        name: Name of the seismogram parameter.
        seismogram_id: UUID (or unique prefix) of the seismogram to query.
    """
    from sqlalchemy.exc import NoResultFound
    from sqlmodel import Session

    from aimbat.db import engine
    from aimbat.models import AimbatSeismogram

    with Session(engine) as session:
        seismogram = session.get(AimbatSeismogram, seismogram_id)
        if seismogram is None:
            raise NoResultFound(f"Unable to find seismogram with id: {seismogram_id}.")
        value = seismogram.parameters.model_dump(mode="json").get(name)
        print(value)

cli_seismogram_parameter_list

cli_seismogram_parameter_list(
    event_id: Annotated[
        UUID | Literal["all"], event_parameter_with_all()
    ],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None

List processing parameter values for seismograms in an event.

Displays per-seismogram parameters (e.g. select, flip, t1 pick) in a table. Use seismogram parameter set to modify individual values.

Source code in src/aimbat/_cli/seismogram.py
@parameter.command(name="list")
@handle_issues
def cli_seismogram_parameter_list(
    event_id: Annotated[UUID | Literal["all"], event_parameter_with_all()],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None:
    """List processing parameter values for seismograms in an event.

    Displays per-seismogram parameters (e.g. `select`, `flip`, `t1` pick)
    in a table. Use `seismogram parameter set` to modify individual values.
    """

    from sqlmodel import Session

    from aimbat.core import dump_seismogram_parameter_table, resolve_event
    from aimbat.db import engine
    from aimbat.models import (
        AimbatEvent,
        AimbatSeismogram,
        AimbatSeismogramParameters,
    )
    from aimbat.models._format import RichColSpec
    from aimbat.utils import uuid_shortener
    from aimbat.utils.formatters import fmt_flip

    from .common import json_to_table

    raw = table_parameters.raw

    with Session(engine) as session:
        if event_parameter_is_all(event_id):
            event = None
            title = "Seismogram parameters for all events"
        else:
            event = resolve_event(session, event_id)
            label = id_label(session, AimbatEvent, event.id, raw=raw)
            title = f"Seismogram parameters for event: {label}"

        data = dump_seismogram_parameter_table(
            session, event_id=event.id if event else None
        )

        json_to_table(
            data=data,
            model=AimbatSeismogramParameters,
            title=title,
            raw=raw,
            col_specs={
                "id": RichColSpec(
                    style="yellow",
                    no_wrap=True,
                    highlight=False,
                    formatter=lambda x: uuid_shortener(
                        session, AimbatSeismogramParameters, str_uuid=str(x)
                    ),
                ),
                "seismogram_id": RichColSpec(
                    style="magenta",
                    no_wrap=True,
                    highlight=False,
                    formatter=lambda x: uuid_shortener(
                        session, AimbatSeismogram, str_uuid=str(x)
                    ),
                ),
                "flip": RichColSpec(formatter=fmt_flip),
            },
            column_order=["id"],
        )

cli_seismogram_parameter_reset

cli_seismogram_parameter_reset(
    seismogram_id: Annotated[
        UUID,
        id_parameter(
            AimbatSeismogram,
            help="UUID (or unique prefix) of seismogram to reset parameters for.",
        ),
    ],
    *,
    _: DebugParameter = DebugParameter(),
) -> None

Reset all processing parameters to their default values.

Source code in src/aimbat/_cli/seismogram.py
@parameter.command(name="reset")
@handle_issues
def cli_seismogram_parameter_reset(
    seismogram_id: Annotated[
        UUID,
        id_parameter(
            AimbatSeismogram,
            help="UUID (or unique prefix) of seismogram to reset parameters for.",
        ),
    ],
    *,
    _: DebugParameter = DebugParameter(),
) -> None:
    """Reset all processing parameters to their default values."""
    from sqlmodel import Session

    from aimbat.core import reset_seismogram_parameters
    from aimbat.db import engine

    with Session(engine) as session:
        reset_seismogram_parameters(session, seismogram_id)

cli_seismogram_parameter_set

cli_seismogram_parameter_set(
    name: SeismogramParameter,
    value: str,
    *,
    seismogram_id: Annotated[
        UUID,
        id_parameter(
            AimbatSeismogram,
            help="UUID (or unique prefix) of seismogram to modify.",
        ),
    ],
    _: DebugParameter = DebugParameter(),
) -> None

Set value of a processing parameter.

Parameters:

Name Type Description Default
name SeismogramParameter

Name of the seismogram parameter.

required
value str

Value of the seismogram parameter.

required
seismogram_id Annotated[UUID, id_parameter(AimbatSeismogram, help='UUID (or unique prefix) of seismogram to modify.')]

UUID (or unique prefix) of the seismogram to modify.

required
Source code in src/aimbat/_cli/seismogram.py
@parameter.command(name="set")
@handle_issues
def cli_seismogram_parameter_set(
    name: SeismogramParameter,
    value: str,
    *,
    seismogram_id: Annotated[
        UUID,
        id_parameter(
            AimbatSeismogram, help="UUID (or unique prefix) of seismogram to modify."
        ),
    ],
    _: DebugParameter = DebugParameter(),
) -> None:
    """Set value of a processing parameter.

    Args:
        name: Name of the seismogram parameter.
        value: Value of the seismogram parameter.
        seismogram_id: UUID (or unique prefix) of the seismogram to modify.
    """
    from sqlmodel import Session

    from aimbat.core import set_seismogram_parameter
    from aimbat.db import engine

    with Session(engine) as session:
        set_seismogram_parameter(session, seismogram_id, name, value)

shell

Interactive AIMBAT shell with tab-completion and command history.

All CLI commands are available. Press Tab to complete commands, Ctrl+D or type exit to leave.

Shell-only commands

event switch [ID] Switch the shell's event context.

Functions:

Name Description
cli_shell

Start an interactive AIMBAT shell.

cli_shell

cli_shell(
    event_id: Annotated[
        UUID | None,
        event_parameter(
            help="Start the shell in the context of a specific event. Full UUID or any"
            + " unique prefix as shown in the table. "
        ),
    ] = None,
    *,
    _: DebugParameter = DebugParameter(),
) -> None

Start an interactive AIMBAT shell.

Source code in src/aimbat/_cli/shell.py
@app.default
@handle_issues
def cli_shell(
    event_id: Annotated[
        uuid.UUID | None,
        event_parameter(
            help=(
                "Start the shell in the context of a specific event. Full UUID or any"
                + " unique prefix as shown in the table. "
            )
        ),
    ] = None,
    *,
    _: DebugParameter = DebugParameter(),
) -> None:
    """Start an interactive AIMBAT shell."""
    import os
    import shlex
    from pathlib import Path

    from cyclopts import CycloptsError
    from prompt_toolkit import PromptSession
    from prompt_toolkit.completion import NestedCompleter
    from prompt_toolkit.history import FileHistory
    from rich.console import Console

    from aimbat.app import app as aimbat_app

    console = Console()

    # Shell-local event context, None if no event specified.
    # Modified by `event switch`.
    shell_event_id: uuid.UUID | None = event_id

    # Propagate the shell event context to commands via the DEFAULT_EVENT_ID env
    # var, which all event_parameter() / event_parameter_with_all() converters
    # pick up as a fallback.  Explicit --event flags always take precedence.
    _prev_default_event = os.environ.get("DEFAULT_EVENT_ID")

    def _set_shell_event(eid: uuid.UUID | None) -> None:
        nonlocal shell_event_id
        shell_event_id = eid
        if eid is not None:
            os.environ["DEFAULT_EVENT_ID"] = str(eid)
        else:
            os.environ.pop("DEFAULT_EVENT_ID", None)

    _set_shell_event(shell_event_id)

    completion_dict = _build_completion_dict(aimbat_app)
    completion_dict.pop("shell", None)
    completion_dict.pop("tui", None)

    # Inject the shell-only `event switch` subcommand into tab completion.
    event_completions = completion_dict.get("event")
    if isinstance(event_completions, dict):
        event_completions["switch"] = None

    import sys

    pt_session: PromptSession[str] | None = None
    if sys.stdin.isatty():
        pt_session = PromptSession(
            history=FileHistory(str(Path.home() / ".aimbat_history")),
            completer=NestedCompleter.from_nested_dict(completion_dict),
            complete_while_typing=True,
        )

    console.print(
        "[bold]AIMBAT shell[/bold]  (Tab to complete, Ctrl+D or "
        + "[bold]exit[/bold] to quit)"
    )

    current_bound = _check_iccs(console, None, startup=True, event_id=shell_event_id)

    def _prompt() -> str:
        if shell_event_id is not None:
            return f"aimbat [{str(shell_event_id)[:8]}]> "
        return "aimbat> "

    try:
        while True:
            try:
                if pt_session is not None:
                    text = pt_session.prompt(_prompt).strip()
                else:
                    raw = sys.stdin.readline()
                    if not raw:
                        break
                    text = raw.strip()
            except KeyboardInterrupt:
                continue
            except EOFError:
                break

            if not text:
                continue
            if text in ("exit", "quit", "q"):
                break

            try:
                tokens = shlex.split(text)
            except ValueError as exc:
                console.print(f"[red]Parse error:[/red] {exc}")
                continue

            # Strip a leading "aimbat" token typed out of habit and remind the user.
            if tokens and tokens[0] == "aimbat":
                tokens = tokens[1:]
                console.print("[dim]Tip: no need to type 'aimbat' in the shell.[/dim]")
                if not tokens:
                    continue

            # Shell-only: "event switch [id]" changes context.
            if tokens[:2] == ["event", "switch"]:
                if len(tokens) < 3:
                    # No argument: clear shell event context.
                    _set_shell_event(None)
                    current_bound = _check_iccs(
                        console, None, startup=True, event_id=None
                    )
                else:
                    try:
                        _set_shell_event(_parse_event_id(tokens[2]))
                        current_bound = _check_iccs(
                            console, None, startup=True, event_id=shell_event_id
                        )
                    except Exception as exc:
                        print_error_panel(exc)
                continue

            try:
                aimbat_app(tokens, exit_on_error=False)

                # Check ICCS for the event targeted by the command.  If the
                # user passed --event all (or no explicit flag), fall back to
                # the shell context event.
                explicit_flag = _extract_event_flag(tokens)
                check_event_id: uuid.UUID | None
                if explicit_flag and explicit_flag.lower() != "all":
                    check_event_id = _parse_event_id(explicit_flag)
                else:
                    check_event_id = shell_event_id
                current_bound = _check_iccs(
                    console, current_bound, event_id=check_event_id
                )
            except (CycloptsError, SystemExit):
                # Errors already printed by Cyclopts or subcommand
                pass
            except Exception as exc:
                print_error_panel(exc)
    finally:
        # Restore DEFAULT_EVENT_ID to whatever it was before the shell started.
        if _prev_default_event is not None:
            os.environ["DEFAULT_EVENT_ID"] = _prev_default_event
        else:
            os.environ.pop("DEFAULT_EVENT_ID", None)

snapshot

View and manage snapshots of processing parameters.

A snapshot captures the current event and seismogram processing parameters (e.g. time window, bandpass filter, picks) so they can be restored later. Use snapshot create before making experimental changes, and snapshot rollback to undo them if needed.

Functions:

Name Description
cli_snapshot_create

Create a new snapshot of current processing parameters.

cli_snapshot_delete

Delete an existing snapshot.

cli_snapshot_details

Print information on the event parameters saved in a snapshot.

cli_snapshot_dump

Dump the contents of the AIMBAT snapshot tables to json.

cli_snapshot_list

Print information on the snapshots for an event.

cli_snapshot_preview

Preview the ICCS stack/matrix of a snapshot.

cli_snapshot_quality_dump

Dump snapshot quality statistics to JSON.

cli_snapshot_quality_list

Show aggregated quality statistics for snapshots of an event or all events.

cli_snapshot_results

Export per-seismogram MCCC results from a snapshot as JSON.

cli_snapshot_rollback

Restore saved parameters from a snapshot as the current live values.

cli_snapshot_create

cli_snapshot_create(
    event_id: Annotated[UUID, event_parameter()],
    comment: str | None = None,
    *,
    _: DebugParameter = DebugParameter(),
) -> None

Create a new snapshot of current processing parameters.

Saves the current event and seismogram parameters for an event so they can be restored later with snapshot rollback.

Parameters:

Name Type Description Default
event_id Annotated[UUID, event_parameter()]

UUID (or unique prefix) of the event to snapshot.

required
comment str | None

Optional description to help identify this snapshot later.

None
Source code in src/aimbat/_cli/snapshot.py
@app.command(name="create")
@handle_issues
def cli_snapshot_create(
    event_id: Annotated[UUID, event_parameter()],
    comment: str | None = None,
    *,
    _: DebugParameter = DebugParameter(),
) -> None:
    """Create a new snapshot of current processing parameters.

    Saves the current event and seismogram parameters for an event so
    they can be restored later with `snapshot rollback`.

    Args:
        event_id: UUID (or unique prefix) of the event to snapshot.
        comment: Optional description to help identify this snapshot later.
    """
    from sqlmodel import Session

    from aimbat.core import create_snapshot, resolve_event
    from aimbat.db import engine

    with Session(engine) as session:
        event = resolve_event(session, event_id)
        create_snapshot(session, event, comment)

cli_snapshot_delete

cli_snapshot_delete(
    snapshot_id: Annotated[
        UUID,
        id_parameter(
            AimbatSnapshot,
            help="UUID (or unique prefix) of snapshot to delete.",
        ),
    ],
    *,
    confirm: ConfirmParameters = ConfirmParameters(),
) -> None

Delete an existing snapshot.

Only removes the snapshot's frozen parameter and quality records; current event and seismogram data are unaffected.

Source code in src/aimbat/_cli/snapshot.py
@app.command(name="delete")
@handle_issues
def cli_snapshot_delete(
    snapshot_id: Annotated[
        UUID,
        id_parameter(
            AimbatSnapshot,
            help="UUID (or unique prefix) of snapshot to delete.",
        ),
    ],
    *,
    confirm: ConfirmParameters = ConfirmParameters(),
) -> None:
    """Delete an existing snapshot.

    Only removes the snapshot's frozen parameter and quality records;
    current event and seismogram data are unaffected.
    """
    from sqlmodel import Session

    from aimbat.core import delete_snapshot
    from aimbat.db import engine

    confirm_or_abort(f"Delete snapshot {snapshot_id}?", yes=confirm.yes)
    with Session(engine) as session:
        delete_snapshot(session, snapshot_id)

cli_snapshot_details

cli_snapshot_details(
    snapshot_id: Annotated[
        UUID,
        id_parameter(
            AimbatSnapshot,
            help="UUID (or unique prefix) of snapshot to show details for.",
        ),
    ],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None

Print information on the event parameters saved in a snapshot.

Source code in src/aimbat/_cli/snapshot.py
@app.command(name="details")
@handle_issues
def cli_snapshot_details(
    snapshot_id: Annotated[
        UUID,
        id_parameter(
            AimbatSnapshot,
            help="UUID (or unique prefix) of snapshot to show details for.",
        ),
    ],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None:
    """Print information on the event parameters saved in a snapshot."""
    from sqlmodel import Session

    from aimbat.db import engine
    from aimbat.models import AimbatEventParametersSnapshot

    from .common import json_to_table

    with Session(engine) as session:
        snapshot = session.get(AimbatSnapshot, snapshot_id)

        if snapshot is None:
            raise ValueError(
                f"Unable to print snapshot parameters: snapshot with id={snapshot_id} not found."
            )

        raw = table_parameters.raw
        label = id_label(session, AimbatSnapshot, snapshot.id, raw=raw)
        title = f"Saved event parameters in snapshot: {label}"

        parameters_snapshot = snapshot.event_parameters_snapshot

        json_to_table(
            data=parameters_snapshot.model_dump(
                mode="json", exclude={"id", "snapshot_id", "parameters_id"}
            ),
            model=AimbatEventParametersSnapshot,
            title=title,
            key_header="Parameter",
        )

cli_snapshot_dump

cli_snapshot_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None

Dump the contents of the AIMBAT snapshot tables to json.

Source code in src/aimbat/_cli/snapshot.py
@app.command(name="dump")
@handle_issues
def cli_snapshot_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None:
    """Dump the contents of the AIMBAT snapshot tables to json."""
    from rich import print_json
    from sqlmodel import Session

    from aimbat.core import (
        dump_event_parameter_snapshot_table,
        dump_event_quality_snapshot_table,
        dump_seismogram_parameter_snapshot_table,
        dump_seismogram_quality_snapshot_table,
        dump_snapshot_table,
    )
    from aimbat.db import engine

    with Session(engine) as session:
        data = {
            "snapshots": dump_snapshot_table(
                session, by_alias=dump_parameters.by_alias
            ),
            "event_parameters": dump_event_parameter_snapshot_table(
                session, by_alias=dump_parameters.by_alias
            ),
            "seismogram_parameters": dump_seismogram_parameter_snapshot_table(
                session, by_alias=dump_parameters.by_alias
            ),
            "event_quality": dump_event_quality_snapshot_table(
                session, by_alias=dump_parameters.by_alias
            ),
            "seismogram_quality": dump_seismogram_quality_snapshot_table(
                session, by_alias=dump_parameters.by_alias
            ),
        }
        print_json(data=data)

cli_snapshot_list

cli_snapshot_list(
    event_id: Annotated[
        UUID | Literal["all"], event_parameter_with_all()
    ],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None

Print information on the snapshots for an event.

Source code in src/aimbat/_cli/snapshot.py
@app.command(name="list")
@handle_issues
def cli_snapshot_list(
    event_id: Annotated[UUID | Literal["all"], event_parameter_with_all()],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None:
    """Print information on the snapshots for an event."""
    from sqlmodel import Session

    from aimbat.core import dump_snapshot_table, resolve_event
    from aimbat.db import engine
    from aimbat.logger import logger
    from aimbat.models import AimbatSnapshotRead
    from aimbat.utils import uuid_shortener

    from .common import json_to_table

    if raw := table_parameters.raw:
        exclude = {"short_id", "short_event_id"}
    else:
        exclude = {"id", "event_id"}

    with Session(engine) as session:
        logger.info("Printing AIMBAT snapshots table.")

        if event_parameter_is_all(event_id):
            event = None
            title = "AIMBAT snapshots for all events"
        else:
            event = resolve_event(session, event_id)
            if raw:
                time = event.time.isoformat()
                id = str(event.id)
                exclude.add("event_id")
            else:
                time = event.time.strftime("%Y-%m-%d %H:%M:%S")
                id = uuid_shortener(session, event)
                exclude.add("short_event_id")
            title = f"AIMBAT snapshots for event {time} (ID={id})"

        snapshot_data = dump_snapshot_table(
            session,
            from_read_model=True,
            event_id=event.id if event else None,
            exclude=exclude,
        )

        json_to_table(
            data=snapshot_data, model=AimbatSnapshotRead, title=title, raw=raw
        )

cli_snapshot_preview

cli_snapshot_preview(
    snapshot_id: Annotated[
        UUID,
        id_parameter(
            AimbatSnapshot,
            help="UUID (or unique prefix) of snapshot to preview.",
        ),
    ],
    *,
    iccs_plot_parameters: IccsPlotParameters = IccsPlotParameters(),
    as_matrix: Annotated[
        bool, Parameter(name=matrix)
    ] = False,
    _: DebugParameter = DebugParameter(),
) -> None

Preview the ICCS stack/matrix of a snapshot.

Source code in src/aimbat/_cli/snapshot.py
@app.command(name="preview")
@handle_issues
def cli_snapshot_preview(
    snapshot_id: Annotated[
        UUID,
        id_parameter(
            AimbatSnapshot, help="UUID (or unique prefix) of snapshot to preview."
        ),
    ],
    *,
    iccs_plot_parameters: IccsPlotParameters = IccsPlotParameters(),
    as_matrix: Annotated[bool, Parameter(name="matrix")] = False,
    _: DebugParameter = DebugParameter(),
) -> None:
    """Preview the ICCS stack/matrix of a snapshot."""
    from sqlmodel import Session

    from aimbat.core import build_iccs_from_snapshot
    from aimbat.db import engine
    from aimbat.plot import plot_matrix_image, plot_stack

    with Session(engine) as session:
        iccs = build_iccs_from_snapshot(session, snapshot_id).iccs

    if as_matrix:
        plot_matrix_image(
            iccs,
            iccs_plot_parameters.context,
            all_seismograms=iccs_plot_parameters.all_seismograms,
            return_fig=False,
        )
    else:
        plot_stack(
            iccs,
            iccs_plot_parameters.context,
            all_seismograms=iccs_plot_parameters.all_seismograms,
            return_fig=False,
        )

cli_snapshot_quality_dump

cli_snapshot_quality_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None

Dump snapshot quality statistics to JSON.

Output can be piped or redirected for use in external tools or scripts.

Source code in src/aimbat/_cli/snapshot.py
@_quality.command(name="dump")
@handle_issues
def cli_snapshot_quality_dump(
    *, dump_parameters: JsonDumpParameters = JsonDumpParameters()
) -> None:
    """Dump snapshot quality statistics to JSON.

    Output can be piped or redirected for use in external tools or scripts.
    """
    from aimbat.core import dump_snapshot_quality_table

    print_json_dump(dump_snapshot_quality_table, by_alias=dump_parameters.by_alias)

cli_snapshot_quality_list

cli_snapshot_quality_list(
    event_id: Annotated[
        UUID | Literal["all"], event_parameter_with_all()
    ],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None

Show aggregated quality statistics for snapshots of an event or all events.

Displays ICCS and MCCC quality metrics (means, SEMs, RMSE) from the frozen quality records of each snapshot.

Source code in src/aimbat/_cli/snapshot.py
@_quality.command(name="list")
@handle_issues
def cli_snapshot_quality_list(
    event_id: Annotated[UUID | Literal["all"], event_parameter_with_all()],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None:
    """Show aggregated quality statistics for snapshots of an event or all events.

    Displays ICCS and MCCC quality metrics (means, SEMs, RMSE) from the frozen
    quality records of each snapshot.
    """
    from sqlmodel import Session

    from aimbat.core import dump_snapshot_quality_table, resolve_event
    from aimbat.db import engine
    from aimbat.models import AimbatEvent

    raw = table_parameters.raw

    with Session(engine) as session:
        if event_parameter_is_all(event_id):
            title, filter_id = "Quality statistics for all snapshots", None
        else:
            event = resolve_event(session, event_id)
            label = id_label(session, AimbatEvent, event.id, raw=raw)
            title = f"Quality statistics for snapshots of event: {label}"
            filter_id = event.id

        print_quality_table(
            session,
            dump_snapshot_quality_table,
            title=title,
            filter_id=filter_id,
            id_field="event_id",
            id_columns={"event_id": AimbatEvent, "snapshot_id": AimbatSnapshot},
            raw=raw,
        )

cli_snapshot_results

cli_snapshot_results(
    snapshot_id: Annotated[
        UUID,
        id_parameter(
            AimbatSnapshot,
            help="UUID (or unique prefix) of snapshot.",
        ),
    ],
    *,
    output: Annotated[
        Path | None,
        Parameter(
            name=output,
            help="Write results to this JSON file instead of printing to stdout.",
            validator=Path(dir_okay=False),
        ),
    ] = None,
    force: Annotated[
        bool,
        Parameter(
            help="Overwrite `--output` if it already exists."
        ),
    ] = False,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None

Export per-seismogram MCCC results from a snapshot as JSON.

Each row contains the frozen pick time (T1), ICCS correlation coefficient, per-seismogram MCCC quality metrics, and the event-level MCCC RMSE.

Results are printed to stdout unless --output is given, in which case they are written to the specified file.

Source code in src/aimbat/_cli/snapshot.py
@app.command(name="results")
@handle_issues
def cli_snapshot_results(
    snapshot_id: Annotated[
        UUID,
        id_parameter(AimbatSnapshot, help="UUID (or unique prefix) of snapshot."),
    ],
    *,
    output: Annotated[
        Path | None,
        Parameter(
            name="output",
            help="Write results to this JSON file instead of printing to stdout.",
            validator=validators.Path(dir_okay=False),
        ),
    ] = None,
    force: Annotated[
        bool,
        Parameter(help="Overwrite `--output` if it already exists."),
    ] = False,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None:
    """Export per-seismogram MCCC results from a snapshot as JSON.

    Each row contains the frozen pick time (T1), ICCS correlation coefficient,
    per-seismogram MCCC quality metrics, and the event-level MCCC RMSE.

    Results are printed to stdout unless `--output` is given, in which case
    they are written to the specified file.
    """
    import json

    from sqlmodel import Session

    from aimbat.core import dump_snapshot_results
    from aimbat.db import engine

    if output is not None and output.exists() and not force:
        raise FileExistsError(f"{output} already exists; pass --force to overwrite.")

    with Session(engine) as session:
        data = dump_snapshot_results(
            session,
            snapshot_id,
            by_alias=dump_parameters.by_alias,
        )

    if output is None:
        from rich import print_json

        print_json(data=data)
    else:
        output.write_text(json.dumps(data, indent=2), encoding="utf-8")

cli_snapshot_rollback

cli_snapshot_rollback(
    snapshot_id: Annotated[
        UUID,
        id_parameter(
            AimbatSnapshot,
            help="UUID (or unique prefix) of snapshot to use for rollback.",
        ),
    ],
    *,
    confirm: ConfirmParameters = ConfirmParameters(),
) -> None

Restore saved parameters from a snapshot as the current live values.

Overwrites the current event and per-seismogram parameters for the event with those recorded in the snapshot. Any ICCS runs or parameter changes made after the snapshot was taken are undone. The snapshot itself is not deleted, so you can roll back to it again.

If the snapshot has MCCC quality data, the live quality metrics are also restored from the best matching snapshot (same parameter hash, most recent MCCC run).

Source code in src/aimbat/_cli/snapshot.py
@app.command(name="rollback")
@handle_issues
def cli_snapshot_rollback(
    snapshot_id: Annotated[
        UUID,
        id_parameter(
            AimbatSnapshot,
            help="UUID (or unique prefix) of snapshot to use for rollback.",
        ),
    ],
    *,
    confirm: ConfirmParameters = ConfirmParameters(),
) -> None:
    """Restore saved parameters from a snapshot as the current live values.

    Overwrites the current event and per-seismogram parameters for the event
    with those recorded in the snapshot. Any ICCS runs or parameter changes
    made after the snapshot was taken are undone. The snapshot itself is not
    deleted, so you can roll back to it again.

    If the snapshot has MCCC quality data, the live quality metrics are also
    restored from the best matching snapshot (same parameter hash, most recent
    MCCC run).
    """
    from sqlmodel import Session

    from aimbat.core import rollback_to_snapshot
    from aimbat.db import engine

    confirm_or_abort(
        "Overwrite current live parameters with this snapshot?", yes=confirm.yes
    )
    with Session(engine) as session:
        rollback_to_snapshot(session, snapshot_id)

station

View and manage stations.

Functions:

Name Description
cli_station_delete

Delete an existing station.

cli_station_dump

Dump the contents of the AIMBAT station table to JSON.

cli_station_list

Print information on the stations used in an event.

cli_station_quality_dump

Dump station quality statistics to JSON.

cli_station_quality_list

Show aggregated quality statistics for a station or all stations.

cli_station_seismograms_plot

Plot input seismograms for events recorded at this station.

cli_station_delete

cli_station_delete(
    station_id: Annotated[
        UUID,
        id_parameter(
            AimbatStation,
            help="UUID (or unique prefix) of station to delete.",
        ),
    ],
    *,
    confirm: ConfirmParameters = ConfirmParameters(),
) -> None

Delete an existing station.

Cascades to every seismogram recorded at this station across all events, including their data sources, parameters, quality records, and snapshot history.

Source code in src/aimbat/_cli/station.py
@app.command(name="delete")
@handle_issues
def cli_station_delete(
    station_id: Annotated[
        UUID,
        id_parameter(
            AimbatStation,
            help="UUID (or unique prefix) of station to delete.",
        ),
    ],
    *,
    confirm: ConfirmParameters = ConfirmParameters(),
) -> None:
    """Delete an existing station.

    Cascades to every seismogram recorded at this station across all events,
    including their data sources, parameters, quality records, and snapshot
    history.
    """
    from sqlmodel import Session

    from aimbat.core import delete_station
    from aimbat.db import engine

    confirm_or_abort(
        f"Delete station {station_id} and all of its seismograms?", yes=confirm.yes
    )
    with Session(engine) as session:
        delete_station(session, station_id)

cli_station_dump

cli_station_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None

Dump the contents of the AIMBAT station table to JSON.

Output can be piped or redirected for use in external tools or scripts.

Source code in src/aimbat/_cli/station.py
@app.command(name="dump")
@handle_issues
def cli_station_dump(
    *, dump_parameters: JsonDumpParameters = JsonDumpParameters()
) -> None:
    """Dump the contents of the AIMBAT station table to JSON.

    Output can be piped or redirected for use in external tools or scripts.
    """
    from aimbat.core import dump_station_table

    print_json_dump(dump_station_table, by_alias=dump_parameters.by_alias)

cli_station_list

cli_station_list(
    event_id: Annotated[
        UUID | Literal["all"], event_parameter_with_all()
    ],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None

Print information on the stations used in an event.

Source code in src/aimbat/_cli/station.py
@app.command(name="list")
@handle_issues
def cli_station_list(
    event_id: Annotated[UUID | Literal["all"], event_parameter_with_all()],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None:
    """Print information on the stations used in an event."""
    from sqlmodel import Session

    from aimbat.core import dump_station_table, resolve_event
    from aimbat.db import engine
    from aimbat.logger import logger
    from aimbat.models import AimbatStationRead

    from .common import json_to_table

    if raw := table_parameters.raw:
        exclude = {"short_id"}
    else:
        exclude = {"id"}

    with Session(engine) as session:
        if event_parameter_is_all(event_id):
            logger.debug("Selecting all AIMBAT stations.")
            data = dump_station_table(session, from_read_model=True, exclude=exclude)
            title = "AIMBAT stations for all events"
        else:
            logger.debug("Selecting AIMBAT stations used by event.")
            event = resolve_event(session, event_id)
            data = dump_station_table(
                session,
                event_id=event.id,
                from_read_model=True,
                exclude={"seismogram_count", "event_count"} | exclude,
            )
            title = event_table_title(session, event, subject="stations", raw=raw)

        json_to_table(data, model=AimbatStationRead, title=title, raw=raw)

cli_station_quality_dump

cli_station_quality_dump(
    *,
    dump_parameters: JsonDumpParameters = JsonDumpParameters(),
) -> None

Dump station quality statistics to JSON.

Output can be piped or redirected for use in external tools or scripts.

Source code in src/aimbat/_cli/station.py
@_quality.command(name="dump")
@handle_issues
def cli_station_quality_dump(
    *, dump_parameters: JsonDumpParameters = JsonDumpParameters()
) -> None:
    """Dump station quality statistics to JSON.

    Output can be piped or redirected for use in external tools or scripts.
    """
    from aimbat.core import dump_station_quality_table

    print_json_dump(dump_station_quality_table, by_alias=dump_parameters.by_alias)

cli_station_quality_list

cli_station_quality_list(
    station_id: Annotated[
        UUID | Literal["all"], station_parameter_with_all()
    ],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None

Show aggregated quality statistics for a station or all stations.

Displays ICCS and MCCC quality metrics (means, SEMs) aggregated across all seismograms of each station.

Source code in src/aimbat/_cli/station.py
@_quality.command(name="list")
@handle_issues
def cli_station_quality_list(
    station_id: Annotated[UUID | Literal["all"], station_parameter_with_all()],
    *,
    table_parameters: TableParameters = TableParameters(),
) -> None:
    """Show aggregated quality statistics for a station or all stations.

    Displays ICCS and MCCC quality metrics (means, SEMs) aggregated across
    all seismograms of each station.
    """
    from sqlmodel import Session

    from aimbat.core import dump_station_quality_table
    from aimbat.db import engine

    raw = table_parameters.raw

    with Session(engine) as session:
        if station_parameter_is_all(station_id):
            title, filter_id = "Quality statistics for all stations", None
        else:
            label = id_label(session, AimbatStation, station_id, raw=raw)
            title, filter_id = f"Quality statistics for station: {label}", station_id

        print_quality_table(
            session,
            dump_station_quality_table,
            title=title,
            filter_id=filter_id,
            id_field="station_id",
            id_columns={"station_id": AimbatStation},
            raw=raw,
        )

cli_station_seismograms_plot

cli_station_seismograms_plot(
    station_id: Annotated[
        UUID,
        id_parameter(
            AimbatStation,
            help="UUID (or unique prefix) of station to plot seismograms for.",
        ),
    ],
    *,
    _: DebugParameter = DebugParameter(),
) -> None

Plot input seismograms for events recorded at this station.

Source code in src/aimbat/_cli/station.py
@app.command(name="plotseis")
@handle_issues
def cli_station_seismograms_plot(
    station_id: Annotated[
        UUID,
        id_parameter(
            AimbatStation,
            help="UUID (or unique prefix) of station to plot seismograms for.",
        ),
    ],
    *,
    _: DebugParameter = DebugParameter(),
) -> None:
    """Plot input seismograms for events recorded at this station."""
    from sqlmodel import Session

    from aimbat.db import engine
    from aimbat.models import AimbatStation
    from aimbat.plot import plot_seismograms, show_figure

    with Session(engine) as session:
        station = session.get(AimbatStation, station_id)
        if station is None:
            raise ValueError(f"Station with ID {station_id} not found.")
        fig, _axes = plot_seismograms(station, return_fig=True)

    show_figure(fig)

tool

Launch interactive tools for picking phase arrival times and processing parameters.

Each subcommand opens an interactive matplotlib plot for an event. Use --event-id or set the DEFAULT_EVENT_ID environment variable to choose which event to work with. Interact with the plot (click or scroll, depending on the tool) to set the chosen value, then close the window to save it.

The database session is closed before the window opens, and a new one is opened to save the new value once it closes, so an open plot holds no database connection.

Functions:

Name Description
cli_pick_min_cc

Interactively pick a new minimum cross-correlation for auto-selection.

cli_pick_timewindow

Interactively pick a new cross-correlation time window for an event.

cli_update_bandpass

Interactively update the bandpass filter parameters for an event.

cli_update_phase_pick

Interactively pick a new phase arrival time (t1) for an event.

cli_pick_min_cc

cli_pick_min_cc(
    event_id: Annotated[UUID, event_parameter()],
    *,
    iccs_plot_parameters: IccsPlotParameters = IccsPlotParameters(),
    use_matrix_image: Annotated[
        bool, use_matrix_image()
    ] = True,
    causal: Annotated[bool, causal()] = CAUSAL_DEFAULTS[
        "cc"
    ],
    _: DebugParameter = DebugParameter(),
) -> None

Interactively pick a new minimum cross-correlation for auto-selection.

Opens an interactive plot; scroll to set the cc threshold. Seismograms whose cross-correlation with the stack falls below this value will be automatically de-selected when running ICCS with --autoselect.

Source code in src/aimbat/_cli/tool.py
@app.command(name="cc")
@handle_issues
def cli_pick_min_cc(
    event_id: Annotated[UUID, event_parameter()],
    *,
    iccs_plot_parameters: IccsPlotParameters = IccsPlotParameters(),
    use_matrix_image: Annotated[bool, use_matrix_image_param()] = True,
    causal: Annotated[bool, causal_param()] = CAUSAL_DEFAULTS["cc"],
    _: DebugParameter = DebugParameter(),
) -> None:
    """Interactively pick a new minimum cross-correlation for auto-selection.

    Opens an interactive plot; scroll to set the cc threshold. Seismograms
    whose cross-correlation with the stack falls below this value will be
    automatically de-selected when running ICCS with `--autoselect`.
    """
    from sqlmodel import Session

    from aimbat.core import create_iccs_instance, resolve_event
    from aimbat.db import engine
    from aimbat.plot import update_min_cc

    with Session(engine) as session:
        event = resolve_event(session, event_id)
        iccs = create_iccs_instance(session, event).iccs
        resolved_id = event.id
        session.commit()

    update_min_cc(
        resolved_id,
        iccs,
        iccs_plot_parameters.context,
        all_seismograms=iccs_plot_parameters.all_seismograms,
        causal=causal,
        return_fig=False,
    )

cli_pick_timewindow

cli_pick_timewindow(
    event_id: Annotated[UUID, event_parameter()],
    *,
    iccs_plot_parameters: IccsPlotParameters = IccsPlotParameters(),
    use_matrix_image: Annotated[
        bool, use_matrix_image()
    ] = False,
    causal: Annotated[bool, causal()] = CAUSAL_DEFAULTS[
        "window"
    ],
    _: DebugParameter = DebugParameter(),
) -> None

Interactively pick a new cross-correlation time window for an event.

Opens an interactive plot; click to set the left and right window boundaries, then close the window to save. The window controls which portion of each seismogram is used during ICCS alignment.

Source code in src/aimbat/_cli/tool.py
@app.command(name="window")
@handle_issues
def cli_pick_timewindow(
    event_id: Annotated[UUID, event_parameter()],
    *,
    iccs_plot_parameters: IccsPlotParameters = IccsPlotParameters(),
    use_matrix_image: Annotated[bool, use_matrix_image_param()] = False,
    causal: Annotated[bool, causal_param()] = CAUSAL_DEFAULTS["window"],
    _: DebugParameter = DebugParameter(),
) -> None:
    """Interactively pick a new cross-correlation time window for an event.

    Opens an interactive plot; click to set the left and right window boundaries,
    then close the window to save. The window controls which portion of each
    seismogram is used during ICCS alignment.
    """
    from sqlmodel import Session

    from aimbat.core import create_iccs_instance, resolve_event
    from aimbat.db import engine
    from aimbat.plot import update_timewindow

    with Session(engine) as session:
        event = resolve_event(session, event_id)
        iccs = create_iccs_instance(session, event).iccs
        resolved_id = event.id
        session.commit()

    update_timewindow(
        resolved_id,
        iccs,
        iccs_plot_parameters.context,
        all_seismograms=iccs_plot_parameters.all_seismograms,
        use_matrix_image=use_matrix_image,
        causal=causal,
        return_fig=False,
    )

cli_update_bandpass

cli_update_bandpass(
    event_id: Annotated[UUID, event_parameter()],
    *,
    iccs_plot_parameters: IccsPlotParameters = IccsPlotParameters(),
    use_matrix_image: Annotated[
        bool, use_matrix_image()
    ] = False,
    _: DebugParameter = DebugParameter(),
) -> None

Interactively update the bandpass filter parameters for an event.

Opens an interactive plot with controls for enabling/disabling the bandpass filter and adjusting the minimum and maximum frequencies. Close the window to save the updated parameters.

Source code in src/aimbat/_cli/tool.py
@app.command(name="bandpass")
@handle_issues
def cli_update_bandpass(
    event_id: Annotated[UUID, event_parameter()],
    *,
    iccs_plot_parameters: IccsPlotParameters = IccsPlotParameters(),
    use_matrix_image: Annotated[bool, use_matrix_image_param()] = False,
    _: DebugParameter = DebugParameter(),
) -> None:
    """Interactively update the bandpass filter parameters for an event.

    Opens an interactive plot with controls for enabling/disabling the bandpass
    filter and adjusting the minimum and maximum frequencies. Close the window
    to save the updated parameters.
    """
    from sqlmodel import Session

    from aimbat.core import create_iccs_instance, resolve_event
    from aimbat.db import engine
    from aimbat.plot import update_bandpass

    with Session(engine) as session:
        event = resolve_event(session, event_id)
        iccs = create_iccs_instance(session, event).iccs
        resolved_id = event.id
        session.commit()

    update_bandpass(
        resolved_id,
        iccs,
        context=iccs_plot_parameters.context,
        all_seismograms=iccs_plot_parameters.all_seismograms,
        use_matrix_image=use_matrix_image,
        return_fig=False,
    )

cli_update_phase_pick

cli_update_phase_pick(
    event_id: Annotated[UUID, event_parameter()],
    *,
    iccs_plot_parameters: IccsPlotParameters = IccsPlotParameters(),
    use_matrix_image: Annotated[
        bool, use_matrix_image()
    ] = False,
    causal: Annotated[bool, causal()] = CAUSAL_DEFAULTS[
        "phase"
    ],
    _: DebugParameter = DebugParameter(),
) -> None

Interactively pick a new phase arrival time (t1) for an event.

Opens an interactive plot; click to place the new pick, then close the window to save. The pick is stored as t1 for each seismogram in the ICCS instance.

Source code in src/aimbat/_cli/tool.py
@app.command(name="phase")
@handle_issues
def cli_update_phase_pick(
    event_id: Annotated[UUID, event_parameter()],
    *,
    iccs_plot_parameters: IccsPlotParameters = IccsPlotParameters(),
    use_matrix_image: Annotated[bool, use_matrix_image_param()] = False,
    causal: Annotated[bool, causal_param()] = CAUSAL_DEFAULTS["phase"],
    _: DebugParameter = DebugParameter(),
) -> None:
    """Interactively pick a new phase arrival time (t1) for an event.

    Opens an interactive plot; click to place the new pick, then close the window
    to save. The pick is stored as `t1` for each seismogram in the ICCS instance.
    """
    from sqlmodel import Session

    from aimbat.core import create_iccs_instance, resolve_event
    from aimbat.db import engine
    from aimbat.plot import update_pick

    with Session(engine) as session:
        event = resolve_event(session, event_id)
        iccs = create_iccs_instance(session, event).iccs
        resolved_id = event.id
        session.commit()

    update_pick(
        resolved_id,
        iccs,
        context=iccs_plot_parameters.context,
        all_seismograms=iccs_plot_parameters.all_seismograms,
        use_matrix_image=use_matrix_image,
        causal=causal,
        return_fig=False,
    )

tui

Launch the AIMBAT terminal user interface.

Functions:

Name Description
cli_tui

Launch the AIMBAT terminal user interface.

cli_tui

cli_tui(*, _: DebugParameter = DebugParameter()) -> None

Launch the AIMBAT terminal user interface.

Source code in src/aimbat/_cli/tui.py
@app.default
@handle_issues
def cli_tui(*, _: DebugParameter = DebugParameter()) -> None:
    """Launch the AIMBAT terminal user interface."""
    import warnings

    from aimbat._tui.app import main
    from aimbat.core._migrations import SchemaStaleWarning

    # The TUI has its own complete handling for a stale schema - a blocking
    # modal built in on_mount() directly from _build_staleness_warning, not
    # from the warnings module at all. AimbatTUI().run() blocks for the
    # whole session, so handle_issues's CLI-wide SchemaStaleWarning-to-error
    # promotion would otherwise still be active when on_mount()'s first DB
    # connection fires first_connect, crashing before the modal logic ever
    # runs. Scoped to this call only; restored automatically on exit.
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", SchemaStaleWarning)
        main()

utils

Utilities for AIMBAT.

Modules:

Name Description
app

Utilities for AIMBAT.

sampledata

Download or delete AIMBAT sample data.

app

Utilities for AIMBAT.

The utils subcommand contains useful tools that are not strictly part of an AIMBAT workflow.

sampledata

Download or delete AIMBAT sample data.

The sampledata subcommand manages an example dataset that can be used for testing or learning how to use AIMBAT.

The sample data destination folder can be viewed with aimbat utils settings and changed by setting AIMBAT_SAMPLEDATA_DIR.

Functions:

Name Description
sampledata_cli_delete

Recursively delete sample data directory.

sampledata_cli_download

Download AIMBAT sample data.

sampledata_cli_delete

sampledata_cli_delete(
    *, confirm: ConfirmParameters = ConfirmParameters()
) -> None

Recursively delete sample data directory.

Source code in src/aimbat/_cli/utils/sampledata.py
@app.command(name="delete")
@handle_issues
def sampledata_cli_delete(*, confirm: ConfirmParameters = ConfirmParameters()) -> None:
    """Recursively delete sample data directory."""
    from aimbat import settings
    from aimbat.utils import delete_sampledata

    confirm_or_abort(f"Recursively delete {settings.sampledata_dir}?", yes=confirm.yes)
    delete_sampledata()

sampledata_cli_download

sampledata_cli_download(
    *,
    force: Annotated[
        bool,
        Parameter(
            help="Delete the download directory and re-download"
        ),
    ] = False,
    confirm: ConfirmParameters = ConfirmParameters(),
) -> None

Download AIMBAT sample data.

Downloads an example dataset to the directory specified in the sampledata_dir AIMBAT default variable.

Source code in src/aimbat/_cli/utils/sampledata.py
@app.command(name="download")
@handle_issues
def sampledata_cli_download(
    *,
    force: Annotated[
        bool, Parameter(help="Delete the download directory and re-download")
    ] = False,
    confirm: ConfirmParameters = ConfirmParameters(),
) -> None:
    """Download AIMBAT sample data.

    Downloads an example dataset to the directory specified in the
    `sampledata_dir` AIMBAT default variable.
    """
    from aimbat import settings
    from aimbat.utils import download_sampledata

    if force:
        confirm_or_abort(
            f"Delete and replace the existing contents of {settings.sampledata_dir}?",
            yes=confirm.yes,
        )
    download_sampledata(force)