def testCum(self): t = self.mseed[0] # we did not write any processing to the trace: assert 'processing' not in t.stats or not t.stats.processing c1 = cumsumsq(t) assert t is not c1 assert not np.allclose(t.data, c1.data, equal_nan=True) assert max(c1.data) <= 1 # we wrote processing information in the trace: assert c1.stats.processing assert cumsumsq.__name__ in c1.stats.processing[0] c3 = cumsumsq(t, normalize=False) assert t is not c3 assert not np.allclose(c1.data, c3.data, equal_nan=True) assert max(c3.data) > 1 # we wrote processing information in the trace: assert c3.stats.processing assert cumsumsq.__name__ in c3.stats.processing[0] c2 = cumsumsq(t, copy=False) assert t is c2 assert max(c2.data) <= 1 assert np.allclose(c1.data, c2.data, equal_nan=True) # we wrote processing information in the trace: assert t.stats.processing assert cumsumsq.__name__ in c3.stats.processing[0]
def test_timeswhere(self): c1 = cumsumsq(self.mseed[0], normalize=True) t0, t1 = timeswhere(c1, 0, 1) assert t0 == c1.stats.starttime assert t1 == c1.stats.endtime assert c1[0] == 0 assert c1[-1] == 1 c2 = cumsumsq(self.mseed[0], normalize=False) t0, t1 = timeswhere(c2, 0, 1) assert t0 == c2.stats.starttime assert t1 < c2.stats.endtime assert c2[0] > 0 assert c2[-1] > 1 t0, t1 = timeswhere(c1, 0.1, .9) assert t0 > c1.stats.starttime assert t1 < c1.stats.endtime # test the old implementation, and check that values are the same: starttime = c1.stats.starttime delta = c1.stats.delta tracedata = c1.data tt0, tt1 = [ starttime + delta * np.searchsorted(tracedata, v) for v in (0.1, .9) ] assert t0 == tt0 and t1 == tt1 # padding with nans does not change the result: # left pad with nan tmp_pt = c1.data[0] c1.data[0] = np.nan t0, t1 = timeswhere(c1, 0, 1) assert t0 == c1.stats.starttime assert t1 == c1.stats.endtime c1.data[0] = tmp_pt # right pad with nan: tmp_pt = c1.data[-1] c1.data[-1] = np.nan t0, t1 = timeswhere(c1, 0, 1) assert t0 == c1.stats.starttime assert t1 == c1.stats.endtime c1.data[-1] = tmp_pt # test what happens if all are nans c1.data = np.array([np.nan] * len(c1.data)) t0, t1 = timeswhere(c1, 0, 1) assert t0 == t1 == c1.stats.starttime
def cumsumsq_normalized(segment, config): '''Computes the cumulative of the squares of the segment's trace in the form of a Plot object. DOES modify the segment's stream or traces in-place. Normalizes the returned trace values in [0,1] -Being decorated with '@gui.sideplot' or '@gui.customplot', this function must return a numeric sequence y taken at successive equally spaced points in any of these forms: - a Trace object - a Stream object - the tuple (x0, dx, y) or (x0, dx, y, label), where - x0 (numeric, `datetime` or `UTCDateTime`) is the abscissa of the first point - dx (numeric or `timedelta`) is the sampling period - y (numpy array or numeric list) are the sequence values - label (string, optional) is the sequence name to be displayed on the plot legend. (if x0 is numeric and `dx` is a `timedelta` object, then x0 will be converted to `UTCDateTime(x0)`; if x0 is a `datetime` or `UTCDateTime` object and `dx` is numeric, then `dx` will be converted to `timedelta(seconds=dx)`) - a dict of any of the above types, where the keys (string) will denote each sequence name to be displayed on the plot legend. :return: an obspy.Trace :raise: an Exception if `segment.stream()` is empty or has more than one trace (possible gaps/overlaps) ''' stream = segment.stream() assert1trace(stream) # raise and return if stream has more than one trace return cumsumsq(stream[0], normalize=True)
def cumulative(segment, config): '''Computes the cumulative of the squares of the segment's trace in the form of a Plot object. Modifies the segment's stream or traces in-place. Normalizes the returned trace values in [0,1] :return: an obspy.Trace :raise: an Exception if `segment.stream()` is empty or has more than one trace (possible gaps/overlaps) ''' stream = segment.stream() assert1trace(stream) # raise and return if stream has more than one trace return cumsumsq(stream[0], normalize=True, copy=False)
def derivcum2(segment, config): """ compute the second derivative of the cumulative function using savitzy-golay. Modifies the segment's stream or traces in-place :return: the tuple (starttime, timedelta, values) :raise: an Exception if `segment.stream()` is empty or has more than one trace (possible gaps/overlaps) """ stream = segment.stream() assert1trace(stream) # raise and return if stream has more than one trace cum = cumsumsq(stream[0], normalize=True, copy=False) cfg = config['savitzky_golay'] sec_der = savitzky_golay(cum.data, cfg['wsize'], cfg['order'], cfg['deriv']) sec_der_abs = np.abs(sec_der) sec_der_abs /= np.nanmax(sec_der_abs) # the stream object has surely only one trace (see 'cumulative') return segment.stream()[0].stats.starttime, segment.stream( )[0].stats.delta, sec_der_abs
def main(segment, config): """{{ PROCESS_PY_MAINFUNC | indent }} """ stream = segment.stream() assert1trace(stream) # raise and return if stream has more than one trace trace = stream[0] # work with the (surely) one trace now # discard saturated signals (according to the threshold set in the config file): amp_ratio = ampratio(trace) if amp_ratio >= config['amp_ratio_threshold']: raise ValueError('possibly saturated (amp. ratio exceeds)') # bandpass the trace, according to the event magnitude. # WARNING: this modifies the segment.stream() permanently! # If you want to preserve the original stream, store trace.copy() beforehand. # Also, use a 'try catch': sometimes Inventories are corrupted and obspy raises # a TypeError, which would break the WHOLE processing execution. # Raising a ValueError will stop the execution of the currently processed # segment only (logging the error message): try: trace = bandpass_remresp(segment, config) except TypeError as type_error: raise ValueError("Error in 'bandpass_remresp': %s" % str(type_error)) spectra = signal_noise_spectra(segment, config) normal_f0, normal_df, normal_spe = spectra['Signal'] noise_f0, noise_df, noise_spe = spectra['Noise'] evt = segment.event fcmin = mag2freq(evt.magnitude) fcmax = config['preprocess'][ 'bandpass_freq_max'] # used in bandpass_remresp snr_ = snr(normal_spe, noise_spe, signals_form=config['sn_spectra']['type'], fmin=fcmin, fmax=fcmax, delta_signal=normal_df, delta_noise=noise_df) snr1_ = snr(normal_spe, noise_spe, signals_form=config['sn_spectra']['type'], fmin=fcmin, fmax=1, delta_signal=normal_df, delta_noise=noise_df) snr2_ = snr(normal_spe, noise_spe, signals_form=config['sn_spectra']['type'], fmin=1, fmax=10, delta_signal=normal_df, delta_noise=noise_df) snr3_ = snr(normal_spe, noise_spe, signals_form=config['sn_spectra']['type'], fmin=10, fmax=fcmax, delta_signal=normal_df, delta_noise=noise_df) if snr_ < config['snr_threshold']: raise ValueError('low snr %f' % snr_) # calculate cumulative cum_labels = [0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99] cum_trace = cumsumsq( trace, normalize=True, copy=True) # copy=True prevent original trace from being modified cum_times = timeswhere(cum_trace, *cum_labels) # double event try: (score, t_double, tt1, tt2) = \ get_multievent_sg( cum_trace, cum_times[1], cum_times[-2], config['savitzky_golay'], config['multievent_thresholds'] ) except IndexError as _ierr: raise ValueError("Error in 'get_multievent_sg': %s" % str(_ierr)) if score in {1, 3}: raise ValueError('Double event detected %d %s %s %s' % (score, t_double, tt1, tt2)) # calculate PGA and times of occurrence (t_PGA): # note: you can also provide tstart tend for slicing t_PGA, PGA = maxabs(trace, cum_times[1], cum_times[-2]) trace_int = trace.copy() trace_int.integrate() t_PGV, PGV = maxabs(trace_int, cum_times[1], cum_times[-2]) meanoff = meanslice(trace_int, 100, cum_times[-1], trace_int.stats.endtime) # calculates amplitudes at the frequency bins given in the config file: required_freqs = config['freqs_interp'] ampspec_freqs = normal_f0 + np.arange(len(normal_spe)) * normal_df required_amplitudes = np.interp(np.log10(required_freqs), np.log10(ampspec_freqs), normal_spe) / segment.sample_rate # compute synthetic WA. trace_wa = synth_wood_anderson(segment, config, trace.copy()) t_WA, maxWA = maxabs(trace_wa) # write stuff to csv: ret = OrderedDict() ret['snr'] = snr_ ret['snr1'] = snr1_ ret['snr2'] = snr2_ ret['snr3'] = snr3_ for cum_lbl, cum_t in zip(cum_labels[slice(1, 8, 3)], cum_times[slice(1, 8, 3)]): ret['cum_t%f' % cum_lbl] = float( cum_t) # convert cum_times to float for saving ret['dist_deg'] = segment.event_distance_deg # dist ret['dist_km'] = d2km(segment.event_distance_deg) # dist_km # t_PGA is a obspy UTCDateTime. This type is not supported in HDF output, thus # convert it to Python datetime. Note that in CSV output, the value will be written as # str(t_PGA.datetime): another option might be to store it as string # with str(t_PGA) (returns the iso-formatted string, supported in all output formats): ret['t_PGA'] = t_PGA.datetime # peak info ret['PGA'] = PGA # (for t_PGV, see note above for t_PGA) ret['t_PGV'] = t_PGV.datetime # peak info ret['PGV'] = PGV # (for t_WA, see note above for t_PGA) ret['t_WA'] = t_WA.datetime ret['maxWA'] = maxWA ret['channel'] = segment.channel.channel ret['channel_component'] = segment.channel.channel[-1] # event metadata: ret['ev_id'] = segment.event.id ret['ev_lat'] = segment.event.latitude ret['ev_lon'] = segment.event.longitude ret['ev_dep'] = segment.event.depth_km ret['ev_mag'] = segment.event.magnitude ret['ev_mty'] = segment.event.mag_type # station metadata: ret['st_id'] = segment.station.id ret['st_name'] = segment.station.station ret['st_net'] = segment.station.network ret['st_lat'] = segment.station.latitude ret['st_lon'] = segment.station.longitude ret['st_ele'] = segment.station.elevation ret['score'] = score ret['d2max'] = float(tt1) ret['offset'] = np.abs(meanoff / PGV) for freq, amp in zip(required_freqs, required_amplitudes): ret['f_%.5f' % freq] = float(amp) return ret
def _main(segment, config, raw_trace_for_noisepsd, inventory_used): """ called by main with supplied inventory_used, which MUST be the inventory used on the raw trace to obtain `segment.stream()[0]` """ trace = segment.stream()[0] # cumulative of squares: cum_labels = [0.05, 0.95] cum_trace = cumsumsq(trace, normalize=True, copy=True) cum_times = timeswhere(cum_trace, *cum_labels) # Caluclate PGA and PGV # FIXME! THERE IS AN ERROR HERE WE SHOULD ITNEGRATE ONLY IF WE HAVE AN # ACCELEROMETER! ISN't IT? t_PGA, PGA = maxabs(trace, cum_times[0], cum_times[-1]) trace_int = trace.copy().integrate() t_PGV, PGV = maxabs(trace_int, cum_times[0], cum_times[-1]) # CALCULATE SPECTRA (SIGNAL and NOISE) spectra = _sn_spectra(segment, config) normal_f0, normal_df, normal_spe = spectra['Signal'] noise_f0, noise_df, noise_spe = spectra['Noise'] # @UnusedVariable # AMPLITUDE (or POWER) SPECTRA VALUES and FREQUENCIES: required_freqs = config['freqs_interp'] ampspec_freqs = normal_f0 + normal_df * np.arange(len(normal_spe)) required_amplitudes = np.interp(np.log10(required_freqs), np.log10(ampspec_freqs), normal_spe) / segment.sample_rate # SNR: magnitude = segment.event.magnitude fcmin = mag2freq(magnitude) fcmax = config['preprocess']['bandpass_freq_max'] # used in bandpass_remresp spectrum_type = config['sn_spectra']['type'] snr_ = snr(normal_spe, noise_spe, signals_form=spectrum_type, fmin=fcmin, fmax=fcmax, delta_signal=normal_df, delta_noise=noise_df) # PSD NOISE VALUES: # FIXME! DO I HAVE TO PASS THE PROCESSED TRACE (AS IT IS) or THE RAW ONE # (segment.stream(True)[0])? required_psd_periods = config['noise_psd_periods'] required_psd_values = psd_values(segment, required_psd_periods, raw_trace_for_noisepsd, inventory_used) # calculates amplitudes at the frequency bins given in the config file: # write stuff to csv: ret = OrderedDict() distance = segment.event_distance_km ret['event_id'] = segment.event_id ret['station_id'] = segment.station.id ret['event_time'] = segment.event.time ret['snr'] = snr_ ret['magnitude'] = magnitude ret['distance_km'] = distance ret['pga_observed'] = PGA ret['pga_predicted'] = gmpe_reso_14(magnitude, distance, mode='pga') ret['pgv_observed'] = PGV ret['pgv_predicted'] = gmpe_reso_14(magnitude, distance, mode='pgv') for f, a in zip(required_freqs, required_amplitudes): ret['%s@%shz' % (spectrum_type, str(f))] = float(a) for f, a in zip(required_psd_periods, required_psd_values): ret['noise_psd@%ssec' % str(f)] = float(a) ret['outlier'] = 0 ret['modified'] = '' return ret