def test_remove_unclustered_parallel(self): distance_cutoff = 100 cat_back = remove_unclustered(self.cat.copy(), distance_cutoff, num_threads=4) assert len(cat_back) < len(self.cat) for i, event in enumerate(cat_back): master_ori = event.preferred_origin() or event.origins[0] distances = [] for j, other_event in enumerate(cat_back): slave_ori = (other_event.preferred_origin() or other_event.origins[0]) if i == j: continue distances.append(dist_calc( (master_ori.latitude, master_ori.longitude, master_ori.depth / 1000), (slave_ori.latitude, slave_ori.longitude, slave_ori.depth / 1000))) assert any(np.array(distances) < distance_cutoff) # Check that all events not in the catalog are correctly ignored for i, event in enumerate(self.cat): if event not in cat_back: master_ori = event.preferred_origin() or event.origins[0] distances = [] for j, other_event in enumerate(self.cat): slave_ori = (other_event.preferred_origin() or other_event.origins[0]) if i == j: continue distances.append(dist_calc( (master_ori.latitude, master_ori.longitude, master_ori.depth / 1000), (slave_ori.latitude, slave_ori.longitude, slave_ori.depth / 1000))) assert all(np.array(distances) > distance_cutoff)
def test_space_cluster(self): """Test the wrapper around dist_mat_km.""" groups = catalog_cluster( catalog=self.cat, thresh=self.distance_threshold, metric="distance", show=False) self.assertEqual(len([ev for group in groups for ev in group]), len(self.cat)) # Check that events within each group are within distance-threshold for group in groups: if len(group) > 1: master_ori = group[0].preferred_origin() or group[0].origins[0] for event in group[1:]: slave_ori = event.preferred_origin() or event.origins[0] self.assertLessEqual(dist_calc( (master_ori.latitude, master_ori.longitude, master_ori.depth / 1000), (slave_ori.latitude, slave_ori.longitude, slave_ori.depth / 1000)), self.distance_threshold) # Check that groups are separated by at least distance-threshold for i, group in enumerate(groups): for event in group: master_ori = event.preferred_origin() or event.origins[0] for j, other_group in enumerate(groups): if i == j: continue for other_event in other_group: slave_ori = ( other_event.preferred_origin() or other_event.origins[0]) self.assertGreater(dist_calc( (master_ori.latitude, master_ori.longitude, master_ori.depth / 1000), (slave_ori.latitude, slave_ori.longitude, slave_ori.depth / 1000)), self.distance_threshold)
def test_space_time_cluster(self): """Test clustering in space and time.""" groups = space_time_cluster( catalog=self.cat, t_thresh=self.time_threshold, d_thresh=self.distance_threshold) self.assertEqual(len([ev for group in groups for ev in group]), len(self.cat)) # Check that events within each group are within distance-threshold and # time-threshold for group in groups: if len(group) > 1: master_ori = group[0].preferred_origin() or group[0].origins[0] for event in group[1:]: slave_ori = event.preferred_origin() or event.origins[0] self.assertLessEqual(dist_calc( (master_ori.latitude, master_ori.longitude, master_ori.depth / 1000), (slave_ori.latitude, slave_ori.longitude, slave_ori.depth / 1000)), self.distance_threshold) self.assertLessEqual(abs(master_ori.time - slave_ori.time), self.time_threshold) # Check that groups are separated by at least distance-threshold, # or, by some time. for i, group in enumerate(groups): master_times = [] for master in group: master_ori = ( master.preferred_origin() or master.origins[0]) master_times.append(master_ori.time) master_times.sort() master_median_time = master_times[0] + np.median( [m - master_times[0] for m in master_times]) # Just use the origin of one event master_ori = group[0].preferred_origin() or group[0].origins[0] for j, other_group in enumerate(groups): if i == j: continue slave_times = [] for slave in other_group: slave_ori = ( slave.preferred_origin() or slave.origins[0]) slave_times.append(slave_ori.time) slave_times.sort() slave_median_time = slave_times[0] + np.median( [s - slave_times[0] for s in slave_times]) for event in other_group: slave_ori = event.preferred_origin() or event.origins[0] separation = dist_calc( (master_ori.latitude, master_ori.longitude, master_ori.depth / 1000), (slave_ori.latitude, slave_ori.longitude, slave_ori.depth / 1000)) if separation < self.distance_threshold: self.assertGreater( abs(master_median_time - slave_median_time), self.time_threshold) else: self.assertGreater(separation, self.distance_threshold)
def template_extents(cat, temp_cat, temp_list='all', param='avg_dist', show=True): """ Measure parameters of the areal extent of template detections :param cat: Detections catalog :param temp_cat: Templates catalog :param param: What parameter are we measuring? Average template-detection distance or area? :return: """ dets_dict = template_det_cats(cat, temp_list) temp_dict = {str(ev.resource_id).split('/')[-1]: ev for ev in temp_cat if str(ev.resource_id).split('/')[-1] in temp_list} # Remove keys which aren't common for key in temp_dict.keys(): if key not in dets_dict: del temp_dict[key] param_dict = {} for key, ev in temp_dict.iteritems(): temp_o = ev.origins[-1] if param == 'avg_dist': param_dict[key] = np.mean([dist_calc((temp_o.latitude, temp_o.longitude, temp_o.depth / 1000.0), (det.origins[-1].latitude, det.origins[-1].longitude, det.origins[-1].depth / 1000.0)) for det in dets_dict[key]]) ax = sns.distplot([avg for key, avg in param_dict.iteritems()]) ax.set_title('') ax.set_xlabel('Average template-detection distance per template (km)') fig = plt.gcf() if show: fig.show() return fig
def dist_mat_km(catalog): """ Function to compute the distance matrix for all events in a catalog - \ will give physical distance in kilometers. :type catalog: List of obspy.Catalog :param catalog: Catalog for which to compute the distance matrix :returns: ndarray - distance matrix """ from eqcorrscan.utils.mag_calc import dist_calc # Initialize square matrix dist_mat = np.array([np.array([0.0] * len(catalog))] * len(catalog)) # Calculate distance vector for each event for i, master in enumerate(catalog): mast_list = [] master_tup = (master.preferred_origin().latitude, master.preferred_origin().longitude, master.preferred_origin().depth // 1000) for slave in catalog: slave_tup = (slave.preferred_origin().latitude, slave.preferred_origin().longitude, slave.preferred_origin().depth // 1000) mast_list.append(dist_calc(master_tup, slave_tup)) # Sort the list into the dist_mat structure for j in range(i, len(catalog)): dist_mat[i, j] = mast_list[j] # Reshape the distance matrix for i in range(1, len(catalog)): for j in range(i): dist_mat[i, j] = dist_mat.T[i, j] return dist_mat
def test_dist_calc(self): """ Test the distance calculation that comes with mag_calc. """ self.assertEqual(dist_calc((0, 0, 0), (0, 0, 10)), 10) self.assertEqual(round(dist_calc((0, 0, 0), (0, 1, 0))), 111) self.assertEqual(round(dist_calc((0, 0, 0), (1, 0, 0))), 111) self.assertEqual(round(dist_calc((45, 45, 0), (45, 45, 10))), 10) self.assertEqual(round(dist_calc((45, 45, 0), (45, 46, 0))), 79) self.assertEqual(round(dist_calc((45, 45, 0), (46, 45, 0))), 111) self.assertEqual(round(dist_calc((90, 90, 0), (90, 90, 10))), 10) self.assertEqual(round(dist_calc((90, 90, 0), (90, 89, 0))), 0) self.assertEqual(round(dist_calc((90, 90, 0), (89, 90, 0))), 111)
def test_dist_calc(self): """ Test the distance calculation that comes with mag_calc. """ from eqcorrscan.utils.mag_calc import dist_calc self.assertEqual(dist_calc((0, 0, 0), (0, 0, 10)), 10) self.assertEqual(round(dist_calc((0, 0, 0), (0, 1, 0))), 111) self.assertEqual(round(dist_calc((0, 0, 0), (1, 0, 0))), 111) self.assertEqual(round(dist_calc((45, 45, 0), (45, 45, 10))), 10) self.assertEqual(round(dist_calc((45, 45, 0), (45, 46, 0))), 79) self.assertEqual(round(dist_calc((45, 45, 0), (46, 45, 0))), 111) self.assertEqual(round(dist_calc((90, 90, 0), (90, 90, 10))), 10) self.assertEqual(round(dist_calc((90, 90, 0), (90, 89, 0))), 0) self.assertEqual(round(dist_calc((90, 90, 0), (89, 90, 0))), 111)
def plot_rand_correlation(cat, d_thresh, temp_dict, stream_dict): """ Calculate cross correlation coefficients for events located further than d_thresh from each other. This should represent correlation of uncorrelated signals and help determine ccval_cutoff :param cat: :param dist_thresh: :param temp_dict: :param stream_dict: :return: """ from eqcorrscan.utils.mag_calc import dist_calc from eqcorrscan.core.match_filter import normxcorr2 import numpy as np corrs = [] for i, ev in enumerate(cat): print i ev_tup = (ev.preferred_origin().latitude, ev.preferred_origin().longitude, ev.preferred_origin().depth / 1000.) for ev2 in cat[i+1:]: ev_tup2 = (ev2.preferred_origin().latitude, ev2.preferred_origin().longitude, ev2.preferred_origin().depth / 1000.) dist = dist_calc(ev_tup, ev_tup2) if dist > d_thresh: det_rid = str(ev2.resource_id).split('/')[-1] temp_rid = str(ev.resource_id).split('/')[-1].split('_')[0] temp = temp_dict[temp_rid + '_1sec'] stream = stream_dict[det_rid] for pk in ev.picks: if pk.phase_hint == 'P': sta = pk.waveform_id.station_code chan = pk.waveform_id.channel_code if len(temp.select(station=sta, channel=chan)) > 0: tr = temp.select(station=sta, channel=chan)[0] else: continue if len(stream.select(station=sta, channel=chan)) > 0: st_tr = stream.select(station=sta, channel=chan)[0] else: continue # # still correcting for 0.1 sec pre-pick time here...gross # pk_samp = # corr_start = pk_samp - 5 # corr_end = pk_samp + 6 ccc = normxcorr2(tr.data, st_tr.data[140:201])[0] corrs.append(max(ccc.max(), ccc.min(), key=abs)) return corrs
def event_diff_dict(cat1, cat2): """ Generates a dictionary of differences between two catalogs with identical events :type cat1: obspy.Catalog :param cat1: catalog of events parallel to cat2 :type cat2: obspy.Catalog :param cat2: catalog of events parallel to cat1 :return: dict """ diff_dict = {} for ev1 in cat1: ev1_o = ev1.origins[-1] ev2 = [ev for ev in cat2 if ev.resource_id == ev1.resource_id][0] ev2_o = ev2.origins[-1] diff_dict[ev1.resource_id] = {'dist': dist_calc((ev1_o.latitude, ev1_o.longitude, ev1_o.depth / 1000.00), (ev2_o.latitude, ev2_o.longitude, ev2_o.depth / 1000.00)), 'pick_residuals1': {'P': [], 'S': []}, 'pick_residuals2': {'P': [], 'S': []}, 'cat1_RMS': ev1_o.quality.standard_error, 'cat2_RMS': ev2_o.quality.standard_error, 'RMS_change': ev2_o.quality.standard_error - ev1_o.quality.standard_error, 'cat1_picks': len(ev1.picks), 'cat2_picks': len(ev2.picks), 'x_diff': ev2_o.longitude - ev1_o.longitude, 'y_diff': ev2_o.latitude - ev1_o.latitude, 'z_diff': ev2_o.depth - ev1_o.depth, 'min_uncert_diff': ev2_o.origin_uncertainty.min_horizontal_uncertainty - ev1_o.origin_uncertainty.min_horizontal_uncertainty, 'max_uncert_diff': ev2_o.origin_uncertainty.max_horizontal_uncertainty - ev1_o.origin_uncertainty.max_horizontal_uncertainty, 'az_max_uncert_diff': ev2_o.origin_uncertainty.azimuth_max_horizontal_uncertainty - ev1_o.origin_uncertainty.azimuth_max_horizontal_uncertainty} for ar in ev1_o.arrivals: phs = ar.pick_id.get_referred_object().phase_hint diff_dict[ev1.resource_id]['pick_residuals1'][phs].append((ar.pick_id.get_referred_object().waveform_id.station_code + '.' + ar.pick_id.get_referred_object().waveform_id.channel_code, ar.time_residual)) for ar in ev2_o.arrivals: phs = ar.pick_id.get_referred_object().phase_hint diff_dict[ev1.resource_id]['pick_residuals2'][phs].append((ar.pick_id.get_referred_object().waveform_id.station_code + '.' + ar.pick_id.get_referred_object().waveform_id.channel_code, ar.time_residual)) return diff_dict
def space_time_cluster(detections, t_thresh, d_thresh): """ Function to cluster detections in space and time, use to seperate \ repeaters from other events. :type detections: list :param detections: List of tuple of tuple of location (lat, lon, depth \ (km)), and time as a datetime object :type t_thresh: float :param t_thresh: Maximum inter-event time threshold in seconds :type d_thresh: float :param d_thresh: Maximum inter-event distance in km :returns: List of tuple (detections, clustered) and list of indices of\ clustered detections """ from eqcorrscan.utils.mag_calc import dist_calc # Ensure they are sorted by time first, not that we need it. detections.sort(key=lambda tup: tup[1]) clustered = [] clustered_indices = [] for master_ind, master in enumerate(detections): keep = False mast_o = master.preferred_origin() mast_time = mast_o.time mast_loc = (mast_o.latitude, mast_o.longitude, mast_o.depth // 1000) for slave in detections: slave_o = slave.preferred_origin() slave_time = slave_o.time slave_loc = (slave_o.latitude, slave_o.longitude, slave_o.depth // 1000) if not master.resource_id == slave.resource_id and\ abs((mast_time - slave_time).total_seconds()) <= t_thresh and\ dist_calc(mast_loc, slave_loc) <= d_thresh: # If the slave events is close in time and space to the master # keep it and break out of the loop. keep = True break if keep: clustered.append(master) clustered_indices.append(master_ind) return clustered, clustered_indices
def dist_mat_km(catalog): """ Compute the distance matrix for all a catalog using epicentral separation. Will give physical distance in kilometers. :type catalog: obspy.core.event.Catalog :param catalog: Catalog for which to compute the distance matrix :returns: distance matrix :rtype: :class:`numpy.ndarray` """ # Initialize square matrix dist_mat = np.array([np.array([0.0] * len(catalog))] * len(catalog)) # Calculate distance vector for each event for i, master in enumerate(catalog): mast_list = [] if master.preferred_origin(): master_ori = master.preferred_origin() else: master_ori = master.origins[-1] master_tup = (master_ori.latitude, master_ori.longitude, master_ori.depth // 1000) for slave in catalog: if slave.preferred_origin(): slave_ori = slave.preferred_origin() else: slave_ori = slave.origins[-1] slave_tup = (slave_ori.latitude, slave_ori.longitude, slave_ori.depth // 1000) mast_list.append(dist_calc(master_tup, slave_tup)) # Sort the list into the dist_mat structure for j in range(i, len(catalog)): dist_mat[i, j] = mast_list[j] # Reshape the distance matrix for i in range(1, len(catalog)): for j in range(i): dist_mat[i, j] = dist_mat.T[i, j] return dist_mat
def test_dist_mat_km(self): """Test spacial clustering.""" dist_mat = dist_mat_km(self.cat) self.assertEqual(len(dist_mat), len(self.cat)) # Diagonal should be zeros self.assertTrue(np.all(dist_mat.diagonal() == 0)) # Should be symmetric for i in range(len(self.cat)): for j in range(len(self.cat)): self.assertEqual(dist_mat[i, j], dist_mat[j, i]) master_ori = (self.cat[i].preferred_origin() or self.cat[i].origins[0]) slave_ori = (self.cat[j].preferred_origin() or self.cat[j].origins[0]) self.assertAlmostEqual( dist_mat[i, j], dist_calc((master_ori.latitude, master_ori.longitude, master_ori.depth / 1000), (slave_ori.latitude, slave_ori.longitude, slave_ori.depth / 1000)), 6)
def space_time_cluster(detections, t_thresh, d_thresh): """ Function to cluster detections in space and time, use to seperate repeaters from other events :type detections: List :param detections: List of tuple of tuple of location (lat, lon, depth (km)),\ and time as a datetime object :type t_thresh: float :param t_thresh: Maximum inter-event time threshold in seconds :type d_thresh: float :param d_thresh: Maximum inter-event distance in km :returns: List of tuple (detections, clustered) and list of indeces of\ clustered detections """ from eqcorrscan.utils.mag_calc import dist_calc import datetime as dt # Ensure they are sorted by time first, not that we need it. detections.sort(key=lambda tup:tup[1]) clustered=[] clustered_indeces=[] for master_ind, master in enumerate(detections): keep=False for slave in detections: if not master==slave and\ abs((master[1] - slave[1]).total_seconds()) <= t_thresh and \ dist_calc(master[0], slave[0]) <= d_thresh: # If the slave events is close in time and space to the master # keep it and break out of the loop. keep=True break if keep: clustered.append(master) clustered_indeces.append(master_ind) return clustered, clustered_indeces
def test_write_catalog(self): """ Simple testing function for the write_catalogue function in \ catalog_to_dd. """ from eqcorrscan.utils.catalog_to_dd import write_catalog from eqcorrscan.utils.mag_calc import dist_calc from eqcorrscan.utils import sfile_util import glob import os # Set forced variables maximum_seperation = 1 # Maximum inter-event seperation in km minimum_links = 8 # Minimum inter-event links to generate a pair # We have to make an event list first testing_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'test_data', 'REA', 'TEST_') sfile_list = glob.glob(os.path.join(testing_path, '*L.S??????')) event_ids = list(range(len(sfile_list))) event_list = zip(event_ids, sfile_list) write_catalog(event_list=event_list, max_sep=maximum_seperation, min_link=minimum_links) self.assertTrue(os.path.isfile('dt.ct')) # Check dt.ct file, should contain only a few linked events dt_file_out = open('dt.ct', 'r') event_pairs = [] for i, line in enumerate(dt_file_out): if line[0] == '#': if i != 0: # Check the number of links self.assertTrue(len(event_links) >= minimum_links) # Check the distance between events event_1_name = [event[1] for event in event_list if event[0] == int(event_pair.split()[1])][0] event_2_name = [event[1] for event in event_list if event[0] == int(event_pair.split()[2])][0] event_1 = sfile_util.readheader(event_1_name) event_2 = sfile_util.readheader(event_2_name) event_1_location = (event_1.origins[0].latitude, event_1.origins[0].longitude, event_1.origins[0].depth / 1000) event_2_location = (event_2.origins[0].latitude, event_2.origins[0].longitude, event_2.origins[0].depth / 1000) hypocentral_seperation = dist_calc(event_1_location, event_2_location) self.assertTrue(hypocentral_seperation < maximum_seperation) # Check that the differential times are accurate event_1_picks = sfile_util.readpicks(event_1_name).picks event_2_picks = sfile_util.readpicks(event_2_name).picks for pick_pair in event_links: station = pick_pair.split()[0] event_1_travel_time_output = pick_pair.split()[1] event_2_travel_time_output = pick_pair.split()[2] weight = pick_pair.split()[3] phase = pick_pair.split()[4] # Extract the relevant pick information from the # two sfiles for pick in event_1_picks: if pick.waveform_id.station_code == station: if pick.phase_hint[0].upper() == phase: event_1_pick = pick for pick in event_2_picks: if pick.waveform_id.station_code == station: if pick.phase_hint[0].upper() == phase: event_2_pick = pick # Calculate the travel-time event_1_travel_time_input = event_1_pick.time -\ event_1.origins[0].time event_2_travel_time_input = event_2_pick.time -\ event_2.origins[0].time self.assertEqual(event_1_travel_time_input, float(event_1_travel_time_output)) self.assertEqual(event_2_travel_time_input, float(event_2_travel_time_output)) event_pair = line event_pairs.append(line) event_links = [] else: event_links.append(line) self.assertTrue(os.path.isfile('phase.dat')) dt_file_out.close() os.remove('phase.dat') os.remove('dt.ct') if os.path.isfile('dt.ct2'): os.remove('dt.ct2')
def write_correlations(event_list, wavbase, extract_len, pre_pick, shift_len, lowcut=1.0, highcut=10.0, max_sep=8, min_link=8, cc_thresh=0.0, plotvar=False, debug=0): """ Write a dt.cc file for hypoDD input for a given list of events. Takes an input list of events and computes pick refinements by correlation. Outputs two files, dt.cc and dt.cc2, each provides a different weight, dt.cc uses weights of the cross-correlation, and dt.cc2 provides weights as the square of the cross-correlation. :type event_list: list :param event_list: List of tuples of event_id (int) and sfile (String) :type wavbase: str :param wavbase: Path to the seisan wave directory that the wavefiles in the S-files are stored :type extract_len: float :param extract_len: Length in seconds to extract around the pick :type pre_pick: float :param pre_pick: Time before the pick to start the correlation window :type shift_len: float :param shift_len: Time to allow pick to vary :type lowcut: float :param lowcut: Lowcut in Hz - default=1.0 :type highcut: float :param highcut: Highcut in Hz - default=10.0 :type max_sep: float :param max_sep: Maximum separation between event pairs in km :type min_link: int :param min_link: Minimum links for an event to be paired :type cc_thresh: float :param cc_thresh: Threshold to include cross-correlation results. :type plotvar: bool :param plotvar: To show the pick-correction plots, defualts to False. :type debug: int :param debug: Variable debug levels from 0-5, higher=more output. .. warning:: This is not a fast routine! .. warning:: In contrast to seisan's corr routine, but in accordance with the hypoDD manual, this outputs corrected differential time. .. note:: Currently we have not implemented a method for taking unassociated event objects and wavefiles. As such if you have events \ with associated wavefiles you are advised to generate Sfiles for each \ event using the sfile_util module prior to this step. .. note:: There is no provision to taper waveforms within these functions, if you desire this functionality, you should apply the taper before calling this. Note the :func:`obspy.Trace.taper` functions. """ warnings.filterwarnings(action="ignore", message="Maximum of cross correlation " + "lower than 0.8: *") corr_list = [] f = open('dt.cc', 'w') f2 = open('dt.cc2', 'w') k_events = len(list(event_list)) for i, master in enumerate(event_list): master_sfile = master[1] if debug > 1: print('Computing correlations for master: %s' % master_sfile) master_event_id = master[0] master_event = read_nordic(master_sfile)[0] master_picks = master_event.picks master_ori_time = master_event.origins[0].time master_location = (master_event.origins[0].latitude, master_event.origins[0].longitude, master_event.origins[0].depth / 1000.0) master_wavefiles = readwavename(master_sfile) masterpath = glob.glob(wavbase + os.sep + master_wavefiles[0]) if masterpath: masterstream = read(masterpath[0]) if len(master_wavefiles) > 1: for wavefile in master_wavefiles: try: masterstream += read(os.join(wavbase, wavefile)) except: raise IOError("Couldn't find wavefile") continue for j in range(i + 1, k_events): # Use this tactic to only output unique event pairings slave_sfile = event_list[j][1] if debug > 2: print('Comparing to event: %s' % slave_sfile) slave_event_id = event_list[j][0] slave_wavefiles = readwavename(slave_sfile) try: slavestream = read(wavbase + os.sep + slave_wavefiles[0]) except: raise IOError('No wavefile found: ' + slave_wavefiles[0] + ' ' + slave_sfile) if len(slave_wavefiles) > 1: for wavefile in slave_wavefiles: try: slavestream += read(wavbase + os.sep + wavefile) except IOError: print('No waveform found: %s' % (wavbase + os.sep + wavefile)) continue # Write out the header line event_text = '#' + str(master_event_id).rjust(10) +\ str(slave_event_id).rjust(10) + ' 0.0 \n' event_text2 = '#' + str(master_event_id).rjust(10) +\ str(slave_event_id).rjust(10) + ' 0.0 \n' slave_event = read_nordic(slave_sfile)[0] slave_picks = slave_event.picks slave_ori_time = slave_event.origins[0].time slave_location = (slave_event.origins[0].latitude, slave_event.origins[0].longitude, slave_event.origins[0].depth / 1000.0) if dist_calc(master_location, slave_location) > max_sep: if debug > 0: print('Seperation exceeds max_sep: %s' % (dist_calc(master_location, slave_location))) continue links = 0 phases = 0 for pick in master_picks: if not hasattr(pick, 'phase_hint') or \ len(pick.phase_hint) == 0: warnings.warn('No phase-hint for pick:') print(pick) continue if pick.phase_hint[0].upper() not in ['P', 'S']: warnings.warn('Will only use P or S phase picks') print(pick) continue # Only use P and S picks, not amplitude or 'other' # Find station, phase pairs # Added by Carolin slave_matches = [ p for p in slave_picks if hasattr(p, 'phase_hint') and p.phase_hint == pick.phase_hint and p.waveform_id.station_code == pick.waveform_id.station_code ] if masterstream.select(station=pick.waveform_id.station_code, channel='*' + pick.waveform_id.channel_code[-1]): mastertr = masterstream.\ select(station=pick.waveform_id.station_code, channel='*' + pick.waveform_id.channel_code[-1])[0] elif debug > 1: print('No waveform data for ' + pick.waveform_id.station_code + '.' + pick.waveform_id.channel_code) print(pick.waveform_id.station_code + '.' + pick.waveform_id.channel_code + ' ' + slave_sfile + ' ' + master_sfile) break # Loop through the matches for slave_pick in slave_matches: if slavestream.select( station=slave_pick.waveform_id.station_code, channel='*' + slave_pick.waveform_id.channel_code[-1]): slavetr = slavestream.\ select(station=slave_pick.waveform_id.station_code, channel='*' + slave_pick.waveform_id. channel_code[-1])[0] else: print('No slave data for ' + slave_pick.waveform_id.station_code + '.' + slave_pick.waveform_id.channel_code) print(pick.waveform_id.station_code + '.' + pick.waveform_id.channel_code + ' ' + slave_sfile + ' ' + master_sfile) break # Correct the picks try: correction, cc =\ xcorr_pick_correction( pick.time, mastertr, slave_pick.time, slavetr, pre_pick, extract_len - pre_pick, shift_len, filter="bandpass", filter_options={'freqmin': lowcut, 'freqmax': highcut}, plot=plotvar) # Get the differential travel time using the # corrected time. # Check that the correction is within the allowed shift # This can occur in the obspy routine when the # correlation function is increasing at the end of the # window. if abs(correction) > shift_len: warnings.warn('Shift correction too large, ' + 'will not use') continue correction = (pick.time - master_ori_time) -\ (slave_pick.time + correction - slave_ori_time) links += 1 if cc >= cc_thresh: weight = cc phases += 1 # added by Caro event_text += pick.waveform_id.station_code.\ ljust(5) + _cc_round(correction, 3).\ rjust(11) + _cc_round(weight, 3).rjust(8) +\ ' ' + pick.phase_hint + '\n' event_text2 += pick.waveform_id.station_code\ .ljust(5) + _cc_round(correction, 3).\ rjust(11) +\ _cc_round(weight * weight, 3).rjust(8) +\ ' ' + pick.phase_hint + '\n' if debug > 3: print(event_text) else: print('cc too low: %s' % cc) corr_list.append(cc * cc) except: msg = "Couldn't compute correlation correction" warnings.warn(msg) continue if links >= min_link and phases > 0: f.write(event_text) f2.write(event_text2) if plotvar: plt.hist(corr_list, 150) plt.show() # f.write('\n') f.close() f2.close() return
def write_catalog(event_list, max_sep=8, min_link=8, debug=0): """ Generate a dt.ct for hypoDD for a series of events. Takes input event list from :func:`eqcorrscan.utils.catalog_to_dd.write_event` as a list of tuples of event id and sfile. It will read the pick information from the seisan formated s-file using the sfile_util utilities. :type event_list: list :param event_list: List of tuples of event_id (int) and sfile (String) :type max_sep: float :param max_sep: Maximum separation between event pairs in km :type min_link: int :param min_link: Minimum links for an event to be paired, e.g. minimum number of picks from the same station and channel (and phase) that are shared between two events for them to be paired. :type debug: int :param debug: Debug output level. :returns: list of stations that have been used in this catalog .. note:: We have not yet implemented a method for taking unassociated event objects and wavefiles. As such if you have events with associated wavefiles you are advised to generate Sfiles for each event using the :mod:`eqcorrscan.utils.sfile_util` module prior to this step. """ # Cope with possibly being passed a zip in python 3.x event_list = list(event_list) f = open('dt.ct', 'w') f2 = open('dt.ct2', 'w') fphase = open('phase.dat', 'w') stations = [] evcount = 0 for i, master in enumerate(event_list): master_sfile = master[1] master_event_id = master[0] master_event = read_nordic(master_sfile)[0] master_ori_time = master_event.origins[0].time master_location = (master_event.origins[0].latitude, master_event.origins[0].longitude, master_event.origins[0].depth / 1000) if len(master_event.magnitudes) > 0: master_magnitude = master_event.magnitudes[0].mag or ' ' else: master_magnitude = ' ' header = '# ' + \ master_ori_time.strftime('%Y %m %d %H %M %S.%f') +\ ' ' + str(master_location[0]).ljust(8) + ' ' +\ str(master_location[1]).ljust(8) + ' ' +\ str(master_location[2]).ljust(4) + ' ' +\ str(master_magnitude).ljust(4) + ' 0.0 0.0 0.0' +\ str(master_event_id).rjust(4) fphase.write(header + '\n') for pick in master_event.picks: if not hasattr(pick, 'phase_hint') or len(pick.phase_hint) == 0: warnings.warn('No phase-hint for pick:') print(pick) continue if pick.phase_hint[0].upper() in ['P', 'S']: weight = [ arrival.time_weight for arrival in master_event.origins[0].arrivals if arrival.pick_id == pick.resource_id ][0] # Convert seisan weight to hypoDD 0-1 weights if weight == 0: weight = 1.0 elif weight == 9: weight = 0.0 else: weight = 1 - weight / 4.0 fphase.write(pick.waveform_id.station_code + ' ' + _cc_round(pick.time - master_ori_time, 3).rjust(6) + ' ' + str(weight).ljust(5) + pick.phase_hint + '\n') for j in range(i + 1, len(event_list)): # Use this tactic to only output unique event pairings slave_sfile = event_list[j][1] slave_event_id = event_list[j][0] # Write out the header line event_text = '#' + str(master_event_id).rjust(10) +\ str(slave_event_id).rjust(10) + '\n' event_text2 = '#' + str(master_event_id).rjust(10) +\ str(slave_event_id).rjust(10) + '\n' slave_event = read_nordic(slave_sfile)[0] slave_ori_time = slave_event.origins[0].time slave_location = (slave_event.origins[0].latitude, slave_event.origins[0].longitude, slave_event.origins[0].depth / 1000) if dist_calc(master_location, slave_location) > max_sep: continue links = 0 # Count the number of linkages for pick in master_event.picks: if not hasattr(pick, 'phase_hint') or\ len(pick.phase_hint) == 0: continue if pick.phase_hint[0].upper() not in ['P', 'S']: continue # Only use P and S picks, not amplitude or 'other' # Added by Carolin slave_matches = [ p for p in slave_event.picks if hasattr(p, 'phase_hint') and p.phase_hint == pick.phase_hint and p.waveform_id.station_code.upper() == pick.waveform_id.station_code.upper() ] # Loop through the matches for slave_pick in slave_matches: links += 1 master_weight = [ arrival.time_weight for arrival in master_event.origins[0].arrivals if arrival.pick_id == pick.resource_id ][0] slave_weight = [ arrival.time_weight for arrival in slave_event.origins[0].arrivals if arrival.pick_id == slave_pick.resource_id ][0] master_weight = str(int(master_weight)) slave_weight = str(int(slave_weight)) event_text += pick.waveform_id.station_code.ljust(5) +\ _cc_round(pick.time - master_ori_time, 3).rjust(11) +\ _cc_round(slave_pick.time - slave_ori_time, 3).rjust(8) +\ _av_weight(master_weight, slave_weight).rjust(7) +\ ' ' + pick.phase_hint + '\n' # Added by Carolin event_text2 += pick.waveform_id.station_code.ljust(5) +\ _cc_round(pick.time - master_ori_time, 3).rjust(11) +\ _cc_round(slave_pick.time - slave_ori_time, 3).rjust(8) +\ _av_weight(master_weight, slave_weight).rjust(7) +\ ' ' + pick.phase_hint + '\n' stations.append(pick.waveform_id.station_code) if links >= min_link: f.write(event_text) f2.write(event_text2) evcount += 1 print('You have ' + str(evcount) + ' links') # f.write('\n') f.close() f2.close() fphase.close() return list(set(stations))
def write_correlations(event_list, wavbase, extract_len, pre_pick, shift_len, lowcut=1.0, highcut=10.0, max_sep=4, min_link=8, coh_thresh=0.0, coherence_weight=True, plotvar=False): """ Function to write a dt.cc file for hypoDD input - takes an input list of events and computes pick refienements by correlation. :type event_list: list of tuple :param event_list: List of tuples of event_id (int) and sfile (String) :type wavbase: str :param wavbase: Path to the seisan wave directory that the wavefiles in the S-files are stored :type extract_len: float :param extract_len: Length in seconds to extract around the pick :type pre_pick: float :param pre_pick: Time before the pick to start the correlation window :type shift_len: float :param shift_len: Time to allow pick to vary :type lowcut: float :param lowcut: Lowcut in Hz - default=1.0 :type highcut: float :param highcut: Highcut in Hz - deafult=10.0 :type max_sep: float :param max_sep: Maximum seperation between event pairs in km :type min_link: int :param min_link: Minimum links for an event to be paired :type coherence_weight: bool :param coherence_weight: Use coherence to weight the dt.cc file, or the \ raw cross-correlation value, defaults to false which uses the cross-\ correlation value. :type plotvar: bool :param plotvar: To show the pick-correction plots, defualts to False. .. warning:: This is not a fast routine! .. warning:: In contrast to seisan's \ corr routine, but in accordance with the hypoDD manual, this outputs \ corrected differential time. .. note:: Currently we have not implemented a method for taking \ unassociated event objects and wavefiles. As such if you have events \ with associated wavefiles you are advised to generate Sfiles for each \ event using the sfile_util module prior to this step. """ import obspy if int(obspy.__version__.split('.')[0]) > 0: from obspy.signal.cross_correlation import xcorr_pick_correction else: from obspy.signal.cross_correlation import xcorrPickCorrection \ as xcorr_pick_correction import matplotlib.pyplot as plt from obspy import read from eqcorrscan.utils.mag_calc import dist_calc import glob import warnings corr_list = [] f = open('dt.cc', 'w') f2 = open('dt.cc2', 'w') for i, master in enumerate(event_list): master_sfile = master[1] master_event_id = master[0] master_picks = sfile_util.readpicks(master_sfile).picks master_event = sfile_util.readheader(master_sfile) master_ori_time = master_event.origins[0].time master_location = (master_event.origins[0].latitude, master_event.origins[0].longitude, master_event.origins[0].depth) master_wavefiles = sfile_util.readwavename(master_sfile) masterpath = glob.glob(wavbase + os.sep + master_wavefiles[0]) if masterpath: masterstream = read(masterpath[0]) if len(master_wavefiles) > 1: for wavefile in master_wavefiles: try: masterstream += read(os.join(wavbase, wavefile)) except: continue raise IOError("Couldn't find wavefile") for j in range(i+1, len(event_list)): # Use this tactic to only output unique event pairings slave_sfile = event_list[j][1] slave_event_id = event_list[j][0] slave_wavefiles = sfile_util.readwavename(slave_sfile) try: # slavestream=read(wavbase+'/*/*/'+slave_wavefiles[0]) slavestream = read(wavbase + os.sep + slave_wavefiles[0]) except: # print(slavestream) raise IOError('No wavefile found: '+slave_wavefiles[0]+' ' + slave_sfile) if len(slave_wavefiles) > 1: for wavefile in slave_wavefiles: # slavestream+=read(wavbase+'/*/*/'+wavefile) try: slavestream += read(wavbase+'/'+wavefile) except: continue # Write out the header line event_text = '#'+str(master_event_id).rjust(10) +\ str(slave_event_id).rjust(10)+' 0.0 \n' event_text2 = '#'+str(master_event_id).rjust(10) +\ str(slave_event_id).rjust(10)+' 0.0 \n' slave_picks = sfile_util.readpicks(slave_sfile).picks slave_event = sfile_util.readheader(slave_sfile) slave_ori_time = slave_event.origins[0].time slave_location = (slave_event.origins[0].latitude, slave_event.origins[0].longitude, slave_event.origins[0].depth) if dist_calc(master_location, slave_location) > max_sep: continue links = 0 phases = 0 for pick in master_picks: if pick.phase_hint[0].upper() not in ['P', 'S']: continue # Only use P and S picks, not amplitude or 'other' # Find station, phase pairs # Added by Carolin slave_matches = [p for p in slave_picks if p.phase_hint == pick.phase_hint and p.waveform_id.station_code == pick.waveform_id.station_code] if masterstream.select(station=pick.waveform_id.station_code, channel='*' + pick.waveform_id.channel_code[-1]): mastertr = masterstream.\ select(station=pick.waveform_id.station_code, channel='*' + pick.waveform_id.channel_code[-1])[0] else: print('No waveform data for ' + pick.waveform_id.station_code + '.' + pick.waveform_id.channel_code) print(pick.waveform_id.station_code + '.' + pick.waveform_id.channel_code + ' ' + slave_sfile+' ' + master_sfile) break # Loop through the matches for slave_pick in slave_matches: if slavestream.select(station=slave_pick.waveform_id. station_code, channel='*'+slave_pick.waveform_id. channel_code[-1]): slavetr = slavestream.\ select(station=slave_pick.waveform_id.station_code, channel='*'+slave_pick.waveform_id. channel_code[-1])[0] else: print('No slave data for ' + slave_pick.waveform_id.station_code + '.' + slave_pick.waveform_id.channel_code) print(pick.waveform_id.station_code + '.' + pick.waveform_id.channel_code + ' ' + slave_sfile + ' ' + master_sfile) break # Correct the picks try: correction, cc =\ xcorr_pick_correction(pick.time, mastertr, slave_pick.time, slavetr, pre_pick, extract_len - pre_pick, shift_len, filter="bandpass", filter_options={'freqmin': lowcut, 'freqmax': highcut}, plot=plotvar) # Get the differntial travel time using the # corrected time. # Check that the correction is within the allowed shift # This can occur in the obspy routine when the # correlation function is increasing at the end of the # window. if abs(correction) > shift_len: warnings.warn('Shift correction too large, ' + 'will not use') continue correction = (pick.time - master_ori_time) -\ (slave_pick.time + correction - slave_ori_time) links += 1 if cc * cc >= coh_thresh: if coherence_weight: weight = cc * cc else: weight = cc phases += 1 # added by Caro event_text += pick.waveform_id.station_code.\ ljust(5) + _cc_round(correction, 3).\ rjust(11) + _cc_round(weight, 3).rjust(8) +\ ' '+pick.phase_hint+'\n' event_text2 += pick.waveform_id.station_code\ .ljust(5).upper() +\ _cc_round(correction, 3).rjust(11) +\ _cc_round(weight, 3).rjust(8) +\ ' '+pick.phase_hint+'\n' # links+=1 corr_list.append(cc*cc) except: # Should warn here msg = "Couldn't compute correlation correction" warnings.warn(msg) continue if links >= min_link and phases > 0: f.write(event_text) f2.write(event_text2) if plotvar: plt.hist(corr_list, 150) plt.show() # f.write('\n') f.close() f2.close() return
def write_catalog(event_list, max_sep=1, min_link=8): """ Function to write the dt.ct file needed by hypoDD - takes input event list from write_event as a list of tuples of event id and sfile. It will read the pick information from the seisan formated s-file using the sfile_util utilities. :type event_list: list of tuple :param event_list: List of tuples of event_id (int) and sfile (String) :type max_sep: float :param max_sep: Maximum seperation between event pairs in km :type min_link: int :param min_link: Minimum links for an event to be paired :returns: list stations .. note:: Currently we have not implemented a method for taking \ unassociated event objects and wavefiles. As such if you have events \ with associated wavefiles you are advised to generate Sfiles for each \ event using the sfile_util module prior to this step. """ from eqcorrscan.utils.mag_calc import dist_calc f = open('dt.ct', 'w') f2 = open('dt.ct2', 'w') fphase = open('phase.dat', 'w') stations = [] evcount = 0 for i, master in enumerate(event_list): master_sfile = master[1] master_event_id = master[0] master_event = sfile_util.readpicks(master_sfile) master_ori_time = master_event.origins[0].time master_location = (master_event.origins[0].latitude, master_event.origins[0].longitude, master_event.origins[0].depth / 1000) if len(master_event.magnitudes) > 0: master_magnitude = master_event.magnitudes[0].mag or ' ' else: master_magnitude = ' ' header = '# '+master_ori_time.strftime('%Y %m %d %H %M %S.%f') +\ ' '+str(master_location[0]).ljust(8)+' ' +\ str(master_location[1]).ljust(8)+' ' +\ str(master_location[2]).ljust(4)+' ' +\ str(master_magnitude).ljust(4)+' 0.0 0.0 0.0' +\ str(master_event_id).rjust(4) fphase.write(header+'\n') for pick in master_event.picks: if pick.phase_hint[0].upper() in ['P', 'S']: weight = [arrival.time_weight for arrival in master_event.origins[0].arrivals if arrival.pick_id == pick.resource_id][0] # Convert seisan weight to hypoDD 0-1 weights if weight == 0: weight = 1.0 elif weight == 9: weight = 0.0 else: weight = 1 - weight / 4.0 fphase.write(pick.waveform_id.station_code+' ' + _cc_round(pick.time - master_ori_time, 3).rjust(6) + ' '+str(weight).ljust(5)+pick.phase_hint+'\n') for j in range(i+1, len(event_list)): # Use this tactic to only output unique event pairings slave_sfile = event_list[j][1] slave_event_id = event_list[j][0] # Write out the header line event_text = '#'+str(master_event_id).rjust(10) +\ str(slave_event_id).rjust(10)+'\n' event_text2 = '#'+str(master_event_id).rjust(10) +\ str(slave_event_id).rjust(10)+'\n' slave_event = sfile_util.readpicks(slave_sfile) slave_ori_time = slave_event.origins[0].time slave_location = (slave_event.origins[0].latitude, slave_event.origins[0].longitude, slave_event.origins[0].depth / 1000) if dist_calc(master_location, slave_location) > max_sep: continue links = 0 # Count the number of linkages for pick in master_event.picks: if pick.phase_hint[0].upper() not in ['P', 'S']: continue # Only use P and S picks, not amplitude or 'other' # Added by Carolin slave_matches = [p for p in slave_event.picks if p.phase_hint == pick.phase_hint and p.waveform_id.station_code.upper() == pick.waveform_id.station_code.upper()] # Loop through the matches for slave_pick in slave_matches: links += 1 master_weight = [arrival.time_weight for arrival in master_event. origins[0].arrivals if arrival.pick_id == pick.resource_id][0] slave_weight = [arrival.time_weight for arrival in slave_event. origins[0].arrivals if arrival.pick_id == slave_pick.resource_id][0] event_text += pick.waveform_id.station_code.ljust(5) +\ _cc_round(pick.time-master_ori_time, 3).rjust(11) +\ _cc_round(slave_pick.time-slave_ori_time, 3).rjust(8) +\ _av_weight(master_weight, slave_weight).rjust(7) +\ ' '+pick.phase_hint+'\n' # Added by Carolin event_text2 += pick.waveform_id.station_code.ljust(5) +\ _cc_round(pick.time-master_ori_time, 3).rjust(11) +\ _cc_round(slave_pick.time-slave_ori_time, 3).rjust(8) +\ _av_weight(master_weight, slave_weight).rjust(7) +\ ' '+pick.phase_hint+'\n' stations.append(pick.waveform_id.station_code) if links >= min_link: f.write(event_text) f2.write(event_text2) evcount += 1 print('You have '+str(evcount)+' links') # f.write('\n') f.close() f2.close() fphase.close() return list(set(stations))
def write_correlations(event_list, wavbase, extract_len, pre_pick, shift_len, lowcut=1.0, highcut=10.0, max_sep=4, min_link=8, coh_thresh=0.0, coherence_weight=True, plotvar=False): """ Function to write a dt.cc file for hypoDD input - takes an input list of events and computes pick refienements by correlation. :type event_list: list of tuple :param event_list: List of tuples of event_id (int) and sfile (String) :type wavbase: str :param wavbase: Path to the seisan wave directory that the wavefiles in the S-files are stored :type extract_len: float :param extract_len: Length in seconds to extract around the pick :type pre_pick: float :param pre_pick: Time before the pick to start the correlation window :type shift_len: float :param shift_len: Time to allow pick to vary :type lowcut: float :param lowcut: Lowcut in Hz - default=1.0 :type highcut: float :param highcut: Highcut in Hz - deafult=10.0 :type max_sep: float :param max_sep: Maximum seperation between event pairs in km :type min_link: int :param min_link: Minimum links for an event to be paired :type coherence_weight: bool :param coherence_weight: Use coherence to weight the dt.cc file, or the \ raw cross-correlation value, defaults to false which uses the cross-\ correlation value. :type plotvar: bool :param plotvar: To show the pick-correction plots, defualts to False. .. warning:: This is not a fast routine! .. warning:: In contrast to seisan's \ corr routine, but in accordance with the hypoDD manual, this outputs \ corrected differential time. .. note:: Currently we have not implemented a method for taking \ unassociated event objects and wavefiles. As such if you have events \ with associated wavefiles you are advised to generate Sfiles for each \ event using the sfile_util module prior to this step. """ import obspy if int(obspy.__version__.split('.')[0]) > 0: from obspy.signal.cross_correlation import xcorr_pick_correction else: from obspy.signal.cross_correlation import xcorrPickCorrection \ as xcorr_pick_correction import matplotlib.pyplot as plt from obspy import read from eqcorrscan.utils.mag_calc import dist_calc import glob import warnings corr_list = [] f = open('dt.cc', 'w') f2 = open('dt.cc2', 'w') for i, master in enumerate(event_list): master_sfile = master[1] master_event_id = master[0] master_picks = sfile_util.readpicks(master_sfile).picks master_event = sfile_util.readheader(master_sfile) master_ori_time = master_event.origins[0].time master_location = (master_event.origins[0].latitude, master_event.origins[0].longitude, master_event.origins[0].depth) master_wavefiles = sfile_util.readwavename(master_sfile) masterpath = glob.glob(wavbase + os.sep + master_wavefiles[0]) if masterpath: masterstream = read(masterpath[0]) if len(master_wavefiles) > 1: for wavefile in master_wavefiles: try: masterstream += read(os.join(wavbase, wavefile)) except: continue raise IOError("Couldn't find wavefile") for j in range(i + 1, len(event_list)): # Use this tactic to only output unique event pairings slave_sfile = event_list[j][1] slave_event_id = event_list[j][0] slave_wavefiles = sfile_util.readwavename(slave_sfile) try: # slavestream=read(wavbase+'/*/*/'+slave_wavefiles[0]) slavestream = read(wavbase + os.sep + slave_wavefiles[0]) except: # print(slavestream) raise IOError('No wavefile found: ' + slave_wavefiles[0] + ' ' + slave_sfile) if len(slave_wavefiles) > 1: for wavefile in slave_wavefiles: # slavestream+=read(wavbase+'/*/*/'+wavefile) try: slavestream += read(wavbase + '/' + wavefile) except: continue # Write out the header line event_text = '#'+str(master_event_id).rjust(10) +\ str(slave_event_id).rjust(10)+' 0.0 \n' event_text2 = '#'+str(master_event_id).rjust(10) +\ str(slave_event_id).rjust(10)+' 0.0 \n' slave_picks = sfile_util.readpicks(slave_sfile).picks slave_event = sfile_util.readheader(slave_sfile) slave_ori_time = slave_event.origins[0].time slave_location = (slave_event.origins[0].latitude, slave_event.origins[0].longitude, slave_event.origins[0].depth) if dist_calc(master_location, slave_location) > max_sep: continue links = 0 phases = 0 for pick in master_picks: if pick.phase_hint[0].upper() not in ['P', 'S']: continue # Only use P and S picks, not amplitude or 'other' # Find station, phase pairs # Added by Carolin slave_matches = [ p for p in slave_picks if p.phase_hint == pick.phase_hint and p.waveform_id.station_code == pick.waveform_id.station_code ] if masterstream.select(station=pick.waveform_id.station_code, channel='*' + pick.waveform_id.channel_code[-1]): mastertr = masterstream.\ select(station=pick.waveform_id.station_code, channel='*' + pick.waveform_id.channel_code[-1])[0] else: print('No waveform data for ' + pick.waveform_id.station_code + '.' + pick.waveform_id.channel_code) print(pick.waveform_id.station_code + '.' + pick.waveform_id.channel_code + ' ' + slave_sfile + ' ' + master_sfile) break # Loop through the matches for slave_pick in slave_matches: if slavestream.select( station=slave_pick.waveform_id.station_code, channel='*' + slave_pick.waveform_id.channel_code[-1]): slavetr = slavestream.\ select(station=slave_pick.waveform_id.station_code, channel='*'+slave_pick.waveform_id. channel_code[-1])[0] else: print('No slave data for ' + slave_pick.waveform_id.station_code + '.' + slave_pick.waveform_id.channel_code) print(pick.waveform_id.station_code + '.' + pick.waveform_id.channel_code + ' ' + slave_sfile + ' ' + master_sfile) break # Correct the picks try: correction, cc =\ xcorr_pick_correction(pick.time, mastertr, slave_pick.time, slavetr, pre_pick, extract_len - pre_pick, shift_len, filter="bandpass", filter_options={'freqmin': lowcut, 'freqmax': highcut}, plot=plotvar) # Get the differntial travel time using the # corrected time. # Check that the correction is within the allowed shift # This can occur in the obspy routine when the # correlation function is increasing at the end of the # window. if abs(correction) > shift_len: warnings.warn('Shift correction too large, ' + 'will not use') continue correction = (pick.time - master_ori_time) -\ (slave_pick.time + correction - slave_ori_time) links += 1 if cc * cc >= coh_thresh: if coherence_weight: weight = cc * cc else: weight = cc phases += 1 # added by Caro event_text += pick.waveform_id.station_code.\ ljust(5) + _cc_round(correction, 3).\ rjust(11) + _cc_round(weight, 3).rjust(8) +\ ' '+pick.phase_hint+'\n' event_text2 += pick.waveform_id.station_code\ .ljust(5).upper() +\ _cc_round(correction, 3).rjust(11) +\ _cc_round(weight, 3).rjust(8) +\ ' '+pick.phase_hint+'\n' # links+=1 corr_list.append(cc * cc) except: # Should warn here msg = "Couldn't compute correlation correction" warnings.warn(msg) continue if links >= min_link and phases > 0: f.write(event_text) f2.write(event_text2) if plotvar: plt.hist(corr_list, 150) plt.show() # f.write('\n') f.close() f2.close() return
def write_catalog(event_list, max_sep=1, min_link=8): """ Function to write the dt.ct file needed by hypoDD - takes input event list from write_event as a list of tuples of event id and sfile. It will read the pick information from the seisan formated s-file using the sfile_util utilities. :type event_list: list of tuple :param event_list: List of tuples of event_id (int) and sfile (String) :type max_sep: float :param max_sep: Maximum seperation between event pairs in km :type min_link: int :param min_link: Minimum links for an event to be paired :returns: list stations .. note:: Currently we have not implemented a method for taking \ unassociated event objects and wavefiles. As such if you have events \ with associated wavefiles you are advised to generate Sfiles for each \ event using the sfile_util module prior to this step. """ from eqcorrscan.utils.mag_calc import dist_calc f = open('dt.ct', 'w') f2 = open('dt.ct2', 'w') fphase = open('phase.dat', 'w') stations = [] evcount = 0 for i, master in enumerate(event_list): master_sfile = master[1] master_event_id = master[0] master_event = sfile_util.readpicks(master_sfile) master_ori_time = master_event.origins[0].time master_location = (master_event.origins[0].latitude, master_event.origins[0].longitude, master_event.origins[0].depth / 1000) if len(master_event.magnitudes) > 0: master_magnitude = master_event.magnitudes[0].mag or ' ' else: master_magnitude = ' ' header = '# '+master_ori_time.strftime('%Y %m %d %H %M %S.%f') +\ ' '+str(master_location[0]).ljust(8)+' ' +\ str(master_location[1]).ljust(8)+' ' +\ str(master_location[2]).ljust(4)+' ' +\ str(master_magnitude).ljust(4)+' 0.0 0.0 0.0' +\ str(master_event_id).rjust(4) fphase.write(header + '\n') for pick in master_event.picks: if pick.phase_hint[0].upper() in ['P', 'S']: weight = [ arrival.time_weight for arrival in master_event.origins[0].arrivals if arrival.pick_id == pick.resource_id ][0] # Convert seisan weight to hypoDD 0-1 weights if weight == 0: weight = 1.0 elif weight == 9: weight = 0.0 else: weight = 1 - weight / 4.0 fphase.write(pick.waveform_id.station_code + ' ' + _cc_round(pick.time - master_ori_time, 3).rjust(6) + ' ' + str(weight).ljust(5) + pick.phase_hint + '\n') for j in range(i + 1, len(event_list)): # Use this tactic to only output unique event pairings slave_sfile = event_list[j][1] slave_event_id = event_list[j][0] # Write out the header line event_text = '#'+str(master_event_id).rjust(10) +\ str(slave_event_id).rjust(10)+'\n' event_text2 = '#'+str(master_event_id).rjust(10) +\ str(slave_event_id).rjust(10)+'\n' slave_event = sfile_util.readpicks(slave_sfile) slave_ori_time = slave_event.origins[0].time slave_location = (slave_event.origins[0].latitude, slave_event.origins[0].longitude, slave_event.origins[0].depth / 1000) if dist_calc(master_location, slave_location) > max_sep: continue links = 0 # Count the number of linkages for pick in master_event.picks: if pick.phase_hint[0].upper() not in ['P', 'S']: continue # Only use P and S picks, not amplitude or 'other' # Added by Carolin slave_matches = [ p for p in slave_event.picks if p.phase_hint == pick.phase_hint and p.waveform_id.station_code.upper() == pick.waveform_id.station_code.upper() ] # Loop through the matches for slave_pick in slave_matches: links += 1 master_weight = [ arrival.time_weight for arrival in master_event.origins[0].arrivals if arrival.pick_id == pick.resource_id ][0] slave_weight = [ arrival.time_weight for arrival in slave_event.origins[0].arrivals if arrival.pick_id == slave_pick.resource_id ][0] event_text += pick.waveform_id.station_code.ljust(5) +\ _cc_round(pick.time-master_ori_time, 3).rjust(11) +\ _cc_round(slave_pick.time-slave_ori_time, 3).rjust(8) +\ _av_weight(master_weight, slave_weight).rjust(7) +\ ' '+pick.phase_hint+'\n' # Added by Carolin event_text2 += pick.waveform_id.station_code.ljust(5) +\ _cc_round(pick.time-master_ori_time, 3).rjust(11) +\ _cc_round(slave_pick.time-slave_ori_time, 3).rjust(8) +\ _av_weight(master_weight, slave_weight).rjust(7) +\ ' '+pick.phase_hint+'\n' stations.append(pick.waveform_id.station_code) if links >= min_link: f.write(event_text) f2.write(event_text2) evcount += 1 print('You have ' + str(evcount) + ' links') # f.write('\n') f.close() f2.close() fphase.close() return list(set(stations))
def plot_event_well_dist(catalog, well_file, flow_start, diffs, temp_list='all', method='scatter', starttime=None, endtime=None, title=None, show=True): """ Function to plot events with distance from well as a function of time. :param cat: catalog of events :param well_file: text file of xyz feedzone pts :param flow_start: Start UTCdt of well flow to model :param diffs: list of diffusion values to plot :param temp_list: list of templates for which we'll plot detections :param method: plot the 'scatter' or daily 'average' distance or both :return: matplotlib.pyplot.Axes """ well_pts = format_well_data(well_file) # Grab only templates in the list cat = Catalog() filt_cat = Catalog() if starttime and endtime: filt_cat.events = [ev for ev in catalog if ev.origins[-1].time < endtime and ev.origins[-1].time >= starttime] else: filt_cat = catalog cat.events = [ev for ev in filt_cat if str(ev.resource_id).split('/')[-1].split('_')[0] in temp_list or temp_list == 'all'] time_dist_tups = [] cat_start = min([ev.origins[-1].time.datetime for ev in cat]) cat_end = max([ev.origins[-1].time.datetime for ev in cat]) for ev in cat: if ev.origins[-1]: dist = min([dist_calc((ev.origins[-1].latitude, ev.origins[-1].longitude, ev.origins[-1].depth / 1000.), pt) for pt in well_pts]) time_dist_tups.append((ev.origins[-1].time.datetime, dist)) times, dists = zip(*time_dist_tups) # Make DataFrame for boxplotting dist_df = pd.DataFrame() dist_df['dists'] = pd.Series(dists, index=times) # Add daily grouping column to df (this is crap, but can't find better) dist_df['day_num'] = [date2num(dto.replace(hour=12, minute=0, second=0, microsecond=0).to_pydatetime()) for dto in dist_df.index] dist_df['dto_num'] = [date2num(dt) for dt in dist_df.index] # Now create the pressure envelopes # Creating hourly datetime increments start = pd.Timestamp(flow_start.datetime) end = pd.Timestamp(cat_end) t = pd.to_datetime(pd.date_range(start, end, freq='H')) t = [date2num(d) for d in t] # Now diffusion y vals diff_ys = [] for d in diffs: diff_ys.append([np.sqrt(60 * d * i / 4000 * np.pi) # account for kms for i in range(len(t))]) # Plot 'em up fig, ax = plt.subplots(figsize=(7, 6)) # First boxplots u_days = list(set(dist_df.day_num)) bins = [dist_df.loc[dist_df['day_num'] == d]['dists'].values for d in u_days] positions = [d for d in u_days] bplots = ax.boxplot(bins, positions=positions, patch_artist=True, flierprops={'markersize': 0}, manage_xticks=False) for patch in bplots['boxes']: patch.set_facecolor('lightblue') patch.set_alpha(0.5) # First diffusions for i, diff_y in enumerate(diff_ys): ax.plot(t, diff_y, label='Diffusion envelope, D={} $m^2/s$'.format(str(diffs[i]))) # Now events if method != 'scatter': dates = [] day_avg_dist = [] for date in date_generator(cat_start, cat_end): dates.append(date) tdds = [tdd[1] for tdd in time_dist_tups if tdd[0] > date and tdd[0] < date + timedelta(days=1)] day_avg_dist.append(np.mean(tdds)) if method == 'scatter': ax.scatter(times, dists, color='gray', label='Event', s=10, alpha=0.5) elif method == 'average': ax.plot(dates, day_avg_dist) elif method == 'both': ax.scatter(times, dists) ax.plot(dates, day_avg_dist, color='r') # Plot formatting fig.autofmt_xdate() ax.legend() ax.set_ylim([0, 6]) if title: ax.set_title(title, fontsize=19) else: ax.set_title('Fluid diffusion envelopes and earthquake distance') if starttime: ax.set_xlim([date2num(starttime.datetime), max(t)]) else: ax.set_xlim([min(t), max(t)]) ax.set_xlabel('Date') ax.set_ylabel('Distance (km)') fig.tight_layout() if show: fig.show() return ax
def test_write_catalog(self): """ Simple testing function for the write_catalogue function in \ catalog_to_dd. """ self.assertTrue(os.path.isfile('dt.ct')) # Check dt.ct file, should contain only a few linked events dt_file_out = open('dt.ct', 'r') event_pairs = [] event_links = [] event_pair = '' for i, line in enumerate(dt_file_out): if line[0] == '#': if i != 0: # Check the number of links self.assertTrue(len(event_links) >= self.minimum_links) # Check the distance between events event_1_name = [ event[1] for event in self.event_list if event[0] == int(event_pair.split()[1]) ][0] event_2_name = [ event[1] for event in self.event_list if event[0] == int(event_pair.split()[2]) ][0] event_1 = sfile_util.readheader(event_1_name) event_2 = sfile_util.readheader(event_2_name) event_1_location = (event_1.origins[0].latitude, event_1.origins[0].longitude, event_1.origins[0].depth / 1000) event_2_location = (event_2.origins[0].latitude, event_2.origins[0].longitude, event_2.origins[0].depth / 1000) hypocentral_seperation = dist_calc(event_1_location, event_2_location) self.assertTrue( hypocentral_seperation < self.maximum_separation) # Check that the differential times are accurate event_1_picks = sfile_util.readpicks(event_1_name).picks event_2_picks = sfile_util.readpicks(event_2_name).picks for pick_pair in event_links: station = pick_pair.split()[0] event_1_travel_time_output = pick_pair.split()[1] event_2_travel_time_output = pick_pair.split()[2] # weight = pick_pair.split()[3] phase = pick_pair.split()[4] # Extract the relevant pick information from the # two sfiles for pick in event_1_picks: if pick.waveform_id.station_code == station: if pick.phase_hint[0].upper() == phase: event_1_pick = pick for pick in event_2_picks: if pick.waveform_id.station_code == station: if pick.phase_hint[0].upper() == phase: event_2_pick = pick # Calculate the travel-time event_1_travel_time_input = event_1_pick.time -\ event_1.origins[0].time event_2_travel_time_input = event_2_pick.time -\ event_2.origins[0].time self.assertEqual(event_1_travel_time_input, float(event_1_travel_time_output)) self.assertEqual(event_2_travel_time_input, float(event_2_travel_time_output)) event_pair = line event_pairs.append(line) event_links = [] else: event_links.append(line) self.assertTrue(os.path.isfile('phase.dat')) dt_file_out.close()
def write_hybridMT_input(cat, sac_dir, inv, self_files, outfile, prepick, postpick, file_type='raw', plot=False): """ Umbrella function to handle writing input files for focimt and hybridMT :param cat: Catalog of events to write files for :param sac_dir: Root directory for detection SAC files :param inv: Inventory object containing all necessary station responses :param selfs: List containing directory names for template self detections :param prefilt: List of 4 corners for preconvolution bandpass For details see obspy.core.trace.Trace.remove_response() docs :return: """ selfs = [] for self_file in self_files: with open(self_file, 'r') as f: rdr = csv.reader(f) for row in rdr: selfs.append(str(row[0])) ev_dict = {} # Build prefilt dict (Assuming all wavs have been downsampled to 100 Hz) pf_dict = { 'MERC': [0.001, 1.0, 35., 45.], 'WPRZ': [0.001, 0.5, 35., 45.], 'GEONET': [0.001, 0.01, 40., 48.] } # Loop through events for ev in cat: ev_id = str(ev.resource_id).split('/')[-1] print('Working on {}'.format(ev_id)) self = [self for self in selfs if self.split('_')[0] == ev_id] orig = ev.origins[-1] if len(self ) == 0: # Skip those with no self detection (outside fields) print('No self detection for %s' % ev_id) continue wavs = glob('%s/%s/*' % (sac_dir, self[0])) # Loop through arrivals and populate ev_dict with TOA, Backaz, etc... ev_dict[ev_id] = {} # Allocate subdictionary ev_dict[ev_id]['phases'] = [] ev_dict[ev_id]['header'] = None for arr in orig.arrivals: pick = arr.pick_id.get_referred_object() sta = pick.waveform_id.station_code chan = pick.waveform_id.channel_code print('{}.{}'.format(sta, chan)) if chan[-1] != 'Z': continue sta_inv = inv.select(station=sta, channel=chan) # Do a rough incidence angle calculation based on dist and depth dist = dist_calc( (orig.latitude, orig.longitude, orig.depth / 1000.), (sta_inv[0][0].latitude, sta_inv[0][0].longitude, (sta_inv[0][0].elevation - sta_inv[0][0][0].depth) / 1000.)) aoi = 90. - np.degrees(np.arcsin(orig.depth / 1000. / dist)) if np.isnan(aoi): aoi = 180. - arr.takeoff_angle # Establish which station we're working with if sta.endswith('Z'): if sta == 'WPRZ': prefilt = pf_dict['WPRZ'] else: prefilt = pf_dict['GEONET'] else: prefilt = pf_dict['MERC'] wav_file = [ wav for wav in wavs if wav.split('_')[-1].split('.')[0] == chan and wav.split('_')[-2] == sta ] if len(wav_file) == 0: print('Waveform directory not found.') continue # Read in the corresponding trace # Cosine taper and demeaning applied by default raw = read(wav_file[0])[0] tr = read(wav_file[0])[0].remove_response(inventory=inv, pre_filt=prefilt, output='DISP') # Invert polarity of SP instruments if not sta.endswith('Z'): tr.data *= -1 # Trim around P pulse raw_sliced = raw.slice(starttime=pick.time - 0.2, endtime=pick.time + 1).copy() whole_tr = tr.slice(starttime=pick.time - 0.2, endtime=pick.time + 1).copy() tr.trim(starttime=pick.time - prepick, endtime=pick.time + postpick) pick_sample = int(prepick * tr.stats.sampling_rate) # Find the next index where trace crosses the 'zero' value # which we assume is the value at time of pick. # Take last 'zero' crossing of the trimmed wav, assuming we've # trimmed only half a cycle. Then integrate from pick time to # first sample with a swapped sign (+/- or -/+) # Make pick value zero leveled = tr.data - tr.data[pick_sample] # Determine some polarity info rel_min_max = argrelmax(np.abs(leveled)) #Relative peaks print(rel_min_max) try: if rel_min_max[0].shape[0] > 1: print(leveled[rel_min_max]) rel_pk = np.argmax(np.abs(leveled[rel_min_max])) print(rel_pk) print(rel_min_max[0][rel_pk]) peak = leveled[rel_min_max[0][rel_pk]] # Largest peak else: try: peak = leveled[rel_min_max][0] except IndexError: print('No relative maxima or minima') continue except ValueError: print('No relative maxima or minima') continue print('Peak value: {!s}'.format(peak)) polarity = np.sign(peak) # Sign of largest peak print('Zero crossings at: {!s}'.format( np.where(np.diff(np.sign(leveled[pick_sample + 1:])) != 0)[0])) try: pulse = leveled[pick_sample:pick_sample + 1 + np.where( np.diff(np.sign(leveled[pick_sample + 1:])) != 0)[0][-1] + 2] # 2-sample fudge factor over crossing except IndexError as i: print('IndexError: {}'.format(i)) if polarity == 1: try: pulse = leveled[pick_sample:argrelmin(leveled)[0][-1] + 1] except IndexError: print('No zero crossing OR relative minimum.') continue elif polarity == -1: try: pulse = leveled[pick_sample:argrelmax(leveled)[0][-1] + 1] except IndexError: print('No zero crossing OR relative maximum.') continue # Try to catch case where small min/max just post pick if len(pulse) < 6: print('{}'.format( 'Pulse is too short: likely due to small rel peak')) pulse = leveled[pick_sample:] omega = np.trapz(pulse) if plot: fig, (ax1, ax2, ax3) = plt.subplots(nrows=3, ncols=1) fig.suptitle('{}.{}'.format(sta, chan)) ax1.plot(raw_sliced.data, label='raw') ax2.plot(whole_tr.data, label='Displacement') ax3.plot(leveled, color='k', label='Pulse') ax3.plot(np.arange(pick_sample, pick_sample + len(pulse), step=1), pulse, color='r') ax3.axvline(pick_sample, linestyle='--', color='grey', label='Pick') plt.legend() plt.show() plt.close() # Now we can populate the strings in ev_dict if file_type == 'raw': ev_dict[ev_id]['phases'].append( " {} {} {} {!s} {!s} {!s} {!s} {!s} {!s} {!s}\n".format( sta, chan[-1], pick.phase_hint, omega * polarity, arr.azimuth, aoi, arr.takeoff_angle, 5000, dist * 1000, 2600)) elif file_type == 'vel1d': x, y, z = dec_2_merc_meters( sta_inv[0][0].longitude, sta_inv[0][0].latitude, sta_inv[0][0].elevation - sta_inv[0][0][0].depth) ev_dict[ev_id]['phases'].append( " {} {} {} {!s} {!s} {!s} {!s}\n".format( sta, chan[-1], pick.phase_hint, omega * polarity, y, x, z)) if len(ev_dict[ev_id]['phases']) > 0: if file_type == 'raw': ev_dict[ev_id]['header'] = "{} {!s}\n".format( ev_id, len(ev_dict[ev_id]['phases'])) elif file_type == 'vel1d': ex, ey, ez = dec_2_merc_meters(orig.longitude, orig.latitude, -1 * orig.depth) ev_dict[ev_id][ 'header'] = "{} {!s} {!s} {!s} {!s} {!s}\n".format( ev_id, len(ev_dict[ev_id]['phases']), ey, ex, ez, 2600) with open(outfile, 'w') as fo: for eid, dict in iteritems(ev_dict): if dict['header'] is not None: print('Writing event %s' % eid) fo.write(dict['header']) fo.writelines(dict['phases']) return