Coverage for /usr/local/lib/python3.11/dist-packages/grond/targets/waveform/target.py: 79%
432 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 logging
5import math
6import numpy as num
8from pyrocko import gf, trace, weeding, util
9from pyrocko.guts import (Object, String, Float, Bool, Int, StringChoice,
10 Timestamp, List, Dict)
11from pyrocko.guts_array import Array
12from pyrocko.gui.snuffler.marker import PhaseMarker
14from grond.dataset import NotFound
15from grond.meta import GrondError, store_t, nslcs_to_patterns
17from ..base import (MisfitConfig, MisfitTarget, MisfitResult, TargetGroup)
18from grond.meta import has_get_plot_classes
20from pyrocko import crust2x2
21from string import Template
23guts_prefix = 'grond'
24logger = logging.getLogger('grond.targets.waveform.target')
26g_differentiation_response_1 = trace.DifferentiationResponse(1)
27g_differentiation_response_2 = trace.DifferentiationResponse(2)
30class StoreIDSelectorError(GrondError):
31 pass
34class StoreIDSelector(Object):
35 '''
36 Base class for GF store selectors.
38 GF store selectors can be implemented to select different stores, based on
39 station location, source location or other characteristics.
40 '''
42 pass
45class Crust2StoreIDSelector(StoreIDSelector):
46 '''
47 Store ID selector picking CRUST 2.0 model based on event location.
48 '''
50 template = String.T(
51 help="Template for the GF store ID. For example ``'crust2_${id}'`` "
52 "where ``'${id}'`` will be replaced with the corresponding CRUST "
53 "2.0 profile identifier for the source location.")
55 def get_store_id(self, event, st, cha):
56 s = Template(self.template)
57 return s.substitute(id=(
58 crust2x2.get_profile(event.lat, event.lon)._ident).lower())
61class StationDictStoreIDSelector(StoreIDSelector):
62 '''
63 Store ID selector using a manual station to store ID mapping.
64 '''
66 mapping = Dict.T(
67 String.T(), gf.StringID.T(),
68 help='Dictionary with station to store ID pairs, keys are NET.STA. '
69 "Add a fallback store ID under the key ``'others'``.")
71 def get_store_id(self, event, st, cha):
72 try:
73 store_id = self.mapping['%s.%s' % (st.network, st.station)]
74 except KeyError:
75 try:
76 store_id = self.mapping['others']
77 except KeyError:
78 raise StoreIDSelectorError(
79 'No store ID found for station "%s.%s".' % (
80 st.network, st.station))
82 return store_id
85class DepthRangeToStoreID(Object):
86 depth_min = Float.T()
87 depth_max = Float.T()
88 store_id = gf.StringID.T()
91class StationDepthStoreIDSelector(StoreIDSelector):
92 '''
93 Store ID selector using a mapping from station depth range to store ID.
94 '''
96 depth_ranges = List.T(DepthRangeToStoreID.T())
98 def get_store_id(self, event, st, cha):
99 for r in self.depth_ranges:
100 if r.depth_min <= st.depth < r.depth_max:
101 return r.store_id
103 raise StoreIDSelectorError(
104 'No store ID found for station "%s.%s" at %g m depth.' % (
105 st.network, st.station, st.depth))
108class DomainChoice(StringChoice):
109 choices = [
110 'time_domain',
111 'frequency_domain',
112 'log_frequency_domain',
113 'envelope',
114 'absolute',
115 'cc_max_norm']
118class WaveformMisfitConfig(MisfitConfig):
119 quantity = gf.QuantityType.T(default='displacement')
120 fmin = Float.T(default=0.0, help='minimum frequency of bandpass filter')
121 fmax = Float.T(help='maximum frequency of bandpass filter')
122 ffactor = Float.T(default=1.5)
123 tmin = gf.Timing.T(
124 optional=True,
125 help='Start of main time window used for waveform fitting.')
126 tmax = gf.Timing.T(
127 optional=True,
128 help='End of main time window used for waveform fitting.')
129 tfade = Float.T(
130 optional=True,
131 help='Decay time of taper prepended and appended to main time window '
132 'used for waveform fitting [s].')
133 pick_synthetic_traveltime = gf.Timing.T(
134 optional=True,
135 help='Synthetic phase arrival definition for alignment of observed '
136 'and synthetic traces.')
137 pick_phasename = String.T(
138 optional=True,
139 help='Name of picked phase for alignment of observed and synthetic '
140 'traces.')
141 domain = DomainChoice.T(
142 default='time_domain',
143 help='Type of data characteristic to be fitted.\n\nAvailable choices '
144 'are: %s' % ', '.join("``'%s'``" % s
145 for s in DomainChoice.choices))
146 norm_exponent = Int.T(
147 default=2,
148 help='Exponent to use in norm (1: L1-norm, 2: L2-norm)')
149 tautoshift_max = Float.T(
150 default=0.0,
151 help='If non-zero, allow synthetic and observed traces to be shifted '
152 'against each other by up to +/- the given value [s].')
153 autoshift_penalty_max = Float.T(
154 default=0.0,
155 help='If non-zero, a penalty misfit is added for non-zero shift '
156 'values.\n\nThe penalty value is computed as '
157 '``autoshift_penalty_max * normalization_factor * tautoshift**2 '
158 '/ tautoshift_max**2``')
160 ranges = {}
162 def get_full_frequency_range(self):
163 return self.fmin / self.ffactor, self.fmax * self.ffactor
166def log_exclude(target, reason):
167 logger.debug('Excluding potential target %s: %s' % (
168 target.string_id(), reason))
171class WaveformTargetGroup(TargetGroup):
172 '''Handles seismogram targets or other targets of dynamic ground motion.
173 '''
174 distance_min = Float.T(
175 optional=True,
176 help='excludes targets nearer to source, along a great circle')
177 distance_max = Float.T(
178 optional=True,
179 help='excludes targets farther from source, along a great circle')
180 distance_3d_min = Float.T(
181 optional=True,
182 help='excludes targets nearer from source (direct distance)')
183 distance_3d_max = Float.T(
184 optional=True,
185 help='excludes targets farther from source (direct distance)')
186 depth_min = Float.T(
187 optional=True,
188 help='excludes targets with smaller depths')
189 depth_max = Float.T(
190 optional=True,
191 help='excludes targets with larger depths')
192 include = List.T(
193 String.T(),
194 optional=True,
195 help='If not None, list of stations/components to include according '
196 'to their STA, NET.STA, NET.STA.LOC, or NET.STA.LOC.CHA codes.')
197 exclude = List.T(
198 String.T(),
199 help='Stations/components to be excluded according to their STA, '
200 'NET.STA, NET.STA.LOC, or NET.STA.LOC.CHA codes.')
201 only_use_stations_with_picks = Bool.T(
202 optional=True,
203 default=False,
204 help='Choose if targets without arrival picks should be excluded '
205 '(True) or should be used without time shifts (False).')
206 limit = Int.T(optional=True)
207 channels = List.T(
208 String.T(),
209 optional=True,
210 help="set channels to include, e.g. ['Z', 'T']")
211 misfit_config = WaveformMisfitConfig.T()
212 store_id_selector = StoreIDSelector.T(
213 optional=True,
214 help='select GF store based on event-station geometry.')
216 def get_targets(self, ds, event, default_path='none'):
217 logger.debug('Selecting waveform targets...')
218 origin = event
219 targets = []
221 stations = ds.get_stations()
222 if len(stations) == 0:
223 logger.warning(
224 'No stations found to create waveform target group.')
226 for st in ds.get_stations():
227 logger.debug('Selecting waveforms for station %s.%s.%s' % st.nsl())
228 for cha in self.channels:
229 nslc = st.nsl() + (cha,)
231 logger.debug('Selecting waveforms for %s.%s.%s.%s' % nslc)
233 if self.store_id_selector:
234 store_id = self.store_id_selector.get_store_id(
235 event, st, cha)
236 else:
237 store_id = self.store_id
239 logger.debug('Selecting waveforms for %s.%s.%s.%s' % nslc)
241 if self.misfit_config.quantity in [
242 'displacement',
243 'velocity',
244 'acceleration']:
246 quantity = 'displacement'
248 elif self.misfit_config.quantity in [
249 'rotation_displacement',
250 'rotation_velocity',
251 'rotation_acceleration']:
253 quantity = 'rotation_displacement'
255 else:
256 quantity = self.misfit_config.quantity
258 target = WaveformMisfitTarget(
259 quantity=quantity,
260 codes=nslc,
261 lat=st.lat,
262 lon=st.lon,
263 north_shift=st.north_shift,
264 east_shift=st.east_shift,
265 depth=st.depth,
266 interpolation=self.interpolation,
267 store_id=store_id,
268 misfit_config=self.misfit_config,
269 manual_weight=self.weight,
270 normalisation_family=self.normalisation_family,
271 path=self.path or default_path,
272 only_use_stations_with_picks=self.only_use_stations_with_picks) # noqa
274 if ds.is_excluded(nslc):
275 log_exclude(target, 'excluded by dataset')
276 continue
278 if util.match_nslc(
279 nslcs_to_patterns(self.exclude), nslc):
280 log_exclude(target, 'excluded by target group')
281 continue
283 if self.include is not None and not util.match_nslc(
284 nslcs_to_patterns(self.include), nslc):
285 log_exclude(target, 'excluded by target group')
286 continue
288 if self.distance_min is not None and \
289 target.distance_to(origin) < self.distance_min:
290 log_exclude(target, 'distance < distance_min')
291 continue
293 if self.distance_max is not None and \
294 target.distance_to(origin) > self.distance_max:
295 log_exclude(target, 'distance > distance_max')
296 continue
298 if self.distance_3d_min is not None and \
299 target.distance_3d_to(origin) < self.distance_3d_min:
300 log_exclude(target, 'distance_3d < distance_3d_min')
301 continue
303 if self.distance_3d_max is not None and \
304 target.distance_3d_to(origin) > self.distance_3d_max:
305 log_exclude(target, 'distance_3d > distance_3d_max')
306 continue
308 if self.depth_min is not None and \
309 target.depth < self.depth_min:
310 log_exclude(target, 'depth < depth_min')
311 continue
313 if self.depth_max is not None and \
314 target.depth > self.depth_max:
315 log_exclude(target, 'depth > depth_max')
316 continue
318 if self.only_use_stations_with_picks:
319 all_picks_nsl_ids = set(
320 p.nslc_ids[0][:3]
321 for p in ds.pick_markers
322 if isinstance(p, PhaseMarker))
323 if st.nsl() not in all_picks_nsl_ids:
324 log_exclude(target, 'no pick for nsl')
325 continue
327 azi, _ = target.azibazi_to(origin)
328 if cha == 'R':
329 target.azimuth = azi - 180.
330 target.dip = 0.
331 elif cha == 'T':
332 target.azimuth = azi - 90.
333 target.dip = 0.
334 elif cha == 'Z':
335 target.azimuth = 0.
336 target.dip = -90.
338 target.set_dataset(ds)
339 targets.append(target)
341 if self.limit:
342 return weed(origin, targets, self.limit)[0]
343 else:
344 return targets
346 def is_gf_store_appropriate(self, store, depth_range):
347 ok = TargetGroup.is_gf_store_appropriate(
348 self, store, depth_range)
349 ok &= self._is_gf_store_appropriate_check_extent(
350 store, depth_range)
351 ok &= self._is_gf_store_appropriate_check_sample_rate(
352 store, depth_range)
353 return ok
356class TraceSpectrum(Object):
357 network = String.T()
358 station = String.T()
359 location = String.T()
360 channel = String.T()
361 deltaf = Float.T(default=1.0)
362 fmin = Float.T(default=0.0)
363 ydata = Array.T(
364 shape=(None,),
365 dtype=num.complex128,
366 serialize_dtype='<c16',
367 serialize_as='base64+meta')
369 def get_ydata(self):
370 return self.ydata
372 def get_xdata(self):
373 return self.fmin + num.arange(self.ydata.size) * self.deltaf
376class WaveformPiggybackSubtarget(Object):
377 piggy_id = Int.T()
379 _next_piggy_id = 0
381 @classmethod
382 def new_piggy_id(cls):
383 piggy_id = WaveformPiggybackSubtarget._next_piggy_id
384 WaveformPiggybackSubtarget._next_piggy_id += 1
385 return piggy_id
387 def __init__(self, piggy_id=None, **kwargs):
388 if piggy_id is None:
389 piggy_id = self.new_piggy_id()
391 Object.__init__(self, piggy_id=piggy_id, **kwargs)
393 def evaluate(
394 self, tr_proc_obs, trspec_proc_obs, tr_proc_syn, trspec_proc_syn):
396 raise NotImplementedError()
399class WaveformPiggybackSubresult(Object):
400 piggy_id = Int.T()
403class WaveformMisfitResult(gf.Result, MisfitResult):
404 '''Carries the observations for a target and corresponding synthetics.
406 A number of different waveform or phase representations are possible.
407 '''
408 processed_obs = trace.Trace.T(optional=True)
409 processed_syn = trace.Trace.T(optional=True)
410 filtered_obs = trace.Trace.T(optional=True)
411 filtered_syn = trace.Trace.T(optional=True)
412 spectrum_obs = TraceSpectrum.T(optional=True)
413 spectrum_syn = TraceSpectrum.T(optional=True)
415 taper = trace.Taper.T(optional=True)
416 tobs_shift = Float.T(optional=True)
417 tsyn_pick = Timestamp.T(optional=True)
418 tshift = Float.T(optional=True)
419 cc = trace.Trace.T(optional=True)
421 piggyback_subresults = List.T(WaveformPiggybackSubresult.T())
424@has_get_plot_classes
425class WaveformMisfitTarget(gf.Target, MisfitTarget):
426 flip_norm = Bool.T(default=False)
427 misfit_config = WaveformMisfitConfig.T()
428 only_use_stations_with_picks = Bool.T(default=False, optional=True)
430 can_bootstrap_weights = True
432 def __init__(self, **kwargs):
433 gf.Target.__init__(self, **kwargs)
434 MisfitTarget.__init__(self, **kwargs)
435 self._piggyback_subtargets = []
437 def string_id(self):
438 return '.'.join(x for x in (self.path,) + self.codes)
440 @classmethod
441 def get_plot_classes(cls):
442 from . import plot
443 plots = super(WaveformMisfitTarget, cls).get_plot_classes()
444 plots.extend(plot.get_plot_classes())
445 return plots
447 def get_combined_weight(self):
448 if self._combined_weight is None:
449 w = self.manual_weight
450 for analyser in self.analyser_results.values():
451 w *= analyser.weight
452 self._combined_weight = num.array([w], dtype=float)
453 return self._combined_weight
455 def get_taper_params(self, engine, source):
456 store = engine.get_store(self.store_id)
457 config = self.misfit_config
458 tmin_fit = source.time + store_t(store, config.tmin, source, self)
459 tmax_fit = source.time + store_t(store, config.tmax, source, self)
460 if config.fmin > 0.0:
461 tfade = 1.0/config.fmin
462 else:
463 tfade = 1.0/config.fmax
465 if config.tfade is None:
466 tfade_taper = tfade
467 else:
468 tfade_taper = config.tfade
470 return tmin_fit, tmax_fit, tfade, tfade_taper
472 def get_backazimuth_for_waveform(self):
473 return backazimuth_for_waveform(self.azimuth, self.codes)
475 @property
476 def backazimuth(self):
477 return self.azimuth - 180.
479 def get_freqlimits(self):
480 config = self.misfit_config
482 return (
483 config.fmin/config.ffactor,
484 config.fmin, config.fmax,
485 config.fmax*config.ffactor)
487 def get_pick_shift(self, engine, source):
488 config = self.misfit_config
489 tobs = None
490 tsyn = None
491 ds = self.get_dataset()
493 if config.pick_synthetic_traveltime and config.pick_phasename:
494 store = engine.get_store(self.store_id)
495 tsyn = source.time + store.t(
496 config.pick_synthetic_traveltime, source, self)
498 marker = ds.get_pick(
499 source.name,
500 self.codes[:3],
501 config.pick_phasename)
503 if marker:
504 tobs = marker.tmin
506 return tobs, tsyn
508 def get_cutout_timespan(self, tmin, tmax, tfade):
510 if self.misfit_config.fmin > 0:
511 tinc_obs = 1.0 / self.misfit_config.fmin
512 else:
513 tinc_obs = 10.0 / self.misfit_config.fmax
515 tmin_obs = (math.floor(
516 (tmin - tfade) / tinc_obs) - 1.0) * tinc_obs
517 tmax_obs = (math.ceil(
518 (tmax + tfade) / tinc_obs) + 1.0) * tinc_obs
520 return tmin_obs, tmax_obs
522 def post_process(self, engine, source, tr_syn):
524 tr_syn = tr_syn.pyrocko_trace()
525 nslc = self.codes
527 config = self.misfit_config
529 tmin_fit, tmax_fit, tfade, tfade_taper = \
530 self.get_taper_params(engine, source)
532 ds = self.get_dataset()
534 tobs, tsyn = self.get_pick_shift(engine, source)
536 if None not in (tobs, tsyn):
537 tobs_shift = tobs - tsyn
538 else:
539 if self.only_use_stations_with_picks:
540 err = 'No pick available for target: %s' % self.path
541 logger.debug(err)
542 raise gf.SeismosizerError(err)
543 else:
544 tobs_shift = 0.0
546 tr_syn.extend(
547 tmin_fit - tfade * 2.0,
548 tmax_fit + tfade * 2.0,
549 fillmethod='repeat')
551 freqlimits = self.get_freqlimits()
553 if config.quantity in ('displacement', 'rotation_displacement'):
554 syn_resp = None
555 elif config.quantity in ('velocity', 'rotation_velocity'):
556 syn_resp = g_differentiation_response_1
557 elif config.quantity in ('acceleration', 'rotation_acceleration'):
558 syn_resp = g_differentiation_response_2
559 else:
560 syn_resp = None
562 tr_syn = tr_syn.transfer(
563 freqlimits=freqlimits,
564 tfade=tfade,
565 transfer_function=syn_resp)
567 tr_syn.chop(tmin_fit - 2*tfade, tmax_fit + 2*tfade)
569 tmin_obs, tmax_obs = self.get_cutout_timespan(
570 tmin_fit+tobs_shift, tmax_fit+tobs_shift, tfade)
572 try:
573 tr_obs = ds.get_waveform(
574 nslc,
575 quantity=config.quantity,
576 tinc_cache=1.0/(config.fmin or 0.1*config.fmax),
577 tmin=tmin_fit+tobs_shift-tfade,
578 tmax=tmax_fit+tobs_shift+tfade,
579 tfade=tfade,
580 freqlimits=freqlimits,
581 deltat=tr_syn.deltat,
582 cache=True,
583 backazimuth=self.get_backazimuth_for_waveform())
585 if tobs_shift != 0.0:
586 tr_obs = tr_obs.copy()
587 tr_obs.shift(-tobs_shift)
589 mr = misfit(
590 tr_obs, tr_syn,
591 taper=trace.CosTaper(
592 tmin_fit - tfade_taper,
593 tmin_fit,
594 tmax_fit,
595 tmax_fit + tfade_taper),
596 domain=config.domain,
597 exponent=config.norm_exponent,
598 flip=self.flip_norm,
599 result_mode=self._result_mode,
600 tautoshift_max=config.tautoshift_max,
601 autoshift_penalty_max=config.autoshift_penalty_max,
602 subtargets=self._piggyback_subtargets)
604 self._piggyback_subtargets = []
606 mr.tobs_shift = float(tobs_shift)
607 mr.tsyn_pick = float_or_none(tsyn)
609 return mr
611 except (NotFound, util.UnavailableDecimation) as e:
612 logger.debug(str(e))
613 raise gf.SeismosizerError('No waveform data: %s' % str(e))
615 def get_plain_targets(self, engine, source):
616 d = dict(
617 (k, getattr(self, k)) for k in gf.Target.T.propnames)
618 return [gf.Target(**d)]
620 def add_piggyback_subtarget(self, subtarget):
621 self._piggyback_subtargets.append(subtarget)
624def misfit(
625 tr_obs, tr_syn, taper, domain, exponent, tautoshift_max,
626 autoshift_penalty_max, flip, result_mode='sparse', subtargets=[]):
628 '''
629 Calculate misfit between observed and synthetic trace.
631 :param tr_obs: observed trace as :py:class:`pyrocko.trace.Trace`
632 :param tr_syn: synthetic trace as :py:class:`pyrocko.trace.Trace`
633 :param taper: taper applied in timedomain as
634 :py:class:`pyrocko.trace.Taper`
635 :param domain: how to calculate difference, see :py:class:`DomainChoice`
636 :param exponent: exponent of Lx type norms
637 :param tautoshift_max: if non-zero, return lowest misfit when traces are
638 allowed to shift against each other by up to +/- ``tautoshift_max``
639 :param autoshift_penalty_max: if non-zero, a penalty misfit is added for
640 for non-zero shift values. The penalty value is
641 ``autoshift_penalty_max * normalization_factor * \
642tautoshift**2 / tautoshift_max**2``
643 :param flip: ``bool``, if set to ``True``, normalization factor is
644 computed against *tr_syn* rather than *tr_obs*
645 :param result_mode: ``'full'``, include traces and spectra or ``'sparse'``,
646 include only misfit and normalization factor in result
648 :returns: object of type :py:class:`WaveformMisfitResult`
649 '''
651 trace.assert_same_sampling_rate(tr_obs, tr_syn)
652 deltat = tr_obs.deltat
653 tmin, tmax = taper.time_span()
655 tr_proc_obs, trspec_proc_obs = _process(tr_obs, tmin, tmax, taper, domain)
656 tr_proc_syn, trspec_proc_syn = _process(tr_syn, tmin, tmax, taper, domain)
658 piggyback_results = []
659 for subtarget in subtargets:
660 piggyback_results.append(
661 subtarget.evaluate(
662 tr_proc_obs, trspec_proc_obs, tr_proc_syn, trspec_proc_syn))
664 tshift = None
665 ctr = None
666 deltat = tr_proc_obs.deltat
667 if domain in ('time_domain', 'envelope', 'absolute'):
668 a, b = tr_proc_syn.ydata, tr_proc_obs.ydata
669 if flip:
670 b, a = a, b
672 nshift_max = max(0, min(a.size-1,
673 int(math.floor(tautoshift_max / deltat))))
675 if nshift_max == 0:
676 m, n = trace.Lx_norm(a, b, norm=exponent)
677 else:
678 mns = []
679 for ishift in range(-nshift_max, nshift_max+1):
680 if ishift < 0:
681 a_cut = a[-ishift:]
682 b_cut = b[:ishift]
683 elif ishift == 0:
684 a_cut = a
685 b_cut = b
686 elif ishift > 0:
687 a_cut = a[:-ishift]
688 b_cut = b[ishift:]
690 mns.append(trace.Lx_norm(a_cut, b_cut, norm=exponent))
692 ms, ns = num.array(mns).T
694 iarg = num.argmin(ms)
695 tshift = (iarg-nshift_max)*deltat
697 m, n = ms[iarg], ns[iarg]
698 m += autoshift_penalty_max * n * tshift**2 / tautoshift_max**2
700 elif domain == 'cc_max_norm':
702 ctr = trace.correlate(
703 tr_proc_syn,
704 tr_proc_obs,
705 mode='same',
706 normalization='normal')
708 tshift, cc_max = ctr.max()
709 m = 0.5 - 0.5 * cc_max
710 n = 0.5
712 elif domain == 'frequency_domain':
713 a, b = trspec_proc_syn.ydata, trspec_proc_obs.ydata
714 if flip:
715 b, a = a, b
717 m, n = trace.Lx_norm(num.abs(a), num.abs(b), norm=exponent)
719 elif domain == 'log_frequency_domain':
720 a, b = trspec_proc_syn.ydata, trspec_proc_obs.ydata
721 if flip:
722 b, a = a, b
724 a = num.abs(a)
725 b = num.abs(b)
727 eps = (num.mean(a) + num.mean(b)) * 1e-7
728 if eps == 0.0:
729 eps = 1e-7
731 a = num.log(a + eps)
732 b = num.log(b + eps)
734 m, n = trace.Lx_norm(a, b, norm=exponent)
736 if result_mode == 'full':
737 result = WaveformMisfitResult(
738 misfits=num.array([[m, n]], dtype=float),
739 processed_obs=tr_proc_obs,
740 processed_syn=tr_proc_syn,
741 filtered_obs=tr_obs.copy(),
742 filtered_syn=tr_syn,
743 spectrum_obs=trspec_proc_obs,
744 spectrum_syn=trspec_proc_syn,
745 taper=taper,
746 tshift=tshift,
747 cc=ctr)
749 elif result_mode == 'sparse':
750 result = WaveformMisfitResult(
751 misfits=num.array([[m, n]], dtype=float))
752 else:
753 assert False
755 result.piggyback_subresults = piggyback_results
757 return result
760def _extend_extract(tr, tmin, tmax):
761 deltat = tr.deltat
762 itmin_frame = int(math.floor(tmin/deltat))
763 itmax_frame = int(math.ceil(tmax/deltat))
764 nframe = itmax_frame - itmin_frame + 1
765 n = tr.data_len()
766 a = num.empty(nframe, dtype=float)
767 itmin_tr = int(round(tr.tmin / deltat))
768 itmax_tr = itmin_tr + n
769 icut1 = min(max(0, itmin_tr - itmin_frame), nframe)
770 icut2 = min(max(0, itmax_tr - itmin_frame), nframe)
771 icut1_tr = min(max(0, icut1 + itmin_frame - itmin_tr), n)
772 icut2_tr = min(max(0, icut2 + itmin_frame - itmin_tr), n)
773 a[:icut1] = tr.ydata[0]
774 a[icut1:icut2] = tr.ydata[icut1_tr:icut2_tr]
775 a[icut2:] = tr.ydata[-1]
776 tr = tr.copy(data=False)
777 tr.tmin = itmin_frame * deltat
778 tr.set_ydata(a)
779 return tr
782def _process(tr, tmin, tmax, taper, domain):
783 tr_proc = _extend_extract(tr, tmin, tmax)
784 tr_proc.taper(taper)
786 df = None
787 trspec_proc = None
789 if domain == 'envelope':
790 tr_proc = tr_proc.envelope(inplace=False)
791 tr_proc.set_ydata(num.abs(tr_proc.get_ydata()))
793 elif domain == 'absolute':
794 tr_proc.set_ydata(num.abs(tr_proc.get_ydata()))
796 elif domain in ('frequency_domain', 'log_frequency_domain'):
797 ndata = tr_proc.ydata.size
798 nfft = trace.nextpow2(ndata)
799 padded = num.zeros(nfft, dtype=float)
800 padded[:ndata] = tr_proc.ydata
801 spectrum = num.fft.rfft(padded)
802 df = 1.0 / (tr_proc.deltat * nfft)
804 trspec_proc = TraceSpectrum(
805 network=tr_proc.network,
806 station=tr_proc.station,
807 location=tr_proc.location,
808 channel=tr_proc.channel,
809 deltaf=df,
810 fmin=0.0,
811 ydata=spectrum)
813 return tr_proc, trspec_proc
816def backazimuth_for_waveform(azimuth, nslc):
817 if nslc[-1] == 'R':
818 backazimuth = azimuth + 180.
819 elif nslc[-1] == 'T':
820 backazimuth = azimuth + 90.
821 else:
822 backazimuth = None
824 return backazimuth
827def float_or_none(x):
828 if x is None:
829 return x
830 else:
831 return float(x)
834def weed(origin, targets, limit, neighborhood=3):
835 azimuths = num.zeros(len(targets))
836 dists = num.zeros(len(targets))
837 for i, target in enumerate(targets):
838 _, azimuths[i] = target.azibazi_to(origin)
839 dists[i] = target.distance_to(origin)
841 badnesses = num.ones(len(targets), dtype=float)
842 deleted, meandists_kept = weeding.weed(
843 azimuths, dists, badnesses,
844 nwanted=limit,
845 neighborhood=neighborhood)
847 targets_weeded = [
848 target for (delete, target) in zip(deleted, targets) if not delete]
850 return targets_weeded, meandists_kept, deleted
853__all__ = '''
854 StoreIDSelectorError
855 StoreIDSelector
856 Crust2StoreIDSelector
857 StationDictStoreIDSelector
858 DepthRangeToStoreID
859 StationDepthStoreIDSelector
860 WaveformTargetGroup
861 WaveformMisfitConfig
862 WaveformMisfitTarget
863 WaveformMisfitResult
864 WaveformPiggybackSubtarget
865 WaveformPiggybackSubresult
866'''.split()