Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,15 @@ AIMBAT is a seismological tool for automated and interactive measurement of body
```
src/aimbat/
├── app.py # Cyclopts CLI root — registers all subcommands
├── cli/ # CLI command definitions (thin layer, delegates to core/)
├── _cli/ # CLI command definitions (thin layer, delegates to core/)
├── core/ # Business logic: ICCS/MCCC algorithms, event/seismogram ops
│ ├── _active_event.py # Manages the single active event constraint
│ ├── _data.py # SAC ingestion entry point
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment describes core/_data.py as "SAC ingestion entry point" but after this PR it handles generic data ingestion for all data types (SAC, JSON_STATION, JSON_EVENT). Update to something like: "Data ingestion entry point for all data types"

Suggested change
│ ├── _data.py # SAC ingestion entry point
│ ├── _data.py # Data ingestion entry point for all data types

Copilot uses AI. Check for mistakes.
│ ├── _iccs.py # ICCS alignment (wraps pysmo.tools.iccs)
│ └── _snapshot.py # Parameter state capture for rollback/comparison
├── models/ # SQLModel ORM definitions (Events, Seismograms, Stations, etc.)
│ └── _sqlalchemy.py # SAPandasTimestamp / SAPandasTimedelta type decorators
├── aimbat_types/ # Custom Pydantic types (PydanticTimestamp, enums for parameters)
├── io/ # File I/O — _base.py defines abstract base; _sac.py implements SAC via pysmo
├── _types/ # Custom Pydantic types (PydanticTimestamp, enums for parameters)
Comment thread
smlloyd marked this conversation as resolved.
├── io/ # File I/O — _base.py defines abstract base; sac.py implements SAC via pysmo
├── utils/ # Shared helpers (JSON→table, UUID truncation, styling, sample data)
├── _config.py # Global Settings (pydantic-settings, env prefix AIMBAT_)
├── _lib/ # Internal mixins (EventParametersValidatorMixin)
Expand Down Expand Up @@ -89,11 +88,11 @@ Settings live in `_config.py` as a `pydantic-settings` class. All settings can b

### CLI Pattern

Each CLI module in `cli/` creates a Cyclopts `App` instance and registers it with the root app in `app.py`. CLI functions are thin wrappers that open a `Session` from `aimbat.db.engine` and delegate to `core/` functions.
Each CLI module in `_cli/` creates a Cyclopts `App` instance and registers it with the root app in `app.py`. CLI functions are thin wrappers that open a `Session` from `aimbat.db.engine` and delegate to `core/` functions.

### Custom Types

- Use `PydanticTimestamp` / `PydanticTimedelta` (from `aimbat.aimbat_types`) for pandas-compatible time fields in models
- Use `PydanticTimestamp` / `PydanticTimedelta` (from `aimbat._types`) for pandas-compatible time fields in models
- Use `PydanticNegativeTimedelta` / `PydanticPositiveTimedelta` for constrained sign validation
- Use `SAPandasTimestamp` / `SAPandasTimedelta` (from `aimbat.models._sqlalchemy`) as the `sa_type` in SQLModel fields
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import path aimbat.models._sqlalchemy is incorrect after this PR moved the file to _types/_sqlalchemy.py. Update to: "Use SAPandasTimestamp / SAPandasTimedelta (from aimbat._types) as the sa_type in SQLModel fields"

Suggested change
- Use `SAPandasTimestamp` / `SAPandasTimedelta` (from `aimbat.models._sqlalchemy`) as the `sa_type` in SQLModel fields
- Use `SAPandasTimestamp` / `SAPandasTimedelta` (from `aimbat._types`) as the `sa_type` in SQLModel fields

Copilot uses AI. Check for mistakes.

Expand Down
4 changes: 2 additions & 2 deletions docs/api/aimbat/cli.md → docs/api/aimbat/_cli.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
::: aimbat.cli
::: aimbat._cli
options:
heading_level: 1
toc_label: aimbat.cli
toc_label: aimbat._cli
show_root_heading: true
show_root_toc_entry: true
inherited_members: true
Expand Down
4 changes: 2 additions & 2 deletions docs/api/aimbat/aimbat_types.md → docs/api/aimbat/_types.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
::: aimbat.aimbat_types
::: aimbat._types
options:
heading_level: 1
toc_label: aimbat.aimbat_types
toc_label: aimbat._types
show_root_heading: true
show_root_toc_entry: true
inherited_members: true
Expand Down
43 changes: 43 additions & 0 deletions docs/first-steps/core-concepts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Core concepts

## Motivation

Precise phase arrival picks are the foundation of travel time tomography —
the accuracy of the resulting images of Earth's interior depends directly on
the quality of these measurements. Obtaining them requires picking the phase
arrival and assessing data quality for every seismogram, across every event
in the dataset. With modern seismic arrays recording each earthquake on
increasingly large numbers of seismometers, doing this seismogram by seismogram
quickly becomes impractical.

AIMBAT addresses this by shifting the focus from individual seismograms to the
dataset as a whole. Rather than assessing and processing each trace in
isolation, the focus is at the array level — where data quality and phase
arrivals can be judged in the context of all seismograms at once. Decisions
about filter settings, time windows, and which seismograms to include apply to
the entire dataset, and picks are refined across all traces simultaneously.
Everything is processed in bulk.

## Semi-automatic

This bulk processing happens in a semi-automatic way, whereby initial picks
surrounded by large time windows are iteratively refined into accurate phase
arrival picks with narrow time windows. Selecting high quality seismograms and
updating picks (for all stations simultaneously) are either performed manually,
or automatically by the ICCS algorithm. The automatically refined picks depend
on user-adjustable parameters, which are typically tuned between iterations to
achieve the best results. Once satisfied with the picks and parameter settings,
MCCC is run to produce the final relative arrival time measurements.

## Snapshots and rollback

The iterative nature of the workflow means exploring different parameter
combinations is central to the process. This is safe to do because the
seismogram data themselves are never modified — AIMBAT only stores and updates
processing parameters separately from the data.

To support this further, snapshots of the current parameter state can be saved
at any point during processing — including before any changes are made.
Rolling back to a snapshot restores the parameters exactly as they were, but
does not delete any other snapshots, so it is possible to switch freely between
saved states.
6 changes: 3 additions & 3 deletions docs/first-steps/data.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ things to be mindful of:
that apply to all seismograms of an event need to be changed to
the same value. Organising the data like this means we only need to change
the parameters in one place. And there are some additional
[perks](#snapshots) when setting things up this way!
[benefits](#snapshots) when setting things up this way!

[^1]:
Deleting items from a project simply drops them from the project. AIMBAT
Expand Down Expand Up @@ -93,7 +93,7 @@ behaves, as well as defaults for [event](#event-parameters) and
[seismogram](#seismogram-parameters) parameters when they are instantiated.
The currently used values for these parameters can be found by running
`#!bash aimbat settings` in your terminal. As some settings are relevant before
a project is created, they cannot stored in the project file. To override these
a project is created, they cannot be stored in the project file. To override these
settings you can set the corresponding environment variable directly (e.g.
`export AIMBAT_PROJECT=different_project_name.db`) or place those settings in a
`.env`[^2] file. Note that if you set them in both places the environment
Expand Down Expand Up @@ -124,7 +124,7 @@ The event and seismogram parameters are stored separately from the events and
seismograms (much like the seismograms link to an event and station instead of
saving them in the same object). This opens up the possibility to save an
arbitrary number of copies of these parameters that capture the current state
of processing. This allows for risk free experimentation with different
of processing. This allows for risk-free experimentation with different
parameters - if something goes wrong, you can always roll back to the last (or
any other) snapshot.

Expand Down
2 changes: 1 addition & 1 deletion docs/first-steps/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ installed using the [`pip`](https://pip.pypa.io/en/stable/) module. However, as
AIMBAT is a standalone application (rather than a library), we recommend
installing it using [`uv`](https://docs.astral.sh/uv/) instead. `uv` is a
single binary that doesn't require any dependencies to be installed, and it
allows to install and run AIMBAT in an isolated environment.
allows you to install and run AIMBAT in an isolated environment.

## Running AIMBAT without installing

Expand Down
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
22 changes: 16 additions & 6 deletions src/aimbat/cli/_align.py → src/aimbat/_cli/align.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
starting point instead, with the resulting pick stored in `t1`.
"""

from ._common import GlobalParameters, simple_exception
from .common import GlobalParameters, simple_exception
from cyclopts import App, Parameter
from typing import Annotated

Expand All @@ -20,11 +20,17 @@ def cli_iccs_run(
autoselect: bool = False,
global_parameters: GlobalParameters | None = None,
) -> None:
"""Run the ICCS algorithm.
"""Run the ICCS algorithm to align seismograms for the active 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.

Args:
autoflip: Whether to automatically flip seismograms (multiply data by -1).
autoselect: Whether to automatically de-select seismograms.
autoflip: Whether to automatically flip seismograms (multiply data by -1)
when the cross-correlation is negative.
autoselect: Whether to automatically de-select seismograms whose
cross-correlation with the stack falls below `min_ccnorm`.
"""
from aimbat.db import engine
from aimbat.core import create_iccs_instance, run_iccs
Expand All @@ -44,10 +50,14 @@ def cli_mccc_run(
all_seismograms: Annotated[bool, Parameter(name="all")] = False,
global_parameters: GlobalParameters | None = None,
) -> None:
"""Run the MCCC algorithm.
"""Run the MCCC algorithm to refine arrival time picks for the active event.

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

Args:
all_seismograms: Whether to include all seismograms in the MCCC processing, or just the selected ones.
all_seismograms: Include all seismograms in MCCC processing, not just
the currently selected ones.
"""
from aimbat.db import engine
from aimbat.core import create_iccs_instance, run_mccc
Expand Down
66 changes: 63 additions & 3 deletions src/aimbat/cli/_common.py → src/aimbat/_cli/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

from aimbat import settings
from dataclasses import dataclass
from cyclopts import Parameter
from cyclopts import Parameter, Token
from typing import Callable, Any
import uuid

# --------------------------------------------------
# -----------------------------------------------------------------------
# Common parameters
# --------------------------------------------------
# -----------------------------------------------------------------------


@Parameter(name="*")
Expand Down Expand Up @@ -44,6 +45,65 @@ class TableParameters:
"Shorten UUIDs and format data."


# -----------------------------------------------------------------------
# Shared Parameter instances and factories
# -----------------------------------------------------------------------

#: Shared Parameter for --all (all events) flags.
ALL_EVENTS_PARAMETER = Parameter(
name="all",
help="Include records from all events instead of just the active one.",
)


def _make_uuid_converter(model_class: type) -> Callable[..., uuid.UUID]:
"""Return a cyclopts converter that resolves a UUID prefix for the given model."""

def converter(hint: type, tokens: tuple[Token, ...]) -> uuid.UUID:
(token,) = tokens
value = token.value
try:
return uuid.UUID(value)
except ValueError:
from aimbat.db import engine
from aimbat.utils import string_to_uuid
from sqlmodel import Session

with Session(engine) as session:
return string_to_uuid(session, value, model_class)

return converter


def id_parameter(model_class: type) -> Parameter:
"""Create a Parameter for a record ID with automatic UUID prefix resolution."""
return Parameter(
name="id",
help="Full UUID or any unique prefix as shown in the table.",
converter=_make_uuid_converter(model_class),
)


def use_station_parameter(model_class: type) -> Parameter:
"""Create a Parameter for --use-station with automatic UUID prefix resolution."""
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(model_class),
)


def use_event_parameter(model_class: type) -> Parameter:
"""Create a Parameter for --use-event with automatic UUID prefix resolution."""
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(model_class),
)


# ------------------------------------------------
# Hints for error messages
# ------------------------------------------------
Expand Down
Loading
Loading