A real-time and historical seismology workstation in your browser.
Tremiom streams live waveforms from thousands of global broadband stations, runs the full ObsPy/scipy analysis toolkit server-side, and presents it as a grid of scientific panels — helicorder drum, spectrogram, PSD/PPSD, RSAM, STA/LTA, 3-component, particle motion, H/V site response, and more — shown all at once, alphabetically, at your chosen panels-per-row and height. A live world map and USGS feed drive an event-analysis mode (record sections, TauP arrivals, focal-mechanism beachballs, felt-report + ShakeMap overlays, manual
- automatic phase picking, grid-search location, ML/Md magnitudes, QuakeML/MiniSEED export). A History mode browses any station over any time window with zoom/pan, or opens your own MiniSEED/SAC files. Both History and Event modes also compute spectrogram/PSD/spectrum/raw-scope/STA-LTA/ 3-component/particle-motion/H-V panels over the fetched window. Every panel removes the instrument response on demand (counts → velocity / displacement / acceleration / Wood-Anderson) and applies server-side band-pass filters. And it's self-teaching: every panel carries built-in educational help.
All in the browser, with no install, behind an optional one-token private deploy.
The shape: thin Vite + TypeScript client, Node WebSocket multiplexer,
real DSP done server-side in Python (ObsPy / scipy / numpy). See
ARCHITECTURE.md for the design and
COMPETITIVE.md for how it measures up against the
established tools.
Tremiom is a two-name project:
- Claude Code — Anthropic's
CLI coding agent, sole coder. Every line of TypeScript, Python, the
Dockerfile, the
fly.toml, and the docs you're reading was written by the agent. - Andre Paquette — human maintainer. Set the direction, made design and scope decisions, evaluated each iteration on a real fly.io deployment, and decided when to ship — but didn't write the code by hand.
Bugs and design choices are the agent's; the call to keep them or fix them is the maintainer's.
- Live SeedLink streaming from IRIS/EarthScope (
rtserve) and Raspberry Shake (data.raspberryshake.org), routed per network. - Station picker — 15 curated GSN broadbands plus free-form
NET.STA.LOC.CHAentry, plus a Browse… modal that searches the full FDSN station catalog by network / channel / radius. - Open local files — load your own MiniSEED / SAC / GSE2 (anything ObsPy reads) and view them in History mode.
- Helicorder — 24-hour drum recorder; 24 h backfilled on subscribe, then live. Catalog events marked at their predicted arrival time.
- RSAM — real-time seismic amplitude (1-min bins over 24 h); tremor tracker.
- Spectrogram — sliding time–frequency heatmap.
- Spectrum (FFT) — live magnitude spectrum + decaying peak-hold.
- PSD — Welch power spectral density.
- PPSD — accumulating probabilistic PSD (station noise/quality).
- STA/LTA — trigger ratio with threshold shading.
- Raw scope — rolling waveform window.
- 3-component — Z / N / E (or 1/2) stacked.
- Particle motion — horizontal hodogram (polarization).
- H/V ratio — Nakamura horizontal-to-vertical site response, f₀ peak.
- Network — multi-station RSAM overview.
- Station QC — gaps / latency / RMS health metrics.
- Instrument response removal / units — counts, velocity (m/s), displacement (m), acceleration (m/s²), or Wood-Anderson (mm), via ObsPy + cached StationXML.
- Band-pass / low / high filters — Butterworth presets (local quake, regional, teleseismic, microseism, surface waves, …), zero-phase.
- Pan/zoom world map (wheel + drag, double-click reset) with coastlines, live USGS epicenters (magnitude color/size, age fade), and station markers.
- Event sidebar — 17 USGS feeds; magnitude badges; felt-report intensity chips (CDI/MMI).
- Record section — N nearest stations stacked by epicentral distance, TauP (iasp91) predicted P (yellow) / S (red) arrivals, origin line.
- Z / R / T rotation — rotate horizontals to radial/transverse.
- Focal-mechanism beachball from the USGS moment tensor.
- Independent magnitudes — ML (Wood-Anderson) and Md (coda duration).
- DYFI felt-report polygons + ShakeMap modeled-intensity raster on the map.
- Phase picking — manual P/S (click), auto-pick (STA/LTA onset), persistent per-event pick catalog, QuakeML export.
- Grid-search location from your P picks (offset vs the catalog).
- Export — full-resolution MiniSEED, decimated CSV, or PNG.
- All panels at once, in alphabetical order; the 24-h Helicorder leads full-width and double-height.
- Panels per row (1–6) and Height dropdowns + a Refresh button; both selections persist in the browser.
- Per-panel PNG export (the ⤓ on each panel header).
- STA/LTA trigger alerts — browser notification + banner when a station crosses your threshold.
- Live (the panel grid), Event (record section), History (any station, any time window, zoom/pan/step, or a local file). Event and History also show computed analysis panels over the fetched window.
- A ? in the topbar and on every panel opens a searchable, educational reference: what each panel is, how to read it, and how to use it.
- One-command fly.io deploy (Docker),
TREMIOM_TOKENsingle-token private access with a sign-in form + cookie, or fully open for local use.
# 1. Node deps
npm install
# 2. Python workers (creates workers/.venv with obspy / numpy / scipy)
npm run workers:install
# 3. Terminal A — Node multiplexer (spawns the Python worker)
npm start
# 4. Terminal B — Vite dev server
npm run dev:http # http://localhost:5174 (no certs needed)
# or HTTPS via mkcert:
npm run dev # https://localhost:5173Open the URL Vite prints. Expect the panels to say "waiting for
frames…" for ~10–20 s while the SeedLink handshake completes; that's
normal. Set TREMIOM_SYNTHETIC=1 on the server to develop the UI
offline with synthetic data.
src/
main.ts Entry
ui/
app.ts Top-level layout + state plumbing
dashboard.ts Panel grid (all panels, alphabetical, N-per-row)
analysis-panels.ts History/Event computed-panel strip
station-picker.ts Topbar station selector + Browse modal
station-search.ts FDSN station-catalog search modal
filter-picker.ts Band-pass filter selector
units-picker.ts Response-removal / units selector
alert-picker.ts, alerts.ts STA/LTA trigger alerts
event-list.ts Sidebar (USGS feed + felt chips)
world-map.ts Pan/zoom map + DYFI + ShakeMap overlays
record-section.ts Event mode: record section, picks, locate, export
history-view.ts History mode: arbitrary time window + local files
beachball.ts Focal-mechanism renderer
settings.ts Auth/settings modal
help.ts Educational help overlay (per-panel docs)
panels/
registry.ts Panel registry
drum.ts rsam.ts spectrogram.ts spectrum.ts psd.ts ppsd.ts
sta-lta.ts raw-scope.ts three-comp.ts particle-motion.ts hv.ts
helicorder.ts network.ts qc.ts
axes.ts colormap.ts Shared plot helpers
data/
stations.ts events.ts event-waveforms.ts filters.ts
coastlines.json Natural Earth 1:110m land outlines
transport/
ws.ts events.ts WebSocket client + USGS feed poller
server.mjs Node WS multiplexer + REST/FDSN/USGS proxies + auth
workers/
worker.py Unified SeedLink ingestor + live panel computers
event_fetch.py Record-section waveforms + TauP (+ ZRT rotation)
event_export.py Full-resolution MiniSEED export
event_magnitude.py ML (Wood-Anderson) + Md (coda) estimation
event_autopick.py STA/LTA P-arrival auto-picker
waveform_fetch.py Arbitrary-window fetch (History mode)
waveform_panels.py Compute analysis panels (History/Event window)
parse_waveform.py Parse uploaded local files
requirements.txt obspy, numpy, scipy
ARCHITECTURE.md Design doc + roadmap
COMPETITIVE.md Feature comparison vs 17 established tools
When TREMIOM_TOKEN is set in the environment, every HTTP and
WebSocket request must present a matching cookie or one-time URL
parameter. Use this on shared/public infrastructure (e.g. fly.io) to
keep the instance to yourself.
# Pick a secret (any string; longer is better)
openssl rand -hex 32
# → eg 7f1b…ab98
# On fly.io:
fly secrets set TREMIOM_TOKEN=7f1b…ab98
fly deploy # only needed if the app is currently running an older imageThen visit your instance:
https://tremiom.fly.dev/
A sign-in page asks for the token. After submitting, the server sets a
1-year HttpOnly; SameSite=Lax; Secure cookie and the app loads.
Subsequent visits don't need to re-authenticate; the cookie does the
work, including for the WebSocket connection.
Inside the running app, the ⚙ Settings button in the topbar shows the current auth state, lets you sign out, and provides a token field to apply a new token without leaving the page (useful after a server- side rotation).
A legacy ?token=… URL also works for bookmarks / scripts, but the
sign-in form is the canonical entry. To revoke all access, change
TREMIOM_TOKEN and redeploy — every existing cookie becomes
invalid immediately.
When the env var is unset, the server is open (the default for local dev and self-hosting).
Tremiom doesn't bake the live-data servers into the binary. Settings → SeedLink upstreams lets you set the default server (where most networks go) and any per-network overrides (e.g. AM → Raspberry Shake). Changes take effect immediately; stations whose upstream actually moved reconnect within a few seconds.
You can also seed initial values from the deploy environment so a fresh machine boots with your choice without anyone having to open the UI:
fly secrets set \
TREMIOM_SEEDLINK_DEFAULT="rtserve.iris.washington.edu:18000" \
TREMIOM_SEEDLINK_NETWORKS="AM=data.raspberryshake.org:18000" \
-a tremiomTREMIOM_SEEDLINK_NETWORKS is a comma-separated list of NET=host:port
pairs. If a port is omitted, the SeedLink default 18000 is assumed.
If neither env var is set, built-in defaults
(rtserve.iris.washington.edu:18000 and AM=data.raspberryshake.org:18000)
apply, and you can still override them at runtime in the UI.
A Dockerfile and a fly.toml at the
project root drive everything. Fly's remote builders handle the Docker
build, so no local Docker daemon is needed once you can run fly deploy.
# Install flyctl (macOS/Linux)
curl -L https://fly.io/install.sh | sh
# Sign in (opens a browser)
fly auth logincd /path/to/tremiom
# Create the app (name must be globally unique on fly.io)
fly apps create tremiom
# Set the private-deployment secret (see "Private-deployment token"
# above). Skip this if you intend to leave the instance open.
fly secrets set TREMIOM_TOKEN="$(openssl rand -hex 32)" -a tremiom
# Build + deploy. First build is ~5–8 min (obspy/numpy/scipy wheels);
# subsequent builds are ~30 s–2 min with layer caching.
fly deployOnce the deploy lands, visit https://tremiom.fly.dev/ — you'll see
the sign-in page (or the app directly if you didn't set a token).
git pull # if you fetched changes
fly deploy # rebuilds image, rolls outMulti-stage Docker build:
node:22-alpinebuilds the Vite/TS frontend →dist/python:3.11-slim-bookworminstalls ObsPy + numpy + scipy into a self-contained/opt/venv(in its own layer so changes to TS source don't bust the pip cache)- Final stage: same Python base + Node 22 from NodeSource + the venv +
built frontend +
server.mjs+workers/
Final image size: ~220 MB.
| Setting | Value | Notes |
|---|---|---|
primary_region |
yyz |
Toronto — change to your nearest Fly region |
| VM | 1 shared CPU, 1024 MB | Heavy because obspy + scipy + matplotlib hold ~350 MB RSS |
auto_stop_machines |
stop |
Sleeps when idle, restarts on next request |
auto_start_machines |
true |
Auto-wake on incoming traffic |
min_machines_running |
0 |
Cost-optimized; first request after sleep adds ~5 s cold-start |
force_https |
true |
Fly's edge terminates TLS; the inside is HTTP |
internal_port |
8080 |
Matches PORT env + server.mjs |
Change region by editing fly.toml then fly deploy. Change VM size
by editing [[vm]] (e.g. memory_mb = 2048 if you see OOM-kill in
the logs).
The container needs outbound access to:
| Host | Port | Why |
|---|---|---|
rtserve.iris.washington.edu |
18000/tcp | Live SeedLink (GSN broadbands) |
data.raspberryshake.org |
18000/tcp | Live SeedLink (AM citizen seismometers) |
service.iris.edu |
443/tcp | FDSN station + dataselect (event mode) |
service.earthscope.org |
443/tcp | FDSN — ObsPy redirects IRIS → EarthScope |
earthquake.usgs.gov |
443/tcp | USGS event feed |
Fly allows all outbound TCP by default — nothing to configure.
fly logs -a tremiom # tail logs
fly status -a tremiom # machine status
fly ssh console -a tremiom # shell into the running container
fly machine restart -a tremiom # force restart
fly scale memory 2048 -a tremiom # bump RAM if obspy spikes
fly secrets list -a tremiom # see env var NAMES (values stay hidden)
fly secrets unset TREMIOM_TOKEN -a tremiom # remove the token (instance becomes open)fly certs create tremiom.yourdomain.com -a tremiom
# then point a CNAME from yourdomain.com to tremiom.fly.dev
fly certs show tremiom.yourdomain.com -a tremiom # check issuance- Build fails on a
pip installstep: a Debian native lib may be missing for some obspy dep. Add it toapt-get installin thepydepsDocker stage and retry. - OOM-killed machine: bump
memory_mbinfly.tomlto 2048; ObsPy spikes when many/api/event/waveformsrequests run concurrently. - Stuck at "waiting for first sample" forever: the SeedLink upstream
may be blocked.
fly ssh consolethennc -zv rtserve.iris.washington.edu 18000to verify. Fly logs will showsl[host]: upstream unreachable — backing off 60sif the probe caught it. - 401 after a deploy: the cookie is still valid against the old
TREMIOM_TOKEN, but you may have rotated it. Sign in again with the new token via the form.
v0.7.x. Live, event, and history modes all work end-to-end against IRIS
rtserve + EarthScope FDSN + USGS, with 14 panels on an alphabetical
panels-per-row grid, computed analysis panels in History/Event, response
removal, picking/location/magnitudes, and built-in help. Per COMPETITIVE.md, Tremiom is a superset of the
live-monitoring + single-event-analysis feature sets of the established
tools (Swarm, SeisComP, Snuffler, Wilber 3, GeoNet, Raspberry Shake, …);
the only deliberate exclusions are array/research subsystems (FK,
receiver functions, cross-correlation) and the full automatic
detect→associate→locate pipeline.
TREMIOM_SYNTHETIC=1 on the server forces synthetic ingestion for offline
UI development.
See SECURITY.md for the threat model, cookie flags,
and what is / isn't in the repo. No secrets are committed: .env,
keys, and Fly secret values are gitignored and managed via
fly secrets set ….
Tremiom is a solo-authored project and does not accept pull requests.
An automated workflow closes incoming PRs. Bug reports, feature
requests, and discussions are welcome — see
CONTRIBUTING.md. The MIT license grants you the
right to use, fork, and modify it freely.
- IRIS / EarthScope — SeedLink stream + FDSN station / dataselect web services
- Raspberry Shake — AM-network citizen-seismometer SeedLink + catalog
- USGS — earthquake summary GeoJSON feeds, per-event detail (moment tensor, DYFI felt reports, ShakeMap intensity)
- ObsPy — the seismology toolkit that makes the Python side possible (SeedLink, FDSN, response removal, TauP, triggers, beachballs, …)
- Natural Earth — public-domain 1:110m land outlines
- TauP — iasp91 travel-time tables, via ObsPy
MIT © Andre Paquette. Third-party dependency licenses are
listed in THIRD_PARTY_LICENSES.md.