bepad 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bepad/__init__.py +17 -0
- bepad/__main__.py +206 -0
- bepad/associator.py +339 -0
- bepad/build/__init__.py +11 -0
- bepad/build/config.py +116 -0
- bepad/build/geo.py +64 -0
- bepad/build/labels.py +341 -0
- bepad/build/pool.py +392 -0
- bepad/build/release.py +256 -0
- bepad/build/utdquake.py +76 -0
- bepad/build/windows.py +408 -0
- bepad/data.py +185 -0
- bepad/leaderboard.py +368 -0
- bepad/metrics.py +324 -0
- bepad/plots.py +950 -0
- bepad/stats.py +140 -0
- bepad/torch.py +159 -0
- bepad-0.1.0.dist-info/METADATA +85 -0
- bepad-0.1.0.dist-info/RECORD +23 -0
- bepad-0.1.0.dist-info/WHEEL +5 -0
- bepad-0.1.0.dist-info/entry_points.txt +2 -0
- bepad-0.1.0.dist-info/licenses/LICENSE +674 -0
- bepad-0.1.0.dist-info/top_level.txt +1 -0
bepad/stats.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Dataset statistics: tables that describe what is in a BEPAD split.
|
|
2
|
+
|
|
3
|
+
Every function takes a loaded split (``bepad.load(split)``, with labels) and
|
|
4
|
+
returns a pandas DataFrame; ``bepad.plots`` draws them.
|
|
5
|
+
|
|
6
|
+
network_scales(split) network -> "local" | "regional" | "global"
|
|
7
|
+
source_events(split) one row per source earthquake (catalog values + how often it is used)
|
|
8
|
+
event_pairs(split) one row per pair of earthquakes in the same window: distance, time apart
|
|
9
|
+
window_geometry(split) one row per window: stations, spacing, aperture, distances
|
|
10
|
+
pick_distances(split) one row per pick: type, phase, distance to its earthquake, travel time
|
|
11
|
+
|
|
12
|
+
Large splits are sampled with ``max_windows`` (per network) where the table is per pick or
|
|
13
|
+
per station, so the functions stay fast on the full release.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
import pandas as pd
|
|
20
|
+
|
|
21
|
+
from .build.geo import haversine_km
|
|
22
|
+
|
|
23
|
+
#: typical pick distance [km] separating the three scales
|
|
24
|
+
SCALE_EDGES = (150.0, 1500.0)
|
|
25
|
+
SCALES = ("local", "regional", "global")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _sample_ids(split, max_windows, seed=0):
|
|
29
|
+
w = split.windows
|
|
30
|
+
if max_windows is None:
|
|
31
|
+
return set(w.window_id)
|
|
32
|
+
return set(w.groupby("network", group_keys=False)
|
|
33
|
+
.apply(lambda g: g.sample(min(len(g), max_windows), random_state=seed)).window_id)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _coords(split):
|
|
37
|
+
st = split.window_stations.drop_duplicates(["network", "station"]).set_index(["network", "station"])
|
|
38
|
+
return st[["latitude", "longitude"]]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def pick_distances(split, max_windows=200):
|
|
42
|
+
"""Picks of (a sample of) windows with the hypocentral distance to their own earthquake
|
|
43
|
+
(event picks) or to the nearest earthquake (noise), and the travel time of event picks."""
|
|
44
|
+
p = _pick_distances(split, max_windows)
|
|
45
|
+
p["scale"] = p.network.map(network_scales(split))
|
|
46
|
+
return p[["window_id", "network", "scale", "station", "phase", "pick_type", "prob", "event_idx",
|
|
47
|
+
"distance_km", "travel_time_s"]]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _pick_distances(split, max_windows):
|
|
51
|
+
ids = _sample_ids(split, max_windows)
|
|
52
|
+
net = split.windows.set_index("window_id").network
|
|
53
|
+
p = split.picks[split.picks.window_id.isin(ids)].copy()
|
|
54
|
+
p["network"] = p.window_id.map(net)
|
|
55
|
+
xy = _coords(split).reindex(pd.MultiIndex.from_arrays([p.network, p.station])).to_numpy()
|
|
56
|
+
ev = split.events[split.events.window_id.isin(ids)]
|
|
57
|
+
out = np.full(len(p), np.nan)
|
|
58
|
+
tt = np.full(len(p), np.nan)
|
|
59
|
+
for wid, rows in p.groupby("window_id").indices.items():
|
|
60
|
+
e = ev[ev.window_id == wid]
|
|
61
|
+
d = np.hypot(haversine_km(e.latitude.to_numpy()[:, None], e.longitude.to_numpy()[:, None],
|
|
62
|
+
xy[rows, 0][None, :], xy[rows, 1][None, :]),
|
|
63
|
+
(e.depth.to_numpy() / 1e3)[:, None]) # (E, n)
|
|
64
|
+
own = p.event_idx.to_numpy()[rows]
|
|
65
|
+
pos = {k: i for i, k in enumerate(e.event_idx)}
|
|
66
|
+
idx = np.array([pos.get(k, -1) for k in own])
|
|
67
|
+
out[rows] = np.where(idx >= 0, d[np.maximum(idx, 0), np.arange(len(rows))], d.min(axis=0))
|
|
68
|
+
t0 = e.set_index("event_idx").t0_rel_s.reindex(own).to_numpy() # NaN for noise (-1)
|
|
69
|
+
tt[rows] = p.t_rel_s.to_numpy()[rows] - t0
|
|
70
|
+
p["distance_km"], p["travel_time_s"] = out, tt
|
|
71
|
+
return p
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def network_scales(split, max_windows=50):
|
|
75
|
+
"""Scale of each network from the 90th percentile of the hypocentral distance of its real picks:
|
|
76
|
+
local (< 150 km), regional (150-1500 km) or global (> 1500 km)."""
|
|
77
|
+
p = _pick_distances(split, max_windows)
|
|
78
|
+
p90 = p[p.pick_type == "real"].groupby("network").distance_km.quantile(0.9)
|
|
79
|
+
return p90.map(lambda v: SCALES[int(np.searchsorted(SCALE_EDGES, v))])
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def source_events(split):
|
|
83
|
+
"""One row per source earthquake: catalog location, depth, magnitude, farthest station,
|
|
84
|
+
stations and gap in its first window, and the number of windows it appears in."""
|
|
85
|
+
ev = split.events.merge(split.windows[["window_id", "network"]], on="window_id")
|
|
86
|
+
uses = ev.groupby("src_event_id").size().rename("n_windows")
|
|
87
|
+
first = ev.sort_values(["src_event_id", "is_edge"]).drop_duplicates("src_event_id")
|
|
88
|
+
out = first.set_index("src_event_id").join(uses).reset_index()
|
|
89
|
+
out["depth_km"] = out.depth / 1e3
|
|
90
|
+
out["scale"] = out.network.map(network_scales(split))
|
|
91
|
+
return out[["src_event_id", "network", "scale", "latitude", "longitude", "depth_km", "magnitude",
|
|
92
|
+
"magnitude_type", "last_station_km", "n_stations", "gap_deg", "n_windows"]]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def event_pairs(split):
|
|
96
|
+
"""Every pair of earthquakes in the same window: epicentral distance and origin-time difference.
|
|
97
|
+
|
|
98
|
+
Pairs close in both are the hardest to associate: their picks overlap in time at the same
|
|
99
|
+
stations."""
|
|
100
|
+
ev = split.events.merge(split.windows[["window_id", "network", "scenario"]], on="window_id")
|
|
101
|
+
rows = []
|
|
102
|
+
for wid, g in ev.groupby("window_id"):
|
|
103
|
+
if len(g) < 2:
|
|
104
|
+
continue
|
|
105
|
+
la, lo, t = g.latitude.to_numpy(), g.longitude.to_numpy(), g.t0_rel_s.to_numpy()
|
|
106
|
+
i, j = np.triu_indices(len(g), 1)
|
|
107
|
+
rows.append(pd.DataFrame({"window_id": wid, "network": g.network.iloc[0], "scenario": g.scenario.iloc[0],
|
|
108
|
+
"distance_km": haversine_km(la[i], lo[i], la[j], lo[j]),
|
|
109
|
+
"dt_s": np.abs(t[i] - t[j])}))
|
|
110
|
+
out = pd.concat(rows, ignore_index=True) if rows else pd.DataFrame(
|
|
111
|
+
columns=["window_id", "network", "scenario", "distance_km", "dt_s"])
|
|
112
|
+
out["scale"] = out.network.map(network_scales(split))
|
|
113
|
+
return out
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def window_geometry(split, max_windows=300):
|
|
117
|
+
"""Per window: number of stations, median nearest-neighbour station spacing, aperture
|
|
118
|
+
(largest inter-station distance), and median distance from the earthquakes to the stations
|
|
119
|
+
that picked them."""
|
|
120
|
+
ids = _sample_ids(split, max_windows)
|
|
121
|
+
w = split.windows[split.windows.window_id.isin(ids)]
|
|
122
|
+
ws = split.window_stations[split.window_stations.window_id.isin(ids)]
|
|
123
|
+
real = split.picks[split.picks.window_id.isin(ids) & (split.picks.real == 1)]
|
|
124
|
+
from scipy.spatial import cKDTree
|
|
125
|
+
|
|
126
|
+
n_pick = real.groupby("window_id").station.nunique()
|
|
127
|
+
rows = []
|
|
128
|
+
for wid, g in ws.groupby("window_id"):
|
|
129
|
+
la, lo = np.radians(g.latitude.to_numpy()), np.radians(g.longitude.to_numpy())
|
|
130
|
+
xyz = np.c_[np.cos(la) * np.cos(lo), np.cos(la) * np.sin(lo), np.sin(la)] # unit sphere
|
|
131
|
+
chord, _ = cKDTree(xyz).query(xyz, k=2)
|
|
132
|
+
nn_km = 2 * 6371.0 * np.arcsin(np.clip(chord[:, 1] / 2, 0, 1))
|
|
133
|
+
# aperture: farthest station from the farthest station of an arbitrary one (two passes)
|
|
134
|
+
a = np.argmax(haversine_km(g.latitude.iloc[0], g.longitude.iloc[0], g.latitude, g.longitude))
|
|
135
|
+
aperture = float(np.max(haversine_km(g.latitude.iloc[a], g.longitude.iloc[a], g.latitude, g.longitude)))
|
|
136
|
+
rows.append({"window_id": wid, "n_stations": len(g), "spacing_km": float(np.median(nn_km)),
|
|
137
|
+
"aperture_km": aperture, "n_picking_stations": int(n_pick.get(wid, 0))})
|
|
138
|
+
out = pd.DataFrame(rows).merge(w[["window_id", "network", "scenario", "max_hyp_dist_km"]], on="window_id")
|
|
139
|
+
out["scale"] = out.network.map(network_scales(split))
|
|
140
|
+
return out
|
bepad/torch.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""PyTorch datasets for training associators on BEPAD.
|
|
2
|
+
|
|
3
|
+
- ``ReleaseWindowDataset``: the fixed windows of a release split (train/val).
|
|
4
|
+
- ``GeneratedWindowDataset``: fresh windows from the pool on every epoch
|
|
5
|
+
(same generator as the release, different seeds), for unlimited training data.
|
|
6
|
+
|
|
7
|
+
Each item is a dict of tensors for ONE window (variable number of picks and
|
|
8
|
+
stations). ``collate_windows`` concatenates a batch PyG-style, with
|
|
9
|
+
``pick_batch``/``station_batch`` vectors and per-window offsets applied to
|
|
10
|
+
``pick_station``.
|
|
11
|
+
|
|
12
|
+
Item keys::
|
|
13
|
+
|
|
14
|
+
window_id str
|
|
15
|
+
station_coords (S, 3) float32 latitude, longitude [deg], elevation [m]
|
|
16
|
+
pick_station (N,) int64 index into station_coords
|
|
17
|
+
pick_phase (N,) int64 0 = P, 1 = S
|
|
18
|
+
pick_time (N,) float32 seconds from window start
|
|
19
|
+
pick_label (N,) int64 event index in the window, -1 = noise
|
|
20
|
+
pick_prob (N,) float32 1.0-0.5 real, >= 0.5 generated, 0 noise (soft target)
|
|
21
|
+
pick_real (N,) bool pick comes from UTDQuake (not generated, not noise)
|
|
22
|
+
pick_type (N,) int64 0 = real, 1 = generated, 2 = noise
|
|
23
|
+
pick_sigma (N,) float32 timing uncertainty [s] at that distance (NaN for noise)
|
|
24
|
+
pick_scorable (N,) bool pick belongs to a scorable event
|
|
25
|
+
event_origin (E, 4) float32 t0 [s from window start], latitude, longitude, depth [km]
|
|
26
|
+
event_scorable (E,) bool
|
|
27
|
+
|
|
28
|
+
Normalisation is left to the model.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import numpy as np
|
|
34
|
+
import torch
|
|
35
|
+
from torch.utils.data import Dataset
|
|
36
|
+
|
|
37
|
+
from .build.geo import stable_seed
|
|
38
|
+
|
|
39
|
+
PHASE_CODE = {"P": 0, "S": 1}
|
|
40
|
+
TYPE_CODE = {"real": 0, "generated": 1, "noise": 2}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def window_to_tensors(window_id, picks, stations, events=None):
|
|
44
|
+
stations = stations.reset_index(drop=True)
|
|
45
|
+
pos = {s: i for i, s in enumerate(stations["station"])}
|
|
46
|
+
item = {
|
|
47
|
+
"window_id": window_id,
|
|
48
|
+
"station_coords": torch.tensor(stations[["latitude", "longitude", "elevation"]].to_numpy(np.float32)),
|
|
49
|
+
"pick_station": torch.tensor(picks["station"].map(pos).to_numpy(np.int64)),
|
|
50
|
+
"pick_phase": torch.tensor(picks["phase"].map(PHASE_CODE).to_numpy(np.int64)),
|
|
51
|
+
"pick_time": torch.tensor(picks["t_rel_s"].to_numpy(np.float32)),
|
|
52
|
+
}
|
|
53
|
+
if "event_idx" in picks:
|
|
54
|
+
item["pick_label"] = torch.tensor(picks["event_idx"].to_numpy(np.int64))
|
|
55
|
+
item["pick_prob"] = torch.tensor(picks["prob"].to_numpy(np.float32))
|
|
56
|
+
item["pick_real"] = torch.tensor(picks["real"].to_numpy(bool))
|
|
57
|
+
item["pick_type"] = torch.tensor(picks["pick_type"].map(TYPE_CODE).to_numpy(np.int64))
|
|
58
|
+
item["pick_sigma"] = torch.tensor(picks["sigma_s"].to_numpy(np.float32))
|
|
59
|
+
item["pick_scorable"] = torch.tensor(picks["in_scorable_event"].to_numpy(bool))
|
|
60
|
+
if events is not None:
|
|
61
|
+
e = events.sort_values("event_idx")
|
|
62
|
+
item["event_origin"] = torch.tensor(np.stack([
|
|
63
|
+
e["t0_rel_s"], e["latitude"], e["longitude"], e["depth"] / 1e3], axis=1).astype(np.float32))
|
|
64
|
+
item["event_scorable"] = torch.tensor(e["scorable"].to_numpy(bool))
|
|
65
|
+
return item
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ReleaseWindowDataset(Dataset):
|
|
69
|
+
"""Fixed windows of one split (use train/val; never tune on test).
|
|
70
|
+
|
|
71
|
+
``release``: a :class:`bepad.data.Release`, a local release folder, or None
|
|
72
|
+
to download ``split`` from Hugging Face.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, release=None, split="train", networks=None):
|
|
76
|
+
from .data import Release, load
|
|
77
|
+
|
|
78
|
+
if release is None:
|
|
79
|
+
self.split = load(split, networks=networks, labels=True)
|
|
80
|
+
else:
|
|
81
|
+
rel = release if isinstance(release, Release) else Release(release)
|
|
82
|
+
self.split = rel.load(split, networks=networks, labels=True)
|
|
83
|
+
|
|
84
|
+
def __len__(self):
|
|
85
|
+
return len(self.split)
|
|
86
|
+
|
|
87
|
+
def __getitem__(self, i):
|
|
88
|
+
w = self.split[i]
|
|
89
|
+
return window_to_tensors(w.window_id, w.picks, w.stations, w.events)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class GeneratedWindowDataset(Dataset):
|
|
93
|
+
"""On-the-fly windows from the pool.
|
|
94
|
+
|
|
95
|
+
Item ``i`` of epoch ``e`` uses seed sha256(base_seed, e, i), so a run is
|
|
96
|
+
reproducible and every epoch sees new windows. Scenario configs (dicts of
|
|
97
|
+
window parameters, e.g. from ``bepad.materialize.expand_scenarios``) are
|
|
98
|
+
drawn uniformly per item, as is the network.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(self, data_root, networks, scenarios, split="train", length=10_000, base_seed=0):
|
|
102
|
+
from .build.config import WINDOW_DEFAULTS
|
|
103
|
+
from .build.labels import LabelModel
|
|
104
|
+
from .build.pool import load_network, pool_dir
|
|
105
|
+
from .build.windows import NetworkIndex
|
|
106
|
+
|
|
107
|
+
model = LabelModel.load(pool_dir(data_root) / "label_model.json")
|
|
108
|
+
self.index = {n: NetworkIndex(*load_network(data_root, n), network=n, label_model=model) for n in networks}
|
|
109
|
+
self.networks = list(networks)
|
|
110
|
+
self.scenarios = [(k, {**WINDOW_DEFAULTS, **v}) for k, v in
|
|
111
|
+
(scenarios.items() if isinstance(scenarios, dict) else enumerate(scenarios))]
|
|
112
|
+
self.split = split
|
|
113
|
+
self.length = length
|
|
114
|
+
self.base_seed = base_seed
|
|
115
|
+
self.epoch = 0
|
|
116
|
+
|
|
117
|
+
def set_epoch(self, epoch):
|
|
118
|
+
self.epoch = epoch
|
|
119
|
+
|
|
120
|
+
def __len__(self):
|
|
121
|
+
return self.length
|
|
122
|
+
|
|
123
|
+
def __getitem__(self, i):
|
|
124
|
+
from .build.windows import generate_window
|
|
125
|
+
|
|
126
|
+
seed = stable_seed(self.base_seed, self.epoch, i)
|
|
127
|
+
rng = np.random.default_rng(seed)
|
|
128
|
+
net = self.networks[rng.integers(len(self.networks))]
|
|
129
|
+
name, cfg = self.scenarios[rng.integers(len(self.scenarios))]
|
|
130
|
+
for attempt in range(10):
|
|
131
|
+
w = generate_window(self.index[net], self.split, cfg, seed + attempt, f"gen-{self.epoch}-{i}", str(name))
|
|
132
|
+
if w is not None:
|
|
133
|
+
break
|
|
134
|
+
else:
|
|
135
|
+
raise RuntimeError(f"could not generate a window for network {net}, scenario {name}")
|
|
136
|
+
st = self.index[net].stations.loc[w.stations["station"]].reset_index()
|
|
137
|
+
return window_to_tensors(w.window["window_id"], w.picks, st, w.events)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def collate_windows(batch):
|
|
141
|
+
"""Concatenate variable-size windows; adds pick_batch / station_batch / event_batch."""
|
|
142
|
+
out = {"window_id": [b["window_id"] for b in batch]}
|
|
143
|
+
offset = 0
|
|
144
|
+
parts = {k: [] for k in batch[0] if k != "window_id"}
|
|
145
|
+
parts.update(pick_batch=[], station_batch=[], event_batch=[])
|
|
146
|
+
for k, b in enumerate(batch):
|
|
147
|
+
n_sta = len(b["station_coords"])
|
|
148
|
+
for key in parts:
|
|
149
|
+
if key in b:
|
|
150
|
+
parts[key].append(b[key] + offset if key == "pick_station" else b[key])
|
|
151
|
+
parts["pick_batch"].append(torch.full((len(b["pick_time"]),), k, dtype=torch.long))
|
|
152
|
+
parts["station_batch"].append(torch.full((n_sta,), k, dtype=torch.long))
|
|
153
|
+
if "event_origin" in b:
|
|
154
|
+
parts["event_batch"].append(torch.full((len(b["event_origin"]),), k, dtype=torch.long))
|
|
155
|
+
offset += n_sta
|
|
156
|
+
for key, vals in parts.items():
|
|
157
|
+
if vals:
|
|
158
|
+
out[key] = torch.cat(vals)
|
|
159
|
+
return out
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bepad
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: BEPAD: a benchmark dataset for seismic phase association built from UTDQuake
|
|
5
|
+
License-Expression: GPL-3.0-or-later
|
|
6
|
+
Project-URL: Repository, https://github.com/ecastillot/BEPAD
|
|
7
|
+
Project-URL: Dataset, https://huggingface.co/datasets/ecastillot/BEPAD
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Requires-Dist: pandas>=2.0
|
|
13
|
+
Requires-Dist: pyarrow
|
|
14
|
+
Requires-Dist: scipy
|
|
15
|
+
Requires-Dist: pyyaml
|
|
16
|
+
Requires-Dist: matplotlib
|
|
17
|
+
Requires-Dist: huggingface_hub
|
|
18
|
+
Provides-Extra: dl
|
|
19
|
+
Requires-Dist: torch>=2.1; extra == "dl"
|
|
20
|
+
Provides-Extra: baselines
|
|
21
|
+
Requires-Dist: pyocto; extra == "baselines"
|
|
22
|
+
Provides-Extra: build
|
|
23
|
+
Requires-Dist: utdquake>=1.0.1; extra == "build"
|
|
24
|
+
Provides-Extra: docs
|
|
25
|
+
Requires-Dist: sphinx>=7; extra == "docs"
|
|
26
|
+
Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
|
27
|
+
Requires-Dist: myst-parser; extra == "docs"
|
|
28
|
+
Requires-Dist: sphinx-copybutton; extra == "docs"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# BEPAD — a benchmark dataset for seismic phase association
|
|
32
|
+
|
|
33
|
+
A phase associator groups the P and S picks of a seismic network into earthquakes. BEPAD gives
|
|
34
|
+
thousands of association problems built from **real earthquakes and real picks**
|
|
35
|
+
([UTDQuake](https://huggingface.co/datasets/ecastillot/UTDQuake)), with the answer for every pick.
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
*One window (Texas network): (a) earthquakes and stations, (b) every pick at its distance from
|
|
40
|
+
its earthquake, (c) picks by type.*
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import bepad
|
|
44
|
+
|
|
45
|
+
test = bepad.load("test", labels=False) # download the test split from Hugging Face
|
|
46
|
+
for w in test: # one window = one association problem
|
|
47
|
+
w.picks, w.stations # picks (station, P/S, time) and station coordinates
|
|
48
|
+
report = bepad.evaluate(predictions, test) # score your associator
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Share your associator and get on the leaderboard in four commands:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
bepad new my-method # template folder
|
|
55
|
+
bepad run associators/my-method --split test --out results/my-method
|
|
56
|
+
bepad submit results/my-method --code associators/my-method # opens a Hugging Face pull request
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| | |
|
|
60
|
+
|---|---|
|
|
61
|
+
| **What is in a window** | several overlapping earthquakes on one network, with **real**, **generated** (plausible extra arrivals) and **noise** picks; every pick labelled with its earthquake and a confidence `prob` (≥ 0.5 arrival, 0 noise) |
|
|
62
|
+
| **Difficulty** | 23 scenarios: 1–20 earthquakes per window × 5 noise levels, plus earthquakes outside the network |
|
|
63
|
+
| **Size** | per network and scenario: 1,000 train · 200 val · 100 test windows (23 scenarios) |
|
|
64
|
+
| **Associator authors** | fill in `bepad.Associator`, run on `test`, submit to the leaderboard — see *Contribute your associator* |
|
|
65
|
+
| **Deep learning** | `bepad.torch.ReleaseWindowDataset` — see *Train a model* |
|
|
66
|
+
|
|
67
|
+
Code: https://github.com/ecastillot/BEPAD · Data: https://huggingface.co/datasets/ecastillot/BEPAD · Documentation: `docs/` (Read the Docs). Install: `pip install bepad` (or `pip install -e .` from
|
|
68
|
+
this folder; `.[dl]` for PyTorch, `.[build]` to rebuild the dataset).
|
|
69
|
+
|
|
70
|
+
## Repository
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
bepad/ load, evaluate, Associator, leaderboard, torch, plots (what users need)
|
|
74
|
+
bepad/build/ pool, labels, windows, release (how the dataset is built)
|
|
75
|
+
configs/ pool.yaml (quality control, split, labels), v0.1.yaml, dev.yaml (scenarios, sizes)
|
|
76
|
+
docs/ Read the Docs sources; docs/gallery/ = the code of every figure
|
|
77
|
+
associators/ contributed associators, one folder each (pyocto = worked example)
|
|
78
|
+
pipeline/ the recipe of a release (SLURM): pool -> release -> baselines (pipeline/README.md)
|
|
79
|
+
hf/README.md Hugging Face dataset card
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## License and citation
|
|
83
|
+
|
|
84
|
+
Code: GPL-3.0. Data: the terms of the original providers, as in UTDQuake. Please cite UTDQuake and
|
|
85
|
+
the network operators of the data you use.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
bepad/__init__.py,sha256=whHazCC_dwmQI0QLibRwsC1t1Ab-IZaG6wHCOui6h00,829
|
|
2
|
+
bepad/__main__.py,sha256=aNVf11co3xU3vDlQu7bhwOiRr7j9aKcC6J3PDW8GkMo,9414
|
|
3
|
+
bepad/associator.py,sha256=xw0cQcU8PyRQuPptRAgqk4P9-l4lIBPZjm4Ke5_3T3w,14902
|
|
4
|
+
bepad/data.py,sha256=Pm3ZmwrnxMtIScYyC5HQqENtghHttSsk0bXUHUQ4ge4,8178
|
|
5
|
+
bepad/leaderboard.py,sha256=o_VUDWEsqddIZJrGj6NMNzZ-8mxh31qnO47d6B8vx2k,18525
|
|
6
|
+
bepad/metrics.py,sha256=t0_cEt7cSncw3XjEHff-sLVUpqsrD4QqYY-Ju1dz15Q,15272
|
|
7
|
+
bepad/plots.py,sha256=5J_ahnncE7tLMk0ebL1ldbQNhkGBVzffr1Gedy7MbVY,50491
|
|
8
|
+
bepad/stats.py,sha256=O5NEGcznxb831AYNJgcSKFEZODKQAYv6tUmDy7UuY3c,7223
|
|
9
|
+
bepad/torch.py,sha256=l1lHSYLU0rn5smLeDb3M7A_zw4xgPpbDNINDPpXWcJY,6989
|
|
10
|
+
bepad/build/__init__.py,sha256=bs2kYx4znsNu4mI6yc5fg1hV17sOD6os4pq2M-QTeM0,513
|
|
11
|
+
bepad/build/config.py,sha256=mmG04H-7MFQIVyZ0qyg4GabY_gXO6m8r7ZRu-DdMXv0,3891
|
|
12
|
+
bepad/build/geo.py,sha256=ucen45viVT8u_qxw1R4cjCIN4qSCGzzobVyx0UbiBZI,2130
|
|
13
|
+
bepad/build/labels.py,sha256=7d6C5R1-efcP1RoTtWHvZZvwlj05HECApZWBeJ6KkNg,15812
|
|
14
|
+
bepad/build/pool.py,sha256=Ugz1md1bmndQZNuAEVWrXXo4eUZSscxX8_4wFZBeAlg,16731
|
|
15
|
+
bepad/build/release.py,sha256=2oEtph0Jy9ljsscEVoN_slkEVg6dumQMvc-cDvBUeZw,11282
|
|
16
|
+
bepad/build/utdquake.py,sha256=1PGuJYP1eqW0PRnqDFVSCR5kcGCi8CbS-mbkFGciFI4,2957
|
|
17
|
+
bepad/build/windows.py,sha256=1Ml4gMn6-mIT48cnj8mIaMebkY2mLXUQrIcLdRaN0Og,21640
|
|
18
|
+
bepad-0.1.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
19
|
+
bepad-0.1.0.dist-info/METADATA,sha256=mnEFOq40J3TlQlhITzQxy7BC29f1CJ9xWXqh34CUrEQ,4031
|
|
20
|
+
bepad-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
21
|
+
bepad-0.1.0.dist-info/entry_points.txt,sha256=IAY9QLOeY3bfXwKOylC9rJJ9usZHOu1uW-wyQvQKVLU,46
|
|
22
|
+
bepad-0.1.0.dist-info/top_level.txt,sha256=ohUvSDWJNaGyN7oCBdk_OZOdF2qgo0AtV33vCRHWvqI,6
|
|
23
|
+
bepad-0.1.0.dist-info/RECORD,,
|