PhaseNet 0.1.0__tar.gz → 0.2.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.
- phasenet-0.2.1/.github/workflows/publish.yml +28 -0
- phasenet-0.2.1/.gitignore +15 -0
- phasenet-0.2.1/PKG-INFO +163 -0
- phasenet-0.2.1/README.md +133 -0
- phasenet-0.2.1/docs/notebooks/lab2a_phasenet_das_inference.ipynb +285 -0
- phasenet-0.2.1/docs/notebooks/lab2b_pick_association.ipynb +195 -0
- phasenet-0.2.1/docs/notebooks/lab2c_semisupervised_training.ipynb +487 -0
- phasenet-0.2.1/examples/ceed_training_plots.py +181 -0
- phasenet-0.2.1/examples/italy/prepare_inventory.py +233 -0
- phasenet-0.2.1/examples/italy/prepare_mseed_list.py +118 -0
- phasenet-0.2.1/examples/italy/run_all_years.sh +64 -0
- phasenet-0.2.1/examples/italy/upload_loop.sh +12 -0
- phasenet-0.2.1/examples/italy/upload_phasenet_origin.sh +47 -0
- phasenet-0.2.1/phasenet/__init__.py +3 -0
- phasenet-0.2.1/phasenet/data/__init__.py +7 -0
- phasenet-0.2.1/phasenet/data/ceed.py +2294 -0
- phasenet-0.2.1/phasenet/data/das.py +2555 -0
- phasenet-0.2.1/phasenet/data/transforms.py +776 -0
- phasenet-0.2.1/phasenet/models/__init__.py +8 -0
- phasenet-0.2.1/phasenet/models/phasenet.py +480 -0
- phasenet-0.2.1/phasenet/models/phasenet_das.py +75 -0
- phasenet-0.2.1/phasenet/models/phasenet_das_plus.py +46 -0
- phasenet-0.2.1/phasenet/models/phasenet_plus.py +27 -0
- phasenet-0.2.1/phasenet/models/phasenet_tf.py +21 -0
- phasenet-0.2.1/phasenet/models/phasenet_tf_plus.py +27 -0
- phasenet-0.2.1/phasenet/models/prompt/__init__.py +11 -0
- phasenet-0.2.1/phasenet/models/prompt/common.py +43 -0
- phasenet-0.2.1/phasenet/models/prompt/mask_decoder.py +185 -0
- phasenet-0.2.1/phasenet/models/prompt/prompt_encoder.py +242 -0
- phasenet-0.2.1/phasenet/models/prompt/transformer.py +232 -0
- phasenet-0.2.1/phasenet/models/unet.py +1316 -0
- phasenet-0.2.1/phasenet/models/unet2018.py +253 -0
- phasenet-0.2.1/phasenet/utils/__init__.py +3 -0
- PhaseNet-0.1.0/phasenet/detect_peaks.py → phasenet-0.2.1/phasenet/utils/detect_peaks_cpu.py +46 -14
- phasenet-0.2.1/phasenet/utils/inference.py +405 -0
- phasenet-0.2.1/phasenet/utils/postprocess.py +711 -0
- phasenet-0.2.1/phasenet/utils/visualization.py +1465 -0
- phasenet-0.2.1/phasenet.egg-info/PKG-INFO +163 -0
- phasenet-0.2.1/phasenet.egg-info/SOURCES.txt +58 -0
- phasenet-0.2.1/phasenet.egg-info/entry_points.txt +3 -0
- phasenet-0.2.1/phasenet.egg-info/requires.txt +16 -0
- phasenet-0.2.1/predict.py +630 -0
- phasenet-0.2.1/pyproject.toml +48 -0
- phasenet-0.2.1/requirements.txt +16 -0
- phasenet-0.2.1/scripts/predict_ceed.py +489 -0
- phasenet-0.2.1/scripts/predict_ceed.sh +41 -0
- phasenet-0.2.1/scripts/predict_das.py +607 -0
- phasenet-0.2.1/scripts/predict_das.sh +55 -0
- phasenet-0.2.1/scripts/predict_mseed.py +487 -0
- phasenet-0.2.1/scripts/semisupervised_das.sh +221 -0
- phasenet-0.2.1/scripts/train_ceed.sh +21 -0
- phasenet-0.2.1/scripts/train_das.sh +57 -0
- phasenet-0.2.1/tests/benchmark_dataloader.py +373 -0
- phasenet-0.2.1/tests/test_augmentations.py +234 -0
- phasenet-0.2.1/tests/test_labels.py +288 -0
- phasenet-0.2.1/train.py +807 -0
- phasenet-0.2.1/utils.py +488 -0
- PhaseNet-0.1.0/PKG-INFO +0 -20
- PhaseNet-0.1.0/PhaseNet.egg-info/PKG-INFO +0 -20
- PhaseNet-0.1.0/PhaseNet.egg-info/SOURCES.txt +0 -14
- PhaseNet-0.1.0/PhaseNet.egg-info/requires.txt +0 -9
- PhaseNet-0.1.0/README.md +0 -124
- PhaseNet-0.1.0/phasenet/__init__.py +0 -1
- PhaseNet-0.1.0/phasenet/data_reader.py +0 -520
- PhaseNet-0.1.0/phasenet/model.py +0 -467
- PhaseNet-0.1.0/phasenet/predict.py +0 -143
- PhaseNet-0.1.0/phasenet/train.py +0 -297
- PhaseNet-0.1.0/phasenet/util.py +0 -203
- PhaseNet-0.1.0/setup.py +0 -116
- {PhaseNet-0.1.0/PhaseNet.egg-info → phasenet-0.2.1/phasenet.egg-info}/dependency_links.txt +0 -0
- {PhaseNet-0.1.0/PhaseNet.egg-info → phasenet-0.2.1/phasenet.egg-info}/top_level.txt +0 -0
- {PhaseNet-0.1.0 → phasenet-0.2.1}/setup.cfg +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
id-token: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
environment: pypi
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.10"
|
|
20
|
+
|
|
21
|
+
- name: Install build tools
|
|
22
|
+
run: pip install build
|
|
23
|
+
|
|
24
|
+
- name: Build package
|
|
25
|
+
run: python -m build
|
|
26
|
+
|
|
27
|
+
- name: Publish to PyPI
|
|
28
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
phasenet-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: phasenet
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: A PyTorch implementation of PhaseNet for seismic and DAS phase picking
|
|
5
|
+
Author: Weiqiang Zhu
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/AI4EPS/phasenet-pytorch
|
|
8
|
+
Project-URL: Repository, https://github.com/AI4EPS/phasenet-pytorch
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: torch
|
|
15
|
+
Requires-Dist: torchvision
|
|
16
|
+
Requires-Dist: einops
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: scipy
|
|
19
|
+
Requires-Dist: h5py
|
|
20
|
+
Requires-Dist: matplotlib
|
|
21
|
+
Requires-Dist: pandas
|
|
22
|
+
Requires-Dist: tqdm
|
|
23
|
+
Requires-Dist: fsspec
|
|
24
|
+
Requires-Dist: obspy
|
|
25
|
+
Requires-Dist: gcsfs
|
|
26
|
+
Requires-Dist: datasets
|
|
27
|
+
Requires-Dist: pyarrow
|
|
28
|
+
Requires-Dist: wandb
|
|
29
|
+
Requires-Dist: scikit-learn
|
|
30
|
+
|
|
31
|
+
# PhaseNet-PyTorch
|
|
32
|
+
|
|
33
|
+
PyTorch implementation of PhaseNet for seismic and DAS phase picking, event detection, and polarity classification.
|
|
34
|
+
|
|
35
|
+
## Models
|
|
36
|
+
|
|
37
|
+
| Model | Features | Data Type |
|
|
38
|
+
|-------|----------|-----------|
|
|
39
|
+
| `phasenet` | Phase (P/S) picking | Seismic 3-component |
|
|
40
|
+
| `phasenet_tf` | Phase picking + STFT spectrogram | Seismic 3-component |
|
|
41
|
+
| `phasenet_plus` | Phase + polarity + event detection | Seismic 3-component |
|
|
42
|
+
| `phasenet_tf_plus` | Phase + polarity + event detection + STFT | Seismic 3-component |
|
|
43
|
+
| `phasenet_das` | Phase picking | DAS single-channel |
|
|
44
|
+
| `phasenet_das_plus` | Phase + event detection | DAS single-channel |
|
|
45
|
+
|
|
46
|
+
The `_tf` variants add a Short-Time Fourier Transform (STFT) branch that extracts frequency features alongside the temporal waveform, improving performance on noisy data.
|
|
47
|
+
|
|
48
|
+
## Prediction
|
|
49
|
+
|
|
50
|
+
### CEED (Seismic) Prediction
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Demo: process a few events with plots
|
|
54
|
+
python scripts/predict_ceed.py --n-events 5
|
|
55
|
+
|
|
56
|
+
# Process all days for a year (saves parquets to results/ceed/)
|
|
57
|
+
python scripts/predict_ceed.py --all --year 2025 --output-dir results/ceed
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Output: one parquet per day file in `results/ceed/{region}/`, with columns:
|
|
61
|
+
`event_id, station_id, waveform_index, origin_id, origin_index, origin_time, phase_index, phase_time, phase_score, phase_type, phase_polarity`
|
|
62
|
+
|
|
63
|
+
### DAS Prediction
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Predict with base PhaseNet on a HuggingFace subset
|
|
67
|
+
python scripts/predict_das.py --subset arcata --plot
|
|
68
|
+
|
|
69
|
+
# Predict with a trained DAS model on local data
|
|
70
|
+
python scripts/predict_das.py \
|
|
71
|
+
--data-dir data/quakeflow_das/arcata/data \
|
|
72
|
+
--model-type phasenet_das_plus \
|
|
73
|
+
--checkpoint output/train_das_arcata/checkpoint.pth \
|
|
74
|
+
--output-dir results/das/train_das_arcata/arcata \
|
|
75
|
+
--no-ema --plot
|
|
76
|
+
|
|
77
|
+
# Predict from a file list
|
|
78
|
+
python scripts/predict_das.py \
|
|
79
|
+
--file-list file_list.txt \
|
|
80
|
+
--model-type phasenet_das_plus \
|
|
81
|
+
--checkpoint output/model.pth \
|
|
82
|
+
--plot
|
|
83
|
+
|
|
84
|
+
# Multi-GPU prediction
|
|
85
|
+
bash scripts/predict_das.sh phasenet_das_plus arcata 8 output/model.pth
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Output: one parquet per event in `results/das/{model_name}/{subset}/`, with columns:
|
|
89
|
+
`event_id, channel_index, origin_id, origin_index, origin_time, phase_index, phase_time, phase_score, phase_type, dt_s, ps_center, ps_interval`
|
|
90
|
+
|
|
91
|
+
By default, picks are associated using P-S pairing. Use `--use-event-head` to associate via the model's event detection head instead.
|
|
92
|
+
|
|
93
|
+
## Training
|
|
94
|
+
|
|
95
|
+
### CEED (Seismic) Training
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
python train.py \
|
|
99
|
+
--model phasenet_plus \
|
|
100
|
+
--dataset-type ceed \
|
|
101
|
+
--label-path results/ceed \
|
|
102
|
+
--nx 16 \
|
|
103
|
+
--max-iters 100000 \
|
|
104
|
+
--batch-size 8 \
|
|
105
|
+
--workers 4 \
|
|
106
|
+
--lr 3e-4 \
|
|
107
|
+
--eval-interval 5000 \
|
|
108
|
+
--output-dir output/train_ceed
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### DAS Training
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Using the training script
|
|
115
|
+
bash scripts/train_das.sh 0 arcata v26
|
|
116
|
+
|
|
117
|
+
# Or directly
|
|
118
|
+
python train.py \
|
|
119
|
+
--model phasenet_das_plus \
|
|
120
|
+
--dataset-type das \
|
|
121
|
+
--data-path data/quakeflow_das/arcata/data \
|
|
122
|
+
--label-path results/das/phasenet/arcata/picks \
|
|
123
|
+
--label-list results/das/phasenet/arcata/labels.txt \
|
|
124
|
+
--nx 2048 --nt 4096 \
|
|
125
|
+
--num-patch 16 \
|
|
126
|
+
--max-iters 50000 \
|
|
127
|
+
--batch-size 2 --workers 8 \
|
|
128
|
+
--lr 1e-4 --weight-decay 0.01 \
|
|
129
|
+
--model-ema --model-ema-decay 0.999 \
|
|
130
|
+
--eval-interval 1000 --save-interval 1000 \
|
|
131
|
+
--output-dir output/train_das_arcata_v26
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Key Training Options
|
|
135
|
+
|
|
136
|
+
| Option | Description | Default |
|
|
137
|
+
|--------|-------------|---------|
|
|
138
|
+
| `--num-patch N` | Random crops per DAS sample (amortizes IO) | 2 |
|
|
139
|
+
| `--model-ema` | Enable exponential moving average | off |
|
|
140
|
+
| `--gradient-accumulation-steps N` | Accumulate gradients for larger effective batch | 1 |
|
|
141
|
+
| `--clip-grad-norm V` | Gradient clipping | 1.0 |
|
|
142
|
+
| `--compile` | Enable torch.compile | off |
|
|
143
|
+
| `--resume --checkpoint PATH` | Resume from checkpoint | - |
|
|
144
|
+
| `--reset-lr` | Reset LR schedule when resuming | off |
|
|
145
|
+
|
|
146
|
+
## Semi-supervised Training
|
|
147
|
+
|
|
148
|
+
Iterative self-training pipeline for DAS: predict → train on predictions → predict with new model → repeat.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# Start from PhaseNet (train DAS model from scratch)
|
|
152
|
+
bash scripts/semisupervised_das.sh arcata 5 0
|
|
153
|
+
|
|
154
|
+
# Start from a pretrained DAS model
|
|
155
|
+
bash scripts/semisupervised_das.sh arcata 5 0 phasenet_das output/train_das_v26/checkpoint.pth
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Arguments: `[subset] [num_iterations] [gpu] [start_from] [checkpoint]`
|
|
159
|
+
|
|
160
|
+
- **From phasenet**: iteration 0 predicts with PhaseNet, iteration 1 trains from scratch (10k steps, warmup), iterations 2+ continue (1k steps, no warmup)
|
|
161
|
+
- **From phasenet_das**: iteration 0 predicts with pretrained DAS model, iterations 1+ continue (1k steps, no warmup)
|
|
162
|
+
|
|
163
|
+
Results are saved to `results/semisupervised_das/` and checkpoints to `output/semisupervised_das_v{N}/`.
|
phasenet-0.2.1/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# PhaseNet-PyTorch
|
|
2
|
+
|
|
3
|
+
PyTorch implementation of PhaseNet for seismic and DAS phase picking, event detection, and polarity classification.
|
|
4
|
+
|
|
5
|
+
## Models
|
|
6
|
+
|
|
7
|
+
| Model | Features | Data Type |
|
|
8
|
+
|-------|----------|-----------|
|
|
9
|
+
| `phasenet` | Phase (P/S) picking | Seismic 3-component |
|
|
10
|
+
| `phasenet_tf` | Phase picking + STFT spectrogram | Seismic 3-component |
|
|
11
|
+
| `phasenet_plus` | Phase + polarity + event detection | Seismic 3-component |
|
|
12
|
+
| `phasenet_tf_plus` | Phase + polarity + event detection + STFT | Seismic 3-component |
|
|
13
|
+
| `phasenet_das` | Phase picking | DAS single-channel |
|
|
14
|
+
| `phasenet_das_plus` | Phase + event detection | DAS single-channel |
|
|
15
|
+
|
|
16
|
+
The `_tf` variants add a Short-Time Fourier Transform (STFT) branch that extracts frequency features alongside the temporal waveform, improving performance on noisy data.
|
|
17
|
+
|
|
18
|
+
## Prediction
|
|
19
|
+
|
|
20
|
+
### CEED (Seismic) Prediction
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Demo: process a few events with plots
|
|
24
|
+
python scripts/predict_ceed.py --n-events 5
|
|
25
|
+
|
|
26
|
+
# Process all days for a year (saves parquets to results/ceed/)
|
|
27
|
+
python scripts/predict_ceed.py --all --year 2025 --output-dir results/ceed
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Output: one parquet per day file in `results/ceed/{region}/`, with columns:
|
|
31
|
+
`event_id, station_id, waveform_index, origin_id, origin_index, origin_time, phase_index, phase_time, phase_score, phase_type, phase_polarity`
|
|
32
|
+
|
|
33
|
+
### DAS Prediction
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Predict with base PhaseNet on a HuggingFace subset
|
|
37
|
+
python scripts/predict_das.py --subset arcata --plot
|
|
38
|
+
|
|
39
|
+
# Predict with a trained DAS model on local data
|
|
40
|
+
python scripts/predict_das.py \
|
|
41
|
+
--data-dir data/quakeflow_das/arcata/data \
|
|
42
|
+
--model-type phasenet_das_plus \
|
|
43
|
+
--checkpoint output/train_das_arcata/checkpoint.pth \
|
|
44
|
+
--output-dir results/das/train_das_arcata/arcata \
|
|
45
|
+
--no-ema --plot
|
|
46
|
+
|
|
47
|
+
# Predict from a file list
|
|
48
|
+
python scripts/predict_das.py \
|
|
49
|
+
--file-list file_list.txt \
|
|
50
|
+
--model-type phasenet_das_plus \
|
|
51
|
+
--checkpoint output/model.pth \
|
|
52
|
+
--plot
|
|
53
|
+
|
|
54
|
+
# Multi-GPU prediction
|
|
55
|
+
bash scripts/predict_das.sh phasenet_das_plus arcata 8 output/model.pth
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Output: one parquet per event in `results/das/{model_name}/{subset}/`, with columns:
|
|
59
|
+
`event_id, channel_index, origin_id, origin_index, origin_time, phase_index, phase_time, phase_score, phase_type, dt_s, ps_center, ps_interval`
|
|
60
|
+
|
|
61
|
+
By default, picks are associated using P-S pairing. Use `--use-event-head` to associate via the model's event detection head instead.
|
|
62
|
+
|
|
63
|
+
## Training
|
|
64
|
+
|
|
65
|
+
### CEED (Seismic) Training
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
python train.py \
|
|
69
|
+
--model phasenet_plus \
|
|
70
|
+
--dataset-type ceed \
|
|
71
|
+
--label-path results/ceed \
|
|
72
|
+
--nx 16 \
|
|
73
|
+
--max-iters 100000 \
|
|
74
|
+
--batch-size 8 \
|
|
75
|
+
--workers 4 \
|
|
76
|
+
--lr 3e-4 \
|
|
77
|
+
--eval-interval 5000 \
|
|
78
|
+
--output-dir output/train_ceed
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### DAS Training
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Using the training script
|
|
85
|
+
bash scripts/train_das.sh 0 arcata v26
|
|
86
|
+
|
|
87
|
+
# Or directly
|
|
88
|
+
python train.py \
|
|
89
|
+
--model phasenet_das_plus \
|
|
90
|
+
--dataset-type das \
|
|
91
|
+
--data-path data/quakeflow_das/arcata/data \
|
|
92
|
+
--label-path results/das/phasenet/arcata/picks \
|
|
93
|
+
--label-list results/das/phasenet/arcata/labels.txt \
|
|
94
|
+
--nx 2048 --nt 4096 \
|
|
95
|
+
--num-patch 16 \
|
|
96
|
+
--max-iters 50000 \
|
|
97
|
+
--batch-size 2 --workers 8 \
|
|
98
|
+
--lr 1e-4 --weight-decay 0.01 \
|
|
99
|
+
--model-ema --model-ema-decay 0.999 \
|
|
100
|
+
--eval-interval 1000 --save-interval 1000 \
|
|
101
|
+
--output-dir output/train_das_arcata_v26
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Key Training Options
|
|
105
|
+
|
|
106
|
+
| Option | Description | Default |
|
|
107
|
+
|--------|-------------|---------|
|
|
108
|
+
| `--num-patch N` | Random crops per DAS sample (amortizes IO) | 2 |
|
|
109
|
+
| `--model-ema` | Enable exponential moving average | off |
|
|
110
|
+
| `--gradient-accumulation-steps N` | Accumulate gradients for larger effective batch | 1 |
|
|
111
|
+
| `--clip-grad-norm V` | Gradient clipping | 1.0 |
|
|
112
|
+
| `--compile` | Enable torch.compile | off |
|
|
113
|
+
| `--resume --checkpoint PATH` | Resume from checkpoint | - |
|
|
114
|
+
| `--reset-lr` | Reset LR schedule when resuming | off |
|
|
115
|
+
|
|
116
|
+
## Semi-supervised Training
|
|
117
|
+
|
|
118
|
+
Iterative self-training pipeline for DAS: predict → train on predictions → predict with new model → repeat.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Start from PhaseNet (train DAS model from scratch)
|
|
122
|
+
bash scripts/semisupervised_das.sh arcata 5 0
|
|
123
|
+
|
|
124
|
+
# Start from a pretrained DAS model
|
|
125
|
+
bash scripts/semisupervised_das.sh arcata 5 0 phasenet_das output/train_das_v26/checkpoint.pth
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Arguments: `[subset] [num_iterations] [gpu] [start_from] [checkpoint]`
|
|
129
|
+
|
|
130
|
+
- **From phasenet**: iteration 0 predicts with PhaseNet, iteration 1 trains from scratch (10k steps, warmup), iterations 2+ continue (1k steps, no warmup)
|
|
131
|
+
- **From phasenet_das**: iteration 0 predicts with pretrained DAS model, iterations 1+ continue (1k steps, no warmup)
|
|
132
|
+
|
|
133
|
+
Results are saved to `results/semisupervised_das/` and checkpoints to `output/semisupervised_das_v{N}/`.
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"metadata": {},
|
|
6
|
+
"source": "[](https://colab.research.google.com/github/AI4EPS/phasenet-pytorch/blob/main/docs/notebooks/lab2a_phasenet_das_inference.ipynb)\n\n# Lab 2a: PhaseNet-DAS Inference\n\nIn this lab, you will learn how to:\n- Load DAS (Distributed Acoustic Sensing) data from the USGS Arcata array\n- Apply PhaseNet for P/S phase picking on DAS data\n- Apply PhaseNet-DAS+, a DAS-specific model with event detection\n- Compare picking quality between a general seismic model and a DAS-specific model\n\n**References:**\n- Zhu, W. & Beroza, G. (2019). PhaseNet: a deep-neural-network-based seismic arrival-time picking method. *GJI*.\n- Zhu, W. et al. (2023). Seismic arrival-time picking on distributed acoustic sensing data using semi-supervised learning. *Nature Communications*, 14, 8192.\n- Zhu, W. et al. (2025). Towards end-to-end earthquake monitoring using a multitask deep learning model. [arXiv:2506.06939](https://arxiv.org/abs/2506.06939)\n- McGuire, J.J. et al. (2025). The GorDAS DAS Experiment Above the Cascadia Locked Zone and Subducted Gorda Slab. *SRL*, 96(4), 2489-2503.\n\n---"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"cell_type": "markdown",
|
|
10
|
+
"metadata": {},
|
|
11
|
+
"source": [
|
|
12
|
+
"## Setup"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"cell_type": "code",
|
|
17
|
+
"execution_count": null,
|
|
18
|
+
"metadata": {},
|
|
19
|
+
"outputs": [],
|
|
20
|
+
"source": "# Install phasenet (only needed once)\n# !pip install phasenet"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"cell_type": "code",
|
|
24
|
+
"execution_count": null,
|
|
25
|
+
"metadata": {},
|
|
26
|
+
"outputs": [],
|
|
27
|
+
"source": "import os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport h5py\nimport torch\nfrom huggingface_hub import hf_hub_download\n\nimport phasenet.models\nfrom phasenet.utils.inference import predict_2d, detect_picks\nfrom phasenet.utils.postprocess import (\n estimate_origins_from_ps_pairs,\n associate_picks,\n)\n\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"Using device: {DEVICE}\")\n\nDS = 10 # downsample factor for plotting\n\ndef plot_das(ax, arr, nx, nt, dt_s, **kwargs):\n \"\"\"Plot a downsampled DAS 2D array.\"\"\"\n arr = np.asarray(arr)\n ax.imshow(arr[::DS, ::DS], aspect=\"auto\", interpolation=\"bilinear\",\n extent=[0, nt * dt_s, nx, 0], **kwargs)\n\ndef plot_picks(ax, df, dt_s):\n \"\"\"Plot P and S picks.\"\"\"\n for phase, color in [(\"P\", \"red\"), (\"S\", \"blue\")]:\n sub = df[df[\"phase_type\"] == phase]\n ax.scatter(sub[\"phase_index\"] * dt_s, sub[\"channel_index\"].astype(int),\n s=0.5, c=color, alpha=0.3,\n label=f\"{phase} ({len(sub)})\")"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"cell_type": "markdown",
|
|
31
|
+
"metadata": {},
|
|
32
|
+
"source": "## 1. Load DAS Data\n\nWe use DAS data from the USGS Arcata array in Northern California (McGuire et al. 2025), deployed to monitor the 2022 M6.4 Ferndale aftershock sequence. The array records strain rate along a fiber-optic cable with ~2m channel spacing at 250 Hz sampling rate.\n\nWe chose this dataset because:\n1. It is **independent** from any data used in previous PhaseNet/PhaseNet-DAS development — allowing unbiased evaluation\n2. It is a **small, self-contained** dataset (~2,470 events) — ideal for demonstrating how semi-supervised learning can rapidly improve a model on a new DAS array\n\nEach event file is an H5 file containing a 2D array of shape `(nx, nt)` — number of spatial channels by number of time samples."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"cell_type": "code",
|
|
36
|
+
"execution_count": null,
|
|
37
|
+
"metadata": {},
|
|
38
|
+
"outputs": [],
|
|
39
|
+
"source": [
|
|
40
|
+
"HF_REPO = \"AI4EPS/quakeflow_das\"\n",
|
|
41
|
+
"\n",
|
|
42
|
+
"# Download a strong event with clear P and S arrivals\n",
|
|
43
|
+
"EVENT_ID = \"20230106T122215Z\"\n",
|
|
44
|
+
"h5_path = hf_hub_download(\n",
|
|
45
|
+
" HF_REPO, f\"arcata/data/{EVENT_ID}.h5\", repo_type=\"dataset\",\n",
|
|
46
|
+
" local_dir=\"data/quakeflow_das\",\n",
|
|
47
|
+
")\n",
|
|
48
|
+
"\n",
|
|
49
|
+
"with h5py.File(h5_path, \"r\") as fp:\n",
|
|
50
|
+
" data = fp[\"data\"][:]\n",
|
|
51
|
+
" attrs = dict(fp[\"data\"].attrs)\n",
|
|
52
|
+
"\n",
|
|
53
|
+
"dt_s = float(attrs.get(\"dt_s\", 0.008))\n",
|
|
54
|
+
"begin_time = str(attrs.get(\"begin_time\", \"\"))\n",
|
|
55
|
+
"nx, nt = data.shape\n",
|
|
56
|
+
"\n",
|
|
57
|
+
"print(f\"Event: {EVENT_ID}\")\n",
|
|
58
|
+
"print(f\"Shape: (nx={nx}, nt={nt})\")\n",
|
|
59
|
+
"print(f\"Sampling interval: {dt_s} s ({1/dt_s:.0f} Hz)\")\n",
|
|
60
|
+
"print(f\"Duration: {nt * dt_s:.1f} s\")\n",
|
|
61
|
+
"print(f\"Begin time: {begin_time}\")"
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"cell_type": "code",
|
|
66
|
+
"execution_count": null,
|
|
67
|
+
"metadata": {},
|
|
68
|
+
"outputs": [],
|
|
69
|
+
"source": "# Plot raw DAS waveform\nfig, ax = plt.subplots(figsize=(12, 8))\nvmax = 2 * np.percentile(np.abs(data), 95)\nplot_das(ax, data, nx, nt, dt_s, cmap=\"seismic\", vmin=-vmax, vmax=vmax)\nax.set_xlabel(\"Time (s)\")\nax.set_ylabel(\"Channel index\")\nax.set_title(f\"Raw DAS waveform — {EVENT_ID}\")\nplt.tight_layout()\nplt.show()"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"cell_type": "markdown",
|
|
73
|
+
"metadata": {},
|
|
74
|
+
"source": [
|
|
75
|
+
"## 2. Run PhaseNet (Seismic Model)\n",
|
|
76
|
+
"\n",
|
|
77
|
+
"PhaseNet (Zhu & Beroza, 2019) is a deep learning model originally designed for 3-component seismic data. It can be applied to single-component DAS data by treating the DAS channel as the vertical (Z) component and zero-padding the other two components.\n",
|
|
78
|
+
"\n",
|
|
79
|
+
"PhaseNet processes each channel independently — it sees `(3, 1, nt)` per channel, treating each DAS channel as a single seismic station."
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"cell_type": "code",
|
|
84
|
+
"execution_count": null,
|
|
85
|
+
"metadata": {},
|
|
86
|
+
"outputs": [],
|
|
87
|
+
"source": [
|
|
88
|
+
"# Prepare input: DAS → 3-component format (E=0, N=0, Z=data)\n",
|
|
89
|
+
"waveform_3ch = np.zeros((3, nx, nt), dtype=np.float32)\n",
|
|
90
|
+
"waveform_3ch[2] = data.astype(np.float32) # Z-component\n",
|
|
91
|
+
"\n",
|
|
92
|
+
"print(f\"Input shape: {waveform_3ch.shape} (3 channels, {nx} stations, {nt} samples)\")"
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"cell_type": "code",
|
|
97
|
+
"execution_count": null,
|
|
98
|
+
"metadata": {},
|
|
99
|
+
"outputs": [],
|
|
100
|
+
"source": [
|
|
101
|
+
"# Load pretrained PhaseNet model\n",
|
|
102
|
+
"import urllib.request\n",
|
|
103
|
+
"\n",
|
|
104
|
+
"MODELS_REPO = \"AI4EPS/models\"\n",
|
|
105
|
+
"\n",
|
|
106
|
+
"def download_model(release, filename):\n",
|
|
107
|
+
" \"\"\"Download a model from GitHub releases (cached locally).\"\"\"\n",
|
|
108
|
+
" cache_dir = os.path.expanduser(f\"~/.cache/phasenet/{release}\")\n",
|
|
109
|
+
" local_path = os.path.join(cache_dir, filename)\n",
|
|
110
|
+
" if not os.path.exists(local_path):\n",
|
|
111
|
+
" os.makedirs(cache_dir, exist_ok=True)\n",
|
|
112
|
+
" url = f\"https://github.com/{MODELS_REPO}/releases/download/{release}/{filename}\"\n",
|
|
113
|
+
" print(f\"Downloading {url}\")\n",
|
|
114
|
+
" urllib.request.urlretrieve(url, local_path)\n",
|
|
115
|
+
" return local_path\n",
|
|
116
|
+
"\n",
|
|
117
|
+
"# Load PhaseNet-2018\n",
|
|
118
|
+
"ckpt_path = download_model(\"PhaseNet-2018\", \"phasenet2018.pth\")\n",
|
|
119
|
+
"ckpt = torch.load(ckpt_path, map_location=\"cpu\", weights_only=False)\n",
|
|
120
|
+
"model_pn = phasenet.models.phasenet.build_model(\n",
|
|
121
|
+
" backbone=\"unet2018\", **ckpt.get(\"model_config\", {})\n",
|
|
122
|
+
")\n",
|
|
123
|
+
"model_pn.load_state_dict(ckpt[\"model\"])\n",
|
|
124
|
+
"model_pn.to(DEVICE).eval()\n",
|
|
125
|
+
"print(\"PhaseNet-2018 loaded\")"
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"cell_type": "code",
|
|
130
|
+
"execution_count": null,
|
|
131
|
+
"metadata": {},
|
|
132
|
+
"outputs": [],
|
|
133
|
+
"source": [
|
|
134
|
+
"# Run inference\n",
|
|
135
|
+
"# PhaseNet uses a sliding window of (256 channels, 3000 samples)\n",
|
|
136
|
+
"NX_PN, NT_PN = 256, 3000\n",
|
|
137
|
+
"MIN_PROB = 0.3\n",
|
|
138
|
+
"\n",
|
|
139
|
+
"with torch.no_grad():\n",
|
|
140
|
+
" scores_pn = predict_2d(\n",
|
|
141
|
+
" model_pn, waveform_3ch, NX_PN, NT_PN,\n",
|
|
142
|
+
" min_t_overlap=0.1, device=DEVICE,\n",
|
|
143
|
+
" taper_nx=False, taper_nt=True, # Independent channels → no nx taper\n",
|
|
144
|
+
" )\n",
|
|
145
|
+
"\n",
|
|
146
|
+
"print(f\"Output scores shape: {scores_pn.shape}\") # (3, nx, nt) — P, S, Noise"
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"cell_type": "code",
|
|
151
|
+
"execution_count": null,
|
|
152
|
+
"metadata": {},
|
|
153
|
+
"outputs": [],
|
|
154
|
+
"source": [
|
|
155
|
+
"# Detect picks from probability scores using peak detection\n",
|
|
156
|
+
"channel_ids = [str(i) for i in range(nx)]\n",
|
|
157
|
+
"\n",
|
|
158
|
+
"picks_pn = detect_picks(\n",
|
|
159
|
+
" scores_pn, channel_ids, EVENT_ID, begin_time,\n",
|
|
160
|
+
" dt_s=dt_s, vmin=MIN_PROB, device=DEVICE,\n",
|
|
161
|
+
")\n",
|
|
162
|
+
"\n",
|
|
163
|
+
"df_pn = pd.DataFrame(picks_pn)\n",
|
|
164
|
+
"n_p = (df_pn[\"phase_type\"] == \"P\").sum()\n",
|
|
165
|
+
"n_s = (df_pn[\"phase_type\"] == \"S\").sum()\n",
|
|
166
|
+
"print(f\"PhaseNet picks: {len(df_pn)} total ({n_p} P, {n_s} S)\")"
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"cell_type": "code",
|
|
171
|
+
"execution_count": null,
|
|
172
|
+
"metadata": {},
|
|
173
|
+
"outputs": [],
|
|
174
|
+
"source": "# Plot picks on waveform\nfig, ax = plt.subplots(figsize=(12, 8))\nplot_das(ax, data, nx, nt, dt_s, cmap=\"seismic\", vmin=-vmax, vmax=vmax)\nplot_picks(ax, df_pn, dt_s)\nax.set_xlabel(\"Time (s)\")\nax.set_ylabel(\"Channel index\")\nax.set_title(f\"PhaseNet picks — {EVENT_ID}\")\nax.legend(loc=\"upper right\", markerscale=10)\nplt.tight_layout()\nplt.show()"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"cell_type": "markdown",
|
|
178
|
+
"metadata": {},
|
|
179
|
+
"source": "## 3. Run PhaseNet-DAS+\n\nPhaseNet-DAS+ (Zhu et al. 2023, 2025) is specifically designed for DAS data. Unlike PhaseNet which treats each channel independently, PhaseNet-DAS+ uses 2D convolutions across both spatial (channel) and temporal dimensions, learning the spatiotemporal patterns of seismic waves propagating along the fiber.\n\nThe model was trained using a semi-supervised approach: initial labels from PhaseNet picks were iteratively refined by retraining on associated picks.\n\nKey differences from PhaseNet:\n- **Input**: 1 channel (native DAS strain rate) instead of 3\n- **Architecture**: 2D convolutions across (nx, nt) instead of independent 1D per channel\n- **Output**: P/S scores + event detection head (PhaseNet+ multi-task framework)"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"cell_type": "code",
|
|
183
|
+
"execution_count": null,
|
|
184
|
+
"metadata": {},
|
|
185
|
+
"outputs": [],
|
|
186
|
+
"source": [
|
|
187
|
+
"# Prepare input: native DAS format (1 channel)\n",
|
|
188
|
+
"waveform_1ch = data.astype(np.float32)[np.newaxis, :, :] # (1, nx, nt)\n",
|
|
189
|
+
"print(f\"Input shape: {waveform_1ch.shape} (1 channel, {nx} stations, {nt} samples)\")"
|
|
190
|
+
]
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"cell_type": "code",
|
|
194
|
+
"execution_count": null,
|
|
195
|
+
"metadata": {},
|
|
196
|
+
"outputs": [],
|
|
197
|
+
"source": [
|
|
198
|
+
"# Load pretrained PhaseNet-DAS-Plus (Arcata)\n",
|
|
199
|
+
"ckpt_path = download_model(\"PhaseNet-DAS-Plus-Arcata-v1\", \"phasenet_das_plus_arcata_v1.pth\")\n",
|
|
200
|
+
"ckpt = torch.load(ckpt_path, map_location=\"cpu\", weights_only=False)\n",
|
|
201
|
+
"model_das = phasenet.models.phasenet_das_plus.build_model(**ckpt.get(\"model_config\", {}))\n",
|
|
202
|
+
"\n",
|
|
203
|
+
"# Use EMA (Exponential Moving Average) weights for better generalization\n",
|
|
204
|
+
"if \"model_ema_weights\" in ckpt:\n",
|
|
205
|
+
" model_das.load_state_dict(ckpt[\"model_ema_weights\"])\n",
|
|
206
|
+
"else:\n",
|
|
207
|
+
" model_das.load_state_dict(ckpt[\"model\"])\n",
|
|
208
|
+
"\n",
|
|
209
|
+
"model_das.to(DEVICE).eval()\n",
|
|
210
|
+
"print(\"PhaseNet-DAS-Plus loaded\")"
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"cell_type": "code",
|
|
215
|
+
"source": "# Run inference\n# PhaseNet-DAS uses a larger window: (2048 channels, 4096 samples)\nNX_DAS, NT_DAS = 2048, 4096\n\nwith torch.no_grad():\n result = predict_2d(\n model_das, waveform_1ch, NX_DAS, NT_DAS,\n min_t_overlap=0.1, device=DEVICE,\n taper_nx=True, taper_nt=True, # DAS model: taper both axes\n )\n\n# PhaseNet-DAS+ returns (scores, event_detections)\nif isinstance(result, tuple):\n scores_das, event_detections = result\nelse:\n scores_das = result\n event_detections = None\n\nprint(f\"Phase scores shape: {scores_das.shape}\")",
|
|
216
|
+
"metadata": {},
|
|
217
|
+
"execution_count": null,
|
|
218
|
+
"outputs": []
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"cell_type": "code",
|
|
222
|
+
"execution_count": null,
|
|
223
|
+
"metadata": {},
|
|
224
|
+
"outputs": [],
|
|
225
|
+
"source": [
|
|
226
|
+
"# Detect picks\n",
|
|
227
|
+
"picks_das = detect_picks(\n",
|
|
228
|
+
" scores_das, channel_ids, EVENT_ID, begin_time,\n",
|
|
229
|
+
" dt_s=dt_s, vmin=MIN_PROB, device=DEVICE,\n",
|
|
230
|
+
")\n",
|
|
231
|
+
"\n",
|
|
232
|
+
"df_das = pd.DataFrame(picks_das)\n",
|
|
233
|
+
"n_p = (df_das[\"phase_type\"] == \"P\").sum()\n",
|
|
234
|
+
"n_s = (df_das[\"phase_type\"] == \"S\").sum()\n",
|
|
235
|
+
"print(f\"PhaseNet-DAS picks: {len(df_das)} total ({n_p} P, {n_s} S)\")"
|
|
236
|
+
]
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"cell_type": "code",
|
|
240
|
+
"execution_count": null,
|
|
241
|
+
"metadata": {},
|
|
242
|
+
"outputs": [],
|
|
243
|
+
"source": "# Side-by-side comparison\nfig, axes = plt.subplots(1, 2, figsize=(20, 8))\n\nfor ax, df, title in zip(axes, [df_pn, df_das], [\"PhaseNet\", \"PhaseNet-DAS+\"]):\n plot_das(ax, data, nx, nt, dt_s, cmap=\"seismic\", vmin=-vmax, vmax=vmax)\n plot_picks(ax, df, dt_s)\n ax.set_xlabel(\"Time (s)\")\n ax.set_ylabel(\"Channel index\")\n ax.set_title(f\"{title} — {len(df)} picks\")\n ax.legend(loc=\"upper right\", markerscale=10)\n\nplt.tight_layout()\nplt.show()"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"cell_type": "markdown",
|
|
247
|
+
"source": "## 4. Compare at Scale\n\nWe have pre-computed picks for all 2,470 Arcata events using both models. Let's compare on specific examples.",
|
|
248
|
+
"metadata": {}
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"cell_type": "code",
|
|
252
|
+
"metadata": {},
|
|
253
|
+
"source": "# Visualize a weak event: PhaseNet fails, PhaseNet-DAS+ succeeds\nWEAK_EVENT = \"20230119T202515Z\"\n\nh5_path = hf_hub_download(\n HF_REPO, f\"arcata/data/{WEAK_EVENT}.h5\", repo_type=\"dataset\",\n local_dir=\"data/quakeflow_das\",\n)\nwith h5py.File(h5_path, \"r\") as fp:\n weak_data = fp[\"data\"][:]\n\npn_path = hf_hub_download(\n HF_REPO, f\"arcata/phasenet/picks/{WEAK_EVENT}.parquet\",\n repo_type=\"dataset\", local_dir=\"data/quakeflow_das\",\n)\ndas_path = hf_hub_download(\n HF_REPO, f\"arcata/phasenet_das/picks/{WEAK_EVENT}.parquet\",\n repo_type=\"dataset\", local_dir=\"data/quakeflow_das\",\n)\n\npn_weak = pd.read_parquet(pn_path)\ndas_weak = pd.read_parquet(das_path)\n\nfig, axes = plt.subplots(1, 2, figsize=(20, 8))\nvmax_w = 2 * np.percentile(np.abs(weak_data), 95)\nnx_w, nt_w = weak_data.shape\n\nfor ax, df, title in zip(axes, [pn_weak, das_weak], [\"PhaseNet\", \"PhaseNet-DAS+\"]):\n plot_das(ax, weak_data, nx_w, nt_w, dt_s, cmap=\"seismic\", vmin=-vmax_w, vmax=vmax_w)\n\n assoc = df[df[\"origin_id\"] >= 0]\n unassoc = df[df[\"origin_id\"] < 0]\n\n if len(unassoc) > 0:\n ax.scatter(unassoc[\"phase_index\"] * dt_s, unassoc[\"channel_index\"].astype(int),\n s=0.3, c=\"gray\", alpha=0.1, label=f\"Unassociated ({len(unassoc)})\")\n if len(assoc) > 0:\n for phase, color in [(\"P\", \"red\"), (\"S\", \"blue\")]:\n a = assoc[assoc[\"phase_type\"] == phase]\n ax.scatter(a[\"phase_index\"] * dt_s, a[\"channel_index\"].astype(int),\n s=1, c=color, alpha=0.5, label=f\"{phase} assoc ({len(a)})\")\n\n ax.set_xlabel(\"Time (s)\")\n ax.set_ylabel(\"Channel index\")\n ax.set_title(f\"{title} — {WEAK_EVENT}\")\n ax.legend(loc=\"upper right\", markerscale=10)\n\nplt.tight_layout()\nplt.show()",
|
|
254
|
+
"outputs": [],
|
|
255
|
+
"execution_count": null
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"cell_type": "code",
|
|
259
|
+
"execution_count": null,
|
|
260
|
+
"metadata": {},
|
|
261
|
+
"outputs": [],
|
|
262
|
+
"source": "# Noise-only event: PhaseNet hallucinates picks, DAS+ correctly rejects\nNOISE_EVENT = \"20230103T220320Z\"\n\nh5_path = hf_hub_download(\n HF_REPO, f\"arcata/data/{NOISE_EVENT}.h5\", repo_type=\"dataset\",\n local_dir=\"data/quakeflow_das\",\n)\nwith h5py.File(h5_path, \"r\") as fp:\n noise_data = fp[\"data\"][:]\n\npn_noise = pd.read_parquet(hf_hub_download(\n HF_REPO, f\"arcata/phasenet/picks/{NOISE_EVENT}.parquet\",\n repo_type=\"dataset\", local_dir=\"data/quakeflow_das\",\n))\n\nfig, axes = plt.subplots(1, 2, figsize=(20, 8))\nvmax_n = 2 * np.percentile(np.abs(noise_data), 95)\nnx_n, nt_n = noise_data.shape\n\nax = axes[0]\nplot_das(ax, noise_data, nx_n, nt_n, dt_s, cmap=\"seismic\", vmin=-vmax_n, vmax=vmax_n)\nax.scatter(pn_noise[\"phase_index\"] * dt_s, pn_noise[\"channel_index\"].astype(int),\n s=0.3, c=\"orange\", alpha=0.1)\nax.set_title(f\"PhaseNet — {len(pn_noise)} false picks\")\nax.set_xlabel(\"Time (s)\")\nax.set_ylabel(\"Channel index\")\n\nax = axes[1]\nplot_das(ax, noise_data, nx_n, nt_n, dt_s, cmap=\"seismic\", vmin=-vmax_n, vmax=vmax_n)\nax.set_title(\"PhaseNet-DAS+ — 0 picks (correct)\")\nax.set_xlabel(\"Time (s)\")\nax.set_ylabel(\"Channel index\")\n\nplt.tight_layout()\nplt.show()"
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"cell_type": "markdown",
|
|
266
|
+
"execution_count": null,
|
|
267
|
+
"metadata": {},
|
|
268
|
+
"outputs": [],
|
|
269
|
+
"source": "## Summary\n\n| | PhaseNet (seismic) | PhaseNet-DAS+ |\n|---|---|---|\n| **Input** | 3-component (zero-padded DAS) | Native 1-component DAS |\n| **Architecture** | Independent per-channel 1D | Spatiotemporal 2D convolutions |\n| **Strong events** | Detects P/S arrivals | Detects P/S + event heads |\n| **Weak events** | Misses most arrivals | Detects using spatial coherence |\n| **Noise** | Many false picks | Correctly rejects noise |\n\nBrowse all 2,467 prediction figures: [Google Drive](https://drive.google.com/drive/folders/1natqPKvg0q07AxLWFOSEpTEcE1OTzt2h?usp=sharing)\n\n**Next:** [Lab 2b](lab2b_pick_association.ipynb) explores how raw picks are grouped into seismic events using P-S pairing and origin time estimation."
|
|
270
|
+
}
|
|
271
|
+
],
|
|
272
|
+
"metadata": {
|
|
273
|
+
"kernelspec": {
|
|
274
|
+
"display_name": "Python 3",
|
|
275
|
+
"language": "python",
|
|
276
|
+
"name": "python3"
|
|
277
|
+
},
|
|
278
|
+
"language_info": {
|
|
279
|
+
"name": "python",
|
|
280
|
+
"version": "3.10.0"
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
"nbformat": 4,
|
|
284
|
+
"nbformat_minor": 4
|
|
285
|
+
}
|