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