You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
306 lines
9.1 KiB
Python
306 lines
9.1 KiB
Python
# GPLv3
|
|
#
|
|
# The Developers, 21st Century
|
|
import copy
|
|
import logging
|
|
|
|
import numpy as num
|
|
|
|
from pyrocko import orthodrome as pod, gf, marker
|
|
from pyrocko.guts import Float, List, String, Tuple, StringChoice, Object
|
|
from pyrocko.io import stationxml
|
|
|
|
from ewrica import util
|
|
from .base import TargetGroupConfig, TargetGroup
|
|
|
|
logger = logging.getLogger('ewrica.si.ids.targets.waveform')
|
|
|
|
|
|
km = 1e3
|
|
|
|
|
|
quantity2unit = dict([
|
|
('displacement', 'M'),
|
|
('velocity', 'M/S'),
|
|
('acceleration', 'M/S**2')])
|
|
|
|
|
|
class WaveformQuantities(StringChoice):
|
|
choices = ['displacement', 'velocity', 'acceleration']
|
|
|
|
|
|
class ComponentWeight(Object):
|
|
north = Float.T(default=1.)
|
|
east = Float.T(default=1.)
|
|
up = Float.T(default=1.)
|
|
|
|
def get_weight(self, channel):
|
|
if channel[-1] == 'N':
|
|
return self.north
|
|
elif channel[-1] == 'E':
|
|
return self.east
|
|
elif channel[-1] == 'Z':
|
|
return self.up
|
|
else:
|
|
logger.warning(
|
|
'Channel %s is not valid. Last channel id letter '
|
|
'should be "Z", N" or "E". Weight of 0 assigned', channel)
|
|
|
|
return 0.
|
|
|
|
|
|
class RestitutionConfig(Object):
|
|
frequency_limits = Tuple.T(4, Float.T(), default=(0.001, 0.05, 100., 200.))
|
|
t_fade = Float.T(optional=True)
|
|
t_min = Float.T(
|
|
default=-60.,
|
|
help='start time of raw waveforms relative event origin time')
|
|
t_max = Float.T(
|
|
default=100.,
|
|
help='end time of raw waveforms relative event origin time')
|
|
|
|
target_quantity = WaveformQuantities.T(optional=True)
|
|
|
|
@staticmethod
|
|
def example():
|
|
return RestitutionConfig()
|
|
|
|
@property
|
|
def target_unit(self):
|
|
if self.target_quantity is not None:
|
|
return quantity2unit[self.target_quantity]
|
|
|
|
return None
|
|
|
|
|
|
class WaveformTargetGroupConfig(TargetGroupConfig):
|
|
distance_min = Float.T(
|
|
default=0.,
|
|
help='Minimum desired origin station distance in [m].')
|
|
|
|
distance_max = Float.T(
|
|
default=100. * km,
|
|
help='Maximum desired origin station distance in [m].')
|
|
|
|
ppicks_paths__ = List.T(
|
|
String.T(),
|
|
optional=True,
|
|
help='P-wave arrival picks file in snuffler marker format.')
|
|
|
|
waveform_paths__ = List.T(
|
|
String.T(),
|
|
default=['.'],
|
|
help='Paths to waveform directories.')
|
|
|
|
response_stationxml_paths__ = List.T(
|
|
String.T(),
|
|
default=['response.xml'],
|
|
help='Paths to station response files')
|
|
|
|
pphase = String.T(
|
|
optional=True,
|
|
help='Phase id of the P phase arrival of the given store (needed, if '
|
|
'no picks are provided.')
|
|
|
|
store_id = String.T(
|
|
optional=True,
|
|
help='Phase id of the P phase arrival of the given store (needed, if '
|
|
'no picks are provided.')
|
|
|
|
component_weights = ComponentWeight.T(
|
|
default=ComponentWeight(),
|
|
help='Weight of the different station channels.')
|
|
|
|
restitution_config = RestitutionConfig.T(
|
|
default=RestitutionConfig(),
|
|
help='Waveform restitution settings.')
|
|
|
|
t_prearrival = Float.T(
|
|
default=-15.,
|
|
help='data starting from time in [sec] relative to P arrivals is '
|
|
'included in dat files for baseline corrections.')
|
|
|
|
t_length = Float.T(
|
|
optional=True,
|
|
help='If given, all traces are cutted to given length in [s].')
|
|
|
|
static_weight = Float.T(
|
|
default=0.,
|
|
help='Weight of the static offsets (baseline shifts).')
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
TargetGroupConfig.__init__(self, *args, **kwargs)
|
|
|
|
self._blocked = []
|
|
self._allowed = []
|
|
self._stations = []
|
|
|
|
@staticmethod
|
|
def example():
|
|
return WaveformTargetGroupConfig(
|
|
blocklist_paths=['path/to/blocklist.txt'],
|
|
blocklist=['AB.BLOCK'],
|
|
allowlist_paths=['path/to/allowlist.txt'],
|
|
allowlist=['AB.ALLOW'],
|
|
restitution_config=RestitutionConfig.example(),
|
|
waveform_paths=[
|
|
'relative/path/to/waveforms1', 'relative/path/to/waveforms1'],
|
|
response_stationxml_paths=[
|
|
'path/to/responsefile1.xml', 'path/to/responsefile2.xml'])
|
|
|
|
@property
|
|
def blocked(self):
|
|
if not self._blocked:
|
|
blocklist = copy.deepcopy(self.blocklist) or []
|
|
|
|
if self.blocklist_paths:
|
|
blocklist += [
|
|
str(s) for p in self.blocklist_paths
|
|
for s in num.loadtxt(p, dtype='<U20')]
|
|
|
|
self._blocked = list(num.unique(blocklist))
|
|
|
|
return self._blocked
|
|
|
|
@property
|
|
def allowed(self):
|
|
if not self._allowed:
|
|
allowlist = copy.deepcopy(self.allowlist) or []
|
|
|
|
if self.allowlist_paths:
|
|
allowlist += [
|
|
str(s) for p in self.allowlist_paths
|
|
for s in num.loadtxt(p, dtype='<U20')]
|
|
|
|
self._allowed = list(num.unique(allowlist))
|
|
|
|
return self._allowed
|
|
|
|
@property
|
|
def ppicks_path(self):
|
|
return self._expand_paths(self.ppicks_path__)
|
|
|
|
@ppicks_path.setter
|
|
def ppicks_path(self, ppicks_path):
|
|
self.ppicks_path__ = ppicks_path
|
|
|
|
@property
|
|
def waveform_paths(self):
|
|
return self._expand_paths(self.waveform_paths__)
|
|
|
|
@waveform_paths.setter
|
|
def waveform_paths(self, waveform_paths):
|
|
self.waveform_paths__ = waveform_paths
|
|
|
|
@property
|
|
def response_stationxml_paths(self):
|
|
return self._expand_paths(self.response_stationxml_paths__)
|
|
|
|
@response_stationxml_paths.setter
|
|
def response_stationxml_paths(self, response_stationxml_paths):
|
|
self.response_stationxml_paths__ = response_stationxml_paths
|
|
|
|
|
|
class WaveformTargetGroup(TargetGroup):
|
|
config = WaveformTargetGroupConfig.T(default=WaveformTargetGroupConfig())
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
TargetGroup.__init__(self, *args, **kwargs)
|
|
|
|
self._p_arrivals = {}
|
|
self._stations = []
|
|
self._sx = None
|
|
|
|
@property
|
|
def sx(self):
|
|
if self._sx is None:
|
|
sxs = []
|
|
for f in self.config.response_stationxml_paths:
|
|
sxs.append(stationxml.load_xml(filename=f))
|
|
|
|
self._sx = stationxml.primitive_merge(sxs)
|
|
|
|
return self._sx
|
|
|
|
@property
|
|
def stations(self):
|
|
if not self._stations:
|
|
stations = self.sx.get_pyrocko_stations()
|
|
|
|
stations = util.keep_allowlisted_stations(
|
|
stations, self.config.allowed)
|
|
stations = util.remove_blocklisted_stations(
|
|
stations, self.config.blocked)
|
|
|
|
self._stations = stations
|
|
|
|
return self._stations
|
|
|
|
def _event_station_distance(self, event):
|
|
return pod.distance_accurate50m_numpy(
|
|
event.lat, event.lon,
|
|
[s.lat for s in self.stations], [s.lon for s in self.stations])
|
|
|
|
def p_arrivals(self, event):
|
|
if self._p_arrivals:
|
|
return self._p_arrivals
|
|
|
|
wf_conf = self.config
|
|
en_conf = wf_conf._parent.engine_config
|
|
|
|
arrivals = []
|
|
if (wf_conf.ppicks_paths is not None and wf_conf.pphase is None and
|
|
wf_conf.store_id is None):
|
|
|
|
for fn in wf_conf.ppicks_paths:
|
|
arrivals += marker.load_markers(fn)
|
|
|
|
elif (wf_conf.ppicks_paths is None and wf_conf.pphase is not None and
|
|
wf_conf.store_id is not None):
|
|
|
|
engine = gf.LocalEngine(
|
|
store_superdirs=en_conf.pyrocko_store_superdirs)
|
|
store = engine.get_store(wf_conf.store_id)
|
|
|
|
markers = []
|
|
for sta in self.stations:
|
|
try:
|
|
arr = store.t(wf_conf.pphase, event, sta) + event.time
|
|
|
|
markers.append(marker.Marker(
|
|
tmin=arr, tmax=arr,
|
|
nslc_ids=[
|
|
sta.nsl() + (c,) for c in ['*E', '*N', '*Z']]))
|
|
|
|
except gf.store.NoSuchPhase as e:
|
|
raise e
|
|
except gf.meta.OutOfBounds as e:
|
|
logger.warn(
|
|
'Got error \n {} \n for station {}. '
|
|
'It is excluded'.format(e, sta.nsl()))
|
|
|
|
arrivals += markers
|
|
|
|
else:
|
|
raise ValueError('Argument "ppicks_paths" is mutually exclusive '
|
|
'with "pphase" and "store_id".')
|
|
|
|
arrivals = {(ar.nslc_ids[0][:3]): ar for ar in arrivals}
|
|
|
|
self._p_arrivals = arrivals
|
|
|
|
return self._p_arrivals
|
|
|
|
def get_targets(self, event, *args, **kwargs):
|
|
dist = self._event_station_distance(event)
|
|
|
|
return [
|
|
s for s, d in zip(self.stations, dist)
|
|
if d >= self.config.distance_min and d <= self.config.distance_max]
|
|
|
|
def get_dataset(self):
|
|
raise NotImplementedError('Method not implemented')
|
|
|
|
def dump_ids_input(self, path):
|
|
pass
|