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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ All notable changes to the **AIMBAT** project will be documented in this file.
- Fix importlib error for Python 3.7
- Fix typo on readme
- Listing snapshots when there were non causes error
- Debug flag from cli commands didn't do anything

### 📚 Documentation

Expand Down Expand Up @@ -83,6 +84,9 @@ All notable changes to the **AIMBAT** project will be documented in this file.
- Make data reading more modular
- Move aimbat source to src directory
- Use pandas Timestamp and Timedelta
- Improve docstrings, io DI pattern, and data source terminology
- **(core)** Re-arange core, move set_default_event and friends out of core.
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

The changelog entry on line 22 contains a spelling error: "didn't" should read "didn't" (curly apostrophe vs straight apostrophe is acceptable, but "did't" is not present — the actual text "didn't do anything" is fine). However, line 88 contains "Re-arange" which should be "Re-arrange".

Suggested change
- **(core)** Re-arange core, move set_default_event and friends out of core.
- **(core)** Re-arrange core, move set_default_event and friends out of core.

Copilot uses AI. Check for mistakes.
- Active event -> default event

### 🚀 New Features

Expand Down Expand Up @@ -114,6 +118,9 @@ All notable changes to the **AIMBAT** project will be documented in this file.
- Add bandpass filtering ([#214](https://github.com/pysmo/aimbat/issues/214))
- Add mccc
- Add in-memory seismogram data cache
- Add JSON datasource
- Add TUI and supporting changes
- Implement interactive shell and major documentation update for v2

### 🧪 Testing

Expand Down
105 changes: 60 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,61 +22,76 @@
</img></a></div>

<p align="center">
<em>Documentation:</em> <a href="https://aimbat.readthedocs.io" target="_blank">https://aimbat.readthedocs.io</a>
<em>Documentation:</em> <a href="https://aimbat.pysmo.org" target="_blank">https://aimbat.pysmo.org</a>
</p>
<p align="center">
<em>Source Code:</em> <a href="https://github.com/pysmo/aimbat" target="_blank">https://github.com/pysmo/aimbat</a>
</p>


---

AIMBAT (Automated and Interactive Measurement of Body wave Arrival Times) is an
open-source software package for efficiently measuring teleseismic body wave arrival
times for large seismic arrays [[1]](#1). It is based on a widely used method called
MCCC (Multi-Channel Cross-Correlation) [[2]](#2). The package is automated in the sense
of initially aligning seismograms for MCCC, which is achieved by an ICCS (Iterative Cross
Correlation and Stack) algorithm. Meanwhile, a GUI (graphical user interface) is built to
perform seismogram quality control interactively. Therefore, user processing time is
reduced while valuable input from a user's expertise is retained. As a byproduct, SAC
[[3]](#3) plotting and phase picking functionalities are replicated and enhanced.

Modules and scripts included in the AIMBAT package were developed using
[Python](http://www.python.org/) and its open-source modules on the Mac OS X platform
since 2009. The original MCCC [[2]](#2) code was transcribed into Python.
The GUI of AIMBAT was inspired and initiated at the
[2009 EarthScope USArray Data Processing and Analysis Short Course](https://www.iris.edu/hq/es_course/content/2009.html).
AIMBAT runs on Mac OS X, Linux/Unix and Windows thanks to the platform-independent
feature of Python.

For more information visit the
[project website](http://www.earth.northwestern.edu/~xlou/aimbat.html) or the
[pysmo repositories](https://github.com/pysmo).

open-source tool for measuring teleseismic body wave arrival times. Seismograms
are automatically aligned using the ICCS [Iterative Cross-Correlation and Stack][^1]
algorithm; picks are then reviewed and refined interactively before a final
MCCC (Multi-Channel Cross-Correlation) [^2] pass computes the definitive
arrival times.

## Version 2

AIMBAT v2 is a complete rewrite. It shares the same goal as v1 but none of the
code.

- **Complete rewrite.** The algorithms are optimised and projects are stored in
a SQLite database (via [SQLModel](https://sqlmodel.tiangolo.com)), making them
persistent, portable, and inspectable.
- **Focused scope.** Much of the underlying code has moved into the
[pysmo](https://github.com/pysmo/pysmo) library, leaving AIMBAT to focus on
the user-facing ICCS → quality-control → MCCC workflow rather than
reimplementing general seismogram utilities.
- **Flexible data storage.** A single project can hold any number of seismic
events. Files from different events can live anywhere on disk — no need to
keep them in separate directories or follow a particular layout.
- **Maintainable.** v2 is built on modern, typed Python with a comprehensive
test suite and strict dependency management, so it keeps working as the
ecosystem evolves.
- **Multiple interfaces.** AIMBAT can be used via a CLI, an interactive shell,
a terminal UI, or directly as a Python library.

## Quick Start

```bash
pip install aimbat

# Create a project in the current directory
aimbat project create

# Import SAC files — events and stations are detected automatically
aimbat data add *.sac

# List events to find their IDs, then set one as the default
aimbat event list
aimbat event default <ID>

# Open the terminal UI to run ICCS, review picks, and run MCCC
aimbat tui

# Or work interactively from the shell (tab-completion, command history)
aimbat shell
```

## Authors' Contacts

* [Xiaoting Lou](http://geophysics.earth.northwestern.edu/people/xlou/aimbat.html) Email: xlou at u.northwestern.edu

* [Suzan van der Lee](http://geophysics.earth.northwestern.edu/seismology/suzan/) Email: suzan at northwestern.edu

* [Simon Lloyd](https://www.slloyd.net/) Email: simon at pysmo.org

## References

<a id="1">[1]</a>
Xiaoting Lou, Suzan van der Lee, and Simon Lloyd (2013),
AIMBAT: A Python/Matplotlib Tool for Measuring Teleseismic Arrival Times.
*Seismol. Res. Lett.*, 84(1), 85-93, doi:10.1785/0220120033.
- Xiaoting Lou — xlou at u.northwestern.edu
- Suzan van der Lee — suzan at northwestern.edu
- Simon Lloyd — simon at pysmo.org

<a id="2">[2]</a>
VanDecar, J. C., and R. S. Crosson (1990),
Determination of teleseismic relative phase arrival times using multi-channel
cross-correlation and
least squares.
*Bulletin of the Seismological Society of America*, 80(1), 150–169.
[^1]: Xiaoting Lou, Suzan van der Lee, and Simon Lloyd, “AIMBAT: A Python/Matplotlib
Tool for Measuring Teleseismic Arrival Times.” Seismological Research Letters,
vol. 84, no. 1, Jan. 2013, pp. 85–93, <https://doi.org/10.1785/0220120033>.

<a id="3">[3]</a>
Goldstein, P., D. Dodge, M. Firpo, and L. Minner (2003),
SAC2000: Signal processing and analysis tools for seismologists and engineers,
*International Geophysics*, 81, 1613–1614.
[^2]: VanDecar, J. C., and R. S. Crosson. “Determination of Teleseismic
Relative Phase Arrival Times Using Multi-Channel Cross-Correlation and
Least Squares.” Bulletin of the Seismological Society of America,
vol. 80, no. 1, Feb. 1990, pp. 150–69,
<https://doi.org/10.1785/BSSA0800010150>.
21 changes: 21 additions & 0 deletions docs/snippets/api_alignment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from sqlmodel import Session
from aimbat.db import engine
from aimbat.core import (
create_iccs_instance,
create_snapshot,
get_default_event,
run_iccs,
run_mccc,
)

with Session(engine) as session:
event = get_default_event(session)
assert event is not None

bound = create_iccs_instance(session, event)

run_iccs(session, bound.iccs, autoflip=True, autoselect=True)
create_snapshot(session, event, comment="after ICCS")

run_mccc(session, event, bound.iccs, all_seismograms=False)
create_snapshot(session, event, comment="after MCCC")
96 changes: 96 additions & 0 deletions docs/snippets/api_deduplicate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
Deduplicate events that were imported from sources reporting slightly different
origin times for the same earthquake.

Background
----------
``add_data_to_project`` deduplicates stations by SEED code
``(network, name, location, channel)`` — so importing the same station twice,
even with different coordinates, always reuses the existing record. Station
duplicates therefore cannot arise through the normal import path.

Events are deduplicated by exact origin time. When two data sources report
the same earthquake with origin times that differ by a second or two, they are
stored as *separate* ``AimbatEvent`` records. This script finds such
near-duplicate events, merges their seismograms into the canonical record
(the one with the most seismograms), averages the location and depth, then
removes the duplicates.

Run this script *before* starting any processing, and take a snapshot
afterwards so the clean state is recoverable.
"""

from pandas import Timedelta
from sqlmodel import Session, select

from aimbat.db import engine
from aimbat.models import AimbatEvent

# Merge events whose origin times differ by less than this value.
TIME_TOLERANCE = Timedelta(seconds=10)


def _mean(values: list[float]) -> float:
return sum(values) / len(values)


def _mean_opt(values: list[float | None]) -> float | None:
clean = [v for v in values if v is not None]
return sum(clean) / len(clean) if clean else None


def deduplicate_events(session: Session, tolerance: Timedelta = TIME_TOLERANCE) -> int:
"""Merge event records whose origin times are within *tolerance*.

Events are sorted by time and clustered greedily: a new cluster begins
whenever the gap to the previous event exceeds *tolerance*.

For each cluster the record with the most seismograms is kept as the
canonical entry; its location and depth are updated to the group mean.

Returns the number of duplicate records removed.
"""
events = sorted(
session.exec(select(AimbatEvent)).all(),
key=lambda e: e.time,
)

# Build clusters of near-simultaneous events.
clusters: list[list[AimbatEvent]] = []
for event in events:
if clusters and event.time - clusters[-1][-1].time <= tolerance:
clusters[-1].append(event)
else:
clusters.append([event])

removed = 0
for cluster in clusters:
if len(cluster) < 2:
continue

canonical = max(cluster, key=lambda e: len(e.seismograms))
duplicates = [e for e in cluster if e.id != canonical.id]

# Set location / depth to the group mean.
canonical.latitude = _mean([e.latitude for e in cluster])
canonical.longitude = _mean([e.longitude for e in cluster])
canonical.depth = _mean_opt([e.depth for e in cluster])

for dup in duplicates:
for seis in list(dup.seismograms):
seis.event_id = canonical.id
session.add(seis)
session.flush() # apply FK changes before deleting the row
session.delete(dup)
removed += 1

session.add(canonical)

session.commit()
return removed


with Session(engine) as session:
n = deduplicate_events(session)

print(f"Removed {n} duplicate event(s).")
Loading