Coverage for /usr/local/lib/python3.11/dist-packages/grond/targets/waveform_phase_ratio/measure.py: 22%
156 statements
« prev ^ index » next coverage.py v6.5.0, created at 2025-04-03 09:31 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2025-04-03 09:31 +0000
1# https://pyrocko.org/grond - GPLv3
2#
3# The Grond Developers, 21st Century
4import numpy as num
5from pyrocko import gf, trace
6from pyrocko.guts import Object, Float, StringChoice, List, String
7from pyrocko.gui import marker
8from grond.meta import store_t
11guts_prefix = 'grond'
14response_wwssn_lp = trace.PoleZeroResponse(
15 poles=[
16 -0.2513 + 0.3351J,
17 -0.2513 - 0.3351J,
18 -0.0628 + 0.0304J,
19 -0.0628 - 0.0304J],
20 zeros=[0., 0., 0.],
21 constant=383.6)
24response_wwssn_lp_2 = trace.PoleZeroResponse(
25 poles=[
26 -0.40180 + 0.08559J,
27 -0.40180 - 0.08559J,
28 -0.04841,
29 -0.08816],
30 zeros=[0., 0., 0.])
32response_wa = trace.PoleZeroResponse(
33 poles=[
34 -5.49779 - 5.60886J,
35 -5.49779 + 5.60886J],
36 zeros=[0., 0.])
39class NamedResponse(StringChoice):
40 choices = [
41 'wood-anderson',
42 'wwssn-lp']
44 map = {
45 'wood-anderson': response_wa,
46 'wwssn-lp': response_wwssn_lp}
49class BruneResponse(trace.FrequencyResponse):
51 duration = Float.T()
53 def evaluate(self, freqs):
54 return 1.0 / (1.0 + (freqs*self.duration)**2)
56# from pyrocko.plot import response as pr
57# pr.plot([response_wwssn_lp, response_wwssn_lp_2, response_wa])
60class FeatureMethod(StringChoice):
61 choices = [
62 'peak_component',
63 'peak_to_peak_component',
64 'peak_absolute_vector',
65 'spectral_average']
68class WaveformQuantity(StringChoice):
69 choices = [
70 'displacement',
71 'velocity',
72 'acceleration']
75class FeatureMeasurementFailed(Exception):
76 pass
79class FeatureMeasure(Object):
80 name = String.T()
81 timing_tmin = gf.Timing.T(default='vel:8')
82 timing_tmax = gf.Timing.T(default='vel:2')
83 fmin = Float.T(optional=True)
84 fmax = Float.T(optional=True)
85 response = trace.FrequencyResponse.T(optional=True)
86 named_response = NamedResponse.T(optional=True)
87 channels = List.T(String.T())
88 quantity = WaveformQuantity.T(default='displacement')
89 method = FeatureMethod.T(default='peak_component')
91 def get_nmodelling_targets(self):
92 return len(self.channels)
94 def get_modelling_targets(
95 self, codes, lat, lon, depth, store_id, backazimuth):
97 mtargets = []
98 for channel in self.channels:
99 target = gf.Target(
100 quantity='displacement',
101 codes=codes + (channel, ),
102 lat=lat,
103 lon=lon,
104 depth=depth,
105 store_id=store_id)
107 if channel == 'R':
108 target.azimuth = backazimuth - 180.
109 target.dip = 0.
110 elif channel == 'T':
111 target.azimuth = backazimuth - 90.
112 target.dip = 0.
113 elif channel == 'Z':
114 target.azimuth = 0.
115 target.dip = -90.
117 mtargets.append(target)
119 return mtargets
121 def evaluate(
122 self, engine, source, targets,
123 dataset=None,
124 trs=None,
125 extra_responses=[],
126 debug=False):
128 from ..waveform import target as base
130 trs_processed = []
131 trs_orig = []
132 for itarget, target in enumerate(targets):
133 if target.codes[-1] not in self.channels:
134 continue
136 store = engine.get_store(target.store_id)
138 tmin = source.time + store_t(
139 store, self.timing_tmin, source, target)
140 tmax = source.time + store_t(
141 store, self.timing_tmax, source, target)
143 if self.fmin is not None and self.fmax is not None:
144 freqlimits = [
145 self.fmin/2.,
146 self.fmin,
147 self.fmax,
148 self.fmax*2.]
149 tfade = 1./self.fmin
151 else:
152 freqlimits = None
153 tfade = 0.0
155 if dataset is not None:
156 bazi = base.backazimuth_for_waveform(
157 target.azimuth, target.codes)
159 tr = dataset.get_waveform(
160 target.codes,
161 tinc_cache=1.0/self.fmin,
162 quantity=self.quantity,
163 tmin=tmin,
164 tmax=tmax,
165 freqlimits=freqlimits,
166 tfade=tfade,
167 deltat=store.config.deltat,
168 cache=True,
169 backazimuth=bazi)
171 else:
172 tr = trs[itarget]
174 tr.extend(
175 tmin - tfade,
176 tmax + tfade,
177 fillmethod='repeat')
179 tr = tr.transfer(
180 freqlimits=freqlimits,
181 tfade=tfade)
183 trs_orig.append(tr)
185 tr = tr.copy()
187 responses = []
188 responses.extend(extra_responses)
190 ndiff = \
191 WaveformQuantity.choices.index(self.quantity) - \
192 WaveformQuantity.choices.index(target.quantity)
194 if ndiff > 0:
195 responses.append(trace.DifferentiationResponse(ndiff))
197 if ndiff < 0:
198 responses.append(trace.IntegrationResponse(-ndiff))
200 if self.response:
201 responses.append(self.response)
203 if self.named_response:
204 responses.append(
205 NamedResponse.map[self.named_response])
207 if responses:
208 trans = trace.MultiplyResponse(responses)
209 try:
210 tr = tr.transfer(transfer_function=trans)
212 except trace.TraceTooShort:
213 raise FeatureMeasurementFailed(
214 'transfer: trace too short')
216 if tmin is None or tmax is None:
217 raise FeatureMeasurementFailed(
218 'timing determination failed (phase unavailable?)')
220 tr.chop(tmin, tmax)
222 tr.set_location(tr.location + '-' + self.name + '-proc')
223 trs_processed.append(tr)
225 markers = []
226 marker_candidates = []
227 if self.method in ['peak_component', 'peak_to_peak_component']:
228 component_amp_maxs = []
229 for tr in trs_processed:
230 y = tr.get_ydata()
231 if self.method == 'peak_component':
232 yabs = num.abs(y)
233 i_at_amax = num.argmax(yabs)
234 amax = yabs[i_at_amax]
235 if debug:
236 t_at_amax = tr.tmin + i_at_amax * tr.deltat
237 mark = marker.Marker(
238 [tr.nslc_id],
239 t_at_amax,
240 t_at_amax,
241 0)
243 marker_candidates.append(mark)
245 component_amp_maxs.append(amax)
246 else:
247 i_at_amax = num.argmax(y)
248 i_at_amin = num.argmin(y)
249 amax = y[i_at_amax]
250 amin = y[i_at_amin]
251 if debug:
252 t_at_amax = tr.tmin + i_at_amax * tr.deltat
253 t_at_amin = tr.tmin + i_at_amin * tr.deltat
254 ts = sorted([t_at_amax, t_at_amin])
255 mark = marker.Marker(
256 [tr.nslc_id],
257 ts[0],
258 ts[1],
259 0)
261 marker_candidates.append(mark)
263 component_amp_maxs.append(amax - amin)
265 i_at_amax = num.argmax(component_amp_maxs)
266 if debug:
267 markers.append(marker_candidates[i_at_amax])
268 amp_max = component_amp_maxs[i_at_amax]
270 elif self.method == 'peak_absolute_vector':
271 trsum = None
272 for tr in trs_processed:
273 tr.set_ydata(tr.get_ydata()**2)
274 if trsum is None:
275 trsum = tr
276 else:
277 trsum.add(tr)
279 trsum.set_ydata(num.sqrt(tr.get_ydata))
280 trsum.set_codes(channel='SUM')
282 yabs = trsum.get_ydata()
284 i_at_amax = num.argmax(yabs)
285 amax = yabs[i_at_amax]
286 t_at_amax = tr.tmin + i_at_amax * tr.deltat
287 amp_max = amax
289 if debug:
290 markers.append(marker.Marker(
291 [trsum.nslc_id],
292 t_at_amax,
293 t_at_amax,
294 0))
296 trs_processed.append(trsum)
298 elif self.method == 'spectral_average':
299 component_amp_maxs = []
300 for tr in trs_processed:
301 freqs, values = tr.spectrum()
302 component_amp_maxs.append(num.mean(num.abs(values[
303 num.logical_and(self.fmin <= freqs, freqs <= self.fmax)])))
305 amp_max = num.mean(component_amp_maxs)
307 if debug:
308 trs_out = []
309 for tr in trs_orig:
310 tr_out = tr.copy()
311 tr_out.set_location(tr_out.location + '-' + self.name)
312 trs_out.append(tr_out)
314 return amp_max, (trs_out + trs_processed, markers)
316 return amp_max, None