eqcctpro 0.6.2__py3-none-any.whl → 0.7.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.
@@ -0,0 +1,279 @@
1
+ import obspy
2
+ import numpy as np
3
+ import seisbench.models as sbm
4
+ from pathlib import Path
5
+
6
+ class SeisBenchModels:
7
+ def __init__(self, parent_model_name, child_model_name):
8
+ self.models = {}
9
+ self.parent_model_list = ['PhaseNet', 'PhaseNetLight', 'EQTransformer', 'CRED', 'GPD', 'LFEDetect', 'OBSTransformer'] # List of available models from SeisBench
10
+
11
+ # Check if parent model is valid
12
+ if parent_model_name not in self.parent_model_list:
13
+ raise ValueError(
14
+ f"Parent model {parent_model_name} not found in SeisBench. "
15
+ f"Please choose from {self.parent_model_list}"
16
+ )
17
+ self.parent_model_name = parent_model_name
18
+
19
+ # Check if child model is valid - use getattr to dynamically access the model class
20
+ try:
21
+ model_class = getattr(sbm, self.parent_model_name)
22
+ available_models = model_class.list_pretrained()
23
+ if child_model_name not in available_models:
24
+ raise ValueError(
25
+ f"Child model {child_model_name} not found in {parent_model_name}. "
26
+ f"Please choose from {available_models}"
27
+ )
28
+ except AttributeError:
29
+ raise ValueError(
30
+ f"Model class {self.parent_model_name} not found in seisbench.models. "
31
+ f"Please check the model name."
32
+ )
33
+ self.child_model_name = child_model_name
34
+ self.model = None # Will be loaded in load_model()
35
+
36
+ def load_model(self):
37
+ """
38
+ Load the SeisBench model given the parent model name and its 'child' model subversion name.
39
+ This follows the workflow from integration_phasenet.ipynb where models are loaded with from_pretrained().
40
+ """
41
+ if self.model is None:
42
+ model_class = getattr(sbm, self.parent_model_name)
43
+ self.model = model_class.from_pretrained(self.child_model_name)
44
+ return self.model
45
+
46
+ def annotate(self, stream, **kwargs):
47
+ """
48
+ Annotate a stream with phase probabilities (probability time series).
49
+ This is the primary method used in integration_phasenet.ipynb.
50
+
51
+ Parameters:
52
+ -----------
53
+ stream : obspy.Stream
54
+ Input 3-component ObsPy Stream
55
+ **kwargs : dict
56
+ Additional arguments passed to model.annotate() (e.g., strict, overlap, stacking, etc.)
57
+
58
+ Returns:
59
+ --------
60
+ obspy.Stream
61
+ Stream with phase probability traces
62
+ """
63
+ if self.model is None:
64
+ raise ValueError("Model not loaded. Call load_model() first.")
65
+ return self.model.annotate(stream, **kwargs)
66
+
67
+ def classify(self, stream, **kwargs):
68
+ """
69
+ Classify a stream and return picks directly.
70
+ This method returns picks as a ClassifyOutput object.
71
+
72
+ Parameters:
73
+ -----------
74
+ stream : obspy.Stream
75
+ Input 3-component ObsPy Stream
76
+ **kwargs : dict
77
+ Additional arguments passed to model.classify()
78
+
79
+ Returns:
80
+ --------
81
+ ClassifyOutput
82
+ Object containing picks and metadata
83
+ """
84
+ if self.model is None:
85
+ raise ValueError("Model not loaded. Call load_model() first.")
86
+ return self.model.classify(stream, **kwargs)
87
+
88
+ def predict(self, data):
89
+ """
90
+ Generic predict method for models that support it.
91
+
92
+ Parameters:
93
+ -----------
94
+ data : obspy.Stream or numpy array
95
+ Input data for prediction
96
+
97
+ Returns:
98
+ --------
99
+ Model predictions (format depends on model type)
100
+ """
101
+ if self.model is None:
102
+ raise ValueError("Model not loaded. Call load_model() first.")
103
+
104
+ # Try predict method if available, otherwise fall back to annotate
105
+ if hasattr(self.model, 'predict'):
106
+ return self.model.predict(data)
107
+ elif hasattr(self.model, 'annotate'):
108
+ return self.model.annotate(data)
109
+ else:
110
+ raise AttributeError(
111
+ f"Model {self.parent_model_name} does not have predict() or annotate() methods."
112
+ )
113
+
114
+
115
+ def resampling(st):
116
+ """
117
+ Perform resampling on ObsPy stream objects.
118
+ Fallback resampling method when interpolate() fails.
119
+
120
+ Parameters:
121
+ -----------
122
+ st : obspy.Stream
123
+ Input ObsPy Stream to resample
124
+
125
+ Returns:
126
+ --------
127
+ obspy.Stream
128
+ Resampled stream at 100 Hz
129
+ """
130
+ need_resampling = [tr for tr in st if tr.stats.sampling_rate != 100.0]
131
+ if len(need_resampling) > 0:
132
+ for indx, tr in enumerate(need_resampling):
133
+ if tr.stats.delta < 0.01:
134
+ tr.filter('lowpass', freq=45, zerophase=True)
135
+ tr.resample(100)
136
+ tr.stats.sampling_rate = 100
137
+ tr.stats.delta = 0.01
138
+ tr.data.dtype = 'int32'
139
+ st.remove(tr)
140
+ st.append(tr)
141
+ return st
142
+
143
+
144
+ def mseed2stream_3c(args, files_list, station):
145
+ """
146
+ Read miniSEED files and return a single 3-component ObsPy Stream
147
+ (E/N/Z preferred, otherwise 1/2/Z), aligned in time, filtered, resampled.
148
+
149
+ This function follows the preprocessing workflow from integration_phasenet.ipynb:
150
+ 1. Read and merge mSEED files
151
+ 2. Detrend (demean)
152
+ 3. Apply cosine taper (~5 seconds)
153
+ 4. Apply bandpass filter (1-45 Hz, or station-specific)
154
+ 5. Resample to 100 Hz
155
+ 6. Trim to intersection (common time window, no padding)
156
+ 7. Select best 3 components (E/N/Z or 1/2/Z)
157
+
158
+ Parameters:
159
+ -----------
160
+ args : dict
161
+ Dictionary containing optional 'stations_filters' key with pandas DataFrame
162
+ containing station-specific filter parameters (columns: 'sta', 'hp', 'lp')
163
+ files_list : list
164
+ List of file paths (str or Path) to mSEED files for the station
165
+ station : str
166
+ Station code/name for filtering purposes
167
+
168
+ Returns:
169
+ --------
170
+ tuple : (obspy.Stream, float, float) or None
171
+ Returns (stream, freqmin, freqmax) if successful, None if no data or missing components
172
+ - stream: 3-component ObsPy Stream with channels renamed to *E, *N, *Z
173
+ - freqmin: Minimum frequency used in bandpass filter (Hz)
174
+ - freqmax: Maximum frequency used in bandpass filter (Hz)
175
+
176
+ Raises:
177
+ ------
178
+ ValueError
179
+ If files_list is empty or no valid data is found
180
+ """
181
+ # Check if files_list is empty
182
+ if not files_list or len(files_list) == 0:
183
+ raise ValueError(
184
+ f"No files found for station {station}. "
185
+ f"Please check that the file paths are correct."
186
+ )
187
+
188
+ st = obspy.Stream()
189
+ files_read = 0
190
+
191
+ # --- 1) Read all input files into one stream ---
192
+ for file in files_list:
193
+ try:
194
+ temp_st = obspy.read(str(file)) # Convert Path to string if needed
195
+ temp_st.merge(method=1, fill_value=0) # merge fragments, fill gaps with zeros
196
+ temp_st.detrend("demean")
197
+ if len(temp_st) > 0:
198
+ st += temp_st
199
+ files_read += 1
200
+ except Exception as e:
201
+ # Continue to next file if one fails
202
+ continue
203
+
204
+ if len(st) == 0:
205
+ raise ValueError(
206
+ f"No valid data found for station {station}. "
207
+ f"Attempted to read {len(files_list)} file(s), successfully read {files_read}. "
208
+ f"Please check that the mSEED files are valid and contain data."
209
+ )
210
+
211
+ # --- 2) Taper ---
212
+ max_percentage = 5 / (st[0].stats.delta * st[0].stats.npts) # taper ~5 sec worth
213
+ st.taper(max_percentage=max_percentage, type="cosine")
214
+
215
+ # --- 3) Bandpass (station-specific if provided) ---
216
+ freqmin, freqmax = 1.0, 45.0
217
+ if args.get("stations_filters") is not None:
218
+ try:
219
+ df_filters = args["stations_filters"]
220
+ row = df_filters[df_filters.sta == station].iloc[0]
221
+ freqmin, freqmax = float(row["hp"]), float(row["lp"])
222
+ except Exception:
223
+ pass
224
+
225
+ st.filter("bandpass", freqmin=freqmin, freqmax=freqmax, corners=2, zerophase=True)
226
+
227
+ # --- 4) Resample to 100 Hz if needed ---
228
+ if any(tr.stats.sampling_rate != 100.0 for tr in st):
229
+ try:
230
+ st.interpolate(100.0, method="linear")
231
+ except Exception:
232
+ st = resampling(st) # fallback
233
+
234
+ # --- 5) Force all traces to the SAME time window (intersection) ---
235
+ # This is the correct choice for "combine 3 streams into one waveform"
236
+ t0 = max(tr.stats.starttime for tr in st)
237
+ t1 = min(tr.stats.endtime for tr in st)
238
+ st.trim(t0, t1, pad=False)
239
+
240
+ # --- 6) Pick the best 3 components: prefer E/N/Z, fallback to 1/2/Z ---
241
+ by_last = {}
242
+ for tr in st:
243
+ by_last.setdefault(tr.stats.channel[-1], []).append(tr)
244
+
245
+ def _best_trace(letter):
246
+ """Choose the first trace for a given last-letter component."""
247
+ lst = by_last.get(letter, [])
248
+ return lst[0] if lst else None
249
+
250
+ trE = _best_trace("E") or _best_trace("1")
251
+ trN = _best_trace("N") or _best_trace("2")
252
+ trZ = _best_trace("Z")
253
+
254
+ # Check which components are missing and provide helpful error message
255
+ missing_components = []
256
+ if trZ is None:
257
+ missing_components.append("Z")
258
+ if trE is None:
259
+ missing_components.append("E (or 1)")
260
+ if trN is None:
261
+ missing_components.append("N (or 2)")
262
+
263
+ if missing_components:
264
+ available_channels = [tr.stats.channel for tr in st]
265
+ raise ValueError(
266
+ f"Missing required components for station {station}: {', '.join(missing_components)}. "
267
+ f"Available channels: {available_channels}. "
268
+ f"Please ensure the mSEED files contain 3-component data (E/N/Z or 1/2/Z)."
269
+ )
270
+
271
+ out = obspy.Stream(traces=[trE.copy(), trN.copy(), trZ.copy()])
272
+
273
+ # Optional: normalize naming so your plotting labels look nice
274
+ # (This does NOT rotate; it only renames.)
275
+ out[0].stats.channel = out[0].stats.channel[:-1] + "E"
276
+ out[1].stats.channel = out[1].stats.channel[:-1] + "N"
277
+ out[2].stats.channel = out[2].stats.channel[:-1] + "Z"
278
+
279
+ return out, freqmin, freqmax