Coverage for /usr/local/lib/python3.11/dist-packages/grond/targets/waveform/target.py: 78%
409 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-26 16:25 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-26 16:25 +0000
1from __future__ import print_function
3import logging
4import math
5import numpy as num
7from pyrocko import gf, trace, weeding, util
8from pyrocko.guts import (Object, String, Float, Bool, Int, StringChoice,
9 Timestamp, List, Dict)
10from pyrocko.guts_array import Array
12from grond.dataset import NotFound
13from grond.meta import GrondError, store_t, nslcs_to_patterns
15from ..base import (MisfitConfig, MisfitTarget, MisfitResult, TargetGroup)
16from grond.meta import has_get_plot_classes
18from pyrocko import crust2x2
19from string import Template
21guts_prefix = 'grond'
22logger = logging.getLogger('grond.targets.waveform.target')
25class StoreIDSelectorError(GrondError):
26 pass
29class StoreIDSelector(Object):
30 '''
31 Base class for GF store selectors.
33 GF store selectors can be implemented to select different stores, based on
34 station location, source location or other characteristics.
35 '''
37 pass
40class Crust2StoreIDSelector(StoreIDSelector):
41 '''
42 Store ID selector picking CRUST 2.0 model based on event location.
43 '''
45 template = String.T(
46 help="Template for the GF store ID. For example ``'crust2_${id}'`` "
47 "where ``'${id}'`` will be replaced with the corresponding CRUST "
48 "2.0 profile identifier for the source location.")
50 def get_store_id(self, event, st, cha):
51 s = Template(self.template)
52 return s.substitute(id=(
53 crust2x2.get_profile(event.lat, event.lon)._ident).lower())
56class StationDictStoreIDSelector(StoreIDSelector):
57 '''
58 Store ID selector using a manual station to store ID mapping.
59 '''
61 mapping = Dict.T(
62 String.T(), gf.StringID.T(),
63 help='Dictionary with station to store ID pairs, keys are NET.STA. '
64 "Add a fallback store ID under the key ``'others'``.")
66 def get_store_id(self, event, st, cha):
67 try:
68 store_id = self.mapping['%s.%s' % (st.network, st.station)]
69 except KeyError:
70 try:
71 store_id = self.mapping['others']
72 except KeyError:
73 raise StoreIDSelectorError(
74 'No store ID found for station "%s.%s".' % (
75 st.network, st.station))
77 return store_id
80class DepthRangeToStoreID(Object):
81 depth_min = Float.T()
82 depth_max = Float.T()
83 store_id = gf.StringID.T()
86class StationDepthStoreIDSelector(StoreIDSelector):
87 '''
88 Store ID selector using a mapping from station depth range to store ID.
89 '''
91 depth_ranges = List.T(DepthRangeToStoreID.T())
93 def get_store_id(self, event, st, cha):
94 for r in self.depth_ranges:
95 if r.depth_min <= st.depth < r.depth_max:
96 return r.store_id
98 raise StoreIDSelectorError(
99 'No store ID found for station "%s.%s" at %g m depth.' % (
100 st.network, st.station, st.depth))
103class DomainChoice(StringChoice):
104 choices = [
105 'time_domain',
106 'frequency_domain',
107 'log_frequency_domain',
108 'envelope',
109 'absolute',
110 'cc_max_norm']
113class WaveformMisfitConfig(MisfitConfig):
114 quantity = gf.QuantityType.T(default='displacement')
115 fmin = Float.T(default=0.0, help='minimum frequency of bandpass filter')
116 fmax = Float.T(help='maximum frequency of bandpass filter')
117 ffactor = Float.T(default=1.5)
118 tmin = gf.Timing.T(
119 optional=True,
120 help='Start of main time window used for waveform fitting.')
121 tmax = gf.Timing.T(
122 optional=True,
123 help='End of main time window used for waveform fitting.')
124 tfade = Float.T(
125 optional=True,
126 help='Decay time of taper prepended and appended to main time window '
127 'used for waveform fitting [s].')
128 pick_synthetic_traveltime = gf.Timing.T(
129 optional=True,
130 help='Synthetic phase arrival definition for alignment of observed '
131 'and synthetic traces.')
132 pick_phasename = String.T(
133 optional=True,
134 help='Name of picked phase for alignment of observed and synthetic '
135 'traces.')
136 domain = DomainChoice.T(
137 default='time_domain',
138 help='Type of data characteristic to be fitted.\n\nAvailable choices '
139 'are: %s' % ', '.join("``'%s'``" % s
140 for s in DomainChoice.choices))
141 norm_exponent = Int.T(
142 default=2,
143 help='Exponent to use in norm (1: L1-norm, 2: L2-norm)')
144 tautoshift_max = Float.T(
145 default=0.0,
146 help='If non-zero, allow synthetic and observed traces to be shifted '
147 'against each other by up to +/- the given value [s].')
148 autoshift_penalty_max = Float.T(
149 default=0.0,
150 help='If non-zero, a penalty misfit is added for non-zero shift '
151 'values.\n\nThe penalty value is computed as '
152 '``autoshift_penalty_max * normalization_factor * tautoshift**2 '
153 '/ tautoshift_max**2``')
155 ranges = {}
157 def get_full_frequency_range(self):
158 return self.fmin / self.ffactor, self.fmax * self.ffactor
161def log_exclude(target, reason):
162 logger.debug('Excluding potential target %s: %s' % (
163 target.string_id(), reason))
166class WaveformTargetGroup(TargetGroup):
167 '''Handles seismogram targets or other targets of dynamic ground motion.
168 '''
169 distance_min = Float.T(
170 optional=True,
171 help='excludes targets nearer to source, along a great circle')
172 distance_max = Float.T(
173 optional=True,
174 help='excludes targets farther from source, along a great circle')
175 distance_3d_min = Float.T(
176 optional=True,
177 help='excludes targets nearer from source (direct distance)')
178 distance_3d_max = Float.T(
179 optional=True,
180 help='excludes targets farther from source (direct distance)')
181 depth_min = Float.T(
182 optional=True,
183 help='excludes targets with smaller depths')
184 depth_max = Float.T(
185 optional=True,
186 help='excludes targets with larger depths')
187 include = List.T(
188 String.T(),
189 optional=True,
190 help='If not None, list of stations/components to include according '
191 'to their STA, NET.STA, NET.STA.LOC, or NET.STA.LOC.CHA codes.')
192 exclude = List.T(
193 String.T(),
194 help='Stations/components to be excluded according to their STA, '
195 'NET.STA, NET.STA.LOC, or NET.STA.LOC.CHA codes.')
196 limit = Int.T(optional=True)
197 channels = List.T(
198 String.T(),
199 optional=True,
200 help="set channels to include, e.g. ['Z', 'T']")
201 misfit_config = WaveformMisfitConfig.T()
202 store_id_selector = StoreIDSelector.T(
203 optional=True,
204 help='select GF store based on event-station geometry.')
206 def get_targets(self, ds, event, default_path='none'):
207 logger.debug('Selecting waveform targets...')
208 origin = event
209 targets = []
211 stations = ds.get_stations()
212 if len(stations) == 0:
213 logger.warning(
214 'No stations found to create waveform target group.')
216 for st in ds.get_stations():
217 logger.debug('Selecting waveforms for station %s.%s.%s' % st.nsl())
218 for cha in self.channels:
219 nslc = st.nsl() + (cha,)
221 logger.debug('Selecting waveforms for %s.%s.%s.%s' % nslc)
223 if self.store_id_selector:
224 store_id = self.store_id_selector.get_store_id(
225 event, st, cha)
226 else:
227 store_id = self.store_id
229 logger.debug('Selecting waveforms for %s.%s.%s.%s' % nslc)
231 target = WaveformMisfitTarget(
232 quantity='displacement',
233 codes=nslc,
234 lat=st.lat,
235 lon=st.lon,
236 north_shift=st.north_shift,
237 east_shift=st.east_shift,
238 depth=st.depth,
239 interpolation=self.interpolation,
240 store_id=store_id,
241 misfit_config=self.misfit_config,
242 manual_weight=self.weight,
243 normalisation_family=self.normalisation_family,
244 path=self.path or default_path)
246 if ds.is_blacklisted(nslc):
247 log_exclude(target, 'excluded by dataset')
248 continue
250 if util.match_nslc(
251 nslcs_to_patterns(self.exclude), nslc):
252 log_exclude(target, 'excluded by target group')
253 continue
255 if self.include is not None and not util.match_nslc(
256 nslcs_to_patterns(self.include), nslc):
257 log_exclude(target, 'excluded by target group')
258 continue
260 if self.distance_min is not None and \
261 target.distance_to(origin) < self.distance_min:
262 log_exclude(target, 'distance < distance_min')
263 continue
265 if self.distance_max is not None and \
266 target.distance_to(origin) > self.distance_max:
267 log_exclude(target, 'distance > distance_max')
268 continue
270 if self.distance_3d_min is not None and \
271 target.distance_3d_to(origin) < self.distance_3d_min:
272 log_exclude(target, 'distance_3d < distance_3d_min')
273 continue
275 if self.distance_3d_max is not None and \
276 target.distance_3d_to(origin) > self.distance_3d_max:
277 log_exclude(target, 'distance_3d > distance_3d_max')
278 continue
280 if self.depth_min is not None and \
281 target.depth < self.depth_min:
282 log_exclude(target, 'depth < depth_min')
283 continue
285 if self.depth_max is not None and \
286 target.depth > self.depth_max:
287 log_exclude(target, 'depth > depth_max')
288 continue
290 azi, _ = target.azibazi_to(origin)
291 if cha == 'R':
292 target.azimuth = azi - 180.
293 target.dip = 0.
294 elif cha == 'T':
295 target.azimuth = azi - 90.
296 target.dip = 0.
297 elif cha == 'Z':
298 target.azimuth = 0.
299 target.dip = -90.
301 target.set_dataset(ds)
302 targets.append(target)
304 if self.limit:
305 return weed(origin, targets, self.limit)[0]
306 else:
307 return targets
310class TraceSpectrum(Object):
311 network = String.T()
312 station = String.T()
313 location = String.T()
314 channel = String.T()
315 deltaf = Float.T(default=1.0)
316 fmin = Float.T(default=0.0)
317 ydata = Array.T(shape=(None,), dtype=num.complex128, serialize_as='list')
319 def get_ydata(self):
320 return self.ydata
322 def get_xdata(self):
323 return self.fmin + num.arange(self.ydata.size) * self.deltaf
326class WaveformPiggybackSubtarget(Object):
327 piggy_id = Int.T()
329 _next_piggy_id = 0
331 @classmethod
332 def new_piggy_id(cls):
333 piggy_id = WaveformPiggybackSubtarget._next_piggy_id
334 WaveformPiggybackSubtarget._next_piggy_id += 1
335 return piggy_id
337 def __init__(self, piggy_id=None, **kwargs):
338 if piggy_id is None:
339 piggy_id = self.new_piggy_id()
341 Object.__init__(self, piggy_id=piggy_id, **kwargs)
343 def evaluate(
344 self, tr_proc_obs, trspec_proc_obs, tr_proc_syn, trspec_proc_syn):
346 raise NotImplementedError()
349class WaveformPiggybackSubresult(Object):
350 piggy_id = Int.T()
353class WaveformMisfitResult(gf.Result, MisfitResult):
354 '''Carries the observations for a target and corresponding synthetics.
356 A number of different waveform or phase representations are possible.
357 '''
358 processed_obs = trace.Trace.T(optional=True)
359 processed_syn = trace.Trace.T(optional=True)
360 filtered_obs = trace.Trace.T(optional=True)
361 filtered_syn = trace.Trace.T(optional=True)
362 spectrum_obs = TraceSpectrum.T(optional=True)
363 spectrum_syn = TraceSpectrum.T(optional=True)
365 taper = trace.Taper.T(optional=True)
366 tobs_shift = Float.T(optional=True)
367 tsyn_pick = Timestamp.T(optional=True)
368 tshift = Float.T(optional=True)
369 cc = trace.Trace.T(optional=True)
371 piggyback_subresults = List.T(WaveformPiggybackSubresult.T())
374@has_get_plot_classes
375class WaveformMisfitTarget(gf.Target, MisfitTarget):
376 flip_norm = Bool.T(default=False)
377 misfit_config = WaveformMisfitConfig.T()
379 can_bootstrap_weights = True
381 def __init__(self, **kwargs):
382 gf.Target.__init__(self, **kwargs)
383 MisfitTarget.__init__(self, **kwargs)
384 self._piggyback_subtargets = []
386 def string_id(self):
387 return '.'.join(x for x in (self.path,) + self.codes)
389 @classmethod
390 def get_plot_classes(cls):
391 from . import plot
392 plots = super(WaveformMisfitTarget, cls).get_plot_classes()
393 plots.extend(plot.get_plot_classes())
394 return plots
396 def get_combined_weight(self):
397 if self._combined_weight is None:
398 w = self.manual_weight
399 for analyser in self.analyser_results.values():
400 w *= analyser.weight
401 self._combined_weight = num.array([w], dtype=float)
402 return self._combined_weight
404 def get_taper_params(self, engine, source):
405 store = engine.get_store(self.store_id)
406 config = self.misfit_config
407 tmin_fit = source.time + store_t(store, config.tmin, source, self)
408 tmax_fit = source.time + store_t(store, config.tmax, source, self)
409 if config.fmin > 0.0:
410 tfade = 1.0/config.fmin
411 else:
412 tfade = 1.0/config.fmax
414 if config.tfade is None:
415 tfade_taper = tfade
416 else:
417 tfade_taper = config.tfade
419 return tmin_fit, tmax_fit, tfade, tfade_taper
421 def get_backazimuth_for_waveform(self):
422 return backazimuth_for_waveform(self.azimuth, self.codes)
424 @property
425 def backazimuth(self):
426 return self.azimuth - 180.
428 def get_freqlimits(self):
429 config = self.misfit_config
431 return (
432 config.fmin/config.ffactor,
433 config.fmin, config.fmax,
434 config.fmax*config.ffactor)
436 def get_pick_shift(self, engine, source):
437 config = self.misfit_config
438 tobs = None
439 tsyn = None
440 ds = self.get_dataset()
442 if config.pick_synthetic_traveltime and config.pick_phasename:
443 store = engine.get_store(self.store_id)
444 tsyn = source.time + store.t(
445 config.pick_synthetic_traveltime, source, self)
447 marker = ds.get_pick(
448 source.name,
449 self.codes[:3],
450 config.pick_phasename)
452 if marker:
453 tobs = marker.tmin
455 return tobs, tsyn
457 def get_cutout_timespan(self, tmin, tmax, tfade):
459 if self.misfit_config.fmin > 0:
460 tinc_obs = 1.0 / self.misfit_config.fmin
461 else:
462 tinc_obs = 10.0 / self.misfit_config.fmax
464 tmin_obs = (math.floor(
465 (tmin - tfade) / tinc_obs) - 1.0) * tinc_obs
466 tmax_obs = (math.ceil(
467 (tmax + tfade) / tinc_obs) + 1.0) * tinc_obs
469 return tmin_obs, tmax_obs
471 def post_process(self, engine, source, tr_syn):
473 tr_syn = tr_syn.pyrocko_trace()
474 nslc = self.codes
476 config = self.misfit_config
478 tmin_fit, tmax_fit, tfade, tfade_taper = \
479 self.get_taper_params(engine, source)
481 ds = self.get_dataset()
483 tobs, tsyn = self.get_pick_shift(engine, source)
484 if None not in (tobs, tsyn):
485 tobs_shift = tobs - tsyn
486 else:
487 tobs_shift = 0.0
489 tr_syn.extend(
490 tmin_fit - tfade * 2.0,
491 tmax_fit + tfade * 2.0,
492 fillmethod='repeat')
494 freqlimits = self.get_freqlimits()
496 if config.quantity == 'displacement':
497 syn_resp = None
498 elif config.quantity == 'velocity':
499 syn_resp = trace.DifferentiationResponse(1)
500 elif config.quantity == 'acceleration':
501 syn_resp = trace.DifferentiationResponse(2)
502 else:
503 GrondError('Unsupported quantity: %s' % config.quantity)
505 tr_syn = tr_syn.transfer(
506 freqlimits=freqlimits,
507 tfade=tfade,
508 transfer_function=syn_resp)
510 tr_syn.chop(tmin_fit - 2*tfade, tmax_fit + 2*tfade)
512 tmin_obs, tmax_obs = self.get_cutout_timespan(
513 tmin_fit+tobs_shift, tmax_fit+tobs_shift, tfade)
515 try:
516 tr_obs = ds.get_waveform(
517 nslc,
518 quantity=config.quantity,
519 tinc_cache=1.0/(config.fmin or 0.1*config.fmax),
520 tmin=tmin_fit+tobs_shift-tfade,
521 tmax=tmax_fit+tobs_shift+tfade,
522 tfade=tfade,
523 freqlimits=freqlimits,
524 deltat=tr_syn.deltat,
525 cache=True,
526 backazimuth=self.get_backazimuth_for_waveform())
528 if tobs_shift != 0.0:
529 tr_obs = tr_obs.copy()
530 tr_obs.shift(-tobs_shift)
532 mr = misfit(
533 tr_obs, tr_syn,
534 taper=trace.CosTaper(
535 tmin_fit - tfade_taper,
536 tmin_fit,
537 tmax_fit,
538 tmax_fit + tfade_taper),
539 domain=config.domain,
540 exponent=config.norm_exponent,
541 flip=self.flip_norm,
542 result_mode=self._result_mode,
543 tautoshift_max=config.tautoshift_max,
544 autoshift_penalty_max=config.autoshift_penalty_max,
545 subtargets=self._piggyback_subtargets)
547 self._piggyback_subtargets = []
549 mr.tobs_shift = float(tobs_shift)
550 mr.tsyn_pick = float_or_none(tsyn)
552 return mr
554 except NotFound as e:
555 logger.debug(str(e))
556 raise gf.SeismosizerError('No waveform data: %s' % str(e))
558 def get_plain_targets(self, engine, source):
559 d = dict(
560 (k, getattr(self, k)) for k in gf.Target.T.propnames)
561 return [gf.Target(**d)]
563 def add_piggyback_subtarget(self, subtarget):
564 self._piggyback_subtargets.append(subtarget)
567def misfit(
568 tr_obs, tr_syn, taper, domain, exponent, tautoshift_max,
569 autoshift_penalty_max, flip, result_mode='sparse', subtargets=[]):
571 '''
572 Calculate misfit between observed and synthetic trace.
574 :param tr_obs: observed trace as :py:class:`pyrocko.trace.Trace`
575 :param tr_syn: synthetic trace as :py:class:`pyrocko.trace.Trace`
576 :param taper: taper applied in timedomain as
577 :py:class:`pyrocko.trace.Taper`
578 :param domain: how to calculate difference, see :py:class:`DomainChoice`
579 :param exponent: exponent of Lx type norms
580 :param tautoshift_max: if non-zero, return lowest misfit when traces are
581 allowed to shift against each other by up to +/- ``tautoshift_max``
582 :param autoshift_penalty_max: if non-zero, a penalty misfit is added for
583 for non-zero shift values. The penalty value is
584 ``autoshift_penalty_max * normalization_factor * \
585tautoshift**2 / tautoshift_max**2``
586 :param flip: ``bool``, if set to ``True``, normalization factor is
587 computed against *tr_syn* rather than *tr_obs*
588 :param result_mode: ``'full'``, include traces and spectra or ``'sparse'``,
589 include only misfit and normalization factor in result
591 :returns: object of type :py:class:`WaveformMisfitResult`
592 '''
594 trace.assert_same_sampling_rate(tr_obs, tr_syn)
595 deltat = tr_obs.deltat
596 tmin, tmax = taper.time_span()
598 tr_proc_obs, trspec_proc_obs = _process(tr_obs, tmin, tmax, taper, domain)
599 tr_proc_syn, trspec_proc_syn = _process(tr_syn, tmin, tmax, taper, domain)
601 piggyback_results = []
602 for subtarget in subtargets:
603 piggyback_results.append(
604 subtarget.evaluate(
605 tr_proc_obs, trspec_proc_obs, tr_proc_syn, trspec_proc_syn))
607 tshift = None
608 ctr = None
609 deltat = tr_proc_obs.deltat
610 if domain in ('time_domain', 'envelope', 'absolute'):
611 a, b = tr_proc_syn.ydata, tr_proc_obs.ydata
612 if flip:
613 b, a = a, b
615 nshift_max = max(0, min(a.size-1,
616 int(math.floor(tautoshift_max / deltat))))
618 if nshift_max == 0:
619 m, n = trace.Lx_norm(a, b, norm=exponent)
620 else:
621 mns = []
622 for ishift in range(-nshift_max, nshift_max+1):
623 if ishift < 0:
624 a_cut = a[-ishift:]
625 b_cut = b[:ishift]
626 elif ishift == 0:
627 a_cut = a
628 b_cut = b
629 elif ishift > 0:
630 a_cut = a[:-ishift]
631 b_cut = b[ishift:]
633 mns.append(trace.Lx_norm(a_cut, b_cut, norm=exponent))
635 ms, ns = num.array(mns).T
637 iarg = num.argmin(ms)
638 tshift = (iarg-nshift_max)*deltat
640 m, n = ms[iarg], ns[iarg]
641 m += autoshift_penalty_max * n * tshift**2 / tautoshift_max**2
643 elif domain == 'cc_max_norm':
645 ctr = trace.correlate(
646 tr_proc_syn,
647 tr_proc_obs,
648 mode='same',
649 normalization='normal')
651 tshift, cc_max = ctr.max()
652 m = 0.5 - 0.5 * cc_max
653 n = 0.5
655 elif domain == 'frequency_domain':
656 a, b = trspec_proc_syn.ydata, trspec_proc_obs.ydata
657 if flip:
658 b, a = a, b
660 m, n = trace.Lx_norm(num.abs(a), num.abs(b), norm=exponent)
662 elif domain == 'log_frequency_domain':
663 a, b = trspec_proc_syn.ydata, trspec_proc_obs.ydata
664 if flip:
665 b, a = a, b
667 a = num.abs(a)
668 b = num.abs(b)
670 eps = (num.mean(a) + num.mean(b)) * 1e-7
671 if eps == 0.0:
672 eps = 1e-7
674 a = num.log(a + eps)
675 b = num.log(b + eps)
677 m, n = trace.Lx_norm(a, b, norm=exponent)
679 if result_mode == 'full':
680 result = WaveformMisfitResult(
681 misfits=num.array([[m, n]], dtype=float),
682 processed_obs=tr_proc_obs,
683 processed_syn=tr_proc_syn,
684 filtered_obs=tr_obs.copy(),
685 filtered_syn=tr_syn,
686 spectrum_obs=trspec_proc_obs,
687 spectrum_syn=trspec_proc_syn,
688 taper=taper,
689 tshift=tshift,
690 cc=ctr)
692 elif result_mode == 'sparse':
693 result = WaveformMisfitResult(
694 misfits=num.array([[m, n]], dtype=float))
695 else:
696 assert False
698 result.piggyback_subresults = piggyback_results
700 return result
703def _extend_extract(tr, tmin, tmax):
704 deltat = tr.deltat
705 itmin_frame = int(math.floor(tmin/deltat))
706 itmax_frame = int(math.ceil(tmax/deltat))
707 nframe = itmax_frame - itmin_frame + 1
708 n = tr.data_len()
709 a = num.empty(nframe, dtype=float)
710 itmin_tr = int(round(tr.tmin / deltat))
711 itmax_tr = itmin_tr + n
712 icut1 = min(max(0, itmin_tr - itmin_frame), nframe)
713 icut2 = min(max(0, itmax_tr - itmin_frame), nframe)
714 icut1_tr = min(max(0, icut1 + itmin_frame - itmin_tr), n)
715 icut2_tr = min(max(0, icut2 + itmin_frame - itmin_tr), n)
716 a[:icut1] = tr.ydata[0]
717 a[icut1:icut2] = tr.ydata[icut1_tr:icut2_tr]
718 a[icut2:] = tr.ydata[-1]
719 tr = tr.copy(data=False)
720 tr.tmin = itmin_frame * deltat
721 tr.set_ydata(a)
722 return tr
725def _process(tr, tmin, tmax, taper, domain):
726 tr_proc = _extend_extract(tr, tmin, tmax)
727 tr_proc.taper(taper)
729 df = None
730 trspec_proc = None
732 if domain == 'envelope':
733 tr_proc = tr_proc.envelope(inplace=False)
734 tr_proc.set_ydata(num.abs(tr_proc.get_ydata()))
736 elif domain == 'absolute':
737 tr_proc.set_ydata(num.abs(tr_proc.get_ydata()))
739 elif domain in ('frequency_domain', 'log_frequency_domain'):
740 ndata = tr_proc.ydata.size
741 nfft = trace.nextpow2(ndata)
742 padded = num.zeros(nfft, dtype=float)
743 padded[:ndata] = tr_proc.ydata
744 spectrum = num.fft.rfft(padded)
745 df = 1.0 / (tr_proc.deltat * nfft)
747 trspec_proc = TraceSpectrum(
748 network=tr_proc.network,
749 station=tr_proc.station,
750 location=tr_proc.location,
751 channel=tr_proc.channel,
752 deltaf=df,
753 fmin=0.0,
754 ydata=spectrum)
756 return tr_proc, trspec_proc
759def backazimuth_for_waveform(azimuth, nslc):
760 if nslc[-1] == 'R':
761 backazimuth = azimuth + 180.
762 elif nslc[-1] == 'T':
763 backazimuth = azimuth + 90.
764 else:
765 backazimuth = None
767 return backazimuth
770def float_or_none(x):
771 if x is None:
772 return x
773 else:
774 return float(x)
777def weed(origin, targets, limit, neighborhood=3):
778 azimuths = num.zeros(len(targets))
779 dists = num.zeros(len(targets))
780 for i, target in enumerate(targets):
781 _, azimuths[i] = target.azibazi_to(origin)
782 dists[i] = target.distance_to(origin)
784 badnesses = num.ones(len(targets), dtype=float)
785 deleted, meandists_kept = weeding.weed(
786 azimuths, dists, badnesses,
787 nwanted=limit,
788 neighborhood=neighborhood)
790 targets_weeded = [
791 target for (delete, target) in zip(deleted, targets) if not delete]
793 return targets_weeded, meandists_kept, deleted
796__all__ = '''
797 StoreIDSelectorError
798 StoreIDSelector
799 Crust2StoreIDSelector
800 StationDictStoreIDSelector
801 DepthRangeToStoreID
802 StationDepthStoreIDSelector
803 WaveformTargetGroup
804 WaveformMisfitConfig
805 WaveformMisfitTarget
806 WaveformMisfitResult
807 WaveformPiggybackSubtarget
808 WaveformPiggybackSubresult
809'''.split()