def corr_cluster(trace_list, thresh=0.9): """ Group traces based on correlations above threshold with the stack - will run twice, once with a lower threshold, then again with your threshold to remove large outliers :type trace_list: List of :class:obspy.Trace :param trace_list: Traces to compute similarity between :type thresh: float :param thrsh: Correlation threshold between -1-1 :returns: np.ndarray of bool """ from eqcorrscan.utils import stacking from obspy import Stream from core.match_filter import normxcorr2 stack=stacking.linstack([Stream(tr) for tr in trace_list])[0] output=np.array([False]*len(trace_list)) group1=[] for i, tr in enumerate(trace_list): if normxcorr2(tr.data,stack.data)[0][0] > 0.6: output[i]=True group1.append(tr) stack=stacking.linstack([Stream(tr) for tr in group1])[0] group2=[] for i, tr in enumerate(trace_list): if normxcorr2(tr.data,stack.data)[0][0] > thresh: group2.append(tr) output[i]=True else: output[i]=False return output
def cross_chan_coherence(st1, st2): """ Function to determine the cross-channel coherancy between two streams of multichannel seismic data. :type st1: obspy Stream :param st1: Stream one :type st2: obspy Stream :param st2: Stream two :returns: cross channel coherence, float - normalized by number of channels """ from eqcorrscan.core.match_filter import normxcorr2 cccoh=0.0 kchan=0 for tr in st1: tr1=tr.data # Assume you only have one waveform for each channel tr2=st2.select(station=tr.stats.station, \ channel=tr.stats.channel) if tr2: cccoh+=normxcorr2(tr1,tr2[0].data)[0][0] kchan+=1 cccoh=cccoh/kchan return cccoh
def test_interp_not_enough_samples(self): synth_template = np.sin(np.arange(0, 2, 0.001)) synth_detection = synth_template[11:] synth_template = synth_template[0:-10] ccc = normxcorr2(synth_detection, synth_template)[0] with self.assertRaises(IndexError): _xcorr_interp(ccc, 0.01)
def cross_chan_coherence(st1, st2, i=0): """ Function to determine the cross-channel coherancy between two streams of multichannel seismic data. :type st1: obspy Stream :param st1: Stream one :type st2: obspy Stream :param st2: Stream two :type i: int :param i: index used for parallel async processing, returned unaltered :returns: cross channel coherence, float - normalized by number of channels, if i, returns tuple of (cccoh, i) where i is int, as intput. """ from eqcorrscan.core.match_filter import normxcorr2 cccoh=0.0 kchan=0 for tr in st1: tr1=tr.data # Assume you only have one waveform for each channel tr2=st2.select(station=tr.stats.station, \ channel=tr.stats.channel) if tr2: cccoh+=normxcorr2(tr1,tr2[0].data)[0][0] kchan+=1 if kchan: cccoh=cccoh/kchan return (cccoh, i) else: warnings.warn('No matching channels') return (0, i)
def cassm_clock_correct(gmug_tr, vbox_tr, trig_tr, which=0, debug=0, name=None): """ Find first CASSM shot in common and use cross correlation to estimate the clock offset between the two systems. Will be done for each GMuG file, but not each Vibbox file :param gmug_st: Trace of B81 on gmug :param vbox_st: Trace of B81 on vbox :param trig_tr: Trace of the CASSM trigger :param which: 0 for first or -1 for last trigger :param debug: Debug flag for correlation plot :param name: Name of output h5 file for plot naming if debug > 0 :return: """ # Use derivative of PPS signal to find pulse start dt = np.diff(trig_tr.data) # Use 70 * MAD threshold samp_to_trig = np.where( dt > np.mean(dt) + 70 * median_absolute_deviation(dt))[0][which] trig1_time = vbox_tr.stats.starttime + (float(samp_to_trig) / vbox_tr.stats.sampling_rate) print(' Trigger: {}'.format(trig1_time)) cc_vbox = vbox_tr.copy().trim(trig1_time, endtime=trig1_time + 0.01).detrend('demean') cc_gmug = gmug_tr.copy().trim(trig1_time, endtime=trig1_time + 0.2).detrend('demean') print(' Vbox {}--{}'.format(vbox_tr.stats.starttime, vbox_tr.stats.endtime)) print(' GMuG {}--{}'.format(gmug_tr.stats.starttime, gmug_tr.stats.endtime)) try: cc_gmug.resample(cc_vbox.stats.sampling_rate) except AttributeError as e: # Outside range of gmug waveform return 0., np.array([0.0]), UTCDateTime() ccc = normxcorr2(cc_vbox.data, cc_gmug.data) max_cc = np.argmax(ccc[0]) max_cc_sec = float(max_cc) / cc_vbox.stats.sampling_rate if debug > 0: fig, axes = plt.subplots(nrows=2) vbox_x = np.arange(start=max_cc, stop=max_cc + cc_vbox.data.shape[0]) axes[0].plot(cc_gmug.data / np.max(cc_gmug.data), color='k', linewidth=0.7) axes[1].axvline(x=max_cc, linestyle=':', color='gray') axes[0].axvline(x=max_cc, linestyle=':', color='gray') axes[0].plot(vbox_x, cc_vbox.data / np.max(cc_vbox.data), color='r', linewidth=0.7) axes[1].plot(ccc[0], color='b', linewidth=0.7) plt.savefig(name.replace('.h5', 'time_cc.png')) plt.close('all') return max_cc_sec, ccc, trig1_time
def test_interp_few_samples(self): synth_template = np.sin(np.arange(0, 2, 0.001)) synth_detection = synth_template[13:] synth_template = synth_template[0:-10] ccc = normxcorr2(synth_detection, synth_template) shift, coeff = lag_calc._xcorr_interp(ccc, 0.01) self.assertEqual(shift.round(), 0.0) self.assertEqual(coeff.round(), 1.0)
def test_interp_normal(self): synth_template = np.sin(np.arange(0, 4, 0.01)) image = np.zeros(1000) image[200] = 1 image = np.convolve(image, synth_template) ccc = normxcorr2(synth_template, image) shift, coeff = _xcorr_interp(ccc, 0.01) self.assertEqual(shift.round(), 2.0) self.assertEqual(coeff.round(), 1.0)
def test_fail_normxcorr2(self): """Ensure if template is nan then return is nan """ template = np.array([np.nan] * 100) image = np.zeros(1000) image[200] = 1.0 image = np.convolve(template, image) ccc = normxcorr2(template, image) self.assertTrue(np.all(ccc == 0.0))
def test_perfect_normxcorr2(self): """Simple test of normxcorr2 to ensure data are detected """ template = np.random.randn(100).astype(np.float32) image = np.zeros(1000).astype(np.float32) image[200] = 1.0 image = np.convolve(template, image) ccc = normxcorr2(template, image).astype(np.float16) self.assertEqual(ccc.max(), 1.0)
def test_triple_plot(self): template = self.st[0].copy().trim(self.st[0].stats.starttime + 8, self.st[0].stats.starttime + 12) tr = self.st[0].copy() ccc = normxcorr2(template=template.data, image=tr.data) tr.data = tr.data[0:len(ccc[0])] fig = triple_plot(cccsum=ccc[0], cccsum_hist=ccc[0], trace=tr, threshold=0.8, show=False, return_figure=True) return fig
def template_remove(tr, template, cc_thresh, windowlength, interp_len, debug=0): """ Looks for instances of template in the trace and removes the matches. :type tr: obspy.core.Trace :param tr: Trace to remove spikes from. :type template: osbpy.core.Trace :param template: Spike template to look for in data. :type cc_thresh: float :param cc_thresh: Cross-correlation threshold (-1 - 1). :type windowlength: float :param windowlength: Length of window to look for spikes in in seconds. :type interp_len: float :param interp_len: Window length to remove and fill in seconds. :type debug: int :param debug: Debug level. :returns: tr, works in place. """ from eqcorrscan.core.match_filter import normxcorr2 from eqcorrscan.utils.findpeaks import find_peaks2_short from obspy import Trace from eqcorrscan.utils.timer import Timer import matplotlib.pyplot as plt import warnings data_in = tr.copy() _interp_len = int(tr.stats.sampling_rate * interp_len) if _interp_len < len(template.data): warnings.warn('Interp_len is less than the length of the template,' 'will used the length of the template!') _interp_len = len(template.data) if isinstance(template, Trace): template = template.data with Timer() as t: cc = normxcorr2(tr.data.astype(np.float32), template.astype(np.float32)) if debug > 3: plt.plot(cc.flatten(), 'k', label='cross-correlation') plt.legend() plt.show() peaks = find_peaks2_short(arr=cc.flatten(), thresh=cc_thresh, trig_int=windowlength * tr.stats. sampling_rate) for peak in peaks: tr.data = _interp_gap(data=tr.data, peak_loc=peak[1] + int(0.5 * _interp_len), interp_len=_interp_len) print("Despiking took: %s s" % t.secs) if debug > 2: plt.plot(data_in.data, 'r', label='raw') plt.plot(tr.data, 'k', label='despiked') plt.legend() plt.show() return tr
def test_normal_normxcorr2(self): """Check that if match is not perfect correlation max isn't unity """ template = np.random.randn(100) * 10.0 image = np.zeros(1000) image[200] = 1.0 image = np.convolve(template, image) image += np.random.randn(1099) # Add random noise ccc = normxcorr2(template, image) self.assertNotEqual(ccc.max(), 1.0)
def corr_cluster(trace_list, thresh=0.9): """ Group traces based on correlations above threshold with the stack. Will run twice, once with a lower threshold to remove large outliers that would negatively affect the stack, then again with your threshold. :type trace_list: list :param trace_list: List of :class:`obspy.core.stream.Trace`s to compute similarity between :type thresh: float :param thresh: Correlation threshold between -1-1 :returns: :class:`numpy.ndarray` of bool of whether that trace correlates well enough (above your given threshold) with the stack. .. note:: We recommend that you align the data before computing the clustering, e.g., the P-arrival on all templates for the same channel should appear at the same time in the trace. See the :func:`eqcorrscan.utils.stacking.align_traces` function for a way to do this. """ stack = stacking.linstack([Stream(tr) for tr in trace_list])[0] output = np.array([False] * len(trace_list)) group1 = [] for i, tr in enumerate(trace_list): if normxcorr2(tr.data, stack.data)[0][0] > 0.6: output[i] = True group1.append(tr) if not group1: warnings.warn('Nothing made it past the first 0.6 threshold') return output stack = stacking.linstack([Stream(tr) for tr in group1])[0] group2 = [] for i, tr in enumerate(trace_list): if normxcorr2(tr.data, stack.data)[0][0] > thresh: group2.append(tr) output[i] = True else: output[i] = False return output
def template_remove(tr, template, cc_thresh, windowlength, interp_len, debug=0): """ Looks for instances of template in the trace and removes the matches. :type tr: obspy.core.trace.Trace :param tr: Trace to remove spikes from. :type template: osbpy.core.trace.Trace :param template: Spike template to look for in data. :type cc_thresh: float :param cc_thresh: Cross-correlation threshold (-1 - 1). :type windowlength: float :param windowlength: Length of window to look for spikes in in seconds. :type interp_len: float :param interp_len: Window length to remove and fill in seconds. :type debug: int :param debug: Debug level. :returns: tr, works in place. :rtype: :class:`obspy.core.trace.Trace` """ data_in = tr.copy() _interp_len = int(tr.stats.sampling_rate * interp_len) if _interp_len < len(template.data): warnings.warn('Interp_len is less than the length of the template,' 'will used the length of the template!') _interp_len = len(template.data) if isinstance(template, Trace): template = template.data with Timer() as t: cc = normxcorr2(image=tr.data.astype(np.float32), template=template.astype(np.float32)) if debug > 3: plt.plot(cc.flatten(), 'k', label='cross-correlation') plt.legend() plt.show() peaks = find_peaks2_short(arr=cc.flatten(), thresh=cc_thresh, trig_int=windowlength * tr.stats.sampling_rate) for peak in peaks: tr.data = _interp_gap(data=tr.data, peak_loc=peak[1] + int(0.5 * _interp_len), interp_len=_interp_len) print("Despiking took: %s s" % t.secs) if debug > 2: plt.plot(data_in.data, 'r', label='raw') plt.plot(tr.data, 'k', label='despiked') plt.legend() plt.show() return tr
def test_fail_normxcorr2(self): """Ensure it template is nan then return is nan """ import numpy as np from eqcorrscan.core.match_filter import normxcorr2 template = np.array([np.nan] * 100) image = np.zeros(1000) image[200] = 1.0 image = np.convolve(template, image) ccc = normxcorr2(template, image) self.assertTrue(np.all(ccc == 0.0))
def test_perfect_normxcorr2(self): """Simple test of normxcorr2 to ensure data are detected """ import numpy as np from eqcorrscan.core.match_filter import normxcorr2 template = np.random.randn(100).astype(np.float32) image = np.zeros(1000).astype(np.float32) image[200] = 1.0 image = np.convolve(template, image) ccc = normxcorr2(template, image).astype(np.float16) self.assertEqual(ccc.max(), 1.0)
def test_fail_normxcorr2(self): """Ensure if template is nan then return is nan """ import numpy as np from eqcorrscan.core.match_filter import normxcorr2 template = np.array([np.nan] * 100) image = np.zeros(1000) image[200] = 1.0 image = np.convolve(template, image) ccc = normxcorr2(template, image) self.assertTrue(np.all(ccc == 0.0))
def coherence(stream_in, stations=['all'], clip=False): """ Determine the average network coherence of a given template or detection. You will want your stream to contain only \ signal as noise will reduce the coherence (assuming it is incoherent \ random noise). :type stream_in: obspy.core.stream.Stream :param stream_in: The stream of seismic data you want to calculate the \ coherence for. :type stations: list :param stations: List of stations to use for coherence, default is all :type clip: tuple :param clip: Default is to use all the data given - \ tuple of start and end in seconds from start of trace :return: float - coherence, int number of channels used """ from eqcorrscan.core.match_filter import normxcorr2 stream = stream_in.copy() # Copy the data before we remove stations # First check that all channels in stream have data of the same length maxlen = np.max([len(tr.data) for tr in stream]) if maxlen == 0: warnings.warn('template without data') return 0.0, len(stream) if not stations[0] == 'all': for tr in stream: if tr.stats.station not in stations: stream.remove(tr) # Remove stations we don't want to use for tr in stream: if not len(tr.data) == maxlen and not len(tr.data) == 0: warnings.warn(tr.stats.station + '.' + tr.stats.channel + ' is not the same length, padding \n' + 'Length is ' + str(len(tr.data)) + ' samples') pad = np.zeros(maxlen - len(tr.data)) if tr.stats.starttime.hour == 0: tr.data = np.concatenate((pad, tr.data), axis=0) else: tr.data = np.concatenate((tr.data, pad), axis=0) elif len(tr.data) == 0: tr.data = np.zeros(maxlen) # Clip the data to the set length if clip: for tr in stream: tr.trim(tr.stats.starttime + clip[0], tr.stats.starttime + clip[1]) coherence = 0.0 # Loop through channels and generate a correlation value for each # unique cross-channel pairing for i in range(len(stream)): for j in range(i + 1, len(stream)): coherence += np.abs(normxcorr2(stream[i].data, stream[j].data))[0][0] coherence = 2 * coherence / (len(stream) * (len(stream) - 1)) return coherence, len(stream)
def test_xcorr_plot_cc_vec(self): st = self.st.copy().detrend('simple').filter( 'bandpass', freqmin=2, freqmax=15) cc_vec = normxcorr2( template=st[0].data.astype(np.float32)[40:-40], image=st[1].data.astype(np.float32)) cc_vec = cc_vec[0] fig = xcorr_plot( template=st[1].data[40:-40], image=st[0].data, cc_vec=cc_vec, show=False, return_figure=True) return fig
def coherence(stream_in, stations=['all'], clip=False): """ Determine the average network coherence of a given template or detection. You will want your stream to contain only signal as noise will reduce the coherence (assuming it is incoherent random noise). :type stream_in: obspy.core.stream.Stream :param stream_in: The stream of seismic data you want to calculate the \ coherence for. :type stations: list :param stations: List of stations to use for coherence, default is all :type clip: tuple :param clip: Default is to use all the data given (`False`) - \ tuple of start and end in seconds from start of trace :return: tuple of coherence and number of channels used. :rtype: tuple """ stream = stream_in.copy() # Copy the data before we remove stations # First check that all channels in stream have data of the same length maxlen = np.max([len(tr.data) for tr in stream]) if maxlen == 0: warnings.warn('template without data') return 0.0, len(stream) if not stations[0] == 'all': for tr in stream: if tr.stats.station not in stations: stream.remove(tr) # Remove stations we don't want to use for tr in stream: if not len(tr.data) == maxlen and not len(tr.data) == 0: warnings.warn(tr.stats.station + '.' + tr.stats.channel + ' is not the same length, padding \n' + 'Length is ' + str(len(tr.data)) + ' samples') pad = np.zeros(maxlen - len(tr.data)) if tr.stats.starttime.hour == 0: tr.data = np.concatenate((pad, tr.data), axis=0) else: tr.data = np.concatenate((tr.data, pad), axis=0) elif len(tr.data) == 0: tr.data = np.zeros(maxlen) # Clip the data to the set length if clip: for tr in stream: tr.trim(tr.stats.starttime + clip[0], tr.stats.starttime + clip[1]) _coherence = 0.0 # Loop through channels and generate a correlation value for each # unique cross-channel pairing for i in range(len(stream)): for j in range(i + 1, len(stream)): _coherence += np.abs(normxcorr2(stream[i].data, stream[j].data))[0][0] _coherence = 2 * _coherence / (len(stream) * (len(stream) - 1)) return _coherence, len(stream)
def corr_cluster(trace_list, thresh=0.9): """ Group traces based on correlations above threshold with the stack. Will run twice, once with a lower threshold, then again with your threshold to remove large outliers. :type trace_list: list of obspy.Trace :param trace_list: Traces to compute similarity between :type thresh: float :param thresh: Correlation threshold between -1-1 :returns: np.ndarray of bool .. note:: We recommend that you align the data before computing the \ clustering, e.g., the P-arrival on all templates for the same channel \ should appear at the same time in the trace. See the \ stacking.align_traces function for a way to do this """ from eqcorrscan.utils import stacking from obspy import Stream from eqcorrscan.core.match_filter import normxcorr2 stack = stacking.linstack([Stream(tr) for tr in trace_list])[0] output = np.array([False]*len(trace_list)) group1 = [] for i, tr in enumerate(trace_list): if normxcorr2(tr.data, stack.data)[0][0] > 0.6: output[i] = True group1.append(tr) if not group1: warnings.warn('Nothing made it past the first 0.6 threshold') return output stack = stacking.linstack([Stream(tr) for tr in group1])[0] group2 = [] for i, tr in enumerate(trace_list): if normxcorr2(tr.data, stack.data)[0][0] > thresh: group2.append(tr) output[i] = True else: output[i] = False return output
def corr_cluster(trace_list, thresh=0.9): """ Group traces based on correlations above threshold with the stack. Will run twice, once with a lower threshold, then again with your threshold to remove large outliers. :type trace_list: list of obspy.Trace :param trace_list: Traces to compute similarity between :type thresh: float :param thresh: Correlation threshold between -1-1 :returns: np.ndarray of bool .. note:: We recommend that you align the data before computing the \ clustering, e.g., the P-arrival on all templates for the same channel \ should appear at the same time in the trace. See the \ stacking.align_traces function for a way to do this """ from eqcorrscan.utils import stacking from obspy import Stream from eqcorrscan.core.match_filter import normxcorr2 stack = stacking.linstack([Stream(tr) for tr in trace_list])[0] output = np.array([False] * len(trace_list)) group1 = [] for i, tr in enumerate(trace_list): if normxcorr2(tr.data, stack.data)[0][0] > 0.6: output[i] = True group1.append(tr) if not group1: warnings.warn('Nothing made it past the first 0.6 threshold') return output stack = stacking.linstack([Stream(tr) for tr in group1])[0] group2 = [] for i, tr in enumerate(trace_list): if normxcorr2(tr.data, stack.data)[0][0] > thresh: group2.append(tr) output[i] = True else: output[i] = False return output
def test_normal_normxcorr2(self): """Check that if match is not perfect correlation max isn't unity """ import numpy as np from eqcorrscan.core.match_filter import normxcorr2 template = np.random.randn(100) * 10.0 image = np.zeros(1000) image[200] = 1.0 image = np.convolve(template, image) image += np.random.randn(1099) # Add random noise ccc = normxcorr2(template, image) self.assertNotEqual(ccc.max(), 1.0)
def cross_chan_coherence(st1, st2, allow_shift=False, shift_len=0.2, i=0): """ Calculate cross-channel coherency. Determine the cross-channel coherency between two streams of multichannel seismic data. :type st1: obspy.core.stream.Stream :param st1: Stream one :type st2: obspy.core.stream.Stream :param st2: Stream two :type allow_shift: bool :param allow_shift: Whether to allow the optimum alignment to be found for coherence, defaults to `False` for strict coherence :type shift_len: int :param shift_len: Samples to shift, only used if `allow_shift=True` :type i: int :param i: index used for parallel async processing, returned unaltered :returns: cross channel coherence, float - normalized by number of channels, and i, where i is int, as input. :rtype: tuple """ cccoh = 0.0 kchan = 0 if allow_shift: for tr in st1: tr2 = st2.select(station=tr.stats.station, channel=tr.stats.channel) if tr2: index, corval = xcorr(tr, tr2[0], shift_len) cccoh += corval kchan += 1 else: for tr in st1: tr1 = tr.data # Assume you only have one waveform for each channel tr2 = st2.select(station=tr.stats.station, channel=tr.stats.channel) if tr2: cccoh += normxcorr2(tr1, tr2[0].data)[0][0] kchan += 1 if kchan: cccoh /= kchan return cccoh, i else: warnings.warn('No matching channels') return 0, i
def plot_rand_correlation(cat, d_thresh, temp_dict, stream_dict): """ Calculate cross correlation coefficients for events located further than d_thresh from each other. This should represent correlation of uncorrelated signals and help determine ccval_cutoff :param cat: :param dist_thresh: :param temp_dict: :param stream_dict: :return: """ from eqcorrscan.utils.mag_calc import dist_calc from eqcorrscan.core.match_filter import normxcorr2 import numpy as np corrs = [] for i, ev in enumerate(cat): print i ev_tup = (ev.preferred_origin().latitude, ev.preferred_origin().longitude, ev.preferred_origin().depth / 1000.) for ev2 in cat[i+1:]: ev_tup2 = (ev2.preferred_origin().latitude, ev2.preferred_origin().longitude, ev2.preferred_origin().depth / 1000.) dist = dist_calc(ev_tup, ev_tup2) if dist > d_thresh: det_rid = str(ev2.resource_id).split('/')[-1] temp_rid = str(ev.resource_id).split('/')[-1].split('_')[0] temp = temp_dict[temp_rid + '_1sec'] stream = stream_dict[det_rid] for pk in ev.picks: if pk.phase_hint == 'P': sta = pk.waveform_id.station_code chan = pk.waveform_id.channel_code if len(temp.select(station=sta, channel=chan)) > 0: tr = temp.select(station=sta, channel=chan)[0] else: continue if len(stream.select(station=sta, channel=chan)) > 0: st_tr = stream.select(station=sta, channel=chan)[0] else: continue # # still correcting for 0.1 sec pre-pick time here...gross # pk_samp = # corr_start = pk_samp - 5 # corr_end = pk_samp + 6 ccc = normxcorr2(tr.data, st_tr.data[140:201])[0] corrs.append(max(ccc.max(), ccc.min(), key=abs)) return corrs
def cross_chan_coherence(st1, st2, allow_shift=False, shift_len=0.2, i=0): """ Calculate cross-channel coherency. Determine the cross-channel coherency between two streams of \ multichannel seismic data. :type st1: obspy.core.stream.Stream :param st1: Stream one :type st2: obspy.core.stream.Stream :param st2: Stream two :type allow_shift: bool :param allow_shift: Allow shift? :type shift_len: int :param shift_len: Samples to shift :type i: int :param i: index used for parallel async processing, returned unaltered :returns: cross channel coherence, float - normalized by number of\ channels, if i, returns tuple of (cccoh, i) where i is int, as input. """ from eqcorrscan.core.match_filter import normxcorr2 from obspy.signal.cross_correlation import xcorr cccoh = 0.0 kchan = 0 if allow_shift: for tr in st1: tr2 = st2.select(station=tr.stats.station, channel=tr.stats.channel) if tr2: index, corval = xcorr(tr, tr2[0], shift_len) cccoh += corval kchan += 1 else: for tr in st1: tr1 = tr.data # Assume you only have one waveform for each channel tr2 = st2.select(station=tr.stats.station, channel=tr.stats.channel) if tr2: cccoh += normxcorr2(tr1, tr2[0].data)[0][0] kchan += 1 if kchan: cccoh = cccoh / kchan return (cccoh, i) else: warnings.warn('No matching channels') return (0, i)
def test_set_normxcorr2(self): """Check that correlations output are the same irrespective of version. """ testing_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'test_data') template = read(os.path.join(testing_path, 'test_template.ms')) template = template[0].data.astype(np.float32) image = read(os.path.join(testing_path, 'test_image.ms')) image = image[0].data.astype(np.float32) ccc = normxcorr2(template, image)[0] expected_ccc = np.load(os.path.join(testing_path, 'test_ccc.npy')) # We know that conda installs of openCV give a different results # to source built - allow this and allow it to pass. self.assertTrue((np.gradient(expected_ccc).round(2) == np.gradient(ccc).round(2)).all()) if not (ccc == expected_ccc).all(): warnings.warn('The expected result was not achieved, ' + 'but it has the same shape')
def test_set_normxcorr2(self): """Check that correlations output are the same irrespective of version. """ import numpy as np from eqcorrscan.core.match_filter import normxcorr2 from obspy import read import os import warnings testing_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'test_data') template = read(os.path.join(testing_path, 'test_template.ms')) template = template[0].data.astype(np.float32) image = read(os.path.join(testing_path, 'test_image.ms')) image = image[0].data.astype(np.float32) ccc = normxcorr2(template, image)[0] expected_ccc = np.load(os.path.join(testing_path, 'test_ccc.npy')) # We know that conda installs of openCV give a different results # to source built - allow this and allow it to pass. self.assertTrue((np.gradient(expected_ccc).round(2) == np.gradient(ccc).round(2)).all()) if not (ccc == expected_ccc).all(): warnings.warn('The expected result was not achieved')
def weight_corr_picks(cat, temp_dict=None, stream_dict=None, method='SNR_temp', temp_cat=False, show_ccs=False): """ Implementing pick weighting by SNR of cc function :param cat: catalog of detections :param template_dir: directory where the templates live :param stream: directory where the longer event waveforms :param method: 'SNR_temp', 'SNR_ccval', 'doub_diff_ccval' :return: obspy.core.event.Catalog ** Note: Hypoellipse default quality mapping: 0.05, 0.1, 0.2, 0.4 sec (0, 1, 2, 3?) We can go with these values based on SNR of cc function, I suppose. """ import warnings import numpy as np from eqcorrscan.core.match_filter import normxcorr2 import matplotlib.pyplot as plt from obspy.core.event import QuantityError SNRs = [] if temp_cat: temp_SNRs = {str(ev.resource_id).split('/')[-1].split('_')[0]: {amp.waveform_id.station_code + '.EZ': amp.snr for amp in ev.amplitudes} for ev in temp_cat} for ev in cat: det_rid = str(ev.resource_id).split('/')[-1] temp_rid = str(ev.resource_id).split('/')[-1].split('_')[0] picks = list(ev.picks) for i, pk in enumerate(picks): if pk.phase_hint == 'P': sta = pk.waveform_id.station_code chan = pk.waveform_id.channel_code if method == 'SNR_ccval': temp = temp_dict[temp_rid + '_1sec'] stream = stream_dict[det_rid] tr = temp.select(station=sta, channel=chan)[0] st_tr = stream.select(station=sta, channel=chan)[0] ccc = normxcorr2(tr.data, st_tr.data)[0] pk_samp = int(tr.stats.sampling_rate * (pk.time - st_tr.stats.starttime) - 5) sta_start = pk_samp - 5 sta_end = pk_samp + 5 LTA = np.std(ccc) STA = np.std(ccc[sta_start:sta_end]) # STA = abs(ccc[pk_samp]) SNR = STA / LTA # Here we map the ccval SNR to time uncertainty in the original catalog orig_pk = ev.picks[i] if SNR < 0.75: orig_pk.time_errors = QuantityError(uncertainty=0.40) elif SNR < 1.25: orig_pk.time_errors = QuantityError(uncertainty=0.20) elif SNR < 1.75: orig_pk.time_errors = QuantityError(uncertainty=0.10) elif SNR < 2.25: orig_pk.time_errors = QuantityError(uncertainty=0.05) else: orig_pk.time_errors = QuantityError(uncertainty=0.01) SNRs.append(SNR) if show_ccs: fig, ax = plt.subplots() ax.plot(ccc) ax.set_title('%s.%s: %f' % (sta, chan, SNR)) fig.show() elif method == 'SNR_temp': orig_pk = ev.picks[i] try: SNR = temp_SNRs[temp_rid]['%s.%s' % (sta, chan)] except KeyError: warnings.warn('%s.%s has no amplitude pick' % (sta, chan)) orig_pk.time_errors = QuantityError(uncertainty=0.10) continue if SNR < 1.0: orig_pk.time_errors = QuantityError(uncertainty=0.20) elif SNR < 2.: orig_pk.time_errors = QuantityError(uncertainty=0.10) elif SNR < 5.: orig_pk.time_errors = QuantityError(uncertainty=0.05) else: orig_pk.time_errors = QuantityError(uncertainty=0.01) return cat
def _channel_loop(detection, template, min_cc, interpolate=False, i=0, debug=0): """ Inner loop for correlating and assigning picks. Utility function to take a stream of data for the detected event and write maximum correlation to absolute time as picks in an obspy.core.event.Event object. Only outputs picks for picks above min_cc. :type detection: obspy.core.stream.Stream :param detection: Stream of data for the slave event detected using \ template. :type template: obspy.core.stream.Stream :param template: Stream of data as the template for the detection. :type interpolate: bool :param interpolate: Interpolate the correlation function to achieve \ sub-sample precision. :type i: int :param i: Used to track which process has occurred when running in \ parallel. :returns: Event object containing net, sta, chan information :rtype: obspy.core.event.Event """ from obspy.core.event import Event, Pick, WaveformStreamID from obspy.core.event import ResourceIdentifier event = Event() s_stachans = {} used_s_sta = [] for tr in template: temp_net = tr.stats.network temp_sta = tr.stats.station temp_chan = tr.stats.channel image = detection.select(station=temp_sta, channel=temp_chan) if image: ccc = normxcorr2(tr.data, image[0].data) # Convert the maximum cross-correlation time to an actual time if debug > 3: print('********DEBUG: Maximum cross-corr=%s' % np.amax(ccc)) if np.amax(ccc) > min_cc: if interpolate: try: interp_max = _xcorr_interp(ccc=ccc, dt=image[0].stats.delta) except IndexError: print('Could not interpolate ccc, not smooth') interp_max = np.argmax(ccc) * image[0].stats.delta picktime = image[0].stats.starttime + interp_max else: picktime = image[0].stats.starttime + (np.argmax(ccc) * image[0].stats.delta) else: continue # Perhaps weight each pick by the cc val or cc val^2? # weight = np.amax(ccc) ** 2 if temp_chan[-1:] == 'Z': phase = 'P' # Only take the S-pick with the best correlation elif temp_chan[-1:] in ['E', 'N']: phase = 'S' if temp_sta not in s_stachans and np.amax(ccc) > min_cc: s_stachans[temp_sta] = ((temp_chan, np.amax(ccc), picktime)) elif temp_sta in s_stachans and np.amax(ccc) > min_cc: if np.amax(ccc) > s_stachans[temp_sta][1]: picktime = picktime else: picktime = s_stachans[temp_sta][2] temp_chan = s_stachans[temp_sta][0] elif np.amax(ccc) < min_cc and temp_sta not in used_s_sta: used_s_sta.append(temp_sta) else: continue else: phase = None _waveform_id = WaveformStreamID(network_code=temp_net, station_code=temp_sta, channel_code=temp_chan) event.picks.append(Pick(waveform_id=_waveform_id, time=picktime, method_id=ResourceIdentifier('EQcorrscan'), phase_hint=phase)) return (i, event)
def multi_event_singlechan(streams, catalog, clip=10.0, pre_pick=2.0, freqmin=False, freqmax=False, realign=False, cut=(-3.0, 5.0), PWS=False, title=False): r"""Function to plot data from a single channel at a single station for \ multiple events - data will be alligned by their pick-time given in the \ picks. :type streams: list of :class:obspy.stream :param streams: List of the streams to use, can contain more traces than \ you plan on plotting :type catalog: obspy.core.event.Catalog :param catalog: Catalog of events, one for each trace, with a single pick :type clip: float :param clip: Length in seconds to plot, defaults to 10.0 :type pre_pick: float :param pre_pick: Length in seconds to extract and plot before the pick, \ defaults to 2.0 :type freqmin: float :param freqmin: Low cut for bandpass in Hz :type freqmax: float :param freqmax: High cut for bandpass in Hz :type realign: bool :param realign: To compute best alignement based on correlation or not. :type cut: tuple :param cut: tuple of start and end times for cut in seconds from the pick :type PWS: bool :param PWS: compute Phase Weighted Stack, if False, will compute linear \ stack. :type title: str :param title: Plot title. :returns: Alligned and cut traces, and new picks """ from eqcorrscan.utils import stacking import copy from eqcorrscan.core.match_filter import normxcorr2 from obspy import Stream import warnings fig, axes = plt.subplots(len(catalog)+1, 1, sharex=True, figsize=(7, 12)) axes = axes.ravel() traces = [] al_traces = [] # Keep input safe clist = copy.deepcopy(catalog) st_list = copy.deepcopy(streams) for i, event in enumerate(clist): if st_list[i].select(station=event.picks[0].waveform_id.station_code, channel='*' + event.picks[0].waveform_id.channel_code[-1]): tr = st_list[i].select(station=event.picks[0].waveforms_id. station_code, channel='*' + event.picks[0].waveform_id. channel_code[-1])[0] else: print('No data for '+event.pick[0].waveform_id) continue tr.detrend('linear') if freqmin: tr.filter('bandpass', freqmin=freqmin, freqmax=freqmax) if realign: tr_cut = tr.copy() tr_cut.trim(event.picks[0].time + cut[0], event.picks[0].time + cut[1], nearest_sample=False) if len(tr_cut.data) <= (0.5 * (cut[1] - cut[0]) * tr_cut.stats.sampling_rate): msg = ''.join(['Not enough in the trace for ', tr.stats.station, '.', tr.stats.channel, '\n', 'Suggest removing pick from sfile at time ', str(event.picks[0].time)]) warnings.warn(msg) else: al_traces.append(tr_cut) else: tr.trim(event.picks[0].time - pre_pick, event.picks[0].time + clip - pre_pick, nearest_sample=False) if len(tr.data) == 0: msg = ''.join(['No data in the trace for ', tr.stats.station, '.', tr.stats.channel, '\n', 'Suggest removing pick from sfile at time ', str(event.picks[0].time)]) warnings.warn(msg) continue traces.append(tr) if realign: shift_len = int(0.25 * (cut[1] - cut[0]) * al_traces[0].stats.sampling_rate) shifts = stacking.align_traces(al_traces, shift_len) for i in xrange(len(shifts)): print('Shifting by '+str(shifts[i])+' seconds') event.picks[0].time -= shifts[i] traces[i].trim(event.picks[0].time - pre_pick, event.picks[0].time + clip-pre_pick, nearest_sample=False) # We now have a list of traces traces = [(trace, trace.stats.starttime.datetime) for trace in traces] traces.sort(key=lambda tup: tup[1]) traces = [trace[0] for trace in traces] # Plot the traces for i, tr in enumerate(traces): y = tr.data x = np.arange(len(y)) x = x / tr.stats.sampling_rate # convert to seconds axes[i+1].plot(x, y, 'k', linewidth=1.1) axes[i+1].yaxis.set_ticks([]) traces = [Stream(trace) for trace in traces] if PWS: linstack = stacking.PWS_stack(traces) else: linstack = stacking.linstack(traces) tr = linstack.select(station=event[0].picks[0].waveform_id.station_code, channel='*' + event[0].picks[0].waveform_id.channel_code[-1])[0] y = tr.data x = np.arange(len(y)) x = x / tr.stats.sampling_rate axes[0].plot(x, y, 'r', linewidth=2.0) axes[0].set_ylabel('Stack', rotation=0) axes[0].yaxis.set_ticks([]) for i, slave in enumerate(traces): cc = normxcorr2(tr.data, slave[0].data) axes[i+1].set_ylabel('cc='+str(round(np.max(cc), 2)), rotation=0) axes[i+1].text(0.9, 0.15, str(round(np.max(slave[0].data))), bbox=dict(facecolor='white', alpha=0.95), transform=axes[i+1].transAxes) axes[i+1].text(0.7, 0.85, slave[0].stats.starttime.datetime. strftime('%Y/%m/%d %H:%M:%S'), bbox=dict(facecolor='white', alpha=0.95), transform=axes[i+1].transAxes) axes[-1].set_xlabel('Time (s)') if title: axes[0].set_title(title) plt.subplots_adjust(hspace=0) plt.show() return traces, clist
def align_traces(trace_list, shift_len, master=False, positive=False, plot=False): """ Align traces relative to each other based on their cross-correlation value. Uses the :func:`eqcorrscan.core.match_filter.normxcorr2` function to find the optimum shift to align traces relative to a master event. Either uses a given master to align traces, or uses the trace with the highest MAD amplitude. :type trace_list: list :param trace_list: List of traces to align :type shift_len: int :param shift_len: Length to allow shifting within in samples :type master: obspy.core.trace.Trace :param master: Master trace to align to, if set to False will align to \ the largest amplitude trace (default) :type positive: bool :param positive: Return the maximum positive cross-correlation, or the \ absolute maximum, defaults to False (absolute maximum). :type plot: bool :param plot: If true, will plot each trace aligned with the master. :returns: list of shifts and correlations for best alignment in seconds. :rtype: list """ from eqcorrscan.core.match_filter import normxcorr2 from eqcorrscan.utils.plotting import xcorr_plot traces = deepcopy(trace_list) if not master: # Use trace with largest MAD amplitude as master master = traces[0] MAD_master = np.median(np.abs(master.data)) for i in range(1, len(traces)): if np.median(np.abs(traces[i].data)) > MAD_master: master = traces[i] MAD_master = np.median(np.abs(master.data)) else: print('Using master given by user') shifts = [] ccs = [] for i in range(len(traces)): if not master.stats.sampling_rate == traces[i].stats.sampling_rate: raise ValueError('Sampling rates not the same') cc_vec = normxcorr2(template=traces[i].data.astype( np.float32)[shift_len:-shift_len], image=master.data.astype(np.float32)) cc_vec = cc_vec[0] shift = np.abs(cc_vec).argmax() cc = cc_vec[shift] if plot: xcorr_plot(template=traces[i].data.astype( np.float32)[shift_len:-shift_len], image=master.data.astype(np.float32), shift=shift, cc=cc) shift -= shift_len if cc < 0 and positive: cc = cc_vec.max() shift = cc_vec.argmax() - shift_len shifts.append(shift / master.stats.sampling_rate) ccs.append(cc) return shifts, ccs
def multi_event_singlechan(streams, picks, clip=10.0, pre_pick=2.0,\ freqmin=False, freqmax=False, realign=False, \ cut=(-3.0,5.0), PWS=False, title=False): """ Function to plot data from a single channel at a single station for multiple events - data will be alligned by their pick-time given in the picks :type streams: List of :class:obspy.stream :param streams: List of the streams to use, can contain more traces than\ you plan on plotting :type picks: List of :class:PICK :param picks: List of picks, one for each stream :type clip: float :param clip: Length in seconds to plot, defaults to 10.0 :type pre_pick: Float :param pre_pick: Length in seconds to extract and plot before the pick,\ defaults to 2.0 :type freqmin: float :param freqmin: Low cut for bandpass in Hz :type freqmax: float :param freqmax: High cut for bandpass in Hz :type realign: Bool :param realign: To compute best alignement based on correlation or not. :type cut: tuple: :param cut: tuple of start and end times for cut in seconds from the pick :type PWS: bool :param PWS: compute Phase Weighted Stack, if False, will compute linear stack :type title: str :param title: Plot title. :returns: Alligned and cut traces, and new picks """ from eqcorrscan.utils import stacking import copy from eqcorrscan.core.match_filter import normxcorr2 from obspy import Stream fig, axes = plt.subplots(len(picks) + 1, 1, sharex=True, figsize=(7, 12)) axes = axes.ravel() traces = [] al_traces = [] # Keep input safe plist = copy.deepcopy(picks) st_list = copy.deepcopy(streams) for i, pick in enumerate(plist): if st_list[i].select(station=pick.station, \ channel='*'+pick.channel[-1]): tr=st_list[i].select(station=pick.station, \ channel='*'+pick.channel[-1])[0] else: print 'No data for ' + pick.station + '.' + pick.channel continue tr.detrend('linear') if freqmin: tr.filter('bandpass', freqmin=freqmin, freqmax=freqmax) if realign: tr_cut = tr.copy() tr_cut.trim(pick.time+cut[0], pick.time+cut[1],\ nearest_sample=False) if len(tr_cut.data) <= 0.5 * (cut[1] - cut[0]) * tr_cut.stats.sampling_rate: print 'Not enough in the trace for ' + pick.station + '.' + pick.channel print 'Suggest removing pick from sfile at time ' + str( pick.time) else: al_traces.append(tr_cut) else: tr.trim(pick.time-pre_pick, pick.time+clip-pre_pick,\ nearest_sample=False) if len(tr.data) == 0: print 'No data in the trace for ' + pick.station + '.' + pick.channel print 'Suggest removing pick from sfile at time ' + str(pick.time) continue traces.append(tr) if realign: shift_len = int(0.25 * (cut[1] - cut[0]) * al_traces[0].stats.sampling_rate) shifts = stacking.align_traces(al_traces, shift_len) for i in xrange(len(shifts)): print 'Shifting by ' + str(shifts[i]) + ' seconds' pick.time -= shifts[i] traces[i].trim(pick.time - pre_pick, pick.time + clip-pre_pick,\ nearest_sample=False) # We now have a list of traces traces = [(trace, trace.stats.starttime.datetime) for trace in traces] traces.sort(key=lambda tup: tup[1]) traces = [trace[0] for trace in traces] # Plot the traces for i, tr in enumerate(traces): y = tr.data x = np.arange(len(y)) x = x / tr.stats.sampling_rate # convert to seconds axes[i + 1].plot(x, y, 'k', linewidth=1.1) # axes[i+1].set_ylabel(tr.stats.starttime.datetime.strftime('%Y/%m/%d %H:%M'),\ # rotation=0) axes[i + 1].yaxis.set_ticks([]) traces = [Stream(trace) for trace in traces] if PWS: linstack = stacking.PWS_stack(traces) else: linstack = stacking.linstack(traces) tr=linstack.select(station=picks[0].station, \ channel='*'+picks[0].channel[-1])[0] y = tr.data x = np.arange(len(y)) x = x / tr.stats.sampling_rate axes[0].plot(x, y, 'r', linewidth=2.0) axes[0].set_ylabel('Stack', rotation=0) axes[0].yaxis.set_ticks([]) for i, slave in enumerate(traces): cc = normxcorr2(tr.data, slave[0].data) axes[i + 1].set_ylabel('cc=' + str(round(np.max(cc), 2)), rotation=0) axes[i+1].text(0.9, 0.15, str(round(np.max(slave[0].data))), \ bbox=dict(facecolor='white', alpha=0.95),\ transform=axes[i+1].transAxes) axes[i+1].text(0.7, 0.85, slave[0].stats.starttime.datetime.strftime('%Y/%m/%d %H:%M:%S'), \ bbox=dict(facecolor='white', alpha=0.95),\ transform=axes[i+1].transAxes) axes[-1].set_xlabel('Time (s)') if title: axes[0].set_title(title) plt.subplots_adjust(hspace=0) plt.show() return traces, plist
def _channel_loop(detection, template, min_cc, detection_id, interpolate, i, pre_lag_ccsum=None, detect_chans=0): """ Inner loop for correlating and assigning picks. Utility function to take a stream of data for the detected event and write maximum correlation to absolute time as picks in an obspy.core.event.Event object. Only outputs picks for picks above min_cc. :type detection: obspy.core.stream.Stream :param detection: Stream of data for the slave event detected using template. :type template: obspy.core.stream.Stream :param template: Stream of data as the template for the detection. :type min_cc: float :param min_cc: Minimum cross-correlation value to allow a pick to be made. :type detection_id: str :param detection_id: Detection ID to associate the event with. :type interpolate: bool :param interpolate: Interpolate the correlation function to achieve sub-sample precision. :type i: int :param i: Used to track which process has occurred when running in parallel. :type pre_lag_ccsum: float :param pre_lag_ccsum: Cross-correlation sum before lag-calc, will check that the cross-correlation sum is increased by lag-calc (using all channels, ignoring min_cc) :type detect_chans: int :param detect_chans: Number of channels originally used in detections, must match the number used here to allow for cccsum checking. :returns: Event object containing network, station, channel and pick information. :rtype: :class:`obspy.core.event.Event` """ event = Event() s_stachans = {} used_s_sta = [] cccsum = 0 checksum = 0 used_chans = 0 for tr in template: temp_net = tr.stats.network temp_sta = tr.stats.station temp_chan = tr.stats.channel image = detection.select(station=temp_sta, channel=temp_chan) if image: if interpolate: try: ccc = normxcorr2(tr.data, image[0].data) shift, cc_max = _xcorr_interp(ccc=ccc, dt=image[0].stats.delta) except IndexError: log.error('Could not interpolate ccc, not smooth') ccc = normxcorr2(tr.data, image[0].data) cc_max = np.amax(ccc) shift = np.argmax(ccc) * image[0].stats.delta # Convert the maximum cross-correlation time to an actual time picktime = image[0].stats.starttime + shift else: # Convert the maximum cross-correlation time to an actual time ccc = normxcorr2(tr.data, image[0].data) cc_max = np.amax(ccc) picktime = image[0].stats.starttime + (np.argmax(ccc) * image[0].stats.delta) log.debug('********DEBUG: Maximum cross-corr=%s' % cc_max) checksum += cc_max used_chans += 1 if cc_max < min_cc: continue cccsum += cc_max # Perhaps weight each pick by the cc val or cc val^2? # weight = np.amax(ccc) ** 2 if temp_chan[-1:] == 'Z': phase = 'P' # Only take the S-pick with the best correlation elif temp_chan[-1:] in ['E', 'N']: phase = 'S' if temp_sta not in s_stachans and np.amax(ccc) > min_cc: s_stachans[temp_sta] = ((temp_chan, np.amax(ccc), picktime)) elif temp_sta in s_stachans and np.amax(ccc) > min_cc: if np.amax(ccc) > s_stachans[temp_sta][1]: picktime = picktime else: picktime = s_stachans[temp_sta][2] temp_chan = s_stachans[temp_sta][0] elif np.amax(ccc) < min_cc and temp_sta not in used_s_sta: used_s_sta.append(temp_sta) else: continue else: phase = None _waveform_id = WaveformStreamID(network_code=temp_net, station_code=temp_sta, channel_code=temp_chan) event.picks.append( Pick(waveform_id=_waveform_id, time=picktime, method_id=ResourceIdentifier('EQcorrscan'), phase_hint=phase, creation_info='eqcorrscan.core.lag_calc', comments=[Comment(text='cc_max=%s' % cc_max)])) event.resource_id = detection_id ccc_str = ("detect_val=%s" % cccsum) event.comments.append(Comment(text=ccc_str)) if used_chans == detect_chans: if pre_lag_ccsum is not None and checksum - pre_lag_ccsum < -0.05: msg = ('lag-calc has decreased cccsum from %f to %f - ' 'report this error' % (pre_lag_ccsum, checksum)) raise LagCalcError(msg) else: warnings.warn('Cannot check is cccsum is better, used %i channels ' 'for detection, but %i are used here' % (detect_chans, used_chans)) return i, event
def multi_event_singlechan(streams, catalog, clip=10.0, pre_pick=2.0, freqmin=False, freqmax=False, realign=False, cut=(-3.0, 5.0), PWS=False, title=False, save=False, savefile=None): r"""Function to plot data from a single channel at a single station for \ multiple events - data will be alligned by their pick-time given in the \ picks. :type streams: list of :class:obspy.stream :param streams: List of the streams to use, can contain more traces than \ you plan on plotting :type catalog: obspy.core.event.Catalog :param catalog: Catalog of events, one for each trace, with a single pick :type clip: float :param clip: Length in seconds to plot, defaults to 10.0 :type pre_pick: float :param pre_pick: Length in seconds to extract and plot before the pick, \ defaults to 2.0 :type freqmin: float :param freqmin: Low cut for bandpass in Hz :type freqmax: float :param freqmax: High cut for bandpass in Hz :type realign: bool :param realign: To compute best alignement based on correlation or not. :type cut: tuple :param cut: tuple of start and end times for cut in seconds from the pick :type PWS: bool :param PWS: compute Phase Weighted Stack, if False, will compute linear \ stack. :type title: str :param title: Plot title. :type save: bool :param save: False will plot to screen, true will save plot and not show \ to screen. :type savefile: str :param savefile: Filename to save to, required for save=True :returns: Alligned and cut traces, and new picks """ _check_save_args(save, savefile) from eqcorrscan.utils import stacking import copy from eqcorrscan.core.match_filter import normxcorr2 from obspy import Stream import warnings fig, axes = plt.subplots(len(catalog) + 1, 1, sharex=True, figsize=(7, 12)) axes = axes.ravel() traces = [] al_traces = [] # Keep input safe clist = copy.deepcopy(catalog) st_list = copy.deepcopy(streams) for i, event in enumerate(clist): if st_list[i].select(station=event.picks[0].waveform_id.station_code, channel='*' + event.picks[0].waveform_id.channel_code[-1]): tr = st_list[i].select( station=event.picks[0].waveforms_id.station_code, channel='*' + event.picks[0].waveform_id.channel_code[-1])[0] else: print('No data for ' + event.pick[0].waveform_id) continue tr.detrend('linear') if freqmin: tr.filter('bandpass', freqmin=freqmin, freqmax=freqmax) if realign: tr_cut = tr.copy() tr_cut.trim(event.picks[0].time + cut[0], event.picks[0].time + cut[1], nearest_sample=False) if len(tr_cut.data) <= (0.5 * (cut[1] - cut[0]) * tr_cut.stats.sampling_rate): msg = ''.join([ 'Not enough in the trace for ', tr.stats.station, '.', tr.stats.channel, '\n', 'Suggest removing pick from sfile at time ', str(event.picks[0].time) ]) warnings.warn(msg) else: al_traces.append(tr_cut) else: tr.trim(event.picks[0].time - pre_pick, event.picks[0].time + clip - pre_pick, nearest_sample=False) if len(tr.data) == 0: msg = ''.join([ 'No data in the trace for ', tr.stats.station, '.', tr.stats.channel, '\n', 'Suggest removing pick from sfile at time ', str(event.picks[0].time) ]) warnings.warn(msg) continue traces.append(tr) if realign: shift_len = int(0.25 * (cut[1] - cut[0]) * al_traces[0].stats.sampling_rate) shifts = stacking.align_traces(al_traces, shift_len) for i in xrange(len(shifts)): print('Shifting by ' + str(shifts[i]) + ' seconds') event.picks[0].time -= shifts[i] traces[i].trim(event.picks[0].time - pre_pick, event.picks[0].time + clip - pre_pick, nearest_sample=False) # We now have a list of traces traces = [(trace, trace.stats.starttime.datetime) for trace in traces] traces.sort(key=lambda tup: tup[1]) traces = [trace[0] for trace in traces] # Plot the traces for i, tr in enumerate(traces): y = tr.data x = np.arange(len(y)) x = x / tr.stats.sampling_rate # convert to seconds axes[i + 1].plot(x, y, 'k', linewidth=1.1) axes[i + 1].yaxis.set_ticks([]) traces = [Stream(trace) for trace in traces] if PWS: linstack = stacking.PWS_stack(traces) else: linstack = stacking.linstack(traces) tr = linstack.select(station=event[0].picks[0].waveform_id.station_code, channel='*' + event[0].picks[0].waveform_id.channel_code[-1])[0] y = tr.data x = np.arange(len(y)) x = x / tr.stats.sampling_rate axes[0].plot(x, y, 'r', linewidth=2.0) axes[0].set_ylabel('Stack', rotation=0) axes[0].yaxis.set_ticks([]) for i, slave in enumerate(traces): cc = normxcorr2(tr.data, slave[0].data) axes[i + 1].set_ylabel('cc=' + str(round(np.max(cc), 2)), rotation=0) axes[i + 1].text(0.9, 0.15, str(round(np.max(slave[0].data))), bbox=dict(facecolor='white', alpha=0.95), transform=axes[i + 1].transAxes) axes[i + 1].text( 0.7, 0.85, slave[0].stats.starttime.datetime.strftime('%Y/%m/%d %H:%M:%S'), bbox=dict(facecolor='white', alpha=0.95), transform=axes[i + 1].transAxes) axes[-1].set_xlabel('Time (s)') if title: axes[0].set_title(title) plt.subplots_adjust(hspace=0) if not save: plt.show() else: plt.savefig(savefile) return traces, clist
def _channel_loop(detection, template, min_cc, detection_id, interpolate, i, pre_lag_ccsum=None, detect_chans=0, horizontal_chans=['E', 'N', '1', '2'], vertical_chans=['Z'], debug=0): """ Inner loop for correlating and assigning picks. Utility function to take a stream of data for the detected event and write maximum correlation to absolute time as picks in an obspy.core.event.Event object. Only outputs picks for picks above min_cc. :type detection: obspy.core.stream.Stream :param detection: Stream of data for the slave event detected using template. :type template: obspy.core.stream.Stream :param template: Stream of data as the template for the detection. :type min_cc: float :param min_cc: Minimum cross-correlation value to allow a pick to be made. :type detection_id: str :param detection_id: Detection ID to associate the event with. :type interpolate: bool :param interpolate: Interpolate the correlation function to achieve sub-sample precision. :type i: int :param i: Used to track which process has occurred when running in parallel. :type pre_lag_ccsum: float :param pre_lag_ccsum: Cross-correlation sum before lag-calc, will check that the cross-correlation sum is increased by lag-calc (using all channels, ignoring min_cc) :type detect_chans: int :param detect_chans: Number of channels originally used in detections, must match the number used here to allow for cccsum checking. :type horizontal_chans: list :param horizontal_chans: List of channel endings for horizontal-channels, on which S-picks will be made. :type vertical_chans: list :param vertical_chans: List of channel endings for vertical-channels, on which P-picks will be made. :type debug: int :param debug: Debug output level 0-5. :returns: Event object containing network, station, channel and pick information. :rtype: :class:`obspy.core.event.Event` """ from eqcorrscan.core.match_filter import normxcorr2 event = Event() s_stachans = {} cccsum = 0 checksum = 0 used_chans = 0 for tr in template: temp_net = tr.stats.network temp_sta = tr.stats.station temp_chan = tr.stats.channel debug_print('Working on: %s.%s.%s' % (temp_net, temp_sta, temp_chan), 3, debug) image = detection.select(station=temp_sta, channel=temp_chan) if len(image) == 0: print('No match in image.') continue if interpolate: try: ccc = normxcorr2(tr.data, image[0].data) except Exception: print('Could not calculate cc') print('Image is %i long' % len(image[0].data)) print('Template is %i long' % len(tr.data)) continue try: shift, cc_max = _xcorr_interp(ccc=ccc, dt=image[0].stats.delta) except IndexError: print('Could not interpolate ccc, not smooth') ccc = normxcorr2(tr.data, image[0].data) cc_max = np.amax(ccc) shift = np.argmax(ccc) * image[0].stats.delta # Convert the maximum cross-correlation time to an actual time picktime = image[0].stats.starttime + shift else: # Convert the maximum cross-correlation time to an actual time try: ccc = normxcorr2(tr.data, image[0].data) except Exception: print('Could not calculate cc') print('Image is %i long' % len(image[0].data)) print('Template is %i long' % len(tr.data)) continue cc_max = np.amax(ccc) picktime = image[0].stats.starttime + (np.argmax(ccc) * image[0].stats.delta) debug_print('Maximum cross-corr=%s' % cc_max, 3, debug) checksum += cc_max used_chans += 1 if cc_max < min_cc: debug_print('Correlation below threshold, not used', 3, debug) continue cccsum += cc_max # Perhaps weight each pick by the cc val or cc val^2? # weight = np.amax(ccc) ** 2 if temp_chan[-1] in vertical_chans: phase = 'P' # Only take the S-pick with the best correlation elif temp_chan[-1] in horizontal_chans: phase = 'S' debug_print( 'Making S-pick on: %s.%s.%s' % (temp_net, temp_sta, temp_chan), 4, debug) if temp_sta not in s_stachans.keys(): s_stachans[temp_sta] = ((temp_chan, np.amax(ccc), picktime)) elif temp_sta in s_stachans.keys(): if np.amax(ccc) > s_stachans[temp_sta][1]: picktime = picktime else: continue else: phase = None _waveform_id = WaveformStreamID(network_code=temp_net, station_code=temp_sta, channel_code=temp_chan) event.picks.append( Pick(waveform_id=_waveform_id, time=picktime, method_id=ResourceIdentifier('EQcorrscan'), phase_hint=phase, creation_info='eqcorrscan.core.lag_calc', comments=[Comment(text='cc_max=%s' % cc_max)])) event.resource_id = detection_id ccc_str = ("detect_val=%s" % cccsum) event.comments.append(Comment(text=ccc_str)) if used_chans == detect_chans: if pre_lag_ccsum is not None and\ checksum - pre_lag_ccsum < -(0.30 * pre_lag_ccsum): msg = ('lag-calc has decreased cccsum from %f to %f - ' % (pre_lag_ccsum, checksum)) # warnings.warn(msg) raise LagCalcError(msg) else: warnings.warn('Cannot check if cccsum is better, used %i channels ' 'for detection, but %i are used here' % (detect_chans, used_chans)) return i, event