Add object based approach to autopickstation: preparing traces, modifying cuttimes with taupy
This commit is contained in:
parent
9b787ac4e8
commit
0230f0bf2e
@ -137,6 +137,274 @@ def get_source_coords(parser, station_id):
|
||||
print('Could not get source coordinates for station {}: {}'.format(station_id, e))
|
||||
return station_coords
|
||||
|
||||
class PickingParameters(object):
|
||||
"""
|
||||
Stores parameters used for picking a single station.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Add dictionaries given as positional arguments and the keyword argument dictionary to the instance
|
||||
as attributes. Positional arguments with types differing from dict are ignored.
|
||||
"""
|
||||
# add entries from dictionaries given as positional arguments
|
||||
for arg in args:
|
||||
if type(arg) == dict:
|
||||
self.add_params_from_dict(arg)
|
||||
# add values given as keyword arguments
|
||||
self.add_params_from_dict(kwargs)
|
||||
|
||||
def add_params_from_dict(self, d):
|
||||
"""
|
||||
Add all key-value pairs from dictionary d to the class namespace as attributes.
|
||||
:param d:
|
||||
:type d: dict
|
||||
:rtype: None
|
||||
"""
|
||||
for key, value in d.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
class PickingResults(object):
|
||||
|
||||
def __init__(self):
|
||||
# initialize output
|
||||
self.Pweight = 4 # weight for P onset
|
||||
self.Sweight = 4 # weight for S onset
|
||||
self.FM = 'N' # first motion (polarity)
|
||||
self.SNRP = None # signal-to-noise ratio of P onset
|
||||
self.SNRPdB = None # signal-to-noise ratio of P onset [dB]
|
||||
self.SNRS = None # signal-to-noise ratio of S onset
|
||||
self.SNRSdB = None # signal-to-noise ratio of S onset [dB]
|
||||
self.mpickP = None # most likely P onset
|
||||
self.lpickP = None # latest possible P onset
|
||||
self.epickP = None # earliest possible P onset
|
||||
self.mpickS = None # most likely S onset
|
||||
self.lpickS = None # latest possible S onset
|
||||
self.epickS = None # earliest possible S onset
|
||||
self.Perror = None # symmetrized picking error P onset
|
||||
self.Serror = None # symmetrized picking error S onset
|
||||
|
||||
self.aicSflag = 0
|
||||
self.aicPflag = 0
|
||||
self.Pflag = 0
|
||||
self.Sflag = 0
|
||||
self.Pmarker = []
|
||||
self.Ao = None # Wood-Anderson peak-to-peak amplitude
|
||||
self.picker = 'auto' # type of picks
|
||||
|
||||
class MissingTraceException(ValueError):
|
||||
"""
|
||||
Used to indicate missing traces in a obspy.core.stream.Stream object
|
||||
"""
|
||||
pass
|
||||
|
||||
class AutopickStation(object):
|
||||
|
||||
def __init__(self, wfstream, pickparam, verbose, iplot, fig_dict, metadata, origin):
|
||||
# save given parameters
|
||||
self.wfstream = wfstream
|
||||
self.pickparam = pickparam
|
||||
self.verbose = verbose
|
||||
self.iplot = iplot
|
||||
self.fig_dict = fig_dict
|
||||
self.metadata = metadata
|
||||
self.origin = origin
|
||||
|
||||
# extract additional information
|
||||
pickparams = self.extract_pickparams(pickparam)
|
||||
self.p_params, self.s_params, self.first_motion_params, self.signal_length_params = pickparams
|
||||
self.results = PickingResults()
|
||||
self.channelorder = {'Z': 3, 'N': 1, 'E': 2} # TODO get this from the pylot preferences
|
||||
self.station_name = wfstream[0].stats.station
|
||||
self.network_name = wfstream[0].stats.network
|
||||
self.station_id = '{}.{}'.format(self.network_name, self.station_name)
|
||||
|
||||
# default values used in old autopickstation function #TODO way for user to set those
|
||||
self.detrend_type = 'demean'
|
||||
self.filter_type = 'bandpass'
|
||||
self.zerophase = False
|
||||
self.taper_max_percentage = 0.05
|
||||
self.taper_type = 'hann'
|
||||
|
||||
def vprint(self, s):
|
||||
"""Only print statement if verbose picking is set to true."""
|
||||
if self.verbose:
|
||||
print(s)
|
||||
|
||||
def extract_pickparams(self, pickparam):
|
||||
"""
|
||||
Get parameter names out of pickparam dictionary into PickingParameters objects and return them.
|
||||
:return: PickingParameters objects containing 1. p pick parameters, 2. s pick parameters, 3. first motion determinatiion
|
||||
parameters, 4. signal length parameters
|
||||
:rtype: (PickingParameters, PickingParameters, PickingParameters, PickingParameters)
|
||||
"""
|
||||
# Define names of all parameters in different groups
|
||||
p_parameter_names = 'algoP pstart pstop use_taup taup_model tlta tsnrz hosorder bpz1 bpz2 pickwinP aictsmooth tsmoothP ausP nfacP tpred1z tdet1z Parorder addnoise Precalcwin minAICPslope minAICPSNR timeerrorsP checkwindowP minfactorP'.split(
|
||||
' ')
|
||||
s_parameter_names = 'algoS sstart sstop bph1 bph2 tsnrh pickwinS tpred1h tdet1h tpred2h tdet2h Sarorder aictsmoothS tsmoothS ausS minAICSslope minAICSSNR Srecalcwin nfacS timeerrorsS zfac checkwindowS minfactorS'.split(
|
||||
' ')
|
||||
first_motion_names = 'minFMSNR fmpickwin minfmweight'.split(' ')
|
||||
signal_length_names = 'minsiglength minpercent noisefactor'.split(' ')
|
||||
# Get list of values from pickparam by name
|
||||
p_parameter_values = map(pickparam.get, p_parameter_names)
|
||||
s_parameter_values = map(pickparam.get, s_parameter_names)
|
||||
fm_parameter_values = map(pickparam.get, first_motion_names)
|
||||
sl_parameter_values = map(pickparam.get, signal_length_names)
|
||||
# construct dicts from names and values
|
||||
p_params = dict(zip(p_parameter_names, p_parameter_values))
|
||||
s_params = dict(zip(s_parameter_names, s_parameter_values))
|
||||
first_motion_params = dict(zip(first_motion_names, fm_parameter_values))
|
||||
signal_length_params = dict(zip(signal_length_names, sl_parameter_values))
|
||||
|
||||
p_params['use_taup'] = real_Bool(p_params['use_taup'])
|
||||
|
||||
return PickingParameters(p_params), PickingParameters(s_params), PickingParameters(first_motion_params), PickingParameters(signal_length_params)
|
||||
|
||||
def get_components_from_waveformstream(self):
|
||||
"""
|
||||
Splits waveformstream into multiple components zdat, ndat, edat. For traditional orientation (ZNE) these contain
|
||||
the vertical, north-south or east-west component. Otherwise they contain components numbered 123 with
|
||||
orientation diverging from the traditional orientation.
|
||||
:param waveformstream: Stream containing all three components for one station either by ZNE or 123 channel code
|
||||
(mixture of both options is handled as well)
|
||||
:type waveformstream: obspy.core.stream.Stream
|
||||
:return: Tuple containing (z waveform, n waveform, e waveform) selected by the given channels
|
||||
:rtype: (obspy.core.stream.Stream, obspy.core.stream.Stream, obspy.core.stream.Stream)
|
||||
"""
|
||||
|
||||
waveform_data = {}
|
||||
for key in self.channelorder:
|
||||
waveform_data[key] = self.wfstream.select(component=key) # try ZNE first
|
||||
if len(waveform_data[key]) == 0:
|
||||
waveform_data[key] = self.wfstream.select(component=str(self.channelorder[key])) # use 123 as second option
|
||||
return waveform_data['Z'], waveform_data['N'], waveform_data['E']
|
||||
|
||||
def prepare_wfstream(self, wfstream, freqmin=None, freqmax=None):
|
||||
"""
|
||||
Prepare a waveformstream for picking by applying detrending, filtering and tapering. Creates a copy of the
|
||||
waveform the leave the original unchanged.
|
||||
:param wfstream:
|
||||
:type wfstream:
|
||||
:param freqmin:
|
||||
:type freqmin:
|
||||
:param freqmax:
|
||||
:type freqmax:
|
||||
:return: Tuple containing the changed waveform stream and the first trace of the stream
|
||||
:rtype: (obspy.core.stream.Stream, obspy.core.trace.Trace)
|
||||
"""
|
||||
wfstream_copy = wfstream.copy()
|
||||
trace_copy = wfstream[0].copy()
|
||||
trace_copy.detrend(type=self.detrend_type)
|
||||
trace_copy.filter(self.filter_type, freqmin=freqmin, freqmax=freqmax, zerophase=self.zerophase)
|
||||
trace_copy.taper(max_percentage=self.taper_max_percentage, type=self.taper_type)
|
||||
wfstream_copy[0].data = trace_copy.data
|
||||
return wfstream_copy, trace_copy
|
||||
|
||||
def modify_starttimes_taupy(self):
|
||||
"""
|
||||
Calculate theoretical arrival times for a source at self.origin and a station at self.metadata. Modify
|
||||
self.pstart and self.pstop so they are based on a time window around these theoretical arrival times.
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
def create_arrivals(metadata, origin, station_id, taup_model):
|
||||
"""
|
||||
Create List of arrival times for all phases for a given origin and station
|
||||
:param metadata: tuple containing metadata type string and Parser object read from inventory file
|
||||
:type metadata: tuple (str, ~obspy.io.xseed.parser.Parser)
|
||||
:param origin: list containing origin objects representing origins for all events
|
||||
:type origin: list(~obspy.core.event.origin)
|
||||
:param station_id: Station id with format NETWORKNAME.STATIONNAME
|
||||
:type station_id: str
|
||||
:param taup_model: Model name to use. See obspy.taup.tau.TauPyModel for options
|
||||
:type taup_model: str
|
||||
:return: List of Arrival objects
|
||||
:rtype: obspy.taup.tau.Arrivals
|
||||
"""
|
||||
parser = metadata[1]
|
||||
station_coords = get_source_coords(parser, station_id)
|
||||
source_origin = origin[0]
|
||||
model = TauPyModel(taup_model)
|
||||
arrivals = model.get_travel_times_geo(source_depth_in_km=source_origin.depth,
|
||||
source_latitude_in_deg=source_origin.latitude,
|
||||
source_longitude_in_deg=source_origin.longitude,
|
||||
receiver_latitude_in_deg=station_coords['latitude'],
|
||||
receiver_longitude_in_deg=station_coords['longitude'])
|
||||
return arrivals
|
||||
|
||||
def first_PS_onsets(arrivals):
|
||||
"""
|
||||
Get first P and S onsets from arrivals list
|
||||
:param arrivals: List of Arrival objects
|
||||
:type arrivals: obspy.taup.tau.Arrivals
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
phases = {'P': [], 'S': []}
|
||||
# sort phases in P and S phases
|
||||
for arr in arrivals:
|
||||
phases[identifyPhaseID(arr.phase.name)].append(arr)
|
||||
# get first P and S onsets from arrivals list
|
||||
arrP, estFirstP = min([(arr, arr.time) for arr in phases['P']], key=lambda t: t[1])
|
||||
arrS, estFirstS = min([(arr, arr.time) for arr in phases['S']], key=lambda t: t[1])
|
||||
print('autopick: estimated first arrivals for P: {} s, S:{} s after event'
|
||||
' origin time using TauPy'.format(estFirstP, estFirstS))
|
||||
return estFirstP, estFirstS
|
||||
|
||||
print('autopickstation: use_taup flag active.')
|
||||
if not self.metadata[1]:
|
||||
raise AttributeError('Warning: Could not use TauPy to estimate onsets as there are no metadata given.')
|
||||
if not self.origin:
|
||||
raise AttributeError('No source origins given!')
|
||||
arrivals = create_arrivals(self.metadata, self.origin, self.station_id, self.p_params.taup_model)
|
||||
estFirstP, estFirstS = first_PS_onsets(arrivals)
|
||||
# modifiy pstart and pstop relative to estimated first P arrival (relative to station time axis)
|
||||
self.p_params.pstart += (self.origin[0].time + estFirstP) - self.zstream[0].stats.starttime
|
||||
self.p_params.pstop += (self.origin[0].time + estFirstP) - self.zstream[0].stats.starttime
|
||||
print('autopick: CF calculation times respectively:'
|
||||
' pstart: {} s, pstop: {} s'.format(self.p_params.pstart, self.p_params.pstop))
|
||||
# make sure pstart and pstop are inside the starttime/endtime of vertical trace
|
||||
self.p_params.pstart = max(self.p_params.pstart, 0)
|
||||
self.p_params.pstop = min(self.p_params.pstop, len(self.zstream[0]) * self.zstream[0].stats.starttime)
|
||||
|
||||
def autopickstation(self):
|
||||
self.zstream, self.nstream, self.estream = self.get_components_from_waveformstream()
|
||||
self.ztrace, self.ntrace, self.etrace = self.zstream[0], self.nstream[0], self.estream[0]
|
||||
|
||||
try:
|
||||
self.pick_p_phase()
|
||||
#TODO handle exceptions correctly
|
||||
# requires an overlook of what should be returned in case picking fails at various stages
|
||||
except MissingTraceException as mte:
|
||||
print(mte)
|
||||
return
|
||||
|
||||
def pick_p_phase(self):
|
||||
"""
|
||||
Pick p phase, return results
|
||||
:return: P pick results
|
||||
:rtype: PickingResults
|
||||
:raises:
|
||||
MissingTraceException: If vertical trace is missing.
|
||||
"""
|
||||
if not self.zstream or self.zstream is None:
|
||||
raise MissingTraceException('No z-component found for station {}'.format(self.station_name))
|
||||
|
||||
msg = '##################################################\nautopickstation:' \
|
||||
' Working on P onset of station {station}\nFiltering vertical ' \
|
||||
'trace ...\n{data}'.format(station=self.station_name, data=str(self.zstream))
|
||||
self.vprint(msg)
|
||||
z_copy, tr_filt = self.prepare_wfstream(self.wfstream, self.p_params.bpz1[0], self.p_params.bpz2[0])
|
||||
if self.p_params.use_taup is True and self.origin is not None:
|
||||
Lc = np.inf # what is Lc? DA
|
||||
try:
|
||||
self.modify_starttimes_taupy()
|
||||
except AttributeError as ae:
|
||||
print(ae)
|
||||
else:
|
||||
Lc = self.p_params.pstop - self.p_params.pstop
|
||||
Lwf = self.ztrace.stats.endtime - self.ztrace.stats.starttime
|
||||
|
||||
|
||||
def autopickstation(wfstream, pickparam, verbose=False,
|
||||
iplot=0, fig_dict=None, metadata=None, origin=None):
|
||||
|
Loading…
Reference in New Issue
Block a user