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)) |         print('Could not get source coordinates for station {}: {}'.format(station_id, e)) | ||||||
|     return station_coords |     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, | def autopickstation(wfstream, pickparam, verbose=False, | ||||||
|                     iplot=0, fig_dict=None, metadata=None, origin=None): |                     iplot=0, fig_dict=None, metadata=None, origin=None): | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user