bepad 0.1.0__tar.gz → 0.1.1__tar.gz

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.
Files changed (28) hide show
  1. {bepad-0.1.0 → bepad-0.1.1}/PKG-INFO +1 -1
  2. {bepad-0.1.0 → bepad-0.1.1}/bepad/__init__.py +1 -1
  3. {bepad-0.1.0 → bepad-0.1.1}/bepad/associator.py +11 -14
  4. {bepad-0.1.0 → bepad-0.1.1}/bepad/data.py +55 -10
  5. {bepad-0.1.0 → bepad-0.1.1}/bepad/metrics.py +5 -7
  6. {bepad-0.1.0 → bepad-0.1.1}/bepad.egg-info/PKG-INFO +1 -1
  7. {bepad-0.1.0 → bepad-0.1.1}/pyproject.toml +1 -1
  8. {bepad-0.1.0 → bepad-0.1.1}/LICENSE +0 -0
  9. {bepad-0.1.0 → bepad-0.1.1}/README.md +0 -0
  10. {bepad-0.1.0 → bepad-0.1.1}/bepad/__main__.py +0 -0
  11. {bepad-0.1.0 → bepad-0.1.1}/bepad/build/__init__.py +0 -0
  12. {bepad-0.1.0 → bepad-0.1.1}/bepad/build/config.py +0 -0
  13. {bepad-0.1.0 → bepad-0.1.1}/bepad/build/geo.py +0 -0
  14. {bepad-0.1.0 → bepad-0.1.1}/bepad/build/labels.py +0 -0
  15. {bepad-0.1.0 → bepad-0.1.1}/bepad/build/pool.py +0 -0
  16. {bepad-0.1.0 → bepad-0.1.1}/bepad/build/release.py +0 -0
  17. {bepad-0.1.0 → bepad-0.1.1}/bepad/build/utdquake.py +0 -0
  18. {bepad-0.1.0 → bepad-0.1.1}/bepad/build/windows.py +0 -0
  19. {bepad-0.1.0 → bepad-0.1.1}/bepad/leaderboard.py +0 -0
  20. {bepad-0.1.0 → bepad-0.1.1}/bepad/plots.py +0 -0
  21. {bepad-0.1.0 → bepad-0.1.1}/bepad/stats.py +0 -0
  22. {bepad-0.1.0 → bepad-0.1.1}/bepad/torch.py +0 -0
  23. {bepad-0.1.0 → bepad-0.1.1}/bepad.egg-info/SOURCES.txt +0 -0
  24. {bepad-0.1.0 → bepad-0.1.1}/bepad.egg-info/dependency_links.txt +0 -0
  25. {bepad-0.1.0 → bepad-0.1.1}/bepad.egg-info/entry_points.txt +0 -0
  26. {bepad-0.1.0 → bepad-0.1.1}/bepad.egg-info/requires.txt +0 -0
  27. {bepad-0.1.0 → bepad-0.1.1}/bepad.egg-info/top_level.txt +0 -0
  28. {bepad-0.1.0 → bepad-0.1.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bepad
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: BEPAD: a benchmark dataset for seismic phase association built from UTDQuake
5
5
  License-Expression: GPL-3.0-or-later
6
6
  Project-URL: Repository, https://github.com/ecastillot/BEPAD
@@ -6,7 +6,7 @@
6
6
  class MyMethod(bepad.Associator): ... # template for reusable associators
7
7
  """
8
8
 
9
- __version__ = "0.1.0"
9
+ __version__ = "0.1.1"
10
10
 
11
11
  from .data import CORE_NAME, CORE_NETWORKS, LABEL_COLUMNS, Release, Split, download, load, open, predictions_frame # noqa: E402,A004
12
12
  from .metrics import evaluate # noqa: E402
@@ -90,26 +90,23 @@ class Associator:
90
90
  raise NotImplementedError
91
91
 
92
92
  # --- provided ------------------------------------------------------------------
93
- def run(self, split="test", networks=None, scenarios=None, max_windows=None, workers=1, release=None,
94
- root=None, progress=True):
93
+ def run(self, split="test", networks=None, scenarios=None, max_windows=None, window_ids=None, workers=1,
94
+ release=None, root=None, progress=True):
95
95
  """Run on every window of a split and score it. Returns a :class:`Result`.
96
96
 
97
97
  ``workers``: number of CPU processes (windows are independent).
98
- ``scenarios`` / ``max_windows`` run a subset (quick tests); such runs are
99
- marked partial and are not ranked on the leaderboard.
98
+ ``scenarios`` / ``max_windows`` / ``window_ids`` run a subset (quick tests, as in
99
+ :func:`bepad.load`); such runs are marked partial and are not ranked on the leaderboard.
100
100
  """
101
101
  from .data import RELEASE, load
102
102
  from .metrics import evaluate
103
103
 
104
- data = load(split, networks=networks, labels=False, release=release or RELEASE, root=root)
105
- w = data.windows
106
- keep = w
107
- if scenarios is not None:
108
- keep = keep[keep.scenario.isin(scenarios)]
109
- if max_windows is not None:
110
- keep = keep.groupby("network", group_keys=False).head(max_windows)
104
+ subset = scenarios is not None or max_windows is not None or window_ids is not None
105
+ data = load(split, networks=networks, labels=False, release=release or RELEASE, root=root,
106
+ scenarios=scenarios, max_windows=max_windows, window_ids=window_ids)
107
+ keep = data.windows
111
108
  ids = set(keep.window_id)
112
- todo = [(win.window_id, win.meta["network"], win.picks, win.stations) for win in data if win.window_id in ids]
109
+ todo = [(win.window_id, win.meta["network"], win.picks, win.stations) for win in data]
113
110
  rows, locs, secs, t0, n = [], [], [], time.time(), len(todo)
114
111
  _RUNNING[0] = self
115
112
  if workers > 1:
@@ -137,8 +134,8 @@ class Associator:
137
134
  meta = {
138
135
  "split": split, "release": data.release.path.name,
139
136
  "networks": sorted(keep.network.unique()),
140
- "n_windows": n, "complete": n == len(w),
141
- "scenarios": "all" if scenarios is None else list(scenarios),
137
+ "n_windows": n, "complete": not subset,
138
+ "scenarios": "all" if scenarios is None else ([scenarios] if isinstance(scenarios, str) else list(scenarios)),
142
139
  "workers": workers, "runtime_s": round(runtime, 2),
143
140
  "seconds_per_window": round(float(np.mean(secs)), 4) if secs else None, # time inside associate()
144
141
  "hardware": f"{platform.processor() or platform.machine()} ({platform.system()})",
@@ -97,24 +97,36 @@ class Release:
97
97
  def scenarios(self):
98
98
  return json.loads((self.path / "scenarios.json").read_text())
99
99
 
100
- def _read(self, split, table, networks, columns=None):
100
+ def _read(self, split, table, networks, columns=None, window_ids=None):
101
101
  files = sorted((self.path / split / table).glob("network=*.parquet"))
102
102
  if networks is not None:
103
103
  keep = {f"network={n}.parquet" for n in networks}
104
104
  files = [f for f in files if f.name in keep]
105
105
  if not files:
106
106
  raise FileNotFoundError(f"no {table} files for split={split!r}, networks={networks}")
107
- return pd.concat([pd.read_parquet(f, columns=columns) for f in files], ignore_index=True)
107
+ if window_ids is None:
108
+ return pd.concat([pd.read_parquet(f, columns=columns) for f in files], ignore_index=True)
109
+ return pd.concat([_read_windows(f, columns, window_ids) for f in files], ignore_index=True)
108
110
 
109
- def load(self, split, networks=None, labels=True):
110
- """Load one split. ``labels=False`` returns only what an associator may use."""
111
+ def load(self, split, networks=None, labels=True, scenarios=None, max_windows=None, window_ids=None):
112
+ """Load one split. ``labels=False`` returns only what an associator may use.
113
+
114
+ To save memory, load only some windows: ``scenarios`` (names, e.g. ``"ev-med__noise-high"``),
115
+ ``max_windows`` (a fixed random sample of at most this many windows per network) or
116
+ ``window_ids``. Only the rows of those windows are read.
117
+ """
111
118
  cols = None if labels else list(INPUT_COLUMNS)
119
+ windows = self._read(split, "windows", networks)
120
+ ids = None
121
+ if scenarios is not None or max_windows is not None or window_ids is not None:
122
+ windows = _select(windows, scenarios, max_windows, window_ids)
123
+ ids = set(windows["window_id"])
112
124
  s = Split(
113
- windows=self._read(split, "windows", networks),
114
- picks=self._read(split, "picks", networks, columns=cols),
115
- window_stations=self._read(split, "window_stations", networks),
125
+ windows=windows,
126
+ picks=self._read(split, "picks", networks, columns=cols, window_ids=ids),
127
+ window_stations=self._read(split, "window_stations", networks, window_ids=ids),
116
128
  stations=pd.read_parquet(self.path / "stations.parquet"),
117
- events=self._read(split, "events", networks) if labels else None,
129
+ events=self._read(split, "events", networks, window_ids=ids) if labels else None,
118
130
  labels=labels,
119
131
  )
120
132
  s.release, s.name = self, split
@@ -132,6 +144,32 @@ CORE_NAME = "BEPAD-10"
132
144
  CORE_NETWORKS = ["R05", "R07", "R01", "nc", "tx", "JMA", "CM", "R09", "ak", "NOA"]
133
145
 
134
146
 
147
+ def _select(windows, scenarios=None, max_windows=None, window_ids=None):
148
+ """The windows kept by :meth:`Release.load` (scenario names, a sample per network, or ids)."""
149
+ if window_ids is not None:
150
+ windows = windows[windows["window_id"].isin(set(window_ids))]
151
+ if scenarios is not None:
152
+ windows = windows[windows["scenario"].isin([scenarios] if isinstance(scenarios, str) else scenarios)]
153
+ if max_windows is not None:
154
+ windows = (windows.groupby("network", group_keys=False)
155
+ .apply(lambda g: g.sample(min(len(g), max_windows), random_state=0).sort_index()))
156
+ return windows
157
+
158
+
159
+ def _read_windows(file, columns, window_ids, batch_size=200_000):
160
+ """Rows of one parquet file whose window is in ``window_ids``, read in batches (low memory)."""
161
+ import pyarrow as pa
162
+ import pyarrow.compute as pc
163
+ import pyarrow.parquet as pq
164
+
165
+ pf = pq.ParquetFile(file, pre_buffer=False, buffer_size=1 << 20) # stream, do not read the file at once
166
+ schema = pa.schema([pf.schema_arrow.field(c) for c in columns]) if columns else pf.schema_arrow
167
+ ids = pa.array(sorted(window_ids), type=schema.field("window_id").type)
168
+ parts = [b.filter(pc.is_in(b.column("window_id"), value_set=ids))
169
+ for b in pf.iter_batches(batch_size=batch_size, columns=columns)]
170
+ return pa.Table.from_batches(parts, schema=schema).to_pandas()
171
+
172
+
135
173
  def _networks(networks):
136
174
  """``"core"`` in a list of networks stands for the core networks."""
137
175
  if networks is None:
@@ -164,7 +202,8 @@ def open(path): # noqa: A001 -- mirrors the usual `dataset.open` idiom
164
202
  return Release(path)
165
203
 
166
204
 
167
- def load(split="test", networks=None, labels=True, release=RELEASE, root=None, repo_id=REPO_ID):
205
+ def load(split="test", networks=None, labels=True, release=RELEASE, root=None, repo_id=REPO_ID,
206
+ scenarios=None, max_windows=None, window_ids=None):
168
207
  """Load one split as a :class:`Split` (iterate it to get windows).
169
208
 
170
209
  ``root``: a local release folder (default: the ``BEPAD_ROOT`` environment
@@ -172,11 +211,17 @@ def load(split="test", networks=None, labels=True, release=RELEASE, root=None, r
172
211
  (``repo_id``, ``release``) and cached.
173
212
  ``labels=False`` returns only what an associator may use. ``networks=["core"]``: the
174
213
  :data:`CORE_NETWORKS`.
214
+ Less memory: ``scenarios`` (e.g. ``"ev-med__noise-high"``), ``max_windows`` (a fixed random
215
+ sample of at most this many windows per network) or ``window_ids`` (their networks are then
216
+ the only ones downloaded and read).
175
217
  """
218
+ if networks is None and window_ids is not None: # a window_id starts with its network: "tx.test..."
219
+ networks = sorted({w.split(".", 1)[0] for w in window_ids})
176
220
  networks = _networks(networks)
177
221
  root = root or os.environ.get("BEPAD_ROOT") # a local copy, e.g. export BEPAD_ROOT=data/releases/v0.1
178
222
  path = Path(root) if root else download(release, splits=(split,), networks=networks, repo_id=repo_id)
179
- return Release(path).load(split, networks=networks, labels=labels)
223
+ return Release(path).load(split, networks=networks, labels=labels, scenarios=scenarios,
224
+ max_windows=max_windows, window_ids=window_ids)
180
225
 
181
226
 
182
227
  def predictions_frame(window_ids, pick_idx, pred_event):
@@ -301,15 +301,13 @@ def evaluate(predictions, release, split="test", networks=None, min_shared=3, wi
301
301
  if not isinstance(predictions, pd.DataFrame):
302
302
  p = Path(predictions)
303
303
  predictions = pd.read_parquet(p) if p.suffix == ".parquet" else pd.read_csv(p)
304
+ keep = set(window_ids) if window_ids is not None else None # a partial run: read only its rows
304
305
  windows = rel._read(split, "windows", networks)
305
- picks = rel._read(split, "picks", networks, columns=["window_id", "pick_idx", "event_idx"])
306
- events = rel._read(split, "events", networks, columns=["window_id", "event_idx", "scorable", "latitude",
307
- "longitude", "depth", "t0_rel_s"])
308
- if window_ids is not None:
309
- keep = set(window_ids)
306
+ if keep is not None:
310
307
  windows = windows[windows.window_id.isin(keep)]
311
- picks = picks[picks.window_id.isin(keep)]
312
- events = events[events.window_id.isin(keep)]
308
+ picks = rel._read(split, "picks", networks, columns=["window_id", "pick_idx", "event_idx"], window_ids=keep)
309
+ events = rel._read(split, "events", networks, columns=["window_id", "event_idx", "scorable", "latitude",
310
+ "longitude", "depth", "t0_rel_s"], window_ids=keep)
313
311
  checks = check_predictions(predictions, picks)
314
312
  if checks["n_missing_picks"]:
315
313
  print(f"[evaluate] warning: {checks['n_missing_picks']} picks have no prediction -> counted as noise")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bepad
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: BEPAD: a benchmark dataset for seismic phase association built from UTDQuake
5
5
  License-Expression: GPL-3.0-or-later
6
6
  Project-URL: Repository, https://github.com/ecastillot/BEPAD
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "bepad"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "BEPAD: a benchmark dataset for seismic phase association built from UTDQuake"
9
9
  readme = "README.md"
10
10
  license = "GPL-3.0-or-later"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes