def test_writing_blockette_100(self): """ Tests that blockette 100 is written correctly. It is only used if the sampling rate is higher than 32727 Hz or smaller than 1.0 / 32727.0 Hz. """ # Three traces, only the middle one needs it. tr = Trace(data=np.linspace(0, 100, 101)) st = Stream(traces=[tr.copy(), tr.copy(), tr.copy()]) st[1].stats.sampling_rate = 60000.0 with io.BytesIO() as buf: st.write(buf, format="mseed") buf.seek(0, 0) st2 = read(buf) self.assertTrue(np.allclose(st[0].stats.sampling_rate, st2[0].stats.sampling_rate)) self.assertTrue(np.allclose(st[1].stats.sampling_rate, st2[1].stats.sampling_rate)) self.assertTrue(np.allclose(st[2].stats.sampling_rate, st2[2].stats.sampling_rate)) st[1].stats.sampling_rate = 1.0 / 60000.0 with io.BytesIO() as buf: st.write(buf, format="mseed") buf.seek(0, 0) st2 = read(buf) self.assertTrue(np.allclose(st[0].stats.sampling_rate, st2[0].stats.sampling_rate)) self.assertTrue(np.allclose(st[1].stats.sampling_rate, st2[1].stats.sampling_rate)) self.assertTrue(np.allclose(st[2].stats.sampling_rate, st2[2].stats.sampling_rate))
def test_rtrim_with_padding(self): """ Tests the _rtrim() method of the Trace class with padding. It has already been tested in the two sided trimming tests. This is just to have an explicit test. Also tests issue #429. """ # set up trace = Trace(data=np.arange(10)) start = UTCDateTime(2000, 1, 1, 0, 0, 0, 0) trace.stats.starttime = start trace.stats.sampling_rate = 1.0 trace.verify() # Pad with no fill_value will mask the additional values. tr = trace.copy() end = tr.stats.endtime tr._rtrim(end + 10, pad=True) self.assertEqual(tr.stats.endtime, trace.stats.endtime + 10) np.testing.assert_array_equal(tr.data[0:10], np.arange(10)) # Check that the first couple of entries are not masked. self.assertFalse(tr.data[0:10].mask.any()) # All the other entries should be masked. self.assertTrue(tr.data[10:].mask.all()) # Pad with fill_value. tr = trace.copy() end = tr.stats.endtime tr._rtrim(end + 10, pad=True, fill_value=-33) self.assertEqual(tr.stats.endtime, trace.stats.endtime + 10) # The first ten entries should not have changed. np.testing.assert_array_equal(tr.data[0:10], np.arange(10)) # The rest should be filled with the fill_value. np.testing.assert_array_equal(tr.data[10:], np.ones(10) * -33)
def test_slice_noStarttimeOrEndtime(self): """ Tests the slicing of trace objects with no starttime or endtime provided. Compares results against the equivalent trim() operation """ tr_orig = Trace(data=np.arange(10, dtype='int32')) tr = tr_orig.copy() # two time points outside the trace and two inside t1 = tr.stats.starttime - 2 t2 = tr.stats.starttime + 2 t3 = tr.stats.endtime - 3 t4 = tr.stats.endtime + 2 # test 1: only removing data at left side tr_trim = tr_orig.copy() tr_trim.trim(starttime=t2) self.assertEqual(tr_trim, tr.slice(starttime=t2)) self.assertEqual(tr_trim, tr.slice(starttime=t2, endtime=t4)) # test 2: only removing data at right side tr_trim = tr_orig.copy() tr_trim.trim(endtime=t3) self.assertEqual(tr_trim, tr.slice(endtime=t3)) self.assertEqual(tr_trim, tr.slice(starttime=t1, endtime=t3)) # test 3: not removing data at all tr_trim = tr_orig.copy() tr_trim.trim(starttime=t1, endtime=t4) self.assertEqual(tr_trim, tr.slice()) self.assertEqual(tr_trim, tr.slice(starttime=t1)) self.assertEqual(tr_trim, tr.slice(endtime=t4)) self.assertEqual(tr_trim, tr.slice(starttime=t1, endtime=t4)) tr_trim.trim() self.assertEqual(tr_trim, tr.slice()) self.assertEqual(tr_trim, tr.slice(starttime=t1)) self.assertEqual(tr_trim, tr.slice(endtime=t4)) self.assertEqual(tr_trim, tr.slice(starttime=t1, endtime=t4)) # test 4: removing data at left and right side tr_trim = tr_orig.copy() tr_trim.trim(starttime=t2, endtime=t3) self.assertEqual(tr_trim, tr.slice(t2, t3)) self.assertEqual(tr_trim, tr.slice(starttime=t2, endtime=t3)) # test 5: no data left after operation tr_trim = tr_orig.copy() tr_trim.trim(starttime=t4) self.assertEqual(tr_trim, tr.slice(starttime=t4)) self.assertEqual(tr_trim, tr.slice(starttime=t4, endtime=t4 + 1))
def resample_and_plot(tr: Trace, sampling_rate: float): """ Simplified (made less general) from obspy Trace.resample. Uses a frequency domain method to resample, and a hanning window. Parameters ---------- tr: Obspy Trace to resample. Works on a copy of the trace. Your data are safe here. sampling_rate: Desired sampling rate in Hz. Returns ------- Resampled Trace, Figure. """ from scipy.signal import get_window from scipy.fftpack import rfft, irfft import matplotlib.pyplot as plt factor = tr.stats.sampling_rate / float(sampling_rate) # Copy the trace and work on this copy tr_out = tr.copy() # Copy things for plotting data_in = tr.data max_time = tr.stats.npts * tr.stats.delta dt = tr.stats.delta N = tr.stats.npts # resample in the frequency domain. Make sure the byteorder is native. x = rfft(tr_out.data.newbyteorder("=")) # Cast the value to be inserted to the same dtype as the array to avoid # issues with numpy rule 'safe'. x = np.insert(x, 1, x.dtype.type(0)) if tr_out.stats.npts % 2 == 0: x = np.append(x, [0]) x_r = x[::2] x_i = x[1::2] # Multiply by a hanning window to stabilise the interpolation large_w = np.fft.ifftshift( get_window('hanning', tr_out.stats.npts)) x_r *= large_w[:tr_out.stats.npts // 2 + 1] x_i *= large_w[:tr_out.stats.npts // 2 + 1] # interpolate num = int(tr_out.stats.npts / factor) df = 1.0 / (tr_out.stats.npts * tr_out.stats.delta) d_large_f = 1.0 / num * sampling_rate f = df * np.arange(0, tr_out.stats.npts // 2 + 1, dtype=np.int32) n_large_f = num // 2 + 1 large_f = d_large_f * np.arange(0, n_large_f, dtype=np.int32) large_y = np.zeros((2 * n_large_f)) large_y[::2] = np.interp(large_f, f, x_r) large_y[1::2] = np.interp(large_f, f, x_i) large_y = np.delete(large_y, 1) if num % 2 == 0: large_y = np.delete(large_y, -1) tr_out.data = irfft(large_y) * (float(num) / float(tr_out.stats.npts)) tr_out.stats.sampling_rate = sampling_rate # Plot fig, axes = plt.subplots(ncols=2) axes[0].plot(np.arange(0, max_time, dt), data_in) axes[1].semilogx( np.linspace(0.0, 1.0 / (2. * dt), int(N / 2)), 2./N * np.abs(x[:N//2]), label="Original") axes[1].semilogx( np.linspace(dt, dt + 1.0 / (2. * tr_out.stats.delta), num // 2), 2./N * np.abs(large_y[:num//2]), label="Resampled") axes[0].plot(np.arange(0, max_time, 1 / sampling_rate), tr_out.data) axes[1].legend() axes[0].set_xlabel("Time (s)") axes[1].set_xlabel("Frequency (Hz)") return tr_out, fig
def pro5stack(eq_file, plot_scale_fac=0.05, slowR_lo=-0.1, slowR_hi=0.1, slow_delta=0.0005, start_buff=-50, end_buff=50, ref_lat=36.3, ref_lon=138.5, envelope=1, plot_dyn_range=1000, log_plot=1, norm=1, global_norm_plot=1, color_plot=1, fig_index=401, ARRAY=0): #%% Import functions import obspy import obspy.signal from obspy import UTCDateTime from obspy import Stream, Trace from obspy import read from obspy.geodetics import gps2dist_azimuth import numpy as np import os from obspy.taup import TauPyModel import obspy.signal as sign import matplotlib.pyplot as plt from matplotlib.colors import LogNorm model = TauPyModel(model='iasp91') from scipy.signal import hilbert import math import time # import sys # don't show any warnings # import warnings print('Running pro5a_stack') #%% Get saved event info, also used to name files start_time_wc = time.time() if ARRAY == 0: file = open(eq_file, 'r') elif ARRAY == 1: file = open('EvLocs/' + eq_file, 'r') lines = file.readlines() split_line = lines[0].split() # ids.append(split_line[0]) ignore label for now t = UTCDateTime(split_line[1]) date_label = split_line[1][0:10] ev_lat = float(split_line[2]) ev_lon = float(split_line[3]) ev_depth = float(split_line[4]) #if not sys.warnoptions: # warnings.simplefilter("ignore") #%% Get station location file if ARRAY == 0: # Hinet set and center sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/hinet_sta.txt' ref_lat = 36.3 ref_lon = 138.5 elif ARRAY == 1: # LASA set and center sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/LASA_sta.txt' ref_lat = 46.69 ref_lon = -106.22 else: # NORSAR set and center if 2 sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/NORSAR_sta.txt' ref_lat = 61 ref_lon = 11 with open(sta_file, 'r') as file: lines = file.readlines() print(str(len(lines)) + ' stations read from ' + sta_file) # Load station coords into arrays station_index = range(len(lines)) st_names = [] st_lats = [] st_lons = [] for ii in station_index: line = lines[ii] split_line = line.split() st_names.append(split_line[0]) st_lats.append(split_line[1]) st_lons.append(split_line[2]) #%% Name file, read data # date_label = '2018-04-02' # date for filename if ARRAY == 0: fname = 'HD' + date_label + 'sel.mseed' elif ARRAY == 1: fname = 'Pro_Files/HD' + date_label + 'sel.mseed' st = Stream() print('reading ' + fname) st = read(fname) print('Read in: ' + str(len(st)) + ' traces') nt = len(st[0].data) dt = st[0].stats.delta print('First trace has : ' + str(nt) + ' time pts, time sampling of ' + str(dt) + ' and thus duration of ' + str((nt - 1) * dt)) #%% Build Stack arrays stack = Stream() tr = Trace() tr.stats.delta = dt tr.stats.network = 'stack' tr.stats.channel = 'BHZ' slow_n = int(1 + (slowR_hi - slowR_lo) / slow_delta) # number of slownesses stack_nt = int(1 + ((end_buff - start_buff) / dt)) # number of time points # In English, stack_slows = range(slow_n) * slow_delta - slowR_lo a1 = range(slow_n) stack_slows = [(x * slow_delta + slowR_lo) for x in a1] print(str(slow_n) + ' slownesses.') tr.stats.starttime = t + start_buff tr.data = np.zeros(stack_nt) done = 0 for stack_one in stack_slows: tr1 = tr.copy() tr1.stats.station = str(int(done)) stack.extend([tr1]) done += 1 # stack.append([tr]) # stack += tr # Only need to compute ref location to event distance once ref_distance = gps2dist_azimuth(ev_lat, ev_lon, ref_lat, ref_lon) #%% Select traces by distance, window and adjust start time to align picked times done = 0 for tr in st: # traces one by one, find lat-lon by searching entire inventory. Inefficient but cheap for ii in station_index: if ARRAY == 0: # for hi-net, have to chop off last letter, always 'h' this_name = st_names[ii] this_name_truc = this_name[0:5] name_truc_cap = this_name_truc.upper() elif ARRAY == 1: name_truc_cap = st_names[ii] if (tr.stats.station == name_truc_cap ): # find station in inventory if norm == 1: tr.normalize() # tr.normalize(norm= -len(st)) # mystery command or error stalat = float(st_lats[ii]) stalon = float( st_lons[ii]) # look up lat & lon again to find distance distance = gps2dist_azimuth( stalat, stalon, ev_lat, ev_lon) # Get traveltimes again, hard to store tr.stats.distance = distance[0] # distance in m del_dist = (ref_distance[0] - distance[0]) / (1000) # in km # ALSO NEEDS distance station - hypocenter calculation #isolate components of distance in radial and transverse directions, ref_distR & ref_distT # FIX ref_distR = distance*cos(azi-backazi) # FIX ref_distT = distance*sin(azi-backazi) # for(k=0;k<nslow;k++){ # slow = 110.*(LOWSLOW + k*DELTASLOW); for slow_i in range( slow_n): # for this station, loop over slownesses time_lag = -del_dist * stack_slows[ slow_i] # time shift due to slowness, flipped to match 2D # start_offset = tr.stats.starttime - t # time_correction = (start_buff - (start_offset + time_lag))/dt time_correction = ((t - tr.stats.starttime) + (time_lag + start_buff)) / dt # print('Time lag ' + str(time_lag) + ' for slowness ' + str(stack_slows[slow_i]) + ' and distance ' + str(del_dist) + ' time sample correction is ' + str(time_correction)) for it in range(stack_nt): # check points one at a time it_in = int(it + time_correction) if it_in >= 0 and it_in < nt - 1: # does data lie within seismogram? stack[slow_i].data[it] += tr[it_in] done += 1 if done % 50 == 0: print('Done stacking ' + str(done) + ' out of ' + str(len(st)) + ' stations.') #%% Plot traces global_max = 0 for slow_i in range( slow_n): # find global max, and if requested, take envelope if len(stack[slow_i].data) == 0: print('%d data has zero length ' % (slow_i)) if envelope == 1 or color_plot == 1: stack[slow_i].data = np.abs(hilbert(stack[slow_i].data)) local_max = max(abs(stack[slow_i].data)) if local_max > global_max: global_max = local_max if global_max <= 0: print('global_max ' + str(global_max) + ' slow_n ' + str(slow_n)) # create time axis (x-axis), use of slow_i here is arbitrary, oops ttt = (np.arange(len(stack[slow_i].data)) * stack[slow_i].stats.delta + (stack[slow_i].stats.starttime - t)) # in units of seconds # Plotting if color_plot == 1: # 2D color plot stack_array = np.zeros((slow_n, stack_nt)) # stack_array = np.random.rand(int(slow_n),int(stack_nt)) # test with random numbers min_allowed = global_max / plot_dyn_range if log_plot == 1: for it in range(stack_nt): # check points one at a time for slow_i in range( slow_n): # for this station, loop over slownesses num_val = stack[slow_i].data[it] if num_val < min_allowed: num_val = min_allowed stack_array[ slow_i, it] = math.log10(num_val) - math.log10(min_allowed) else: for it in range(stack_nt): # check points one at a time for slow_i in range( slow_n): # for this station, loop over slownesses stack_array[slow_i, it] = stack[slow_i].data[it] / global_max y, x = np.mgrid[slice(stack_slows[0], stack_slows[-1] + slow_delta, slow_delta), slice(ttt[0], ttt[-1] + dt, dt)] # make underlying x-y grid for plot # y, x = np.mgrid[ stack_slows , time ] # make underlying x-y grid for plot plt.close(fig_index) fig, ax = plt.subplots(1, figsize=(9, 2)) fig.subplots_adjust(bottom=0.3) # c = ax.pcolormesh(x, y, stack_array, cmap=plt.cm.gist_yarg) # c = ax.pcolormesh(x, y, stack_array, cmap=plt.cm.gist_rainbow_r) c = ax.pcolormesh(x, y, stack_array, cmap=plt.cm.binary) ax.axis([x.min(), x.max(), y.min(), y.max()]) fig.colorbar(c, ax=ax) plt.figure(fig_index, figsize=(6, 8)) plt.close(fig_index) else: # line plot for slow_i in range(slow_n): dist_offset = stack_slows[slow_i] # in units of slowness if global_norm_plot != 1: plt.plot( ttt, stack[slow_i].data * plot_scale_fac / (stack[slow_i].data.max() - stack[slow_i].data.min()) + dist_offset, color='black') else: plt.plot(ttt, stack[slow_i].data * plot_scale_fac / (global_max - stack[slow_i].data.min()) + dist_offset, color='black') plt.ylim(slowR_lo, slowR_hi) plt.xlim(start_buff, end_buff) plt.xlabel('Time (s)') plt.ylabel('Slowness (s/km)') plt.title(date_label) plt.show() #%% Save processed files print('Stack has ' + str(len(stack)) + ' traces') if ARRAY == 0: fname = 'HD' + date_label + '_1dstack.mseed' elif ARRAY == 1: fname = 'Pro_Files/HD' + date_label + '_1dstack.mseed' stack.write(fname, format='MSEED') elapsed_time_wc = time.time() - start_time_wc print('This job took ' + str(elapsed_time_wc) + ' seconds') os.system('say "Done"')
def match_filter(template_names, template_list, st, threshold, threshold_type, trig_int, plotvar, plotdir='.', cores=1, tempdir=False, debug=0, plot_format='jpg'): r"""Over-arching code to run the correlations of given templates with a\ day of seismic data and output the detections based on a given threshold. :type template_names: list :param template_names: List of template names in the same order as\ template_list :type template_list: list :class: 'obspy.Stream' :param template_list: A list of templates of which each template is a\ Stream of obspy traces containing seismic data and header information. :type st: :class: 'obspy.Stream' :param st: An obspy.Stream object containing all the data available and\ required for the correlations with templates given. For efficiency\ this should contain no excess traces which are not in one or more of\ the templates. This will now remove excess traces internally, but\ will copy the stream and work on the copy, leaving your input stream\ untouched. :type threshold: float :param threshold: A threshold value set based on the threshold_type :type threshold_type: str :param threshold_type: The type of threshold to be used, can be MAD,\ absolute or av_chan_corr. MAD threshold is calculated as the\ threshold*(median(abs(cccsum))) where cccsum is the cross-correlation\ sum for a given template. absolute threhsold is a true absolute\ threshold based on the cccsum value av_chan_corr is based on the mean\ values of single-channel cross-correlations assuming all data are\ present as required for the template, \ e.g. av_chan_corr_thresh=threshold*(cccsum/len(template)) where\ template is a single template from the input and the length is the\ number of channels within this template. :type trig_int: float :param trig_int: Minimum gap between detections in seconds. :type plotvar: bool :param plotvar: Turn plotting on or off :type plotdir: str :param plotdir: Path to plotting folder, plots will be output here,\ defaults to run location. :type tempdir: String or False :param tempdir: Directory to put temporary files, or False :type cores: int :param cores: Number of cores to use :type debug: int :param debug: Debug output level, the bigger the number, the more the\ output. :return: :class: 'DETECTIONS' detections for each channel formatted as\ :class: 'obspy.UTCDateTime' objects. .. rubric:: Note Plotting within the match-filter routine uses the Agg backend with\ interactive plotting turned off. This is because the function is\ designed to work in bulk. If you wish to turn interactive plotting on\ you must import matplotlib in your script first, when you them import\ match_filter you will get the warning that this call to matplotlib has\ no effect, which will mean that match_filter has not changed the\ plotting behaviour. """ import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt plt.ioff() import copy from eqcorrscan.utils import EQcorrscan_plotting from eqcorrscan.utils import findpeaks from obspy import Trace import time # Copy the stream here because we will f**k about with it stream = st.copy() templates = copy.deepcopy(template_list) # Debug option to confirm that the channel names match those in the # templates if debug >= 2: template_stachan = [] data_stachan = [] for template in templates: for tr in template: template_stachan.append(tr.stats.station + '.' + tr.stats.channel) for tr in stream: data_stachan.append(tr.stats.station + '.' + tr.stats.channel) template_stachan = list(set(template_stachan)) data_stachan = list(set(data_stachan)) if debug >= 3: print 'I have template info for these stations:' print template_stachan print 'I have daylong data for these stations:' print data_stachan # Perform a check that the daylong vectors are daylong for tr in stream: if not tr.stats.sampling_rate * 86400 == tr.stats.npts: msg = ' '.join(['Data are not daylong for', tr.stats.station, tr.stats.channel]) raise ValueError(msg) # Call the _template_loop function to do all the correlation work outtic = time.clock() # Edit here from previous, stable, but slow match_filter # Would be worth testing without an if statement, but with every station in # the possible template stations having data, but for those without real # data make the data NaN to return NaN ccc_sum # Note: this works if debug >= 2: print 'Ensuring all template channels have matches in daylong data' template_stachan = [] for template in templates: for tr in template: template_stachan += [(tr.stats.station, tr.stats.channel)] template_stachan = list(set(template_stachan)) # Copy this here to keep it safe for stachan in template_stachan: if not stream.select(station=stachan[0], channel=stachan[1]): # Remove template traces rather than adding NaN data for template in templates: if template.select(station=stachan[0], channel=stachan[1]): for tr in template.select(station=stachan[0], channel=stachan[1]): template.remove(tr) # Remove un-needed channels for tr in stream: if not (tr.stats.station, tr.stats.channel) in template_stachan: stream.remove(tr) # Also pad out templates to have all channels for template in templates: for stachan in template_stachan: if not template.select(station=stachan[0], channel=stachan[1]): nulltrace = Trace() nulltrace.stats.station = stachan[0] nulltrace.stats.channel = stachan[1] nulltrace.stats.sampling_rate = template[0].stats.sampling_rate nulltrace.stats.starttime = template[0].stats.starttime nulltrace.data = np.array([np.NaN] * len(template[0].data), dtype=np.float32) template += nulltrace if debug >= 2: print 'Starting the correlation run for this day' [cccsums, no_chans] = _channel_loop(templates, stream, cores, debug) if len(cccsums[0]) == 0: raise ValueError('Correlation has not run, zero length cccsum') outtoc = time.clock() print ' '.join(['Looping over templates and streams took:', str(outtoc - outtic), 's']) if debug >= 2: print ' '.join(['The shape of the returned cccsums is:', str(np.shape(cccsums))]) print ' '.join(['This is from', str(len(templates)), 'templates']) print ' '.join(['Correlated with', str(len(stream)), 'channels of data']) detections = [] for i, cccsum in enumerate(cccsums): template = templates[i] if threshold_type == 'MAD': rawthresh = threshold * np.median(np.abs(cccsum)) elif threshold_type == 'absolute': rawthresh = threshold elif threshold == 'av_chan_corr': rawthresh = threshold * (cccsum / len(template)) else: print 'You have not selected the correct threshold type, I will' +\ 'use MAD as I like it' rawthresh = threshold * np.mean(np.abs(cccsum)) # Findpeaks returns a list of tuples in the form [(cccsum, sample)] print ' '.join(['Threshold is set at:', str(rawthresh)]) print ' '.join(['Max of data is:', str(max(cccsum))]) print ' '.join(['Mean of data is:', str(np.mean(cccsum))]) if np.abs(np.mean(cccsum)) > 0.05: warnings.warn('Mean is not zero! Check this!') # Set up a trace object for the cccsum as this is easier to plot and # maintins timing if plotvar: stream_plot = copy.deepcopy(stream[0]) # Downsample for plotting stream_plot.decimate(int(stream[0].stats.sampling_rate / 10)) cccsum_plot = Trace(cccsum) cccsum_plot.stats.sampling_rate = stream[0].stats.sampling_rate # Resample here to maintain shape better cccsum_hist = cccsum_plot.copy() cccsum_hist = cccsum_hist.decimate(int(stream[0].stats.sampling_rate / 10)).data cccsum_plot = EQcorrscan_plotting.chunk_data(cccsum_plot, 10, 'Maxabs').data # Enforce same length stream_plot.data = stream_plot.data[0:len(cccsum_plot)] cccsum_plot = cccsum_plot[0:len(stream_plot.data)] cccsum_hist = cccsum_hist[0:len(stream_plot.data)] EQcorrscan_plotting.triple_plot(cccsum_plot, cccsum_hist, stream_plot, rawthresh, True, plotdir + '/cccsum_plot_' + template_names[i] + '_' + stream[0].stats.starttime.datetime.strftime('%Y-%m-%d') + '.' + plot_format) if debug >= 4: print ' '.join(['Saved the cccsum to:', template_names[i], stream[0].stats.starttime.datetime.strftime('%Y%j')]) np.save(template_names[i] + stream[0].stats.starttime.datetime.strftime('%Y%j'), cccsum) tic = time.clock() if debug >= 4: np.save('cccsum_' + str(i) + '.npy', cccsum) if debug >= 3 and max(cccsum) > rawthresh: peaks = findpeaks.find_peaks2_short(cccsum, rawthresh, trig_int * stream[0].stats.sampling_rate, debug, stream[0].stats.starttime, stream[0].stats.sampling_rate) elif max(cccsum) > rawthresh: peaks = findpeaks.find_peaks2_short(cccsum, rawthresh, trig_int * stream[0].stats.sampling_rate, debug) else: print 'No peaks found above threshold' peaks = False toc = time.clock() if debug >= 1: print ' '.join(['Finding peaks took:', str(toc - tic), 's']) if peaks: for peak in peaks: detecttime = stream[0].stats.starttime +\ peak[1] / stream[0].stats.sampling_rate detections.append(DETECTION(template_names[i], detecttime, no_chans[i], peak[0], rawthresh, 'corr')) del stream, templates return detections
def pro5stack2d(eq_file, plot_scale_fac=0.05, slow_delta=0.0005, slowR_lo=-0.1, slowR_hi=0.1, slowT_lo=-0.1, slowT_hi=0.1, start_buff=-50, end_buff=50, norm=1, global_norm_plot=1, ARRAY=0, NS=0, decimate_fac=0): from obspy import UTCDateTime from obspy import Stream, Trace from obspy import read from obspy.geodetics import gps2dist_azimuth import numpy as np import os from scipy.signal import hilbert import math import time import sys # don't show any warnings import warnings print('Running pro5b_stack2d') start_time_wc = time.time() if ARRAY == 0: file = open(eq_file, 'r') elif ARRAY == 1: goto = '/Users/vidale/Documents/PyCode/LASA/EvLocs' os.chdir(goto) file = open(eq_file, 'r') lines = file.readlines() split_line = lines[0].split() # ids.append(split_line[0]) ignore label for now t = UTCDateTime(split_line[1]) date_label = split_line[1][0:10] ev_lat = float(split_line[2]) ev_lon = float(split_line[3]) # ev_depth = float( split_line[4]) if not sys.warnoptions: warnings.simplefilter("ignore") #%% Get location file if ARRAY == 0: # Hinet set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/hinet_sta.txt' ref_lat = 36.3 ref_lon = 138.5 else: # LASA set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/LASA_sta.txt' ref_lat = 46.69 ref_lon = -106.22 with open(sta_file, 'r') as file: lines = file.readlines() print(str(len(lines)) + ' stations read from ' + sta_file) # Load station coords into arrays station_index = range(len(lines)) st_names = [] st_lats = [] st_lons = [] for ii in station_index: line = lines[ii] split_line = line.split() st_names.append(split_line[0]) st_lats.append(split_line[1]) st_lons.append(split_line[2]) #%% Input parameters # date_label = '2018-04-02' # date for filename fname = 'HD' + date_label + 'sel.mseed' if ARRAY == 1: goto = '/Users/vidale/Documents/PyCode/LASA/Pro_Files' os.chdir(goto) st = Stream() st = read(fname) print('Read in: ' + str(len(st)) + ' traces') nt = len(st[0].data) dt = st[0].stats.delta print('First trace has : ' + str(nt) + ' time pts, time sampling of ' + str(dt) + ' and thus duration of ' + str((nt - 1) * dt)) #%% Make grid of slownesses slowR_n = int(1 + (slowR_hi - slowR_lo) / slow_delta) # number of slownesses slowT_n = int(1 + (slowT_hi - slowT_lo) / slow_delta) # number of slownesses stack_nt = int(1 + ((end_buff - start_buff) / dt)) # number of time points # In English, stack_slows = range(slow_n) * slow_delta - slow_lo a1R = range(slowR_n) a1T = range(slowT_n) stack_Rslows = [(x * slow_delta + slowR_lo) for x in a1R] stack_Tslows = [(x * slow_delta + slowT_lo) for x in a1T] print( str(slowR_n) + ' radial slownesses, ' + str(slowT_n) + ' trans slownesses, ') #%% Build empty Stack array stack = Stream() tr = Trace() tr.stats.delta = dt tr.stats.starttime = t + start_buff tr.stats.npts = stack_nt tr.stats.network = 'stack' tr.stats.channel = 'BHZ' tr.data = np.zeros(stack_nt) done = 0 for stackR_one in stack_Rslows: for stackT_one in stack_Tslows: tr1 = tr.copy() tr1.stats.station = str(int(done)) stack.extend([tr1]) done += 1 # Only need to compute ref location to event distance once ref_dist_az = gps2dist_azimuth(ev_lat, ev_lon, ref_lat, ref_lon) # ref_dist = ref_dist_az[0]/1000 # km ref_back_az = ref_dist_az[2] # print(f'Ref location {ref_lat:.4f} , {ref_lon:.4f}, event location {ev_lat:.4f} {ev_lon:.4f} ref_back_az {ref_back_az:.1f}°') #%% select by distance, window and adjust start time to align picked times done = 0 for tr in st: # traces one by one, find lat-lon by searching entire inventory. Inefficient but cheap for ii in station_index: if ARRAY == 0: # have to chop off last letter, always 'h' this_name = st_names[ii] this_name_truc = this_name[0:5] name_truc_cap = this_name_truc.upper() elif ARRAY == 1: name_truc_cap = st_names[ii] if (tr.stats.station == name_truc_cap ): # find station in inventory # if (tr.stats.station == st_names[ii]): # found station in inventory if norm == 1: tr.normalize() # trace divided abs(max of trace) stalat = float(st_lats[ii]) stalon = float( st_lons[ii]) # use lat & lon to find distance and back-az rel_dist_az = gps2dist_azimuth(stalat, stalon, ref_lat, ref_lon) rel_dist = rel_dist_az[0] / 1000 # km rel_back_az = rel_dist_az[1] # radians # print(f'Sta lat-lon {stalat:.4f} {stalon:.4f}') if NS == 0: del_distR = rel_dist * math.cos( (rel_back_az - ref_back_az) * math.pi / 180) del_distT = rel_dist * math.sin( (rel_back_az - ref_back_az) * math.pi / 180) # North and east else: del_distR = rel_dist * math.cos( rel_back_az * math.pi / 180) del_distT = rel_dist * math.sin( rel_back_az * math.pi / 180) for slowR_i in range( slowR_n ): # for this station, loop over radial slownesses for slowT_i in range( slowT_n): # loop over transverse slownesses time_lag = del_distR * stack_Rslows[ slowR_i] # time shift due to radial slowness time_lag += del_distT * stack_Tslows[ slowT_i] # time shift due to transverse slowness time_correction = ((t - tr.stats.starttime) + (time_lag + start_buff)) / dt indx = int(slowR_i * slowT_n + slowT_i) for it in range( stack_nt): # check points one at a time it_in = int(it + time_correction) if it_in >= 0 and it_in < nt - 2: # does data lie within seismogram? # should be 1, not 2, but 2 prevents the problem "index XX is out of bounds for axis 0 with size XX" stack[indx].data[it] += tr[it_in] done += 1 if done % 20 == 0: print('Done stacking ' + str(done) + ' out of ' + str(len(st)) + ' stations.') #%% take envelope, decimate envelope stack_raw = stack.copy() for slowR_i in range(slowR_n): # loop over radial slownesses for slowT_i in range(slowT_n): # loop over transverse slownesses indx = slowR_i * slowT_n + slowT_i stack[indx].data = np.abs(hilbert(stack[indx].data)) if decimate_fac != 0: stack[indx].decimate(decimate_fac) #%% Save processed files fname = 'HD' + date_label + '_2dstack_env.mseed' stack.write(fname, format='MSEED') fname = 'HD' + date_label + '_2dstack.mseed' stack_raw.write(fname, format='MSEED') elapsed_time_wc = time.time() - start_time_wc print('This job took ' + str(elapsed_time_wc) + ' seconds') os.system('say "Done"')
def match_filter(template_names, template_list, st, threshold, threshold_type, trig_int, plotvar, plotdir='.', cores=1, tempdir=False, debug=0, plot_format='png', output_cat=False, extract_detections=False, arg_check=True): """ Main matched-filter detection function. Over-arching code to run the correlations of given templates with a \ day of seismic data and output the detections based on a given threshold. For a functional example see the tutorials. :type template_names: list :param template_names: List of template names in the same order as \ template_list :type template_list: list :param template_list: A list of templates of which each template is a \ Stream of obspy traces containing seismic data and header information. :type st: obspy.core.stream.Stream :param st: An obspy.Stream object containing all the data available and \ required for the correlations with templates given. For efficiency \ this should contain no excess traces which are not in one or more of \ the templates. This will now remove excess traces internally, but \ will copy the stream and work on the copy, leaving your input stream \ untouched. :type threshold: float :param threshold: A threshold value set based on the threshold_type :type threshold_type: str :param threshold_type: The type of threshold to be used, can be MAD, \ absolute or av_chan_corr. MAD threshold is calculated as the \ threshold*(median(abs(cccsum))) where cccsum is the cross-correlation \ sum for a given template. absolute threhsold is a true absolute \ threshold based on the cccsum value av_chan_corr is based on the mean \ values of single-channel cross-correlations assuming all data are \ present as required for the template, \ e.g. av_chan_corr_thresh=threshold*(cccsum/len(template)) where \ template is a single template from the input and the length is the \ number of channels within this template. :type trig_int: float :param trig_int: Minimum gap between detections in seconds. :type plotvar: bool :param plotvar: Turn plotting on or off :type plotdir: str :param plotdir: Path to plotting folder, plots will be output here, \ defaults to run location. :type tempdir: str :param tempdir: Directory to put temporary files, or False :type cores: int :param cores: Number of cores to use :type debug: int :param debug: Debug output level, the bigger the number, the more the \ output. :type plot_format: str :param plot_format: Specify format of output plots if saved :type output_cat: bool :param output_cat: Specifies if matched_filter will output an \ obspy.Catalog class containing events for each detection. Default \ is False, in which case matched_filter will output a list of \ detection classes, as normal. :type extract_detections: bool :param extract_detections: Specifies whether or not to return a list of \ streams, one stream per detection. :type arg_check: bool :param arg_check: Check arguments, defaults to True, but if running in \ bulk, and you are certain of your arguments, then set to False. :return: :class: 'DETECTIONS' detections for each channel formatted as \ :class: 'obspy.UTCDateTime' objects. :return: :class: obspy.Catalog containing events for each detection. :return: list of :class: obspy.Stream objects for each detection. .. note:: Plotting within the match-filter routine uses the Agg backend \ with interactive plotting turned off. This is because the function \ is designed to work in bulk. If you wish to turn interactive \ plotting on you must import matplotlib in your script first, when you \ them import match_filter you will get the warning that this call to \ matplotlib has no effect, which will mean that match_filter has not \ changed the plotting behaviour. .. note:: The output_cat flag will create an :class: obspy.Catalog \ containing one event for each :class: 'DETECTIONS' generated by \ match_filter. Each event will contain a number of comments dealing \ with correlation values and channels used for the detection. Each \ channel used for the detection will have a corresponding :class: Pick \ which will contain time and waveform information. HOWEVER, the user \ should note that, at present, the pick times do not account for the \ prepick times inherent in each template. For example, if a template \ trace starts 0.1 seconds before the actual arrival of that phase, \ then the pick time generated by match_filter for that phase will be \ 0.1 seconds early. We are looking towards a solution which will \ involve saving templates alongside associated metadata. """ import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt plt.ioff() import copy from eqcorrscan.utils import plotting from eqcorrscan.utils import findpeaks from obspy import Trace, Catalog, UTCDateTime, Stream from obspy.core.event import Event, Pick, CreationInfo, ResourceIdentifier from obspy.core.event import Comment, WaveformStreamID import time if arg_check: # Check the arguments to be nice - if arguments wrong type the parallel # output for the error won't be useful if not type(template_names) == list: raise IOError('template_names must be of type: list') if not type(template_list) == list: raise IOError('templates must be of type: list') for template in template_list: if not type(template) == Stream: msg = 'template in template_list must be of type: ' +\ 'obspy.core.stream.Stream' raise IOError(msg) if not type(st) == Stream: msg = 'st must be of type: obspy.core.stream.Stream' raise IOError(msg) if threshold_type not in ['MAD', 'absolute', 'av_chan_corr']: msg = 'threshold_type must be one of: MAD, absolute, av_chan_corr' raise IOError(msg) # Copy the stream here because we will muck about with it stream = st.copy() templates = copy.deepcopy(template_list) # Debug option to confirm that the channel names match those in the # templates if debug >= 2: template_stachan = [] data_stachan = [] for template in templates: for tr in template: template_stachan.append(tr.stats.station + '.' + tr.stats.channel) for tr in stream: data_stachan.append(tr.stats.station + '.' + tr.stats.channel) template_stachan = list(set(template_stachan)) data_stachan = list(set(data_stachan)) if debug >= 3: print('I have template info for these stations:') print(template_stachan) print('I have daylong data for these stations:') print(data_stachan) # Perform a check that the daylong vectors are daylong for tr in stream: if not tr.stats.sampling_rate * 86400 == tr.stats.npts: msg = ' '.join(['Data are not daylong for', tr.stats.station, tr.stats.channel]) raise ValueError(msg) # Perform check that all template lengths are internally consistent for i, temp in enumerate(template_list): if len(set([tr.stats.npts for tr in temp])) > 1: msg = 'Template %s contains traces of differing length!! THIS \ WILL CAUSE ISSUES' % template_names[i] raise ValueError(msg) # Call the _template_loop function to do all the correlation work outtic = time.clock() # Edit here from previous, stable, but slow match_filter # Would be worth testing without an if statement, but with every station in # the possible template stations having data, but for those without real # data make the data NaN to return NaN ccc_sum # Note: this works if debug >= 2: print('Ensuring all template channels have matches in daylong data') template_stachan = [] for template in templates: for tr in template: template_stachan += [(tr.stats.station, tr.stats.channel)] template_stachan = list(set(template_stachan)) # Copy this here to keep it safe for stachan in template_stachan: if not stream.select(station=stachan[0], channel=stachan[1]): # Remove template traces rather than adding NaN data for template in templates: if template.select(station=stachan[0], channel=stachan[1]): for tr in template.select(station=stachan[0], channel=stachan[1]): template.remove(tr) # Remove un-needed channels for tr in stream: if not (tr.stats.station, tr.stats.channel) in template_stachan: stream.remove(tr) # Also pad out templates to have all channels for template, template_name in zip(templates, template_names): if len(template) == 0: msg = ('No channels matching in continuous data for ' + 'template' + template_name) warnings.warn(msg) templates.remove(template) template_names.remove(template_name) continue for stachan in template_stachan: if not template.select(station=stachan[0], channel=stachan[1]): nulltrace = Trace() nulltrace.stats.station = stachan[0] nulltrace.stats.channel = stachan[1] nulltrace.stats.sampling_rate = template[0].stats.sampling_rate nulltrace.stats.starttime = template[0].stats.starttime nulltrace.data = np.array([np.NaN] * len(template[0].data), dtype=np.float32) template += nulltrace if debug >= 2: print('Starting the correlation run for this day') [cccsums, no_chans, chans] = _channel_loop(templates, stream, cores, debug) if len(cccsums[0]) == 0: raise ValueError('Correlation has not run, zero length cccsum') outtoc = time.clock() print(' '.join(['Looping over templates and streams took:', str(outtoc - outtic), 's'])) if debug >= 2: print(' '.join(['The shape of the returned cccsums is:', str(np.shape(cccsums))])) print(' '.join(['This is from', str(len(templates)), 'templates'])) print(' '.join(['Correlated with', str(len(stream)), 'channels of data'])) detections = [] if output_cat: det_cat = Catalog() for i, cccsum in enumerate(cccsums): template = templates[i] if threshold_type == 'MAD': rawthresh = threshold * np.median(np.abs(cccsum)) elif threshold_type == 'absolute': rawthresh = threshold elif threshold_type == 'av_chan_corr': rawthresh = threshold * no_chans[i] # Findpeaks returns a list of tuples in the form [(cccsum, sample)] print(' '.join(['Threshold is set at:', str(rawthresh)])) print(' '.join(['Max of data is:', str(max(cccsum))])) print(' '.join(['Mean of data is:', str(np.mean(cccsum))])) if np.abs(np.mean(cccsum)) > 0.05: warnings.warn('Mean is not zero! Check this!') # Set up a trace object for the cccsum as this is easier to plot and # maintains timing if plotvar: stream_plot = copy.deepcopy(stream[0]) # Downsample for plotting stream_plot.decimate(int(stream[0].stats.sampling_rate / 10)) cccsum_plot = Trace(cccsum) cccsum_plot.stats.sampling_rate = stream[0].stats.sampling_rate # Resample here to maintain shape better cccsum_hist = cccsum_plot.copy() cccsum_hist = cccsum_hist.decimate(int(stream[0].stats. sampling_rate / 10)).data cccsum_plot = plotting.chunk_data(cccsum_plot, 10, 'Maxabs').data # Enforce same length stream_plot.data = stream_plot.data[0:len(cccsum_plot)] cccsum_plot = cccsum_plot[0:len(stream_plot.data)] cccsum_hist = cccsum_hist[0:len(stream_plot.data)] plotting.triple_plot(cccsum_plot, cccsum_hist, stream_plot, rawthresh, True, plotdir + '/cccsum_plot_' + template_names[i] + '_' + stream[0].stats.starttime. datetime.strftime('%Y-%m-%d') + '.' + plot_format) if debug >= 4: print(' '.join(['Saved the cccsum to:', template_names[i], stream[0].stats.starttime.datetime. strftime('%Y%j')])) np.save(template_names[i] + stream[0].stats.starttime.datetime.strftime('%Y%j'), cccsum) tic = time.clock() if debug >= 4: np.save('cccsum_' + str(i) + '.npy', cccsum) if debug >= 3 and max(cccsum) > rawthresh: peaks = findpeaks.find_peaks2_short(cccsum, rawthresh, trig_int * stream[0].stats. sampling_rate, debug, stream[0].stats.starttime, stream[0].stats.sampling_rate) elif max(cccsum) > rawthresh: peaks = findpeaks.find_peaks2_short(cccsum, rawthresh, trig_int * stream[0].stats. sampling_rate, debug) else: print('No peaks found above threshold') peaks = False toc = time.clock() if debug >= 1: print(' '.join(['Finding peaks took:', str(toc - tic), 's'])) if peaks: for peak in peaks: detecttime = stream[0].stats.starttime +\ peak[1] / stream[0].stats.sampling_rate # Detect time must be valid QuakeML uri within resource_id. # This will write a formatted string which is still readable by UTCDateTime rid = ResourceIdentifier(id=template_names[i] + '_' + str(detecttime.strftime('%Y%m%dT%H%M%S.%f')), prefix='smi:local') ev = Event(resource_id=rid) cr_i = CreationInfo(author='EQcorrscan', creation_time=UTCDateTime()) ev.creation_info = cr_i # All detection info in Comments for lack of a better idea thresh_str = 'threshold=' + str(rawthresh) ccc_str = 'detect_val=' + str(peak[0]) used_chans = 'channels used: ' +\ ' '.join([str(pair) for pair in chans[i]]) ev.comments.append(Comment(text=thresh_str)) ev.comments.append(Comment(text=ccc_str)) ev.comments.append(Comment(text=used_chans)) min_template_tm = min([tr.stats.starttime for tr in template]) for tr in template: if (tr.stats.station, tr.stats.channel) not in chans[i]: continue else: pick_tm = detecttime + (tr.stats.starttime - min_template_tm) wv_id = WaveformStreamID(network_code=tr.stats.network, station_code=tr.stats.station, channel_code=tr.stats.channel) ev.picks.append(Pick(time=pick_tm, waveform_id=wv_id)) detections.append(DETECTION(template_names[i], detecttime, no_chans[i], peak[0], rawthresh, 'corr', chans[i], event=ev)) if output_cat: det_cat.append(ev) if extract_detections: detection_streams = extract_from_stream(stream, detections) del stream, templates if output_cat and not extract_detections: return detections, det_cat elif not extract_detections: return detections elif extract_detections and not output_cat: return detections, detection_streams else: return detections, det_cat, detection_streams
def template_grid(stations, nodes, travel_times, phase, PS_ratio=1.68, \ samp_rate=100, flength=False, phaseout='all'): """ Function to generate a group of synthetic seismograms to simulate phase arrivals from a grid of known sources in a three-dimensional model. Lags must be known and supplied, these can be generated from the bright_lights function: read_tt, and resampled to fit the desired grid dimensions and spacing using other functions therein. These synthetic seismograms are very simple models of seismograms using the seis_sim function herein. These approximate body-wave P and S first arrivals as spikes convolved with damped sine waves. :type stations: List :param stations: List of the station names :type nodes: list of tuple :param nodes: List of node locations in (lon,lat,depth) :type travel_times: np.ndarray :param travel_times: Array of travel times where travel_times[i][:] refers \ to the travel times for station=stations[i], and travel_times[i][j] \ refers to stations[i] for nodes[j] :type phase: String :param phase: Can be either 'P' or 'S' :type PS_ratio: float :param PS_ratio: P/S velocity ratio, defaults to 1.68 :type samp_rate: float :param samp_rate: Desired sample rate in Hz, defaults to 100.0 :type flength: int :param flength: Length of template in samples, defaults to False :type phaseout: str :param phaseout: Either 'S', 'P', 'all' or 'both', determines which phases \ to clip around. 'all' Encompasses both phases in one channel, but\ will return nothing if the flength is not long enough, 'both' will\ return two channels for each stations, one SYN_Z with the synthetic\ P-phase, and one SYN_H with the synthetic S-phase. :returns: List of :class:obspy.Stream """ import warnings if not phase in ['S','P']: raise IOError('Phase is neither P nor S') from obspy import Stream, Trace #Initialize empty list for templates templates=[] # Loop through the nodes, for every node generate a template! for i, node in enumerate(nodes): st=[] # Empty list to be filled with synthetics # Loop through stations for j, station in enumerate(stations): tr=Trace() tr.stats.sampling_rate=samp_rate tr.stats.station=station tr.stats.channel='SYN' tt=travel_times[j][i] if phase=='P': # If the input travel-time is the P-wave travel-time SP_time=(tt*PS_ratio)-tt if phaseout=='S': tr.stats.starttime+=tt+SP_time else: tr.stats.starttime+=tt elif phase=='S': # If the input travel-time is the S-wave travel-time SP_time=tt-(tt/PS_ratio) if phaseout=='S': tr.stats.starttime+=tt else: tr.stats.starttime+=tt-SP_time else: raise IOError('Input grid is not P or S') # Set start-time of trace to be travel-time for P-wave # Check that the template length is long enough to include the SP # if SP_time*samp_rate > flength-11: # print 'No template for '+station # print 'Travel-time is :'+str(tt) # print node # print 'SP time is: '+str(SP_time) # #warnings.warn('Cannot make this template, SP-time '+str(SP_time)+\ # # ' longer than length: '+str(flength/samp_rate)) # continue if SP_time*samp_rate < flength-11 and phaseout=='all': tr.data=seis_sim(SP=int(SP_time*samp_rate), amp_ratio=1.5,\ flength=flength, phaseout=phaseout) st.append(tr) elif phaseout=='all': warnings.warn('Cannot make a bulk synthetic with this fixed '+\ 'length for station '+station) elif phaseout in ['P','S']: tr.data=seis_sim(SP=int(SP_time*samp_rate), amp_ratio=1.5,\ flength=flength, phaseout=phaseout) st.append(tr) elif phaseout == 'both': for _phaseout in ['P', 'S']: _tr=tr.copy() _tr.data=seis_sim(SP=int(SP_time*samp_rate), amp_ratio=1.5,\ flength=flength, phaseout=_phaseout) if _phaseout=='P': _tr.stats.channel='SYN_Z' # starttime defaults to S-time _tr.stats.starttime=_tr.stats.starttime-SP_time elif _phaseout=='S': _tr.stats.channel='SYN_H' st.append(_tr) templates.append(Stream(st)) # Stream(st).plot(size=(800,600)) return templates
if mtype in ['ln_energy_ratio', 'energy_diff']: j = 0.5 * (msr_s - msr_o)**2 elif mtype in [ 'full_waveform', 'windowed_waveform', 'square_envelope', 'envelope' ]: j = 0.5 * np.sum(np.power((msr_s - msr_o), 2)) # left hand side of test 1: # adjt source time function * du = change of misfit wrt u djdc = np.dot(data, d_c) # right hand side of test 1: # Finite difference approx of misfit change for different steps dcheck = [] d_ch = c_syn.copy() for step in steps: d_ch.data = c_ini + 10.**step * d_c msr_sh = m_func(d_ch, **m_a_options) if mtype == 'energy_diff': msr_sh = msr_sh[0] + msr_sh[1] jh = 0.5 * (msr_sh - msr_o)**2 if mtype in [ 'full_waveform', 'windowed_waveform', 'envelope', 'square_envelope' ]: jh = 0.5 * np.sum(np.power((msr_sh - msr_o), 2)) djdch = (jh - j) / (10.**step) dcheck.append(abs(djdc - djdch) / abs(djdc))
channel=ich)[0] s = "%s.%s.%s" % (netwk, ss, ich) # print " s ==", s uts = UTCDateTime( ttt.stats.starttime).timestamp utr = UTCDateTime(reft).timestamp if tdifmin <= 0: timestart = (timex + abs(tdifmin) + (uts - utr)) elif tdifmin > 0: timestart = (timex - abs(tdifmin) + (uts - utr)) timend = timestart + temp_length ta = tc.copy() ta.trim( starttime=timestart, endtime=timend, pad=True, fill_value=0, ) amaxac[il] = max(abs(ta.data)) tid_c = "%s.%s" % (ss, ich) damaxac[tid_c] = float(amaxac[il]) dct = damaxac[tid_c] dtt = damaxat[tid_c] if dct != 0 and dtt != 0: md[il] = mag_detect( mt, damaxat[tid_c], damaxac[tid_c])
class TestCosTaper(unittest.TestCase): def setUp(self): self.sr = 10 # sampling rate st = AttribDict({'sampling_rate': self.sr}) self.testtr = Trace(np.ones(1000), header=st) tl = np.random.randint(1, high=20) self.tls = tl * self.sr # taper len in samples self.tr_res = pu.cos_taper(self.testtr.copy(), tl, False) self.tr_resl = pu.cos_taper(self.testtr.copy(), tl, False, side='left') self.tr_resr = pu.cos_taper(self.testtr.copy(), tl, False, side='right') def test_in_place(self): self.sr = 10 # sampling rate st = AttribDict({'sampling_rate': self.sr}) testtro = Trace(np.ones(1000), header=st) testtr = testtro.copy() self.assertEqual(testtr, testtro) pu.cos_taper(testtr, 5, False) self.assertNotEqual(testtr, testtro) def test_ends(self): # Check that ends reduce to 0 self.assertAlmostEqual(self.tr_res.data[0], 0) self.assertAlmostEqual(self.tr_resl.data[0], 0) self.assertAlmostEqual(self.tr_res.data[-1], 0) self.assertAlmostEqual(self.tr_resr.data[-1], 0) # one-sided taper self.assertAlmostEqual(self.tr_resl.data[-1], 1) self.assertAlmostEqual(self.tr_resr.data[0], 1) def test_middle(self): # Assert that the rest (in the middle) stayed the same self.assertTrue( np.array_equal(self.testtr[self.tls:-self.tls], self.tr_res[self.tls:-self.tls])) self.assertTrue( np.array_equal(self.testtr[self.tls:-self.tls], self.tr_resr[self.tls:-self.tls])) self.assertTrue( np.array_equal(self.testtr[self.tls:-self.tls], self.tr_resl[self.tls:-self.tls])) def test_up_down(self): # Everything else should be between 1 and 0 # up self.assertTrue(np.all(self.tr_res[1:-1] > 0)) self.assertTrue(np.all(self.tr_resr[1:-1] > 0)) self.assertTrue(np.all(self.tr_resl[1:-1] > 0)) self.assertTrue(np.all(self.tr_res[1:self.tls] < 1)) self.assertTrue(np.all(self.tr_resl[1:self.tls] < 1)) # down self.assertTrue(np.all(self.tr_res[-self.tls:-1] < 1)) self.assertTrue(np.all(self.tr_resr[-self.tls:-1] < 1)) def test_empty_trace(self): testtr = Trace(np.array([]), header=self.testtr.stats) with self.assertRaises(ValueError): pu.cos_taper(testtr, 10, False) def test_invalid_taper_len(self): with self.assertRaises(ValueError): pu.cos_taper(self.testtr.copy(), np.random.randint(-100, 0), False) with self.assertRaises(ValueError): pu.cos_taper(self.testtr.copy(), 501 * self.sr, False) def test_masked_value(self): tr0 = read()[0] tr1 = tr0.copy() tr1.stats.starttime += 240 st = Stream([tr0, tr1]) tr = st.merge()[0] tl = np.random.randint(1, high=5) ttr = pu.cos_taper(tr, tl, True) # Check that ends reduce to 0 self.assertAlmostEqual(ttr.data[0], 0) self.assertAlmostEqual(ttr.data[-1], 0) self.assertAlmostEqual(ttr.data[tr0.count() - 1], 0) self.assertAlmostEqual(ttr.data[-tr1.count()], 0) # Also the mask should be retained self.assertEqual(len(ttr.data[ttr.data.mask]), ttr.count() - tr0.count() - tr1.count())
def extract_s(taupy_model, picker, event, station_longitude, station_latitude, stn, ste, ba, win_start=-50, win_end=50, resample_hz=20, bp_freqmins=[0.05, 2], bp_freqmaxs=[0.5, 5], margin=20, max_amplitude=1e8): po = event.preferred_origin if (not po): return None atimes = [] try: atimes = taupy_model.get_travel_times_geo(po.depthkm, po.lat, po.lon, station_latitude, station_longitude, phase_list=('S', )) except: return None # end try if (len(atimes) == 0): return None tat = atimes[0].time # theoretical arrival time tr = None try: stn = stn.slice(po.utctime + tat + win_start, po.utctime + tat + win_end) stn.resample(resample_hz) if (ste): ste = ste.slice(po.utctime + tat + win_start, po.utctime + tat + win_end) ste.resample(resample_hz) # end if if (ste): if (type(stn[0].data) == np.ndarray and type(ste[0].data) == np.ndarray): rc, tc = rotate_ne_rt(stn[0].data, ste[0].data, ba) tr = Trace(data=tc, header=stn[0].stats) #tr = Trace(data=np.sqrt(np.power(rc,2) + np.power(tc,2)), header=stn[0].stats) # end if else: if (type(stn[0].data) == np.ndarray): tr = stn[0] # end if # end if except Exception as e: return None # end try if (tr): if (np.max(tr.data) > max_amplitude): return None pickslist = [] snrlist = [] residuallist = [] tr.detrend('linear') for i in range(len(bp_freqmins)): trc = tr.copy() trc.filter('bandpass', freqmin=bp_freqmins[i], freqmax=bp_freqmaxs[i], corners=4, zerophase=True) try: scnl, picks, polarity, snr, uncert = picker.picks(trc) for ipick, pick in enumerate(picks): actualArrival = pick - po.utctime residual = actualArrival - tat if (np.fabs(residual) < margin): pickslist.append(pick) snrlist.append(snr[ipick]) residuallist.append(residual) #summary = fbpicker.FBSummary(picker, trc) #summary = aicdpicker.AICDSummary(picker, trc) #outputPath = '/home/rakib/work/pst/picking/sarr' #outputPath = '/g/data1a/ha3/rakib/seismic/pst/tests/plots/new' #ofn = '%s/%s.%s_%f_%d.s.png' % (outputPath, scnl, str(po.utctime), snr[0], i) #summary.plot_picks(show=False, savefn=ofn) # end if # end for except: continue # end try # end for if (len(pickslist)): optimal_pick_idx = np.argmax(np.array(snrlist)) return pickslist[optimal_pick_idx], residuallist[optimal_pick_idx], \ snrlist[optimal_pick_idx], optimal_pick_idx # end if # end if return None
def match_filter(template_names, template_list, st, threshold, threshold_type, trig_int, plotvar, plotdir='.', cores=1, tempdir=False, debug=0, plot_format='png', output_cat=False, extract_detections=False, arg_check=True): """ Main matched-filter detection function. Over-arching code to run the correlations of given templates with a \ day of seismic data and output the detections based on a given threshold. For a functional example see the tutorials. :type template_names: list :param template_names: List of template names in the same order as \ template_list :type template_list: list :param template_list: A list of templates of which each template is a \ Stream of obspy traces containing seismic data and header information. :type st: obspy.core.stream.Stream :param st: An obspy.Stream object containing all the data available and \ required for the correlations with templates given. For efficiency \ this should contain no excess traces which are not in one or more of \ the templates. This will now remove excess traces internally, but \ will copy the stream and work on the copy, leaving your input stream \ untouched. :type threshold: float :param threshold: A threshold value set based on the threshold_type :type threshold_type: str :param threshold_type: The type of threshold to be used, can be MAD, \ absolute or av_chan_corr. MAD threshold is calculated as the \ threshold*(median(abs(cccsum))) where cccsum is the cross-correlation \ sum for a given template. absolute threhsold is a true absolute \ threshold based on the cccsum value av_chan_corr is based on the mean \ values of single-channel cross-correlations assuming all data are \ present as required for the template, \ e.g. av_chan_corr_thresh=threshold*(cccsum/len(template)) where \ template is a single template from the input and the length is the \ number of channels within this template. :type trig_int: float :param trig_int: Minimum gap between detections in seconds. :type plotvar: bool :param plotvar: Turn plotting on or off :type plotdir: str :param plotdir: Path to plotting folder, plots will be output here, \ defaults to run location. :type tempdir: str :param tempdir: Directory to put temporary files, or False :type cores: int :param cores: Number of cores to use :type debug: int :param debug: Debug output level, the bigger the number, the more the \ output. :type plot_format: str :param plot_format: Specify format of output plots if saved :type output_cat: bool :param output_cat: Specifies if matched_filter will output an \ obspy.Catalog class containing events for each detection. Default \ is False, in which case matched_filter will output a list of \ detection classes, as normal. :type extract_detections: bool :param extract_detections: Specifies whether or not to return a list of \ streams, one stream per detection. :type arg_check: bool :param arg_check: Check arguments, defaults to True, but if running in \ bulk, and you are certain of your arguments, then set to False. :return: :class: 'DETECTIONS' detections for each channel formatted as \ :class: 'obspy.UTCDateTime' objects. :return: :class: obspy.Catalog containing events for each detection. :return: list of :class: obspy.Stream objects for each detection. .. note:: Plotting within the match-filter routine uses the Agg backend \ with interactive plotting turned off. This is because the function \ is designed to work in bulk. If you wish to turn interactive \ plotting on you must import matplotlib in your script first, when you \ them import match_filter you will get the warning that this call to \ matplotlib has no effect, which will mean that match_filter has not \ changed the plotting behaviour. .. note:: The output_cat flag will create an :class: obspy.Catalog \ containing one event for each :class: 'DETECTIONS' generated by \ match_filter. Each event will contain a number of comments dealing \ with correlation values and channels used for the detection. Each \ channel used for the detection will have a corresponding :class: Pick \ which will contain time and waveform information. HOWEVER, the user \ should note that, at present, the pick times do not account for the \ prepick times inherent in each template. For example, if a template \ trace starts 0.1 seconds before the actual arrival of that phase, \ then the pick time generated by match_filter for that phase will be \ 0.1 seconds early. We are looking towards a solution which will \ involve saving templates alongside associated metadata. """ import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt plt.ioff() import copy from eqcorrscan.utils import plotting from eqcorrscan.utils import findpeaks from obspy import Trace, Catalog, UTCDateTime, Stream from obspy.core.event import Event, Pick, CreationInfo, ResourceIdentifier from obspy.core.event import Comment, WaveformStreamID import time if arg_check: # Check the arguments to be nice - if arguments wrong type the parallel # output for the error won't be useful if not type(template_names) == list: raise IOError('template_names must be of type: list') if not type(template_list) == list: raise IOError('templates must be of type: list') for template in template_list: if not type(template) == Stream: msg = 'template in template_list must be of type: ' +\ 'obspy.core.stream.Stream' raise IOError(msg) if not type(st) == Stream: msg = 'st must be of type: obspy.core.stream.Stream' raise IOError(msg) if str(threshold_type) not in [str('MAD'), str('absolute'), str('av_chan_corr')]: msg = 'threshold_type must be one of: MAD, absolute, av_chan_corr' raise IOError(msg) # Copy the stream here because we will muck about with it stream = st.copy() templates = copy.deepcopy(template_list) # Debug option to confirm that the channel names match those in the # templates if debug >= 2: template_stachan = [] data_stachan = [] for template in templates: for tr in template: template_stachan.append(tr.stats.station + '.' + tr.stats.channel) for tr in stream: data_stachan.append(tr.stats.station + '.' + tr.stats.channel) template_stachan = list(set(template_stachan)) data_stachan = list(set(data_stachan)) if debug >= 3: print('I have template info for these stations:') print(template_stachan) print('I have daylong data for these stations:') print(data_stachan) # Perform a check that the daylong vectors are all the same length min_start_time = min([tr.stats.starttime for tr in stream]) max_end_time = max([tr.stats.endtime for tr in stream]) longest_trace_length = stream[0].stats.sampling_rate * (max_end_time - min_start_time) for tr in stream: if not tr.stats.npts == longest_trace_length: msg = 'Data are not equal length, padding short traces' warnings.warn(msg) start_pad = np.zeros(int(tr.stats.sampling_rate * (tr.stats.starttime - min_start_time))) end_pad = np.zeros(int(tr.stats.sampling_rate * (max_end_time - tr.stats.endtime))) tr.data = np.concatenate([start_pad, tr.data, end_pad]) # Perform check that all template lengths are internally consistent for i, temp in enumerate(template_list): if len(set([tr.stats.npts for tr in temp])) > 1: msg = 'Template %s contains traces of differing length!! THIS \ WILL CAUSE ISSUES' % template_names[i] raise ValueError(msg) # Call the _template_loop function to do all the correlation work outtic = time.clock() # Edit here from previous, stable, but slow match_filter # Would be worth testing without an if statement, but with every station in # the possible template stations having data, but for those without real # data make the data NaN to return NaN ccc_sum # Note: this works if debug >= 2: print('Ensuring all template channels have matches in long data') template_stachan = [] for template in templates: for tr in template: template_stachan += [(tr.stats.station, tr.stats.channel)] template_stachan = list(set(template_stachan)) # Copy this here to keep it safe for stachan in template_stachan: if not stream.select(station=stachan[0], channel=stachan[1]): # Remove template traces rather than adding NaN data for template in templates: if template.select(station=stachan[0], channel=stachan[1]): for tr in template.select(station=stachan[0], channel=stachan[1]): template.remove(tr) # Remove un-needed channels for tr in stream: if not (tr.stats.station, tr.stats.channel) in template_stachan: stream.remove(tr) # Also pad out templates to have all channels for template, template_name in zip(templates, template_names): if len(template) == 0: msg = ('No channels matching in continuous data for ' + 'template' + template_name) warnings.warn(msg) templates.remove(template) template_names.remove(template_name) continue for stachan in template_stachan: if not template.select(station=stachan[0], channel=stachan[1]): nulltrace = Trace() nulltrace.stats.station = stachan[0] nulltrace.stats.channel = stachan[1] nulltrace.stats.sampling_rate = template[0].stats.sampling_rate nulltrace.stats.starttime = template[0].stats.starttime nulltrace.data = np.array([np.NaN] * len(template[0].data), dtype=np.float32) template += nulltrace if debug >= 2: print('Starting the correlation run for this day') [cccsums, no_chans, chans] = _channel_loop(templates=templates, stream=stream, cores=cores, debug=debug) if len(cccsums[0]) == 0: raise ValueError('Correlation has not run, zero length cccsum') outtoc = time.clock() print(' '.join(['Looping over templates and streams took:', str(outtoc - outtic), 's'])) if debug >= 2: print(' '.join(['The shape of the returned cccsums is:', str(np.shape(cccsums))])) print(' '.join(['This is from', str(len(templates)), 'templates'])) print(' '.join(['Correlated with', str(len(stream)), 'channels of data'])) detections = [] if output_cat: det_cat = Catalog() for i, cccsum in enumerate(cccsums): template = templates[i] if str(threshold_type) == str('MAD'): rawthresh = threshold * np.median(np.abs(cccsum)) elif str(threshold_type) == str('absolute'): rawthresh = threshold elif str(threshold_type) == str('av_chan_corr'): rawthresh = threshold * no_chans[i] # Findpeaks returns a list of tuples in the form [(cccsum, sample)] print(' '.join(['Threshold is set at:', str(rawthresh)])) print(' '.join(['Max of data is:', str(max(cccsum))])) print(' '.join(['Mean of data is:', str(np.mean(cccsum))])) if np.abs(np.mean(cccsum)) > 0.05: warnings.warn('Mean is not zero! Check this!') # Set up a trace object for the cccsum as this is easier to plot and # maintains timing if plotvar: stream_plot = copy.deepcopy(stream[0]) # Downsample for plotting stream_plot.decimate(int(stream[0].stats.sampling_rate / 10)) cccsum_plot = Trace(cccsum) cccsum_plot.stats.sampling_rate = stream[0].stats.sampling_rate # Resample here to maintain shape better cccsum_hist = cccsum_plot.copy() cccsum_hist = cccsum_hist.decimate(int(stream[0].stats. sampling_rate / 10)).data cccsum_plot = plotting.chunk_data(cccsum_plot, 10, 'Maxabs').data # Enforce same length stream_plot.data = stream_plot.data[0:len(cccsum_plot)] cccsum_plot = cccsum_plot[0:len(stream_plot.data)] cccsum_hist = cccsum_hist[0:len(stream_plot.data)] plotting.triple_plot(cccsum_plot, cccsum_hist, stream_plot, rawthresh, True, plotdir + '/cccsum_plot_' + template_names[i] + '_' + stream[0].stats.starttime. datetime.strftime('%Y-%m-%d') + '.' + plot_format) if debug >= 4: print(' '.join(['Saved the cccsum to:', template_names[i], stream[0].stats.starttime.datetime. strftime('%Y%j')])) np.save(template_names[i] + stream[0].stats.starttime.datetime.strftime('%Y%j'), cccsum) tic = time.clock() if debug >= 4: np.save('cccsum_' + str(i) + '.npy', cccsum) if debug >= 3 and max(cccsum) > rawthresh: peaks = findpeaks.find_peaks2_short(cccsum, rawthresh, trig_int * stream[0].stats. sampling_rate, debug, stream[0].stats.starttime, stream[0].stats.sampling_rate) elif max(cccsum) > rawthresh: peaks = findpeaks.find_peaks2_short(cccsum, rawthresh, trig_int * stream[0].stats. sampling_rate, debug) else: print('No peaks found above threshold') peaks = False toc = time.clock() if debug >= 1: print(' '.join(['Finding peaks took:', str(toc - tic), 's'])) if peaks: for peak in peaks: detecttime = stream[0].stats.starttime +\ peak[1] / stream[0].stats.sampling_rate # Detect time must be valid QuakeML uri within resource_id. # This will write a formatted string which is still # readable by UTCDateTime rid = ResourceIdentifier(id=template_names[i] + '_' + str(detecttime. strftime('%Y%m%dT%H%M%S.%f')), prefix='smi:local') ev = Event(resource_id=rid) cr_i = CreationInfo(author='EQcorrscan', creation_time=UTCDateTime()) ev.creation_info = cr_i # All detection info in Comments for lack of a better idea thresh_str = 'threshold=' + str(rawthresh) ccc_str = 'detect_val=' + str(peak[0]) used_chans = 'channels used: ' +\ ' '.join([str(pair) for pair in chans[i]]) ev.comments.append(Comment(text=thresh_str)) ev.comments.append(Comment(text=ccc_str)) ev.comments.append(Comment(text=used_chans)) min_template_tm = min([tr.stats.starttime for tr in template]) for tr in template: if (tr.stats.station, tr.stats.channel) not in chans[i]: continue else: pick_tm = detecttime + (tr.stats.starttime - min_template_tm) wv_id = WaveformStreamID(network_code=tr.stats.network, station_code=tr.stats.station, channel_code=tr.stats.channel) ev.picks.append(Pick(time=pick_tm, waveform_id=wv_id)) detections.append(DETECTION(template_names[i], detecttime, no_chans[i], peak[0], rawthresh, 'corr', chans[i], event=ev)) if output_cat: det_cat.append(ev) if extract_detections: detection_streams = extract_from_stream(stream, detections) del stream, templates if output_cat and not extract_detections: return detections, det_cat elif not extract_detections: return detections elif extract_detections and not output_cat: return detections, detection_streams else: return detections, det_cat, detection_streams
def extract_s(taupy_model, pickerlist, event, station_longitude, station_latitude, stn, ste, ba, win_start=-50, win_end=50, resample_hz=20, bp_freqmins=[0.01, 0.01, 0.5], bp_freqmaxs=[1, 2., 5.], margin=None, max_amplitude=1e8, plot_output_folder=None): po = event.preferred_origin if (not po): return None atimes = [] try: atimes = taupy_model.get_travel_times_geo(po.depthkm, po.lat, po.lon, station_latitude, station_longitude, phase_list=('S', )) except: return None # end try if (len(atimes) == 0): return None tat = atimes[0].time # theoretical arrival time buffer_start = -10 buffer_end = 10 snrtr = None try: stn = stn.slice(po.utctime + tat + win_start + buffer_start, po.utctime + tat + win_end + buffer_end) stn = stn.copy() stn.resample(resample_hz) stn.detrend('linear') if (ste): ste = ste.slice(po.utctime + tat + win_start + buffer_start, po.utctime + tat + win_end + buffer_end) ste = ste.copy() ste.resample(resample_hz) ste.detrend('linear') # end if if (ste): if (type(stn[0].data) == np.ndarray and type(ste[0].data) == np.ndarray): rc, tc = rotate_ne_rt(stn[0].data, ste[0].data, ba) snrtr = Trace(data=tc, header=stn[0].stats) snrtr.detrend('linear') # end if else: if (type(stn[0].data) == np.ndarray): snrtr = stn[0] # end if # end if except Exception as e: return None # end try if (type(snrtr.data) == np.ndarray): if (np.max(snrtr.data) > max_amplitude): return None pickslist = [] snrlist = [] residuallist = [] bandindex = -1 pickerindex = -1 taper_percentage = float(buffer_end) / float(win_end) foundpicks = False for i in range(len(bp_freqmins)): trc = snrtr.copy() trc.taper(max_percentage=taper_percentage, type='hann') trc.filter('bandpass', freqmin=bp_freqmins[i], freqmax=bp_freqmaxs[i], corners=4, zerophase=True) trc = trc.slice(po.utctime + tat + win_start, po.utctime + tat + win_end) for ipicker, picker in enumerate(pickerlist): try: scnl, picks, polarity, snr, uncert = picker.picks(trc) for ipick, pick in enumerate(picks): actualArrival = pick - po.utctime residual = actualArrival - tat if ((margin and np.fabs(residual) < margin) or (margin == None)): pickslist.append(pick) plotinfo = None if (plot_output_folder): plotinfo = { 'eventid': event.public_id, 'origintime': po.utctime, 'mag': event.preferred_magnitude.magnitude_value, 'net': trc.stats.network, 'sta': trc.stats.station, 'phase': 's', 'ppsnr': snr[ipick], 'pickid': ipick, 'outputfolder': plot_output_folder } # end if wab = snrtr.slice(pick - 3, pick + 3) wab_filtered = trc.slice(pick - 3, pick + 3) scales = np.logspace(0.5, 4, 30) cwtsnr, dom_freq, slope_ratio = compute_quality_measures( wab, wab_filtered, scales, plotinfo) snrlist.append( [snr[ipick], cwtsnr, dom_freq, slope_ratio]) residuallist.append(residual) bandindex = i pickerindex = ipicker foundpicks = True # end if # end for except: continue # end try if (foundpicks): break # end for if (foundpicks): break # end for if (len(pickslist)): return pickslist, residuallist, \ np.array(snrlist), bandindex, pickerindex # end if # end if return None
def match_filter( template_names, templates, stream, threshold, threshold_type, trig_int, plotvar, cores=1, tempdir=False, debug=0 ): """ Over-arching code to run the correlations of given templates with a day of seismic data and output the detections based on a given threshold. :type templates: list :class: 'obspy.Stream' :param templates: A list of templates of which each template is a Stream of\ obspy traces containing seismic data and header information. :type stream: :class: 'obspy.Stream' :param stream: An obspy.Stream object containing all the data available and\ required for the correlations with templates given. For efficiency this\ should contain no excess traces which are not in one or more of the\ templates. :type threshold: float :param threshold: A threshold value set based on the threshold_type :type threshold_type: str :param threshold_type: The type of threshold to be used, can be MAD,\ absolute or av_chan_corr. MAD threshold is calculated as the\ threshold*(median(abs(cccsum))) where cccsum is the cross-correlation\ sum for a given template. absolute threhsold is a true absolute\ threshold based on the cccsum value av_chan_corr is based on the mean\ values of single-channel cross-correlations assuming all data are\ present as required for the template, \ e.g. av_chan_corr_thresh=threshold*(cccsum/len(template)) where\ template is a single template from the input and the length is the\ number of channels within this template. :type trig_int: float :param trig_int: Minimum gap between detections in seconds. :type tempdir: String or False :param tempdir: Directory to put temporary files, or False :type cores: int :param cores: Number of cores to use :type debug: int :param debug: Debug output level, the bigger the number, the more the output :return: :class: 'DETECTIONS' detections for each channel formatted as\ :class: 'obspy.UTCDateTime' objects. """ from eqcorrscan.utils import findpeaks, EQcorrscan_plotting import time, copy from obspy import Trace match_internal = False # Set to True if memory is an issue, if True, will only # use about the same amount of memory as the seismic dat # take up. If False, it will use 20-100GB per instance # Debug option to confirm that the channel names match those in the templates if debug >= 2: template_stachan = [] data_stachan = [] for template in templates: for tr in template: template_stachan.append(tr.stats.station + "." + tr.stats.channel) for tr in stream: data_stachan.append(tr.stats.station + "." + tr.stats.channel) template_stachan = list(set(template_stachan)) data_stachan = list(set(data_stachan)) if debug >= 3: print "I have template info for these stations:" print template_stachan print "I have daylong data for these stations:" print data_stachan # Perform a check that the daylong vectors are daylong for tr in stream: if not tr.stats.sampling_rate * 86400 == tr.stats.npts: raise ValueError("Data are not daylong for " + tr.stats.station + "." + tr.stats.channel) # Call the _template_loop function to do all the correlation work outtic = time.clock() # Edit here from previous, stable, but slow match_filter # Would be worth testing without an if statement, but with every station in # the possible template stations having data, but for those without real # data make the data NaN to return NaN ccc_sum if debug >= 2: print "Ensuring all template channels have matches in daylong data" template_stachan = [] for template in templates: for tr in template: template_stachan += [(tr.stats.station, tr.stats.channel)] template_stachan = list(set(template_stachan)) # Copy this here to keep it safe for stachan in template_stachan: if not stream.select(station=stachan[0], channel=stachan[1]): # Add a trace of NaN's nulltrace = Trace() nulltrace.stats.station = stachan[0] nulltrace.stats.channel = stachan[1] nulltrace.stats.sampling_rate = stream[0].stats.sampling_rate nulltrace.stats.starttime = stream[0].stats.starttime nulltrace.data = np.array([np.NaN] * len(stream[0].data), dtype=np.float32) stream += nulltrace # Also pad out templates to have all channels for template in templates: for stachan in template_stachan: if not template.select(station=stachan[0], channel=stachan[1]): nulltrace = Trace() nulltrace.stats.station = stachan[0] nulltrace.stats.channel = stachan[1] nulltrace.stats.sampling_rate = template[0].stats.sampling_rate nulltrace.stats.starttime = template[0].stats.starttime nulltrace.data = np.array([np.NaN] * len(template[0].data), dtype=np.float32) template += nulltrace if debug >= 2: print "Starting the correlation run for this day" if match_internal: [cccsums, no_chans] = run_channel_loop(templates, stream, tempdir) else: [cccsums, no_chans] = _channel_loop(templates, stream, cores, debug) if len(cccsums[0]) == 0: raise ValueError("Correlation has not run, zero length cccsum") outtoc = time.clock() print "Looping over templates and streams took: " + str(outtoc - outtic) + " s" if debug >= 2: print "The shape of the returned cccsums is: " + str(np.shape(cccsums)) print "This is from " + str(len(templates)) + " templates" print "Correlated with " + str(len(stream)) + " channels of data" i = 0 detections = [] for cccsum in cccsums: template = templates[i] if threshold_type == "MAD": rawthresh = threshold * np.median(np.abs(cccsum)) elif threshold_type == "absolute": rawthresh = threshold elif threshold == "av_chan_corr": rawthresh = threshold * (cccsum / len(template)) else: print "You have not selected the correct threshold type, I will use MAD as I like it" rawthresh = threshold * np.mean(np.abs(cccsum)) # Findpeaks returns a list of tuples in the form [(cccsum, sample)] print "Threshold is set at: " + str(rawthresh) print "Max of data is: " + str(max(cccsum)) print "Mean of data is: " + str(np.mean(cccsum)) if np.abs(np.mean(cccsum)) > 0.05: warnings.warn("Mean is not zero! Check this!") # Set up a trace object for the cccsum as this is easier to plot and # maintins timing if plotvar: stream_plot = copy.deepcopy(stream[0]) # Downsample for plotting stream_plot.decimate(int(stream[0].stats.sampling_rate / 20)) cccsum_plot = Trace(cccsum) cccsum_plot.stats.sampling_rate = stream[0].stats.sampling_rate # Resample here to maintain shape better cccsum_hist = cccsum_plot.copy() cccsum_hist = cccsum_hist.decimate(int(stream[0].stats.sampling_rate / 20)).data cccsum_plot = EQcorrscan_plotting.chunk_data(cccsum_plot, 20, "Maxabs").data # Enforce same length stream_plot.data = stream_plot.data[0 : len(cccsum_plot)] cccsum_plot = cccsum_plot[0 : len(stream_plot.data)] cccsum_hist = cccsum_hist[0 : len(stream_plot.data)] EQcorrscan_plotting.triple_plot( cccsum_plot, cccsum_hist, stream_plot, rawthresh, True, "plot/cccsum_plot_" + template_names[i] + "_" + str(stream[0].stats.starttime.year) + "-" + str(stream[0].stats.starttime.month) + "-" + str(stream[0].stats.starttime.day) + ".jpg", ) np.save(template_names[i] + stream[0].stats.starttime.datetime.strftime("%Y%j"), cccsum) tic = time.clock() if debug >= 4: np.save("cccsum_" + str(i) + ".npy", cccsum) if debug >= 3 and max(cccsum) > rawthresh: peaks = findpeaks.find_peaks2_short( cccsum, rawthresh, trig_int * stream[0].stats.sampling_rate, debug, stream[0].stats.starttime, stream[0].stats.sampling_rate, ) elif max(cccsum) > rawthresh: peaks = findpeaks.find_peaks2_short(cccsum, rawthresh, trig_int * stream[0].stats.sampling_rate, debug) else: print "No peaks found above threshold" peaks = False toc = time.clock() if debug >= 1: print "Finding peaks took: " + str(toc - tic) + " s" if peaks: for peak in peaks: detecttime = stream[0].stats.starttime + peak[1] / stream[0].stats.sampling_rate detections.append(DETECTION(template_names[i], detecttime, no_chans[i], peak[0], rawthresh, "corr")) i += 1 return detections
def template_grid(stations, nodes, travel_times, phase, PS_ratio=1.68, samp_rate=100, flength=False, phaseout='all'): """ Generate a group of synthetic seismograms for a grid of sources. Used to simulate phase arrivals from a grid of known sources in a three-dimensional model. Lags must be known and supplied, these can be generated from the bright_lights function: read_tt, and resampled to fit the desired grid dimensions and spacing using other functions therein. These synthetic seismograms are very simple models of seismograms using the seis_sim function herein. These approximate body-wave P and S first arrivals as spikes convolved with damped sine waves. :type stations: list :param stations: List of the station names :type nodes: list :param nodes: List of node locations in (lon,lat,depth) :type travel_times: numpy.ndarray :param travel_times: Array of travel times where travel_times[i][:] \ refers to the travel times for station=stations[i], and \ travel_times[i][j] refers to stations[i] for nodes[j] :type phase: str :param phase: Can be either 'P' or 'S' :type PS_ratio: float :param PS_ratio: P/S velocity ratio, defaults to 1.68 :type samp_rate: float :param samp_rate: Desired sample rate in Hz, defaults to 100.0 :type flength: int :param flength: Length of template in samples, defaults to False :type phaseout: str :param phaseout: Either 'S', 'P', 'all' or 'both', determines which \ phases to clip around. 'all' Encompasses both phases in one channel, \ but will return nothing if the flength is not long enough, 'both' \ will return two channels for each stations, one SYN_Z with the \ synthetic P-phase, and one SYN_H with the synthetic S-phase. :returns: List of :class:`obspy.core.stream.Stream` """ if phase not in ['S', 'P']: raise IOError('Phase is neither P nor S') # Initialize empty list for templates templates = [] # Loop through the nodes, for every node generate a template! for i, node in enumerate(nodes): st = [] # Empty list to be filled with synthetics # Loop through stations for j, station in enumerate(stations): tr = Trace() tr.stats.sampling_rate = samp_rate tr.stats.station = station tr.stats.channel = 'SYN' tt = travel_times[j][i] if phase == 'P': # If the input travel-time is the P-wave travel-time SP_time = (tt * PS_ratio) - tt if phaseout == 'S': tr.stats.starttime += tt + SP_time else: tr.stats.starttime += tt elif phase == 'S': # If the input travel-time is the S-wave travel-time SP_time = tt - (tt / PS_ratio) if phaseout == 'S': tr.stats.starttime += tt else: tr.stats.starttime += tt - SP_time else: raise IOError('Input grid is not P or S') # Set start-time of trace to be travel-time for P-wave # Check that the template length is long enough to include the SP if flength and SP_time * samp_rate < flength - 11 \ and phaseout == 'all': tr.data = seis_sim(sp=int(SP_time * samp_rate), amp_ratio=1.5, flength=flength, phaseout=phaseout) st.append(tr) elif flength and phaseout == 'all': warnings.warn('Cannot make a bulk synthetic with this fixed ' + 'length for station ' + station) elif phaseout == 'all': tr.data = seis_sim(sp=int(SP_time * samp_rate), amp_ratio=1.5, flength=flength, phaseout=phaseout) st.append(tr) elif phaseout in ['P', 'S']: tr.data = seis_sim(sp=int(SP_time * samp_rate), amp_ratio=1.5, flength=flength, phaseout=phaseout) st.append(tr) elif phaseout == 'both': for _phaseout in ['P', 'S']: _tr = tr.copy() _tr.data = seis_sim(sp=int(SP_time * samp_rate), amp_ratio=1.5, flength=flength, phaseout=_phaseout) if _phaseout == 'P': _tr.stats.channel = 'SYN_Z' # starttime defaults to S-time _tr.stats.starttime = _tr.stats.starttime - SP_time elif _phaseout == 'S': _tr.stats.channel = 'SYN_H' st.append(_tr) templates.append(Stream(st)) # Stream(st).plot(size=(800,600)) return templates
def match_filter(template_names, template_list, st, threshold, threshold_type, trig_int, plotvar, plotdir='.', cores=1, tempdir=False, debug=0, plot_format='jpg'): r"""Over-arching code to run the correlations of given templates with a\ day of seismic data and output the detections based on a given threshold. :type template_names: list :param template_names: List of template names in the same order as\ template_list :type template_list: list :class: 'obspy.Stream' :param template_list: A list of templates of which each template is a\ Stream of obspy traces containing seismic data and header information. :type st: :class: 'obspy.Stream' :param st: An obspy.Stream object containing all the data available and\ required for the correlations with templates given. For efficiency\ this should contain no excess traces which are not in one or more of\ the templates. This will now remove excess traces internally, but\ will copy the stream and work on the copy, leaving your input stream\ untouched. :type threshold: float :param threshold: A threshold value set based on the threshold_type :type threshold_type: str :param threshold_type: The type of threshold to be used, can be MAD,\ absolute or av_chan_corr. MAD threshold is calculated as the\ threshold*(median(abs(cccsum))) where cccsum is the cross-correlation\ sum for a given template. absolute threhsold is a true absolute\ threshold based on the cccsum value av_chan_corr is based on the mean\ values of single-channel cross-correlations assuming all data are\ present as required for the template, \ e.g. av_chan_corr_thresh=threshold*(cccsum/len(template)) where\ template is a single template from the input and the length is the\ number of channels within this template. :type trig_int: float :param trig_int: Minimum gap between detections in seconds. :type plotvar: bool :param plotvar: Turn plotting on or off :type plotdir: str :param plotdir: Path to plotting folder, plots will be output here,\ defaults to run location. :type tempdir: String or False :param tempdir: Directory to put temporary files, or False :type cores: int :param cores: Number of cores to use :type debug: int :param debug: Debug output level, the bigger the number, the more the\ output. :return: :class: 'DETECTIONS' detections for each channel formatted as\ :class: 'obspy.UTCDateTime' objects. .. rubric:: Note Plotting within the match-filter routine uses the Agg backend with\ interactive plotting turned off. This is because the function is\ designed to work in bulk. If you wish to turn interactive plotting on\ you must import matplotlib in your script first, when you them import\ match_filter you will get the warning that this call to matplotlib has\ no effect, which will mean that match_filter has not changed the\ plotting behaviour. """ import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt plt.ioff() import copy from eqcorrscan.utils import EQcorrscan_plotting from eqcorrscan.utils import findpeaks from obspy import Trace import time # Copy the stream here because we will f**k about with it stream = st.copy() templates = copy.deepcopy(template_list) # Debug option to confirm that the channel names match those in the # templates if debug >= 2: template_stachan = [] data_stachan = [] for template in templates: for tr in template: template_stachan.append(tr.stats.station + '.' + tr.stats.channel) for tr in stream: data_stachan.append(tr.stats.station + '.' + tr.stats.channel) template_stachan = list(set(template_stachan)) data_stachan = list(set(data_stachan)) if debug >= 3: print 'I have template info for these stations:' print template_stachan print 'I have daylong data for these stations:' print data_stachan # Perform a check that the daylong vectors are daylong for tr in stream: if not tr.stats.sampling_rate * 86400 == tr.stats.npts: msg = ' '.join([ 'Data are not daylong for', tr.stats.station, tr.stats.channel ]) raise ValueError(msg) # Call the _template_loop function to do all the correlation work outtic = time.clock() # Edit here from previous, stable, but slow match_filter # Would be worth testing without an if statement, but with every station in # the possible template stations having data, but for those without real # data make the data NaN to return NaN ccc_sum # Note: this works if debug >= 2: print 'Ensuring all template channels have matches in daylong data' template_stachan = [] for template in templates: for tr in template: template_stachan += [(tr.stats.station, tr.stats.channel)] template_stachan = list(set(template_stachan)) # Copy this here to keep it safe for stachan in template_stachan: if not stream.select(station=stachan[0], channel=stachan[1]): # Remove template traces rather than adding NaN data for template in templates: if template.select(station=stachan[0], channel=stachan[1]): for tr in template.select(station=stachan[0], channel=stachan[1]): template.remove(tr) # Remove un-needed channels for tr in stream: if not (tr.stats.station, tr.stats.channel) in template_stachan: stream.remove(tr) # Also pad out templates to have all channels for template in templates: for stachan in template_stachan: if not template.select(station=stachan[0], channel=stachan[1]): nulltrace = Trace() nulltrace.stats.station = stachan[0] nulltrace.stats.channel = stachan[1] nulltrace.stats.sampling_rate = template[0].stats.sampling_rate nulltrace.stats.starttime = template[0].stats.starttime nulltrace.data = np.array([np.NaN] * len(template[0].data), dtype=np.float32) template += nulltrace if debug >= 2: print 'Starting the correlation run for this day' [cccsums, no_chans] = _channel_loop(templates, stream, cores, debug) if len(cccsums[0]) == 0: raise ValueError('Correlation has not run, zero length cccsum') outtoc = time.clock() print ' '.join([ 'Looping over templates and streams took:', str(outtoc - outtic), 's' ]) if debug >= 2: print ' '.join( ['The shape of the returned cccsums is:', str(np.shape(cccsums))]) print ' '.join(['This is from', str(len(templates)), 'templates']) print ' '.join( ['Correlated with', str(len(stream)), 'channels of data']) detections = [] for i, cccsum in enumerate(cccsums): template = templates[i] if threshold_type == 'MAD': rawthresh = threshold * np.median(np.abs(cccsum)) elif threshold_type == 'absolute': rawthresh = threshold elif threshold == 'av_chan_corr': rawthresh = threshold * (cccsum / len(template)) else: print 'You have not selected the correct threshold type, I will' +\ 'use MAD as I like it' rawthresh = threshold * np.mean(np.abs(cccsum)) # Findpeaks returns a list of tuples in the form [(cccsum, sample)] print ' '.join(['Threshold is set at:', str(rawthresh)]) print ' '.join(['Max of data is:', str(max(cccsum))]) print ' '.join(['Mean of data is:', str(np.mean(cccsum))]) if np.abs(np.mean(cccsum)) > 0.05: warnings.warn('Mean is not zero! Check this!') # Set up a trace object for the cccsum as this is easier to plot and # maintins timing if plotvar: stream_plot = copy.deepcopy(stream[0]) # Downsample for plotting stream_plot.decimate(int(stream[0].stats.sampling_rate / 10)) cccsum_plot = Trace(cccsum) cccsum_plot.stats.sampling_rate = stream[0].stats.sampling_rate # Resample here to maintain shape better cccsum_hist = cccsum_plot.copy() cccsum_hist = cccsum_hist.decimate( int(stream[0].stats.sampling_rate / 10)).data cccsum_plot = EQcorrscan_plotting.chunk_data( cccsum_plot, 10, 'Maxabs').data # Enforce same length stream_plot.data = stream_plot.data[0:len(cccsum_plot)] cccsum_plot = cccsum_plot[0:len(stream_plot.data)] cccsum_hist = cccsum_hist[0:len(stream_plot.data)] EQcorrscan_plotting.triple_plot( cccsum_plot, cccsum_hist, stream_plot, rawthresh, True, plotdir + '/cccsum_plot_' + template_names[i] + '_' + stream[0].stats.starttime.datetime.strftime('%Y-%m-%d') + '.' + plot_format) if debug >= 4: print ' '.join([ 'Saved the cccsum to:', template_names[i], stream[0].stats.starttime.datetime.strftime('%Y%j') ]) np.save( template_names[i] + stream[0].stats.starttime.datetime.strftime('%Y%j'), cccsum) tic = time.clock() if debug >= 4: np.save('cccsum_' + str(i) + '.npy', cccsum) if debug >= 3 and max(cccsum) > rawthresh: peaks = findpeaks.find_peaks2_short( cccsum, rawthresh, trig_int * stream[0].stats.sampling_rate, debug, stream[0].stats.starttime, stream[0].stats.sampling_rate) elif max(cccsum) > rawthresh: peaks = findpeaks.find_peaks2_short( cccsum, rawthresh, trig_int * stream[0].stats.sampling_rate, debug) else: print 'No peaks found above threshold' peaks = False toc = time.clock() if debug >= 1: print ' '.join(['Finding peaks took:', str(toc - tic), 's']) if peaks: for peak in peaks: detecttime = stream[0].stats.starttime +\ peak[1] / stream[0].stats.sampling_rate detections.append( DETECTION(template_names[i], detecttime, no_chans[i], peak[0], rawthresh, 'corr')) del stream, templates return detections
# testing the test: # j,data = l2_simple(c_syn,c_obs) # left hand side of test 1: adjt source time function * du = change of misfit wrt u djdc = np.dot(data,d_c) #if mtype in ['envelope','square_envelope']: # djdc = np.dot(data[0],d_c) #djdc += np.dot(data[1],d_c) # right hand side of test 1: Finite difference approx of misfit change for different steps dcheck = [] d_ch = c_syn.copy() for step in steps: d_ch.data = c_ini + 10. ** step * d_c msr_sh = m_func(d_ch,**m_a_options) if mtype == 'energy_diff': msr_sh = msr_sh[0] + msr_sh[1] jh = 0.5 * (msr_sh - msr_o)**2 if mtype in ['windowed_waveform','envelope','square_envelope']: jh = 0.5 * np.sum(np.power((msr_sh-msr_o),2)) # testing the test: # jh, dn = l2_simple(d_ch,c_obs) djdch = (jh - j) / (10.**step) dcheck.append(abs(djdc - djdch) / abs(djdc))
def slant_stack(eq_num, plot_scale_fac = 0.05, slowR_lo = -0.1, slowR_hi = 0.1, stack_option = 1, slow_delta = 0.0005, start_buff = -50, end_buff = 50, ref_lat = 36.3, ref_lon = 138.5, ref_loc = 0, envelope = 1, plot_dyn_range = 1000, log_plot = 1, norm = 1, global_norm_plot = 1, color_plot = 1, fig_index = 401, ARRAY = 0): #%% Import functions import obspy import obspy.signal from obspy import UTCDateTime from obspy import Stream, Trace from obspy import read from obspy.geodetics import gps2dist_azimuth import numpy as np import os from obspy.taup import TauPyModel import obspy.signal as sign import matplotlib.pyplot as plt from matplotlib.colors import LogNorm model = TauPyModel(model='iasp91') from scipy.signal import hilbert import math import time from termcolor import colored env_stack = 0 # flag to stack envelopes instead of oscillating seismograms # import sys # don't show any warnings # import warnings print(colored('Running pro5a_stack', 'cyan')) #%% Get saved event info, also used to name files start_time_wc = time.time() fname = '/Users/vidale/Documents/Research/IC/EvLocs/event' + str(eq_num) + '.txt' file = open(fname, 'r') lines=file.readlines() split_line = lines[0].split() # ids.append(split_line[0]) ignore label for now t = UTCDateTime(split_line[1]) date_label = split_line[1][0:10] ev_lat = float( split_line[2]) ev_lon = float( split_line[3]) ev_depth = float( split_line[4]) #if not sys.warnoptions: # warnings.simplefilter("ignore") #%% Get station location file if ARRAY == 0: # Hinet set and center sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_hinet.txt' if ref_loc == 0: ref_lat = 36.3 ref_lon = 138.5 elif ARRAY == 1: # LASA set and center sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_LASA.txt' if ref_loc == 0: ref_lat = 46.69 ref_lon = -106.22 elif ARRAY == 2: # China set and center sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_ch.txt' if ref_loc == 0: ref_lat = 38 # °N ref_lon = 104.5 # °E else: # NORSAR set and center sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_NORSAR.txt' if ref_loc == 0: ref_lat = 61 ref_lon = 11 with open(sta_file, 'r') as file: lines = file.readlines() print(' ' + str(len(lines)) + ' stations of metadata read from ' + sta_file) # Load station coords into arrays station_index = range(len(lines)) st_names = [] st_lats = [] st_lons = [] for ii in station_index: line = lines[ii] split_line = line.split() st_names.append(split_line[0]) st_lats.append( split_line[1]) st_lons.append( split_line[2]) if ARRAY == 0: # shorten and make upper case Hi-net station names to match station list for ii in station_index: this_name = st_names[ii] this_name_truc = this_name[0:5] st_names[ii] = this_name_truc.upper() #%% Name file, read data # date_label = '2018-04-02' # date for filename fname = 'HD' + date_label + 'sel.mseed' goto = '/Users/vidale/Documents/Research/IC/Pro_Files' os.chdir(goto) # fname = '/Users/vidale/Documents/PyCode/Pro_Files/HD' + date_label + 'sel.mseed' st = Stream() print(' reading ' + fname) print(' Stack option is ' + str(stack_option)) st = read(fname) print(' ' + str(len(st)) + ' traces read in') nt = len(st[0].data) dt = st[0].stats.delta print(f' First trace has {nt} time pts, time sampling of {dt:.2f} and thus duration of {(nt-1)*dt:.0f} and max amp of {max(abs(st[0].data)):.1f}') print(f'st[0].stats.starttime-t {(st[0].stats.starttime-t):.2f} start_buff {start_buff:.2f}') #%% Build Stack arrays stack = Stream() tr = Trace() tr.stats.delta = dt tr.stats.network = 'stack' tr.stats.channel = 'BHZ' slow_n = int(1 + (slowR_hi - slowR_lo)/slow_delta) # number of slownesses stack_nt = int(1 + ((end_buff - start_buff)/dt)) # number of time points # In English, stack_slows = range(slow_n) * slow_delta - slowR_lo a1 = range(slow_n) stack_slows = [(x * slow_delta + slowR_lo) for x in a1] print(' ' + str(slow_n) + ' slownesses.') tr.stats.starttime = t + start_buff # print(f'tr.stats.starttime-t {(tr.stats.starttime-t):.2f} start_buff {start_buff:.2f}') tr.data = np.zeros(stack_nt) done = 0 for stack_one in stack_slows: tr1 = tr.copy() tr1.stats.station = str(int(done)) stack.extend([tr1]) done += 1 # stack.append([tr]) # stack += tr # Only need to compute ref location to event distance once ref_distance = gps2dist_azimuth(ev_lat,ev_lon,ref_lat,ref_lon) #%% Select traces by distance, window and adjust start time to align picked times done = 0 if env_stack == 1: #convert oscillating seismograms to envelopes for tr in st: tr.data = np.abs(hilbert(tr.data)) for tr in st: # traces one by one if tr.stats.station in st_names: # find station in station list ii = st_names.index(tr.stats.station) if norm == 1: tr.normalize() stalat = float(st_lats[ii]) stalon = float(st_lons[ii]) # look up lat & lon again to find distance distance = gps2dist_azimuth(stalat,stalon,ev_lat,ev_lon) # Get traveltimes again, hard to store tr.stats.distance=distance[0] # distance in m del_dist = (ref_distance[0] - distance[0])/(1000) # in km rel_start_buff = tr.stats.starttime - (t + start_buff) print(f'{tr.stats.station} del_dist {del_dist:.2f} ref_dist {ref_distance[0]/1000.:.2f} distance {distance[0]/1000.:.2f} rel_start_buff {rel_start_buff:.2f} tr.stats.starttime-t {(tr.stats.starttime-t):.2f} start_buff {start_buff:.2f}') for slow_i in range(slow_n): # for this station, loop over slownesses time_lag = -del_dist * stack_slows[slow_i] # time shift due to slowness, flipped to match 2D time_correction = (rel_start_buff + time_lag)/dt # print(f'{slow_i} time_lag {time_lag:.1f} time correction {time_correction:.1f}') if stack_option == 0: for it in range(stack_nt): # check points one at a time it_in = int(it + time_correction) if it_in >= 0 and it_in < nt - 1: # does data lie within seismogram? stack[slow_i].data[it] += tr[it_in] if stack_option == 1: arr = tr.data nshift = int(time_correction) if time_correction < 0: nshift = nshift-1 if nshift <= 0: nbeg1 = -nshift nend1 = stack_nt nbeg2 = 0 nend2 = stack_nt + nshift; elif nshift > 0: nbeg1 = 0 nend1 = stack_nt - nshift nbeg2 = nshift nend2 = stack_nt if nend1 >= 0 and nbeg1 <= stack_nt: stack[slow_i].data[nbeg1:nend1] += arr[nbeg2:nend2] done += 1 if done % 50 == 0: print(' Done stacking ' + str(done) + ' out of ' + str(len(st)) + ' stations.') else: print(tr.stats.station + ' not found in station list') #%% Plot traces global_max = 0 for slow_i in range(slow_n): # find global max, and if requested, take envelope if len(stack[slow_i].data) == 0: print('%d data has zero length ' % (slow_i)) if envelope == 1 or color_plot == 1: stack[slow_i].data = np.abs(hilbert(stack[slow_i].data)) local_max = max(abs(stack[slow_i].data)) if local_max > global_max: global_max = local_max if global_max <= 0: print(' global_max ' + str(global_max) + ' slow_n ' + str(slow_n)) # create time axis (x-axis), use of slow_i here is arbitrary, oops ttt = (np.arange(len(stack[slow_i].data)) * stack[slow_i].stats.delta + (stack[slow_i].stats.starttime - t)) # in units of seconds # Plotting if color_plot == 1: # 2D color plot stack_array = np.zeros((slow_n,stack_nt)) # stack_array = np.random.rand(int(slow_n),int(stack_nt)) # test with random numbers min_allowed = global_max/plot_dyn_range if log_plot == 1: for it in range(stack_nt): # check points one at a time for slow_i in range(slow_n): # for this station, loop over slownesses num_val = stack[slow_i].data[it] if num_val < min_allowed: num_val = min_allowed stack_array[slow_i, it] = math.log10(num_val) - math.log10(min_allowed) else: for it in range(stack_nt): # check points one at a time for slow_i in range(slow_n): # for this station, loop over slownesses stack_array[slow_i, it] = stack[slow_i].data[it]/global_max y, x = np.mgrid[slice(stack_slows[0], stack_slows[-1] + slow_delta, slow_delta), slice(ttt[0], ttt[-1] + dt, dt)] # make underlying x-y grid for plot # y, x = np.mgrid[ stack_slows , time ] # make underlying x-y grid for plot plt.close(fig_index) fig, ax = plt.subplots(1, figsize=(9,9)) fig.subplots_adjust(bottom=0.3) c = ax.pcolormesh(x, y, stack_array, cmap=plt.cm.gist_rainbow_r) # c = ax.pcolormesh(x, y, stack_array, cmap=plt.cm.gist_yarg) # c = ax.pcolormesh(x, y, stack_array, cmap=plt.cm.binary) ax.axis([x.min(), x.max(), y.min(), y.max()]) if log_plot == 1: fig.colorbar(c, ax=ax, label='log amplitude') else: fig.colorbar(c, ax=ax, label='linear amplitude') plt.figure(fig_index,figsize=(6,8)) plt.close(fig_index) else: # line plot for slow_i in range(slow_n): dist_offset = stack_slows[slow_i] # in units of slowness if global_norm_plot != 1: plt.plot(ttt, stack[slow_i].data*plot_scale_fac / (stack[slow_i].data.max() - stack[slow_i].data.min()) + dist_offset, color = 'black') else: plt.plot(ttt, stack[slow_i].data*plot_scale_fac / (global_max - stack[slow_i].data.min()) + dist_offset, color = 'black') plt.ylim(slowR_lo,slowR_hi) plt.xlim(start_buff,end_buff) plt.xlabel('Time (s)') plt.ylabel('Slowness (s/km)') plt.title('1Dstack ' + str(eq_num) + ' ' + date_label) # os.chdir('/Users/vidale/Documents/PyCode/Plots') # plt.savefig(date_label + '_' + str(start_buff) + '_' + str(end_buff) + '_1D.png') plt.show() #%% Save processed files print(' Stack has ' + str(len(stack)) + ' slownesses') # # if ARRAY == 0: # goto = '/Users/vidale/Documents/PyCode/Hinet' # if ARRAY == 1: # goto = '/Users/vidale/Documents/PyCode/LASA/Pro_Files' # os.chdir(goto) # fname = 'HD' + date_label + '_1dstack.mseed' # stack.write(fname,format = 'MSEED') elapsed_time_wc = time.time() - start_time_wc print(f' This job took {elapsed_time_wc:.1f} seconds') os.system('say "Done"')