def main(argv=None): parser = ArgumentParser(prog='obspy-print', description=__doc__.strip()) parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__) parser.add_argument('-f', '--format', choices=ENTRY_POINTS['waveform'], help='Waveform format (slightly faster if specified).') parser.add_argument('-n', '--no-merge', action='store_false', dest='merge', help='Switch off cleanup merge.') parser.add_argument('--no-sorting', action='store_false', dest='sort', help='Switch off sorting of traces.') parser.add_argument('-g', '--print-gaps', action='store_true', help='Switch on printing of gap information.') parser.add_argument('files', nargs='+', help='Files to process.') # Deprecated arguments action = _get_deprecated_argument_action( '--nomerge', '--no-merge', real_action='store_false') parser.add_argument('--nomerge', nargs=0, action=action, dest='merge', help=SUPPRESS) args = parser.parse_args(argv) st = Stream() for f in args.files: st += read(f, format=args.format) if args.merge: st.merge(-1) if args.sort: st.sort() print(st.__str__(extended=True)) if args.print_gaps: print() st.printGaps()
def main(argv=None): parser = ArgumentParser(prog='obspy-print', description=__doc__.strip()) parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__) parser.add_argument('-f', '--format', choices=ENTRY_POINTS['waveform'], help='Waveform format (slightly faster if specified).') parser.add_argument('-n', '--no-merge', action='store_false', dest='merge', help='Switch off cleanup merge.') parser.add_argument('--no-sorting', action='store_false', dest='sort', help='Switch off sorting of traces.') parser.add_argument('-g', '--print-gaps', action='store_true', help='Switch on printing of gap information.') parser.add_argument('files', nargs='+', help='Files to process.') args = parser.parse_args(argv) st = Stream() for f in args.files: st += read(f, format=args.format) if args.merge: st.merge(-1) if args.sort: st.sort() print(st.__str__(extended=True)) if args.print_gaps: print() st.print_gaps()
def main(argv=None): parser = ArgumentParser(prog="obspy-print", description=__doc__.strip()) parser.add_argument("-V", "--version", action="version", version="%(prog)s " + __version__) parser.add_argument( "-f", "--format", choices=ENTRY_POINTS["waveform"], help="Waveform format (slightly faster if specified)." ) parser.add_argument("-n", "--no-merge", action="store_false", dest="merge", help="Switch off cleanup merge.") parser.add_argument("--no-sorting", action="store_false", dest="sort", help="Switch off sorting of traces.") parser.add_argument("-g", "--print-gaps", action="store_true", help="Switch on printing of gap information.") parser.add_argument("files", nargs="+", help="Files to process.") args = parser.parse_args(argv) st = Stream() for f in args.files: st += read(f, format=args.format) if args.merge: st.merge(-1) if args.sort: st.sort() print(st.__str__(extended=True)) if args.print_gaps: print() st.print_gaps()
tr.trim(starttime=s_t, endtime=e_t) # deduct theoretical traveltime and start_buf from starttime if rel_time == 1: tr.stats.starttime = tr.stats.starttime - atime st_pickalign2 += tr except: pass print('After alignment and range selection - event 1: ' + str(len(st_pickalign1)) + ' traces') print('After alignment and range selection - event 2: ' + str(len(st_pickalign2)) + ' traces') #%% #print(st) # at length if verbose: print(st1.__str__(extended=True)) print(st2.__str__(extended=True)) if rel_time == 1: print(st_pickalign1.__str__(extended=True)) print(st_pickalign2.__str__(extended=True)) #%% detrend, taper, filter st_pickalign1.detrend(type='simple') st_pickalign2.detrend(type='simple') st_pickalign1.taper(taper_frac) st_pickalign2.taper(taper_frac) st_pickalign1.filter('bandpass', freqmin=freq_min, freqmax=freq_max, corners=2, zerophase=True)
def read_local(data_dir, coord_file, network, station, location, channel, starttime, endtime, merge=True): """ Read in waveforms from "local" 1-hour, IRIS-compliant miniSEED files, and output a Stream object with station/element coordinates attached. NOTE 1: The expected naming convention for the miniSEED files is: <network>.<station>.<location>.<channel>.<year>.<julian_day>.<hour> NOTE 2: This function assumes that the response has been removed from the waveforms in the input miniSEED files. Args: data_dir: Directory containing miniSEED files coord_file: JSON file containing coordinates for local stations (full path required) network: SEED network code [wildcards (*, ?) accepted] station: SEED station code [wildcards (*, ?) accepted] location: SEED location code [wildcards (*, ?) accepted] channel: SEED channel code [wildcards (*, ?) accepted] starttime: Start time for data request (UTCDateTime) endtime: End time for data request (UTCDateTime) merge: Toggle merging of Traces with identical IDs (default: True) Returns: st_out: Stream containing gathered waveforms """ print('-----------------------------') print('GATHERING LOCAL MINISEED DATA') print('-----------------------------') # Take (hour) floor of starttime starttime_hr = UTCDateTime(starttime.year, starttime.month, starttime.day, starttime.hour) # Take (hour) floor of endtime - this ensures we check this miniSEED file endtime_hr = UTCDateTime(endtime.year, endtime.month, endtime.day, endtime.hour) # Define filename template template = f'{network}.{station}.{location}.{channel}.{{}}.{{}}.{{}}' # Initialize Stream object st_out = Stream() # Initialize the starting hour tmp_time = starttime_hr # Cycle forward in time, advancing hour by hour through miniSEED files while tmp_time <= endtime_hr: pattern = template.format(tmp_time.strftime('%Y'), tmp_time.strftime('%j'), tmp_time.strftime('%H')) files = glob.glob(os.path.join(data_dir, pattern)) for file in files: st_out += read(file) tmp_time += HR2SEC # Add an hour! if merge: st_out.merge() # Merge Traces with the same ID st_out.sort() # If the Stream is empty, then we can stop here if st_out.count() == 0: print('No data downloaded.') return st_out # Otherwise, show what the Stream contains print(st_out.__str__(extended=True)) # This syntax prints the WHOLE Stream # Add zeros to ensure all Traces have same length st_out.trim(starttime, endtime, pad=True, fill_value=0) print('Assigning coordinates...') # Assign coordinates by searching through user-supplied JSON file local_coords = load_json_file(coord_file) for tr in st_out: try: tr.stats.latitude, tr.stats.longitude,\ tr.stats.elevation = local_coords[tr.stats.station] except KeyError: print(f'No coordinates available for {tr.id}. Stopping.') raise print('Done') # Return the Stream with coordinates attached return st_out
def gather_waveforms(source, network, station, location, channel, starttime, endtime, time_buffer=0, merge_fill_value=0, trim_fill_value=0, remove_response=False, return_failed_stations=False, watc_url=None, watc_username=None, watc_password=None): """ Gather seismic/infrasound waveforms from IRIS or WATC FDSN, or AVO Winston, and output a :class:`~obspy.core.stream.Stream` with station/element coordinates attached. Optionally remove the sensitivity. **NOTE** Usual RTM usage is to specify a starttime/endtime that brackets the estimated source origin time. Then time_buffer is used to download enough extra data to account for the time required for an infrasound signal to propagate to the farthest station. Args: source (str): Which source to gather waveforms from. Options are: * `'IRIS'` – IRIS FDSN * `'WATC'` – WATC FDSN * `'AVO'` – AVO Winston network (str): SEED network code [wildcards (``*``, ``?``) accepted] station (str): SEED station code [wildcards (``*``, ``?``) accepted] location (str): SEED location code [wildcards (``*``, ``?``) accepted] channel (str): SEED channel code [wildcards (``*``, ``?``) accepted] starttime (:class:`~obspy.core.utcdatetime.UTCDateTime`): Start time for data request endtime (:class:`~obspy.core.utcdatetime.UTCDateTime`): End time for data request time_buffer (int or float): Extra amount of data to download after `endtime` [s] merge_fill_value (bool, int, float, str, or None): Controls merging of :class:`~obspy.core.trace.Trace` objects with identical IDs. If `False`, no merging is performed. Otherwise, a merge is performed with the ``fill_value`` provided to this parameter. For details, see the docstring of :meth:`obspy.core.stream.Stream.trim` trim_fill_value (bool, int, float, or None): Controls trimming of the output :class:`~obspy.core.stream.Stream`, useful if precisely uniform start and end times are desired. If `False`, no trimming is performed. Otherwise, a trim is performed with the ``fill_value`` provided to this parameter. For details, see the docstring of :meth:`obspy.core.stream.Stream.merge` remove_response (bool): Toggle response removal via :meth:`~obspy.core.trace.Trace.remove_sensitivity` or a simple scalar multiplication return_failed_stations (bool): If `True`, returns a list of station codes that were requested but not downloaded. This disables the standard failed station warning message watc_url (str): URL for WATC FDSN server watc_username (str): Username for WATC FDSN server watc_password (str): Password for WATC FDSN server Returns: :class:`~obspy.core.stream.Stream` containing gathered waveforms. If `return_failed_stations` is `True`, additionally returns a list containing station codes that were requested but not downloaded """ # Check for issues with fill value args if merge_fill_value is True or trim_fill_value is True: raise ValueError('Cannot provide True to fill value parameters.') print('--------------') print('GATHERING DATA') print('--------------') # IRIS FDSN if source == 'IRIS': client = FDSN_Client('IRIS') print('Reading data from IRIS FDSN...') try: st_out = client.get_waveforms(network, station, location, channel, starttime, endtime + time_buffer, attach_response=True) except FDSNNoDataException: st_out = Stream() # Just create an empty Stream object # WATC FDSN elif source == 'WATC': print('Connecting to WATC FDSN...') client = FDSN_Client(base_url=watc_url, user=watc_username, password=watc_password) print('Successfully connected. Reading data from WATC FDSN...') try: st_out = client.get_waveforms(network, station, location, channel, starttime, endtime + time_buffer, attach_response=True) except FDSNNoDataException: st_out = Stream() # Just create an empty Stream object # AVO Winston elif source == 'AVO': client = EW_Client('pubavo1.wr.usgs.gov', port=16023) # 16023 is long-term print('Reading data from AVO Winston...') st_out = Stream() # Make empty Stream object to populate # Brute-force "dynamic grid search" over network/station/channel/location codes for nw in _restricted_matching('network', network, client): for sta in _restricted_matching('station', station, client, network=nw): for cha in _restricted_matching('channel', channel, client, network=nw, station=sta): for loc in _restricted_matching('location', location, client, network=nw, station=sta, channel=cha): try: st_out += client.get_waveforms( nw, sta, loc, cha, starttime, endtime + time_buffer) except KeyError: pass else: raise ValueError('Unrecognized source. Valid options are \'IRIS\', ' '\'WATC\', or \'AVO\'.') # Merge, if specified if merge_fill_value is not False: st_out.merge(fill_value=merge_fill_value) # Merge Traces with same ID warnings.warn(f'Merging with "fill_value={merge_fill_value}"', CollectionWarning) st_out.sort() # Check that all requested stations are present in Stream requested_stations = station.split(',') downloaded_stations = [tr.stats.station for tr in st_out] failed_stations = [] for sta in requested_stations: # The below check works with wildcards, but obviously cannot detect if # ALL stations corresponding to a given wildcard (e.g., O??K) were # downloaded. Thus, if careful station selection is desired, specify # each station explicitly and the below check will then be effective. if not fnmatch.filter(downloaded_stations, sta): if not return_failed_stations: # If we're not returning the failed stations, then show this # warning message to alert the user warnings.warn( f'Station {sta} not downloaded from {source} ' 'server for this time period.', CollectionWarning) failed_stations.append(sta) # If the Stream is empty, then we can stop here if st_out.count() == 0: print('No data downloaded.') if return_failed_stations: return st_out, failed_stations else: return st_out # Otherwise, show what the Stream contains print(st_out.__str__(extended=True)) # This syntax prints the WHOLE Stream # Trim, if specified if trim_fill_value is not False: st_out.trim(starttime, endtime + time_buffer, pad=True, fill_value=trim_fill_value) warnings.warn(f'Trimming with "fill_value={trim_fill_value}"', CollectionWarning) print('Assigning coordinates...') # Use IRIS inventory info for AVO data source if source == 'AVO': client = FDSN_Client('IRIS') try: inv = client.get_stations(network=network, station=station, location=location, channel=channel, starttime=starttime, endtime=endtime + time_buffer, level='channel') except FDSNNoDataException: inv = Inventory() # Make an empty inv warnings.warn('Creating empty inventory.', CollectionWarning) for tr in st_out: try: coords = inv.get_coordinates(tr.id) tr.stats.longitude = coords['longitude'] tr.stats.latitude = coords['latitude'] tr.stats.elevation = coords['elevation'] except Exception as e: if str(e) == 'No matching channel metadata found.': warnings.warn(f'No metadata for {tr.id} found in inventory.', CollectionWarning) else: raise # Check if any Trace did NOT get coordinates assigned, and try to use JSON # coordinates if available for tr in st_out: try: tr.stats.longitude, tr.stats.latitude, tr.stats.elevation except AttributeError: try: tr.stats.latitude, tr.stats.longitude,\ tr.stats.elevation = AVO_COORDS[tr.id] warnings.warn(f'Using coordinates from JSON file for {tr.id}.', CollectionWarning) except KeyError: print(f'No coordinates available for {tr.id}. Stopping.') raise # Remove sensitivity if remove_response: print('Removing sensitivity...') for tr in st_out: try: # Just removing sensitivity for now. remove_response() can lead # to errors. This should be sufficient for now. Plus some # IRIS-AVO responses are wonky. tr.remove_sensitivity() except ValueError: # No response information found # This is only set up for infrasound calibration values try: calib = AVO_INFRA_CALIBS[tr.id] tr.data = tr.data * calib warnings.warn( 'Using calibration value from JSON file for ' f'{tr.id}.', CollectionWarning) except KeyError: print(f'No calibration value available for {tr.id}. ' 'Stopping.') raise print('Done') # Return the Stream with coordinates attached (and responses removed if # specified) if return_failed_stations: return st_out, failed_stations else: return st_out
def pro3pair(eq_num1, eq_num2, stat_corr=1, simple_taper=0, skip_SNR=0, dphase='PKIKP', dphase2='PKiKP', dphase3='PKIKP', dphase4='PKiKP', rel_time=1, start_buff=-200, end_buff=500, plot_scale_fac=0.05, qual_threshold=0, corr_threshold=0.5, freq_min=1, freq_max=3, min_dist=0, max_dist=180, auto_dist=True, alt_statics=0, statics_file='nothing', ARRAY=0, ref_loc=False, ref_rad=0.4, max_taper_length=5., no_plots=False, taper_frac=0.05): #%% Import functions from obspy import UTCDateTime from obspy import Stream from obspy import read from obspy.geodetics import gps2dist_azimuth import numpy as np import os from obspy.taup import TauPyModel import matplotlib.pyplot as plt import time model = TauPyModel(model='iasp91') import sys # don't show any warnings import warnings from termcolor import colored if not sys.warnoptions: warnings.simplefilter("ignore") print(colored('Running pro3a_sort_plot_pair', 'cyan')) start_time_wc = time.time() #%% Set some parameters verbose = 0 # more output # rel_time = 1 # timing is relative to a chosen phase, otherwise relative to OT # taper_frac = 0.05 # Fraction of window tapered on both ends signal_dur = 5. # signal length used in SNR calculation plot_tt = 1 # plot the traveltimes? do_decimate = 0 # 0 if no decimation desired # if ref_loc ==true, use ref_rad to filter station distance # if ref_loc ==false, use earthquake loc to filter station distance # ref_rad = 0.4 # ° radius (°) set by input or at top if ARRAY == 0: ref_lat = 36.3 # °N, around middle of Japan ref_lon = 138.5 # °E if ARRAY == 1: ref_lat = 46.7 # °N keep only inner rings A-D if radius is 0.4° ref_lon = -106.22 # °E if ARRAY == 2: ref_lat = 38 # °N ref_lon = 104.5 # °E if rel_time == 0: # SNR requirement not implemented for unaligned traces qual_threshold = 0 # Plot with reduced velocity? red_plot = 0 red_dist = 55 red_time = 300 red_slow = 7.2 # seconds per degree #%% Get saved event info, also used to name files # event 2016-05-28T09:47:00.000 -56.241 -26.935 78 print('Opening locations for events ' + str(eq_num1) + ' and ' + str(eq_num2)) fname1 = '/Users/vidale/Documents/Research/IC/EvLocs/event' + str( eq_num1) + '.txt' fname2 = '/Users/vidale/Documents/Research/IC/EvLocs/event' + str( eq_num2) + '.txt' file1 = open(fname1, 'r') file2 = open(fname2, 'r') lines1 = file1.readlines() lines2 = file2.readlines() split_line1 = lines1[0].split() split_line2 = lines2[0].split() # ids.append(split_line[0]) ignore label for now t1 = UTCDateTime(split_line1[1]) t2 = UTCDateTime(split_line2[1]) date_label1 = split_line1[1][0:10] date_label2 = split_line2[1][0:10] year1 = split_line1[1][0:4] year2 = split_line2[1][0:4] ev_lat1 = float(split_line1[2]) ev_lat2 = float(split_line2[2]) ev_lon1 = float(split_line1[3]) ev_lon2 = float(split_line2[3]) ev_depth1 = float(split_line1[4]) ev_depth2 = float(split_line2[4]) print('1st event: date_label ' + date_label1 + ' time ' + str(t1) + ' lat ' + str(ev_lat1) + ' lon ' + str(ev_lon1) + ' depth ' + str(ev_depth1)) print('2nd event: date_label ' + date_label2 + ' time ' + str(t2) + ' lat ' + str(ev_lat2) + ' lon ' + str(ev_lon2) + ' depth ' + str(ev_depth2)) #%% Get station location file if stat_corr == 1: # load static terms, only applies to Hinet and LASA if ARRAY == 0: if alt_statics == 0: # standard set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_statics_hinet.txt' else: # custom set made by this event for this event print('probably needs fixing') sta_file = ( '/Users/vidale/Documents/PyCode/Hinet/Array_codes/Files/' + 'HA' + date_label1[:10] + 'pro4_' + dphase + '.statics') elif ARRAY == 1: sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_statics_LASA.txt' elif ARRAY == 2: sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_statics_ch.txt' 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_dist = [] st_lats = [] st_lons = [] st_shift = [] st_corr = [] for ii in station_index: line = lines[ii] split_line = line.split() st_names.append(split_line[0]) st_dist.append(split_line[1]) st_lats.append(split_line[2]) st_lons.append(split_line[3]) st_shift.append(split_line[4]) st_corr.append(split_line[5]) else: # no static terms, always true for NORSAR if ARRAY == 0: # Hinet set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_hinet.txt' elif ARRAY == 1: # LASA set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_LASA.txt' elif ARRAY == 2: # LASA set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_ch.txt' else: # NORSAR set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_NORSAR.txt' 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]) 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() #%% Is taper too long compared to noise estimation window? totalt = end_buff - start_buff noise_time_skipped = taper_frac * totalt noise_time_skipped = min(noise_time_skipped, 10.0) # set max of 10s to taper length if simple_taper == 0: if noise_time_skipped >= 0.5 * (-start_buff): print( 'Specified taper of ' + str(taper_frac * totalt) + ' is not big enough compared to available noise estimation window ' + str(-start_buff - noise_time_skipped) + '. May not work well.') old_taper_frac = taper_frac taper_frac = -0.5 * start_buff / totalt print('Taper reset from ' + str(old_taper_frac * totalt) + ' to ' + str(taper_frac * totalt) + ' seconds.') #%% Load waveforms and decimate to 10 sps st1 = Stream() st2 = Stream() fname1 = '/Users/vidale/Documents/GitHub/LASA_data/HD' + date_label1 + '.mseed' fname2 = '/Users/vidale/Documents/GitHub/LASA_data/HD' + date_label2 + '.mseed' st1 = read(fname1) st2 = read(fname2) if do_decimate != 0: st1.decimate(do_decimate, no_filter=True) st2.decimate(do_decimate, no_filter=True) print( f'1st trace for event 1 has {len(st1[0].data)} time pts which is {len(st1[0].data)*st1[0].stats.delta:.1f} s' ) print( f'1st trace for event 2 has {len(st2[0].data)} time pts which is {len(st2[0].data)*st2[0].stats.delta:.1f} s' ) print('st1 has ' + str(len(st1)) + ' traces') print('st2 has ' + str(len(st2)) + ' traces') print('1st trace starts at ' + str(st1[0].stats.starttime) + ', event at ' + str(t1)) print('2nd trace starts at ' + str(st2[0].stats.starttime) + ', event at ' + str(t2)) #%% Select by distance, window and adjust start time to align picked times st_pickalign1 = Stream() st_pickalign2 = Stream() tra1_in_range = 0 tra1_sta_found = 0 nodata1 = 0 tra2_in_range = 0 tra2_sta_found = 0 nodata2 = 0 min_dist_auto = 180 max_dist_auto = 0 min_time_plot = 1000000 max_time_plot = -1000000 # not used in all cases, but printed out below # only used if rel_slow == 1, preserves 0 slowness, otherwise 0 is set to phase slowness ref_distance = gps2dist_azimuth(ref_lat, ref_lon, ev_lat1, ev_lon1) ref1_dist = ref_distance[0] / (1000 * 111) dist_minus = ref1_dist - 0.5 dist_plus = ref1_dist + 0.5 arrivals_ref = model.get_travel_times(source_depth_in_km=ev_depth1, distance_in_degree=ref1_dist, phase_list=[dphase]) arrivals_minus = model.get_travel_times(source_depth_in_km=ev_depth1, distance_in_degree=dist_minus, phase_list=[dphase]) arrivals_plus = model.get_travel_times(source_depth_in_km=ev_depth1, distance_in_degree=dist_plus, phase_list=[dphase]) atime_ref = arrivals_ref[ 0].time # phase arrival time at reference distance ref_slow = arrivals_plus[0].time - arrivals_minus[ 0].time # dt over 1 degree at ref distance for tr in st1: # find lat-lon from list, chop, statics, traces one by one if float( year1 ) < 1970: # fix the damn 1969 -> 2069 bug in Gibbon's LASA data temp_t = str(tr.stats.starttime) temp_tt = '19' + temp_t[2:] tr.stats.starttime = UTCDateTime(temp_tt) if tr.stats.station in st_names: # find station in station list ii = st_names.index(tr.stats.station) tra1_sta_found += 1 if stat_corr != 1 or float( st_corr[ii] ) > corr_threshold: # if using statics, reject low correlations stalat = float( st_lats[ii]) # look up lat & lon again to find distance stalon = float(st_lons[ii]) distance = gps2dist_azimuth( stalat, stalon, ev_lat1, ev_lon1) # Get traveltimes again, hard to store tr.stats.distance = distance[0] # distance in km dist = distance[0] / (1000 * 111) in_range = 0 # flag for whether this trace goes into stack if ref_loc == False: # check whether trace is in distance range from earthquake if min_dist < dist and dist < max_dist: in_range = 1 tra1_in_range += 1 elif ref_loc == True: # alternately, check whether trace is close enough to ref_location ref_distance = gps2dist_azimuth(ref_lat, ref_lon, stalat, stalon) ref2_dist = ref_distance[0] / (1000 * 111) if ref2_dist < ref_rad: in_range = 1 tra1_in_range += 1 if in_range == 1: # trace fulfills the specified criteria for being in range s_t = t1 + start_buff e_t = t1 + end_buff if stat_corr == 1: # apply static station corrections tr.stats.starttime -= float(st_shift[ii]) if rel_time == 0: # don't adjust absolute time tr.trim(starttime=s_t, endtime=e_t) else: # shift relative to a chosen phase arrivals_each = model.get_travel_times( source_depth_in_km=ev_depth1, distance_in_degree=dist, phase_list=[dphase]) atime_each = arrivals_each[0].time if rel_time == 1: # each window has a shift proportional to ref_dist at phase slowness at ref_dist s_t += atime_each e_t += atime_each tr.trim(starttime=s_t, endtime=e_t) tr.stats.starttime -= atime_each - ( dist - ref1_dist) * ref_slow elif rel_time == 2: # each window has a distinct shift, but offset is common to all stations s_t += atime_each e_t += atime_each tr.trim(starttime=s_t, endtime=e_t) tr.stats.starttime -= atime_ref elif rel_time == 3: # each station has an individual, chosen-phase shift, phase arrival set to zero s_t += atime_each e_t += atime_each tr.trim(starttime=s_t, endtime=e_t) tr.stats.starttime -= atime_each elif rel_time == 4: # use same window around chosen phase for all stations, phase arrival set to zero s_t += atime_ref e_t += atime_ref tr.trim(starttime=s_t, endtime=e_t) tr.stats.starttime -= atime_ref else: print('invalid rel_time, must be integer 0 to 4') sys.exit() if len(tr.data) > 0: st_pickalign1 += tr else: nodata1 += 1 else: print(tr.stats.station + ' not found in station list ') # sys.exit() for tr in st2: # find lat-lon from list, chop, statics, traces one by one if float( year2 ) < 1970: # fix the damn 1969 -> 2069 bug in Gibbon's LASA data temp_t = str(tr.stats.starttime) temp_tt = '19' + temp_t[2:] tr.stats.starttime = UTCDateTime(temp_tt) if tr.stats.station in st_names: # find station in station list ii = st_names.index(tr.stats.station) tra2_sta_found += 1 if stat_corr != 1 or float( st_corr[ii] ) > corr_threshold: # if using statics, reject low correlations stalat = float( st_lats[ii]) # look up lat & lon again to find distance stalon = float(st_lons[ii]) distance = gps2dist_azimuth( stalat, stalon, ev_lat2, ev_lon2) # Get traveltimes again, hard to store tr.stats.distance = distance[0] # distance in km dist = distance[0] / (1000 * 111) in_range = 0 # flag for whether this trace goes into stack if ref_loc == False: # check whether trace is in distance range from earthquake if min_dist < dist and dist < max_dist: in_range = 1 tra2_in_range += 1 elif ref_loc == True: # alternately, check whether trace is close enough to ref_location ref_distance = gps2dist_azimuth(ref_lat, ref_lon, stalat, stalon) ref2_dist = ref_distance[0] / (1000 * 111) if ref2_dist < ref_rad: in_range = 1 tra2_in_range += 1 if in_range == 1: # trace fulfills the specified criteria for being in range s_t = t2 + start_buff e_t = t2 + end_buff if stat_corr == 1: # apply static station corrections tr.stats.starttime -= float(st_shift[ii]) if rel_time == 0: # don't adjust absolute time tr.trim(starttime=s_t, endtime=e_t) else: # shift relative to a chosen phase arrivals_each = model.get_travel_times( source_depth_in_km=ev_depth2, distance_in_degree=dist, phase_list=[dphase]) atime_each = arrivals_each[0].time if rel_time == 1: # each window has a shift proportional to ref_dist at phase slowness at ref_dist s_t += atime_each e_t += atime_each tr.trim(starttime=s_t, endtime=e_t) tr.stats.starttime -= atime_each - ( dist - ref1_dist) * ref_slow elif rel_time == 2: # each window has a distinct shift, but offset is common to all stations s_t += atime_each e_t += atime_each tr.trim(starttime=s_t, endtime=e_t) tr.stats.starttime -= atime_ref elif rel_time == 3: # each station has an individual, chosen-phase shift, phase arrival set to zero s_t += atime_each e_t += atime_each tr.trim(starttime=s_t, endtime=e_t) tr.stats.starttime -= atime_each elif rel_time == 4: # use same window around chosen phase for all stations, phase arrival set to zero s_t += atime_ref e_t += atime_ref tr.trim(starttime=s_t, endtime=e_t) tr.stats.starttime -= atime_ref else: print('invalid rel_time, must be integer 0 to 4') sys.exit() if len(tr.data) > 0: st_pickalign2 += tr else: nodata2 += 1 else: print(tr.stats.station + ' not found in station list') print('After alignment + range and correlation selection') print('1st event, Traces found: ' + str(tra1_sta_found) + ' Traces in range: ' + str(tra1_in_range) + ' Traces with no data: ' + str(nodata1)) print('2nd event, Traces found: ' + str(tra2_sta_found) + ' Traces in range: ' + str(tra2_in_range) + ' Traces with no data: ' + str(nodata2)) print( f'ref1_distance {ref1_dist:.3f} relative start time {atime_ref:.3f}' ) if ref_loc == True: print( f'ref2_distance {ref2_dist:.3f} relative start time {atime_ref:.3f}' ) print('ref_loc == True, ref_lat: ' + str(ref_lat) + ' ref_lon: ' + str(ref_lon)) print( f'last station: distance {dist:.3f} last station lat: {stalat:.3f} last station lon: {stalon:.3f}' ) #%% #print(st) # at length if verbose: print(st1.__str__(extended=True)) print(st2.__str__(extended=True)) if rel_time == 1: print(st_pickalign1.__str__(extended=True)) print(st_pickalign2.__str__(extended=True)) #%% Detrend, taper, filter print('Taper fraction is ' + str(taper_frac) + ' bandpass is ' + str(freq_min) + ' to ' + str(freq_max)) st_pickalign1.detrend(type='simple') st_pickalign2.detrend(type='simple') st_pickalign1.taper(taper_frac, max_length=max_taper_length) st_pickalign2.taper(taper_frac, max_length=max_taper_length) st_pickalign1.filter('bandpass', freqmin=freq_min, freqmax=freq_max, corners=4, zerophase=True) st_pickalign2.filter('bandpass', freqmin=freq_min, freqmax=freq_max, corners=4, zerophase=True) st_pickalign1.taper(taper_frac, max_length=max_taper_length) st_pickalign2.taper(taper_frac, max_length=max_taper_length) #%% Cull further by imposing SNR threshold on both traces st1good = Stream() st2good = Stream() for tr1 in st_pickalign1: for tr2 in st_pickalign2: if ((tr1.stats.network == tr2.stats.network) & (tr1.stats.station == tr2.stats.station)): if skip_SNR == 1: st1good += tr1 st2good += tr2 else: # estimate median noise t_noise1_start = int(len(tr1.data) * taper_frac) t_noise2_start = int(len(tr2.data) * taper_frac) t_noise1_end = int( len(tr1.data) * (-start_buff) / (end_buff - start_buff)) t_noise2_end = int( len(tr2.data) * (-start_buff) / (end_buff - start_buff)) noise1 = np.median( abs(tr1.data[t_noise1_start:t_noise1_end])) noise2 = np.median( abs(tr2.data[t_noise2_start:t_noise2_end])) # estimate median signal t_signal1_start = int( len(tr1.data) * (-start_buff) / (end_buff - start_buff)) t_signal2_start = int( len(tr2.data) * (-start_buff) / (end_buff - start_buff)) t_signal1_end = t_signal1_start + int( len(tr1.data) * signal_dur / (end_buff - start_buff)) t_signal2_end = t_signal2_start + int( len(tr2.data) * signal_dur / (end_buff - start_buff)) signal1 = np.median( abs(tr1.data[t_signal1_start:t_signal1_end])) signal2 = np.median( abs(tr2.data[t_signal2_start:t_signal2_end])) # test SNR SNR1 = signal1 / noise1 SNR2 = signal2 / noise2 if (SNR1 > qual_threshold and SNR2 > qual_threshold): st1good += tr1 st2good += tr2 if skip_SNR == 1: print('Matches (no SNR test): ' + str(len(st1good)) + ' traces') else: print('Match and above SNR threshold: ' + str(len(st1good)) + ' traces') #%% get station lat-lon, compute distance for plot min_dist_auto = 180 max_dist_auto = 0 min_time_plot = 1000000 max_time_plot = -1000000 for tr in st1good: if tr.stats.station in st_names: # find station in station list ii = st_names.index(tr.stats.station) stalon = float( st_lons[ii]) # look up lat & lon again to find distance stalat = float(st_lats[ii]) distance = gps2dist_azimuth(stalat, stalon, ev_lat1, ev_lon1) tr.stats.distance = distance[0] / (1000 * 111) # distance in km if tr.stats.distance < min_dist_auto: min_dist_auto = tr.stats.distance if tr.stats.distance > max_dist_auto: max_dist_auto = tr.stats.distance if tr.stats.starttime - t1 < min_time_plot: min_time_plot = tr.stats.starttime - t1 if ((tr.stats.starttime - t1) + ((len(tr.data) - 1) * tr.stats.delta)) > max_time_plot: max_time_plot = ((tr.stats.starttime - t1) + ((len(tr.data) - 1) * tr.stats.delta)) for tr in st2good: if tr.stats.station in st_names: # find station in station list ii = st_names.index(tr.stats.station) stalon = float( st_lons[ii]) # look up lat & lon again to find distance stalat = float(st_lats[ii]) distance = gps2dist_azimuth(stalat, stalon, ev_lat2, ev_lon2) tr.stats.distance = distance[0] / (1000 * 111) # distance in km print( f'Min distance is {min_dist_auto:.3f} Max distance is {max_dist_auto:.3f}' ) print( f'Min time is {min_time_plot:.2f} Max time is {max_time_plot:.2f}') if min_time_plot > start_buff: print(f'Min time {min_time_plot:.2f} > start_buff {start_buff:.2f}') print( colored('Write zero-filling into pro3 for this code to work', 'red')) sys.exit(-1) if max_time_plot < end_buff: print(f'Max time {max_time_plot:.2f} < end_buff {end_buff:.2f}') print( colored('Write zero-filling into pro3 for this code to work', 'red')) sys.exit(-1) #%% # plot traces fig_index = 3 plt.close(fig_index) plt.figure(fig_index, figsize=(8, 8)) plt.xlim(start_buff, end_buff) if auto_dist == True: dist_diff = max_dist_auto - min_dist_auto # add space at extremes plt.ylim(min_dist_auto - 0.1 * dist_diff, max_dist_auto + 0.1 * dist_diff) else: plt.ylim(min_dist, max_dist) for tr in st1good: dist_offset = tr.stats.distance # trying for approx degrees ttt = np.arange(len(tr.data)) * tr.stats.delta + (tr.stats.starttime - t1) if red_plot == 1: shift = red_time + (dist_offset - red_dist) * red_slow ttt = ttt - shift plt.plot(ttt, (tr.data - np.median(tr.data)) * plot_scale_fac / (tr.data.max() - tr.data.min()) + dist_offset, color='green') for tr in st2good: dist_offset = tr.stats.distance # trying for approx degrees ttt = np.arange(len(tr.data)) * tr.stats.delta + (tr.stats.starttime - t2) if red_plot == 1: shift = red_time + (dist_offset - red_dist) * red_slow ttt = ttt - shift ttt = ttt plt.plot(ttt, (tr.data - np.median(tr.data)) * plot_scale_fac / (tr.data.max() - tr.data.min()) + dist_offset, color='red') print('And made it to here.') #%% Plot traveltime curves if rel_time != 1: if plot_tt: # first traveltime curve line_pts = 50 dist_vec = np.arange(min_dist_auto, max_dist_auto, (max_dist_auto - min_dist_auto) / line_pts) # distance grid time_vec1 = np.arange( min_dist_auto, max_dist_auto, (max_dist_auto - min_dist_auto) / line_pts) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times( source_depth_in_km=ev_depth1, distance_in_degree=dist_vec[i], phase_list=[dphase]) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase: time_vec1[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec1[i] = np.nan # second traveltime curve if dphase2 != 'no': time_vec2 = np.arange( min_dist_auto, max_dist_auto, (max_dist_auto - min_dist_auto) / line_pts ) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times( source_depth_in_km=ev_depth1, distance_in_degree=dist_vec[i], phase_list=[dphase2]) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase2: time_vec2[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec2[i] = np.nan if rel_time == 3 or rel_time == 4: time_vec2 = time_vec2 - time_vec1 elif rel_time == 2: time_vec2 = time_vec2 - atime_ref plt.plot(time_vec2, dist_vec, color='orange') # third traveltime curve if dphase3 != 'no': time_vec3 = np.arange( min_dist_auto, max_dist_auto, (max_dist_auto - min_dist_auto) / line_pts ) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times( source_depth_in_km=ev_depth1, distance_in_degree=dist_vec[i], phase_list=[dphase3]) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase3: time_vec3[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec3[i] = np.nan if rel_time == 3 or rel_time == 4: time_vec2 = time_vec2 - time_vec1 elif rel_time == 2: time_vec2 = time_vec2 - atime_ref plt.plot(time_vec3, dist_vec, color='yellow') # fourth traveltime curve # if dphase4 != 'no': # time_vec4 = np.arange(min_dist, max_dist_auto, (max_dist_auto - min_dist)/line_pts) # empty time grid of same length (filled with -1000) # for i in range(0,line_pts): # arrivals = model.get_travel_times(source_depth_in_km=ev_depth1,distance_in_degree # =dist_vec[i],phase_list=[dphase4]) # num_arrivals = len(arrivals) # found_it = 0 # for j in range(0,num_arrivals): # if arrivals[j].name == dphase4: # time_vec4[i] = arrivals[j].time # found_it = 1 # if found_it == 0: # time_vec4[i] = np.nan # if rel_time == 3 or rel_time == 4: # time_vec2 = time_vec2 - time_vec1 # elif rel_time == 2: # time_vec2 = time_vec2 - atime_ref # plt.plot(time_vec4,dist_vec, color = 'purple') # if rel_time == 3 or rel_time == 4: # time_vec1 = time_vec1 - time_vec1 # elif rel_time == 2: # time_vec1 = time_vec1 - atime_ref # plt.plot(time_vec1,dist_vec, color = 'blue') # if no_plots == False: # plt.show() plt.xlabel('Time (s)') plt.ylabel('Epicentral distance from event (°)') plt.title(dphase + ' for ' + fname1[43:53] + ' vs ' + fname2[43:53]) if no_plots == False: plt.show() #%% Save processed files fname1 = '/Users/vidale/Documents/Research/IC/Pro_Files/HD' + date_label1 + 'sel.mseed' fname2 = '/Users/vidale/Documents/Research/IC/Pro_Files/HD' + date_label2 + 'sel.mseed' st1good.write(fname1, format='MSEED') st2good.write(fname2, format='MSEED') elapsed_time_wc = time.time() - start_time_wc print(f'This job took {elapsed_time_wc:.1f} seconds') os.system('say "Done"')
ev_time = init_time + det_times[kk] start_time = ev_time - wtime_before end_time = ev_time + wtime_after print ev_time, start_time, end_time if (diff_times[kk] > wtime_after): # special case: unusually long delay between start and end times end_time = ev_time + diff_times[kk] + wtime_after jday_start = start_time.julday jday_end = end_time.julday if (jday_start != jday_end): print "Warning: start and end day not equal", kk, jday_start, jday_end stalist = [] st =Stream() if int(peaksum[kk])<150:continue for s, sta in zip(ss[:,kk], stations): if np.isnan(s):continue else:st += read(ts_dir+'%03d/*%s*.mseed'%(jday_start, sta), format='MSEED') #st = read(ts_dir+'%03d/*.mseed'%jday_start, format='MSEED') print len(st) print st.__str__(extended=True) st_slice = st.slice(start_time, end_time) out_file = 'event_rank'+format(kk,'05d')+'_nsta'+str(int(num_sta[kk]))\ +'_peaksum'+str(int(peaksum[kk]))+'_ind'+str(int(det_start_ind[kk]))+'_time'\ +str(det_times[kk])+'_'+ev_time.strftime('%Y-%m-%dT%H:%M:%S.%f') st_slice.write(os.path.join(output_eq_dir, out_file+'.mseed'), format='MSEED' ) if plot: st_slice.plot(equal_scale=False, size=(out_width,out_height), outfile=out_file)
def pro3singlet(eq_file, stat_corr=0, rel_time=1, rel_slow=1, simple_taper=0, skip_SNR=0, dphase='PKiKP', dphase2='PKKP', dphase3='PKIKP', dphase4='PPP', start_buff=-10, end_buff=30, plot_scale_fac=0.05, qual_threshold=0, corr_threshold=0, freq_min=0.25, freq_max=1, do_filt=1, min_dist=0, max_dist=180, auto_dist=0, do_decimate=0, alt_statics=0, statics_file='nothing', ARRAY=0, ref_loc=0, ref_rad=0.4, verbose=0, fig_index=101, event_no=0): # 0 is Hinet, 1 is LASA, 2 is NORSAR #%% Import functions from obspy import UTCDateTime from obspy import Stream from obspy import read from obspy.geodetics import gps2dist_azimuth import numpy as np import os from obspy.taup import TauPyModel import matplotlib.pyplot as plt import time model = TauPyModel(model='iasp91') # import sys # don't show any warnings # import warnings # # if not sys.warnoptions: # warnings.simplefilter("ignore") print('Running pro3b_sort_plot_singlet') start_time_wc = time.time() #%% Get saved event info, also used to name files # input event data with 1-line file of format # event 2016-05-28T09:47:00.000 -56.241 -26.935 78 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] year = split_line[1][0:4] ev_lat = float(split_line[2]) ev_lon = float(split_line[3]) ev_depth = float(split_line[4]) print('date_label ' + date_label + ' time ' + str(t) + ' lat ' + str(ev_lat) + ' lon ' + str(ev_lon) + ' depth ' + str(ev_depth)) #%% Get station location file if stat_corr == 1: # load static terms, only applies to Hinet and LASA if ARRAY == 0: if alt_statics == 0: # standard set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/hinet_sta_statics.txt' else: # custom set made by this event for this event sta_file = ( '/Users/vidale/Documents/PyCode/Hinet/Array_codes/Files/' + 'HA' + date_label[:10] + 'pro4_' + dphase + '.statics') elif ARRAY == 1: sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/L_sta_statics.txt' 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_dist = [] st_lats = [] st_lons = [] st_shift = [] st_corr = [] for ii in station_index: line = lines[ii] split_line = line.split() st_names.append(split_line[0]) st_dist.append(split_line[1]) st_lats.append(split_line[2]) st_lons.append(split_line[3]) st_shift.append(split_line[4]) st_corr.append(split_line[5]) else: # no static terms, always true for NORSAR if ARRAY == 0: # Hinet set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/hinet_sta.txt' elif ARRAY == 1: # LASA set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/LASA_sta.txt' else: # NORSAR set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/NORSAR_sta.txt' 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]) #%% Set some parameters # fig_index = 101 # stat_corr = 1 # apply station static corrections # rel_time = 1 # timing is relative to a chosen phase, otherwise relative to OT # dphase = 'PKIKP' # phase to be aligned # dphase2 = 'PKiKP' # another phase to have traveltime plotted # dphase3 = 'PKP' # another phase to have traveltime plotted # dphase4 = 'pP' # another phase to have traveltime plotted taper_frac = .05 #Fraction of window tapered on both ends signal_dur = 10. # signal length used in SNR calculation # plot_scale_fac = 0.5 # Bigger numbers make each trace amplitude bigger on plot # qual_threshold = 2 # minimum SNR # corr_threshold = 0.7 # minimum correlation in measuring shift to use station plot_tt = 1 # plot the traveltimes? # ref_loc = 0 # 1 if selecting stations within ref_rad of ref_lat and ref_lon # 0 if selecting stations by distance from earthquake if ARRAY == 0: ref_lat = 36.3 # °N, around middle of Japan ref_lon = 138.5 # °E ref_rad = 1.5 # ° radius (°) elif ARRAY == 1: ref_lat = 46.7 # °N keep only inner rings A-D ref_lon = -106.22 # °E # ref_rad = 0.4 # ° radius (°) set by input or at top #%% Is taper too long compared to noise estimation window? totalt = end_buff - start_buff noise_time_skipped = taper_frac * totalt if simple_taper == 0: if noise_time_skipped >= -0.5 * start_buff: print( 'Specified taper of ' + str(taper_frac * totalt) + ' is not big enough compared to available noise estimation window ' + str(-start_buff - noise_time_skipped) + '. May not work well.') old_taper_frac = taper_frac taper_frac = -0.5 * start_buff / totalt if start_buff > 0: taper_frac = 0.05 # pick random minimal window if there is no leader print('Taper reset from ' + str(old_taper_frac * totalt) + ' to ' + str(taper_frac * totalt) + ' seconds.') if rel_time == 0: # SNR requirement not implemented for unaligned traces qual_threshold = 0 # Plot with reduced velocity? red_plot = 0 red_dist = 55 red_time = 300 red_slow = 7.2 # seconds per degree #%% In case one wants to manually enter data here # date_label = '2018-04-02' # date for filename # ev_lon = -63.006 # ev_lat = -20.659 # ev_depth = 559 # t = UTCDateTime('2018-04-02T13:40:34.840') #%% Load waveforms and decimate to 10 sps st = Stream() if ARRAY == 0: fname = 'HD' + date_label + '.mseed' elif ARRAY == 1: fname = 'Mseed/HD' + date_label + '.mseed' st = read(fname) if do_decimate != 0: st.decimate(do_decimate) print('Read in: ' + str(len(st)) + ' traces') print('First trace has : ' + str(len(st[0].data)) + ' time pts ') print('Start time : ' + str(st[0].stats.starttime) + ' event time : ' + str(t)) print('After decimation: ' + 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)) # print(f'Sta lat-lon {stalat:.4f} {stalon:.4f}') #%% Select by distance, window and adjust start time to align picked times st_pickalign = Stream() tra_located = 0 tra_in_range = 0 tra_sta_found = 0 if auto_dist != 0: # set plot limit automatically min_dist_auto = 180 max_dist_auto = 0 # for ii in station_index: # print('Station name of index ' + str(ii) + ' is ' + str(st_names[ii])) # enumerate stations # for tr in st: # traces one by one, find lat-lon by searching entire inventory. Inefficient # print('Station name of tr ' + str(tr.stats.station)) # enumerate stations for tr in st: # traces one by one, find lat-lon by searching entire inventory. Inefficient if float( year ) < 1970: # fix the damn 1969 -> 2069 bug in Gibbon's LASA data temp_t = str(tr.stats.starttime) temp_tt = '19' + temp_t[2:] tr.stats.starttime = UTCDateTime(temp_tt) 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 tra_sta_found += 1 if stat_corr != 1 or float( st_corr[ii] ) > corr_threshold: # if using statics, reject low correlations stalat = float(st_lats[ii]) stalon = float( st_lons[ii] ) # look up lat & lon again to find distance # only used if ref_slow == 1: ref_distance = gps2dist_azimuth(ref_lat, ref_lon, ev_lat, ev_lon) ref1_dist = ref_distance[0] / (1000 * 111) distance = gps2dist_azimuth( stalat, stalon, ev_lat, ev_lon) # Get traveltimes again, hard to store tr.stats.distance = distance[0] # distance in km dist = distance[0] / (1000 * 111) if ref_loc != 1: tra_located += 1 if min_dist < dist and dist < max_dist: # select distance range from earthquake tra_in_range += 1 try: if rel_slow == 0: arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist, phase_list=[dphase]) else: arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=ref1_dist, phase_list=[dphase]) atime = arrivals[0].time if stat_corr == 1: # apply static station corrections tr.stats.starttime -= float(st_shift[ii]) if rel_time == 1: s_t = t + atime + start_buff e_t = t + atime + end_buff else: s_t = t + start_buff e_t = t + end_buff tr.trim(starttime=s_t, endtime=e_t) # deduct theoretical traveltime and start_buf from starttime if rel_time == 1: tr.stats.starttime -= atime st_pickalign += tr except: pass elif ref_loc == 1: # only used if ref_loc == 1: ref_distance = gps2dist_azimuth( ref_lat, ref_lon, stalat, stalon) ref2_dist = ref_distance[0] / (1000 * 111) # print('ref_rad ' + str(ref_rad) + ' ref2_dist ' + str(ref2_dist)) if ref2_dist < ref_rad: # alternatively, select based on distance from ref location try: if rel_slow == 0: arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist, phase_list=[dphase]) else: arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=ref1_dist, phase_list=[dphase]) atime = arrivals[0].time if stat_corr == 1: # apply static station corrections tr.stats.starttime -= float(st_shift[ii]) if rel_time == 1: s_t = t + atime + start_buff e_t = t + atime + end_buff else: s_t = t + start_buff e_t = t + end_buff tr.trim(starttime=s_t, endtime=e_t) # deduct theoretical traveltime and start_buf from starttime if rel_time == 1: tr.stats.starttime -= atime st_pickalign += tr except: pass print('After alignment + range and correlation selection - event: ' + str(len(st_pickalign)) + ' traces') print('Traces found: ' + str(tra_sta_found) + ' traces with range examined: ' + str(tra_located) + ' traces in range: ' + str(tra_in_range)) print('distance: ' + str(dist) + ' ref1_distance: ' + str(ref1_dist) + ' atime: ' + str(atime)) print('ref_lat: ' + str(ref_lat) + ' ref_lon: ' + str(ref_lon)) print('ref_lat: ' + str(stalat) + ' ref_lon: ' + str(stalon)) #print(st) # at length if verbose: print(st.__str__(extended=True)) if rel_time == 1: print(st_pickalign.__str__(extended=True)) #%% Detrend, taper, filter st_pickalign.detrend(type='simple') print('taper_frac is ' + str(taper_frac)) st_pickalign.taper(taper_frac) if do_filt == 1: st_pickalign.filter('bandpass', freqmin=freq_min, freqmax=freq_max, corners=4, zerophase=True) st_pickalign.taper(taper_frac) #%% Cull further by imposing SNR threshold on both traces if skip_SNR == 1: stgood = st_pickalign.copy() else: stgood = Stream() for tr in st_pickalign: # estimate median noise t_noise_start = int(len(tr.data) * taper_frac) t_noise_end = int( len(tr.data) * start_buff / (start_buff - end_buff)) noise = np.median(abs(tr.data[t_noise_start:t_noise_end])) # estimate median signal t_signal_start = int( len(tr.data) * start_buff / (start_buff - end_buff)) t_signal_end = t_signal_start + int( len(tr.data) * signal_dur / (end_buff - start_buff)) signal = np.median(abs(tr.data[t_signal_start:t_signal_end])) # test SNR SNR = signal / noise if (SNR > qual_threshold): stgood += tr print('Above SNR threshold: ' + str(len(stgood)) + ' traces') if verbose: for tr in stgood: print('Distance is ' + str(tr.stats.distance / (1000 * 111)) + ' for station ' + tr.stats.station) #%% get station lat-lon, compute distance for plot for tr in stgood: for ii in station_index: if (tr.stats.station == st_names[ii]): # find station in inventory stalon = float( st_lons[ii]) # look up lat & lon again to find distance stalat = float(st_lats[ii]) distance = gps2dist_azimuth(stalat, stalon, ev_lat, ev_lon) tr.stats.distance = distance[0] / (1000 * 111 ) # distance in degrees if auto_dist != 0: if tr.stats.distance < min_dist_auto: min_dist_auto = tr.stats.distance if tr.stats.distance > max_dist_auto: max_dist_auto = tr.stats.distance #%% This section causes a crash in Spyder # plot traces plt.close(fig_index) plt.figure(fig_index, figsize=(10, 10)) plt.xlim(start_buff, end_buff) if auto_dist == 1: dist_diff = max_dist_auto - min_dist_auto # add space at extremes plt.ylim(min_dist_auto - 0.1 * dist_diff, max_dist_auto + 0.1 * dist_diff) else: plt.ylim(min_dist, max_dist) for tr in stgood: dist_offset = tr.stats.distance ttt = np.arange(len(tr.data)) * tr.stats.delta + (tr.stats.starttime - t) if red_plot == 1: shift = red_time + (dist_offset - red_dist) * red_slow ttt = ttt - shift plt.plot(ttt, (tr.data - np.median(tr.data)) * plot_scale_fac / (tr.data.max() - tr.data.min()) + dist_offset, color='black') #%% Plot traveltime curves if plot_tt: # first traveltime curve line_pts = 50 dist_vec = np.arange(min_dist, max_dist, (max_dist - min_dist) / line_pts) # distance grid time_vec1 = np.arange( min_dist, max_dist, (max_dist - min_dist) / line_pts) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times(source_depth_in_km=ev_depth, distance_in_degree=dist_vec[i], phase_list=[dphase]) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase: time_vec1[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec1[i] = np.nan # second traveltime curve if dphase2 != 'no': time_vec2 = np.arange( min_dist, max_dist, (max_dist - min_dist) / line_pts) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist_vec[i], phase_list=[dphase2]) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase2: time_vec2[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec2[i] = np.nan if rel_time == 1: time_vec2 = time_vec2 - time_vec1 plt.plot(time_vec2, dist_vec, color='orange') # third traveltime curve if dphase3 != 'no': time_vec3 = np.arange( min_dist, max_dist, (max_dist - min_dist) / line_pts) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist_vec[i], phase_list=[dphase3]) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase3: time_vec3[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec3[i] = np.nan if rel_time == 1: time_vec3 = time_vec3 - time_vec1 plt.plot(time_vec3, dist_vec, color='yellow') # fourth traveltime curve if dphase4 != 'no': time_vec4 = np.arange( min_dist, max_dist, (max_dist - min_dist) / line_pts) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist_vec[i], phase_list=[dphase4]) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase4: time_vec4[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec4[i] = np.nan if rel_time == 1: time_vec4 = time_vec4 - time_vec1 plt.plot(time_vec4, dist_vec, color='purple') if rel_time == 1: time_vec1 = time_vec1 - time_vec1 plt.plot(time_vec1, dist_vec, color='blue') plt.show() plt.xlabel('Time (s)') plt.ylabel('Epicentral distance from event (°)') if ARRAY == 0: plt.title(dphase + ' for ' + fname) elif ARRAY == 1: plt.title(dphase + ' for ' + fname[8:18] + ' event # ' + str(event_no)) os.chdir('/Users/vidale/Documents/PyCode/LASA/Quake_results/Plots') # plt.savefig(date_label + '_' + str(event_no) + '_raw.png') plt.show() #%% Save processed files if ARRAY == 0: fname3 = '/Users/vidale/Documents/PyCode/LASA/HD' + date_label + 'sel.mseed' elif ARRAY == 1: fname3 = '/Users/vidale/Documents/PyCode/LASA/Pro_Files/HD' + date_label + 'sel.mseed' stgood.write(fname3, format='MSEED') elapsed_time_wc = time.time() - start_time_wc print('This job took ' + str(elapsed_time_wc) + ' seconds') os.system('say "Done"')
"latitude"] = station.latitude st[y].stats["coordinates"][ "longitude"] = station.longitude distance = locations2degrees(EQLAT, EQLON, station.latitude, station.longitude) st[y].stats["distance"] = distance # sort the traces by distance and subsample them, leaving at least SEPARATION between them and copying the desired traces into st2 st.sort(keys=['distance']) # sort the traces by distance from the epicentre st2 = Stream() # set up a blank stream object for data last_distance = -10 # variable to record the last distance copied to the stream for the plot for trace in st: if last_distance + SEPARATION <= trace.stats.distance: # only copy traces that are >= SEPARATION from the previous copied trace st2 += trace.copy() last_distance = trace.stats.distance print(st2.__str__(extended=True)) # filter st2.filter("bandpass", freqmin=F1, freqmax=F2, corners=2, zerophase=True) # Create the section plot fig = plt.figure(figsize=(16, 12), dpi=80) plt.title('Section plot for ' + EQNAME + " " + str(START_TIME.date) + " " + str(START_TIME.time) + " lat:" + str(EQLAT) + " lon:" + str(EQLON), fontsize=12, y=1.07) # plot the data st2.plot(size=(960, 720), type='section', recordlength=DURATION, linewidth=1.5, grid_linewidth=.5, show=False,
def pro3singlet(eq_num, stat_corr=1, rel_time=1, max_taper_length=5., simple_taper=0, skip_SNR=0, dphase='P', dphase2='', dphase3='', dphase4='', start_buff=-10, end_buff=10, start_beam=0, end_beam=0, plot_scale_fac=0.2, qual_threshold=0, corr_threshold=0, freq_min=0.25, freq_max=1, do_filt=1, min_dist=0, max_dist=180, auto_dist=True, do_decimate=0, alt_statics=0, statics_file='nothing', ARRAY=0, JST=0, ref_loc=0, ref_rad=0.4, verbose=0, fig_index=101, event_no=0): # 0 is Hinet, 1 is LASA, 2 is NORSAR #%% Import functions from obspy import UTCDateTime from obspy import Stream from obspy import read from obspy.geodetics import gps2dist_azimuth import numpy as np import os import sys from obspy.taup import TauPyModel import matplotlib.pyplot as plt import time from termcolor import colored model = TauPyModel(model='iasp91') # import sys # don't show any warnings # import warnings # # if not sys.warnoptions: # warnings.simplefilter("ignore") print(colored('Running pro3b_sort_plot_singlet', 'cyan')) start_time_wc = time.time() #%% Get saved event info, also used to name files # input event data with 1-line file of format # event 2016-05-28T09:47:00.000 -56.241 -26.935 78 fname = '/Users/vidale/Documents/Research/IC/EvLocs/event' + str( eq_num) + '.txt' print('Opening ' + fname) 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] year_label = split_line[1][0:4] year_short_label = split_line[1][2:4] month_label = split_line[1][5:7] day_label = split_line[1][8:10] hour_label = split_line[1][11:13] minute_label = split_line[1][14:16] print(date_label + ' year_label ' + year_label + ' hour_label ' + hour_label + ' min_label ' + minute_label) ev_lat = float(split_line[2]) ev_lon = float(split_line[3]) ev_depth = float(split_line[4]) print(' date_label ' + date_label + ' time ' + str(t) + ' lat ' + str(ev_lat) + ' lon ' + str(ev_lon) + ' depth ' + str(ev_depth)) #%% Get station location file if stat_corr == 1: # load static terms, only applies to Hinet, LASA, and China if ARRAY == 0: if alt_statics == 0: # standard set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_statics_hinet.txt' else: # custom set made by this event for this event sta_file = ( '/Users/vidale/Documents/PyCode/Hinet/Array_codes/Files/' + 'HA' + date_label[:10] + 'pro4_' + dphase + '.statics') elif ARRAY == 1: sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_statics_LASA.txt' elif ARRAY == 2: sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_statics_ch.txt' with open(sta_file, 'r') as file: lines = file.readlines() print(' ' + str(len(lines)) + ' coarse station statics read from ' + sta_file) # Load station coords into arrays station_index = range(len(lines)) st_names = [] st_dist = [] st_lats = [] st_lons = [] st_shift = [] st_corr = [] for ii in station_index: line = lines[ii] split_line = line.split() st_names.append(split_line[0]) if ARRAY == 0 or ARRAY == 1: st_dist.append(split_line[1]) st_lats.append(split_line[2]) st_lons.append(split_line[3]) st_shift.append(split_line[4]) st_corr.append(split_line[5]) elif ARRAY == 2: st_lats.append(split_line[1]) st_lons.append(split_line[2]) st_shift.append(split_line[3]) st_corr.append(split_line[4]) # but really std dev else: # no static terms, always true for NORSAR if ARRAY == 0: # Hinet set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_hinet.txt' elif ARRAY == 1: # LASA set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_LASA.txt' elif ARRAY == 2: # China set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_ch.txt' else: # NORSAR set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_NORSAR.txt' 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]) 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() #%% Set some parameters # fig_index = 101 # stat_corr = 1 # apply station static corrections # rel_time = 1 # timing is relative to a chosen phase, otherwise relative to OT # dphase = 'PKIKP' # phase to be aligned # dphase2 = 'PKiKP' # another phase to have traveltime plotted # dphase3 = 'PKP' # another phase to have traveltime plotted # dphase4 = 'pP' # another phase to have traveltime plotted taper_frac = .05 # Fraction of window tapered on both ends noise_win_max = 20 # maximum length of noise window for SNR estimation, seconds # plot_scale_fac = 0.5 # Bigger numbers make each trace amplitude bigger on plot # qual_threshold = 2 # minimum SNR # corr_threshold = 0.7 # minimum correlation in measuring shift to use station plot_tt = 1 # plot the traveltimes? # if ref_loc ==true, use ref_rad to filter station distance # if ref_loc ==false, use earthquake loc to filter station distance # ref_rad = 0.4 # ° radius (°) set by input or at top if ARRAY == 0: ref_lat = 36.3 # °N, around middle of Japan ref_lon = 138.5 # °E if ARRAY == 1: ref_lat = 46.7 # °N keep only inner rings A-D if radius is 0.4° ref_lon = -106.22 # °E if ARRAY == 2: ref_lat = 38 # °N ref_lon = 104.5 # °E # ref_rad = 0.4 # ° radius (°) set by input or at top #%% Is taper too long compared to noise estimation window? totalt = end_buff - start_buff noise_time_skipped = taper_frac * totalt noise_time_skipped = min(noise_time_skipped, 10.0) # set max of 10s to taper length if simple_taper == 0: if noise_time_skipped >= -0.5 * start_buff: print( ' ' + 'Specified taper of ' + str(taper_frac * totalt) + ' is not big enough compared to available noise estimation window ' + str(-start_buff - noise_time_skipped) + '. May not work well.') old_taper_frac = taper_frac taper_frac = -0.5 * start_buff / totalt if start_buff > 0: taper_frac = 0.05 # pick random minimal window if there is no leader print(' ' + 'Taper reset from ' + str(old_taper_frac * totalt) + ' to ' + str(taper_frac * totalt) + ' seconds.') if rel_time == 0: # SNR requirement not implemented for unaligned traces qual_threshold = 0 # Plot with reduced velocity? red_plot = 0 red_dist = 55 red_time = 300 red_slow = 7.2 # seconds per degree #%% Load waveforms and decimate to 10 sps, if not already decimated st = Stream() # fname = '/Users/vidale/Documents/GitHub/LASA_data/HD' + date_label + '.mseed' mseed_name = year_short_label + month_label + day_label + '_' + hour_label + minute_label fname = '/Users/vidale/Documents/Research/IC/Mseed/L' + mseed_name + '.mseed' print('file name attempt: ' + fname) st = read(fname) if do_decimate != 0: st.decimate(do_decimate, no_filter=True) print(' ' + fname) print(' ' + str(len(st)) + ' traces read in') if (len(st) == 0): exit('No traces is a failure') print(' First trace has : ' + str(len(st[0].data)) + ' time pts ') print(' Start time : ' + str(st[0].stats.starttime) + ' event time : ' + str(t)) # print(' ' + str(len(st)) + ' traces after decimation') 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)) #%% Select by distance, window and adjust start time to align picked times st_pickalign = Stream() tra_in_range = 0 tra_sta_found = 0 nodata = 0 min_dist_auto = 180 max_dist_auto = 0 min_time_plot = 1000000 max_time_plot = -1000000 # not used in all cases, but printed out below # only used if rel_slow == 1, preserves 0 slowness, otherwise 0 is set to phase slowness ref_distance = gps2dist_azimuth(ref_lat, ref_lon, ev_lat, ev_lon) ref1_dist = ref_distance[0] / (1000 * 111) dist_minus = ref1_dist - 0.5 dist_plus = ref1_dist + 0.5 arrivals_ref = model.get_travel_times(source_depth_in_km=ev_depth, distance_in_degree=ref1_dist, phase_list=[dphase]) if (len(arrivals_ref) == 0 and ref1_dist < 10 and dphase == 'P'): # in case first arrival is upgoing P, which is 'p' arrivals_ref = model.get_travel_times(source_depth_in_km=ev_depth, distance_in_degree=ref1_dist, phase_list='p') arrivals_minus = model.get_travel_times(source_depth_in_km=ev_depth, distance_in_degree=dist_minus, phase_list=[dphase]) if (len(arrivals_minus) == 0 and dist_minus < 10 and dphase == 'P'): arrivals_minus = model.get_travel_times(source_depth_in_km=ev_depth, distance_in_degree=dist_minus, phase_list='p') arrivals_plus = model.get_travel_times(source_depth_in_km=ev_depth, distance_in_degree=dist_plus, phase_list=[dphase]) if (len(arrivals_plus) == 0 and dist_plus < 10 and dphase == 'P'): arrivals_plus = model.get_travel_times(source_depth_in_km=ev_depth, distance_in_degree=dist_plus, phase_list='p') if (len(arrivals_ref) == 0 or len(arrivals_minus) == 0 or len(arrivals_plus) == 0): print('model.get_travel_times failed: dist, phase ' + str(ref1_dist) + ' ' + dphase) atime_ref = arrivals_ref[ 0].time # phase arrival time at reference distance ref_slow = arrivals_plus[0].time - arrivals_minus[ 0].time # dt over 1 degree at ref distance for tr in st: # traces one by one, find lat-lon if float( year_label ) < 1970: # fix the damn 1969 -> 2069 bug in Gibbon's LASA data temp_t = str(tr.stats.starttime) temp_tt = '19' + temp_t[2:] tr.stats.starttime = UTCDateTime(temp_tt) if JST == 1: # if necessary, convert JST -> UTC, time in Greenwich 9 hours earlier than Japan tr.stats.starttime = tr.stats.starttime - 9 * 60 * 60 # temp_t = str(tr.stats.starttime) # temp_tt = '19' + temp_t[2:] # tr.stats.starttime = UTCDateTime(temp_tt) # times.append( tr.stats.starttime.strptime(split_line[1], dtformat) - dt.timedelta(seconds=offset)) # timesUTC.append(dt.datetime.strptime(split_line[1], dtformat)) # keep UTC version for output if tr.stats.station in st_names: # find station in station list ii = st_names.index(tr.stats.station) tra_sta_found += 1 corr = 1 if stat_corr == 1: corr = float(st_corr[ii]) if corr > corr_threshold: # if using statics, reject low correlations stalat = float( st_lats[ii]) # look up lat & lon again to find distance stalon = float(st_lons[ii]) distance = gps2dist_azimuth( stalat, stalon, ev_lat, ev_lon) # Get traveltimes again, hard to store tr.stats.distance = distance[0] # distance in km dist = distance[0] / (1000 * 111) in_range = 0 # flag for whether this trace goes into stack rejector = False # flag in case model.get_travel_times fails if ref_loc == False: # check whether trace is in distance range from earthquake if min_dist < dist and dist < max_dist: in_range = 1 tra_in_range += 1 elif ref_loc == True: # alternately, check whether trace is close enough to ref_location ref_distance = gps2dist_azimuth(ref_lat, ref_lon, stalat, stalon) ref2_dist = ref_distance[0] / (1000 * 111) if ref2_dist < ref_rad: in_range = 1 tra_in_range += 1 if in_range == 1: # trace fulfills the specified criteria for being in range s_t = t + start_buff e_t = t + end_buff if stat_corr == 1: # apply static station corrections tr.stats.starttime -= float(st_shift[ii]) if rel_time == 0: # don't adjust absolute time tr.trim(starttime=s_t, endtime=e_t) else: # shift relative to a chosen phase arrivals_each = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist, phase_list=[dphase]) if (len(arrivals_each) == 0): if ( dist < 10 and dphase == 'P' ): # in case first arrival is upgoing P, try 'p' arrivals_each = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist, phase_list='p') if (len(arrivals_each) == 0): # did it still fail? print( 'model.get_travel_times failed: dist, depth, phase ' + tr.stats.station + ' ' + str(ref1_dist) + ' ' + ' ' + str(ev_depth) + ' ' + dphase) tra_in_range -= 1 # don't count this trace after all rejector = True else: atime_each = arrivals_each[0].time if rel_time == 1: # each window has a shift proportional to ref_dist at phase slowness at ref_dist s_t += atime_each e_t += atime_each tr.trim(starttime=s_t, endtime=e_t) tr.stats.starttime -= atime_each - ( dist - ref1_dist) * ref_slow elif rel_time == 2: # each window has a distinct shift, but offset is common to all stations s_t += atime_each e_t += atime_each tr.trim(starttime=s_t, endtime=e_t) tr.stats.starttime -= atime_ref elif rel_time == 3: # each station has an individual, chosen-phase shift, phase arrival set to zero s_t += atime_each e_t += atime_each tr.trim(starttime=s_t, endtime=e_t) tr.stats.starttime -= atime_each elif rel_time == 4: # use same window around chosen phase for all stations, phase arrival set to zero s_t += atime_ref e_t += atime_ref tr.trim(starttime=s_t, endtime=e_t) tr.stats.starttime -= atime_ref else: sys.exit( 'Invalid rel_time parameter, must be integer 0 to 4' ) if len(tr.data) > 0 and rejector == False: st_pickalign += tr else: nodata += 1 else: print(tr.stats.station + ' not found in station list with statics') print(' ' + str(tra_in_range) + ' traces in range') print(' ' + str(len(st_pickalign)) + ' traces after alignment and correlation selection') print(' ' + str(nodata) + ' traces with no data') #print(st) # at length if verbose: print(st.__str__(extended=True)) if rel_time == 1: print(st_pickalign.__str__(extended=True)) #%% Detrend, taper, filter st_pickalign.detrend(type='simple') st_pickalign.taper(taper_frac, max_length=max_taper_length) if do_filt == 1: st_pickalign.filter('bandpass', freqmin=freq_min, freqmax=freq_max, corners=4, zerophase=True) st_pickalign.taper(taper_frac, max_length=max_taper_length) #%% Cull further by imposing SNR threshold if skip_SNR == 1: stgood = st_pickalign.copy() else: stgood = Stream() for tr in st_pickalign: # estimate median noise time_to_beam_start = (start_beam - start_buff) if time_to_beam_start - taper_frac * ( end_buff - start_buff) < noise_win_max: # noise window < max length t_noise_start = int(len(tr.data) * taper_frac) # start just after taper t_noise_end = int( len(tr.data) * time_to_beam_start / (end_buff - start_buff)) # end at beam start else: # plenty of leader, set noise window to max length time_to_noise_start = time_to_beam_start - noise_win_max t_noise_start = int( len(tr.data) * time_to_noise_start / (end_buff - start_buff)) # start just after taper t_noise_end = int( len(tr.data) * time_to_beam_start / (end_buff - start_buff)) # end at beam start noise = np.median(abs(tr.data[t_noise_start:t_noise_end])) # estimate median signal t_signal_start = t_noise_end t_signal_end = int( len(tr.data) * (end_beam - start_buff) / (end_buff - start_buff)) # old t_signal_start = int(len(tr.data) * start_buff/(start_buff-end_buff)) # old t_signal_end = t_signal_start + int(len(tr.data) * signal_dur/(end_buff - start_buff)) signal = np.median(abs(tr.data[t_signal_start:t_signal_end])) # test SNR SNR = signal / noise # print('set noise window to max length: ' + str(t_noise_start) + ' start ' + str(t_noise_end) + ' end') # print('set signal window: ' + str(t_signal_start) + ' start ' + str(t_signal_end) + ' end') # print('SNR: ' + str(SNR)) if (SNR > qual_threshold): stgood += tr print(' ' + str(len(stgood)) + ' traces above SNR threshold') if verbose: for tr in stgood: print(' Distance is ' + str(tr.stats.distance / (1000 * 111)) + ' for station ' + tr.stats.station) #%% get station lat-lon, compute distance and time limits for plot for tr in stgood: if tr.stats.station in st_names: # find station in station list ii = st_names.index(tr.stats.station) stalon = float( st_lons[ii]) # look up lat & lon again to find distance stalat = float(st_lats[ii]) distance = gps2dist_azimuth(stalat, stalon, ev_lat, ev_lon) tr.stats.distance = distance[0] / (1000 * 111 ) # distance in degrees if tr.stats.distance < min_dist_auto: min_dist_auto = tr.stats.distance if tr.stats.distance > max_dist_auto: max_dist_auto = tr.stats.distance if tr.stats.starttime - t < min_time_plot: min_time_plot = tr.stats.starttime - t if ((tr.stats.starttime - t) + ((len(tr.data) - 1) * tr.stats.delta)) > max_time_plot: max_time_plot = ((tr.stats.starttime - t) + ((len(tr.data) - 1) * tr.stats.delta)) print( f' Min distance is {min_dist_auto:.3f} Max distance is {max_dist_auto:.3f}' ) print( f' Min time is {min_time_plot:.2f} Max time is {max_time_plot:.2f}' ) if min_time_plot > start_buff: print(f'Min time {min_time_plot:.2f} > start_buff {start_buff:.2f}') print( colored('Write zero-filling into pro3 for this code to work', 'red')) sys.exit(-1) if max_time_plot < end_buff: print(f'Max time {max_time_plot:.2f} < end_buff {end_buff:.2f}') print( colored('Write zero-filling into pro3 for this code to work', 'red')) sys.exit(-1) #%% Plot traces plt.close(fig_index) plt.figure(fig_index, figsize=(10, 10)) plt.xlim(min_time_plot, max_time_plot) if auto_dist == True: dist_diff = max_dist_auto - min_dist_auto # add space at extremes plt.ylim(min_dist_auto - 0.1 * dist_diff, max_dist_auto + 0.1 * dist_diff) max_dist = max_dist_auto min_dist = min_dist_auto else: plt.ylim(min_dist, max_dist) for tr in stgood: dist_offset = tr.stats.distance ttt = np.arange(len(tr.data)) * tr.stats.delta + (tr.stats.starttime - t) if red_plot == 1: shift = red_time + (dist_offset - red_dist) * red_slow ttt = ttt - shift if len(tr.data) > 0: if tr.data.max() - tr.data.min() > 0: plt.plot(ttt, (tr.data - np.median(tr.data)) * plot_scale_fac / (tr.data.max() - tr.data.min()) + dist_offset, color='black') else: print('Max ' + str(tr.data.max()) + ' equals min ' + str(tr.data.min()) + ', skip plotting') else: nodata += 1 print('Trace ' + tr.stats.station + ' has : ' + str(len(tr.data)) + ' time pts, skip plotting') #%% Plot traveltime curves if rel_time != 100: if plot_tt: # first traveltime curve line_pts = 50 dist_vec = np.arange(min_dist_auto, max_dist_auto, (max_dist_auto - min_dist_auto) / line_pts) # distance grid time_vec1 = np.arange( min_dist_auto, max_dist_auto, (max_dist_auto - min_dist_auto) / line_pts) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist_vec[i], phase_list=[dphase]) if (len(arrivals) == 0 and dist_vec[i] < 10 and dphase == 'P' ): # in case first arrival is upgoing P, which is 'p' arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist_vec[i], phase_list='p') if (len(arrivals) == 0): print('model.get_travel_times failed: dist, phase ' + str(dist_vec[i]) + ' ' + dphase) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase: time_vec1[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec1[i] = np.nan # second traveltime curve if dphase2 != 'no': time_vec2 = np.arange( min_dist_auto, max_dist_auto, (max_dist_auto - min_dist_auto) / line_pts ) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist_vec[i], phase_list=[dphase2]) if (len(arrivals) == 0 and dist_vec[i] < 10 and dphase2 == 'P' ): # in case first arrival is upgoing P, which is 'p' arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist_vec[i], phase_list='p') if (len(arrivals) == 0): print( 'model.get_travel_times failed: dist, phase ' + str(dist_vec[i]) + ' ' + dphase2) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase2: time_vec2[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec2[i] = np.nan if rel_time == 3 or rel_time == 4: time_vec2 = time_vec2 - time_vec1 elif rel_time == 2: time_vec2 = time_vec2 - atime_ref plt.plot(time_vec2, dist_vec, color='orange') # third traveltime curve if dphase3 != 'no': time_vec3 = np.arange( min_dist_auto, max_dist_auto, (max_dist_auto - min_dist_auto) / line_pts ) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist_vec[i], phase_list=[dphase3]) if (len(arrivals) == 0 and dist_vec[i] < 10 and dphase3 == 'P' ): # in case first arrival is upgoing P, which is 'p' arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist_vec[i], phase_list='p') if (len(arrivals) == 0): print( 'model.get_travel_times failed: dist, phase ' + str(dist_vec[i]) + ' ' + dphase3) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase3: time_vec3[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec3[i] = np.nan if rel_time == 3 or rel_time == 4: time_vec2 = time_vec2 - time_vec1 elif rel_time == 2: time_vec2 = time_vec2 - atime_ref plt.plot(time_vec3, dist_vec, color='yellow') # fourth traveltime curve if dphase4 != 'no': time_vec4 = np.arange( min_dist_auto, max_dist_auto, (max_dist_auto - min_dist_auto) / line_pts ) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist_vec[i], phase_list=[dphase4]) if (len(arrivals) == 0 and dist_vec[i] < 10 and dphase4 == 'P' ): # in case first arrival is upgoing P, which is 'p' arrivals = model.get_travel_times( source_depth_in_km=ev_depth, distance_in_degree=dist_vec[i], phase_list='p') if (len(arrivals) == 0): print( 'model.get_travel_times failed: dist, phase ' + str(dist_vec[i]) + ' ' + dphase4) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase4: time_vec4[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec4[i] = np.nan if rel_time == 3 or rel_time == 4: time_vec2 = time_vec2 - time_vec1 elif rel_time == 2: time_vec2 = time_vec2 - atime_ref plt.plot(time_vec4, dist_vec, color='purple') if rel_time == 3 or rel_time == 4: time_vec1 = time_vec1 - time_vec1 elif rel_time == 2: time_vec1 = time_vec1 - atime_ref plt.plot(time_vec1, dist_vec, color='blue') plt.xlabel('Time (s)') plt.ylabel('Epicentral distance from event (°)') plt.title(date_label + ' event #' + str(eq_num)) # os.chdir('/Users/vidale/Documents/PyCode/Plots') # plt.savefig(date_label + '_' + str(event_no) + '_raw.png') plt.show() #%% Save processed files fname3 = '/Users/vidale/Documents/Research/IC/Pro_Files/HD' + date_label + 'sel.mseed' stgood.write(fname3, format='MSEED') elapsed_time_wc = time.time() - start_time_wc print(f' This job took {elapsed_time_wc:.1f} seconds') os.system('say "Done"')
def gather_waveforms(source, network, station, location, channel, starttime, endtime, time_buffer=0, merge=True, remove_response=False, return_failed_stations=False, watc_url=None, watc_username=None, watc_password=None): """ Gather seismic/infrasound waveforms from IRIS or WATC FDSN, or AVO Winston, and output a Stream object with station/element coordinates attached. Optionally remove the sensitivity. NOTE 1: Usual RTM usage is to specify a starttime/endtime that brackets the estimated source origin time. Then time_buffer is used to download enough extra data to account for the time required for an infrasound signal to propagate to the farthest station. Args: source: Which source to gather waveforms from - options are: 'IRIS' <-- IRIS FDSN 'WATC' <-- WATC FDSN 'AVO' <-- AVO Winston network: SEED network code station: SEED station code location: SEED location code channel: SEED channel code starttime: Start time for data request (UTCDateTime) endtime: End time for data request (UTCDateTime) time_buffer: [s] Extra amount of data to download after endtime (default: 0) merge: Toggle merging of Traces with identical IDs (default: True) remove_response: Toggle response removal via remove_sensitivity() or a simple scalar multiplication (default: False) return_failed_stations: If True, returns a list of station codes that were requested but not downloaded. This disables the standard failed station warning message (default: False) watc_url: URL for WATC FDSN server (default: None) watc_username: Username for WATC FDSN server (default: None) watc_password: Password for WATC FDSN server (default: None) Returns: st_out: Stream containing gathered waveforms failed_stations: (Optional) List containing station codes that were requested but not downloaded """ print('--------------') print('GATHERING DATA') print('--------------') # IRIS FDSN if source == 'IRIS': client = FDSN_Client('IRIS') print('Reading data from IRIS FDSN...') try: st_out = client.get_waveforms(network, station, location, channel, starttime, endtime + time_buffer, attach_response=True) except FDSNNoDataException: st_out = Stream() # Just create an empty Stream object # WATC FDSN elif source == 'WATC': print('Connecting to WATC FDSN...') client = FDSN_Client(base_url=watc_url, user=watc_username, password=watc_password) print('Successfully connected. Reading data from WATC FDSN...') try: st_out = client.get_waveforms(network, station, location, channel, starttime, endtime + time_buffer, attach_response=True) except FDSNNoDataException: st_out = Stream() # Just create an empty Stream object # AVO Winston elif source == 'AVO': client = EW_Client('pubavo1.wr.usgs.gov', port=16023) # 16023 is long-term print('Reading data from AVO Winston...') st_out = Stream() # Make empty Stream object to populate # Brute-force "dynamic grid search" over network/station/channel/location codes for nw in _restricted_matching('network', network, client): for sta in _restricted_matching('station', station, client, network=nw): for cha in _restricted_matching('channel', channel, client, network=nw, station=sta): for loc in _restricted_matching('location', location, client, network=nw, station=sta, channel=cha): try: st_out += client.get_waveforms( nw, sta, loc, cha, starttime, endtime + time_buffer) except KeyError: pass else: raise ValueError('Unrecognized source. Valid options are \'IRIS\', ' '\'WATC\', or \'AVO\'.') if merge: st_out.merge() # Merge Traces with the same ID st_out.sort() # Check that all requested stations are present in Stream requested_stations = station.split(',') downloaded_stations = [tr.stats.station for tr in st_out] failed_stations = [] for sta in requested_stations: # The below check works with wildcards, but obviously cannot detect if # ALL stations corresponding to a given wildcard (e.g., O??K) were # downloaded. Thus, if careful station selection is desired, specify # each station explicitly and the below check will then be effective. if not fnmatch.filter(downloaded_stations, sta): if not return_failed_stations: # If we're not returning the failed stations, then show this # warning message to alert the user warnings.warn( f'Station {sta} not downloaded from {source} ' 'server for this time period.', CollectionWarning) failed_stations.append(sta) # If the Stream is empty, then we can stop here if st_out.count() == 0: print('No data downloaded.') if return_failed_stations: return st_out, failed_stations else: return st_out # Otherwise, show what the Stream contains print(st_out.__str__(extended=True)) # This syntax prints the WHOLE Stream # Add zeros to ensure all Traces have same length st_out.trim(starttime, endtime + time_buffer, pad=True, fill_value=0) print('Assigning coordinates...') # Use IRIS inventory info for AVO data source if source == 'AVO': client = FDSN_Client('IRIS') try: inv = client.get_stations(network=network, station=station, location=location, channel=channel, starttime=starttime, endtime=endtime + time_buffer, level='channel') except FDSNNoDataException: inv = [] for tr in st_out: for nw in inv: for sta in nw: for cha in sta: # Being very thorough to check if everything matches! if (tr.stats.network == nw.code and tr.stats.station == sta.code and tr.stats.location == cha.location_code and tr.stats.channel == cha.code): tr.stats.longitude = cha.longitude tr.stats.latitude = cha.latitude tr.stats.elevation = cha.elevation # Check if any Trace did NOT get coordinates assigned, and try to use JSON # coordinates if available for tr in st_out: try: tr.stats.longitude, tr.stats.latitude, tr.stats.elevation except AttributeError: try: tr.stats.latitude, tr.stats.longitude,\ tr.stats.elevation = AVO_COORDS[tr.id] warnings.warn(f'Using coordinates from JSON file for {tr.id}.', CollectionWarning) except KeyError: print(f'No coordinates available for {tr.id}. Stopping.') raise # Remove sensitivity if remove_response: print('Removing sensitivity...') for tr in st_out: try: # Just removing sensitivity for now. remove_response() can lead # to errors. This should be sufficient for now. Plus some # IRIS-AVO responses are wonky. tr.remove_sensitivity() except ValueError: # No response information found # This is only set up for infrasound calibration values try: calib = AVO_INFRA_CALIBS[tr.id] tr.data = tr.data * calib warnings.warn( 'Using calibration value from JSON file for ' f'{tr.id}.', CollectionWarning) except KeyError: print(f'No calibration value available for {tr.id}. ' 'Stopping.') raise print('Done') # Return the Stream with coordinates attached (and responses removed if # specified) if return_failed_stations: return st_out, failed_stations else: return st_out
def calculate_onsets(self, data, log=True, timespan=None): """ Calculate onset functions for the requested stations and phases. Returns a stacked array of onset functions for the requested phases, and an :class:`~quakemigrate.signal.onsets.base.OnsetData` object containing all outputs from the onset function calculation: a dict of the onset functions, a Stream containing the pre-processed input waveforms, and a dict of availability info describing which of the requested onset functions could be calculated (depending on data availability and data quality checks). Parameters ---------- data : :class:`~quakemigrate.io.data.WaveformData` object Light class encapsulating data returned by an archive query. log : bool Calculate log(onset) if True, otherwise calculate the raw onset. timespan : float or None, optional If the timespan for which the onsets are being generated is provided, this will be used to calculate the tapered window of data at the start and end of the onset function which should be disregarded. This is necessary to accurately set the pick threshold in GaussianPicker, for example. Returns ------- onsets : `numpy.ndarray` of float Stacked onset functions served up for migration, shape(nonsets, nsamples). onset_data : :class:`~quakemigrate.signal.onsets.base.OnsetData` object Light class encapsulating data generated during onset calculation. """ onsets = [] onsets_dict = {} filtered_waveforms = Stream() availability = {} # Loop through phases, pre-process data, and calculate onsets. for phase in self.phases: # Select traces based on channel map for this phase phase_waveforms = data.waveforms.select( channel=self.channel_maps[phase]) # Convert sta window, lta window lengths from seconds to samples. stw, ltw = self.sta_lta_windows[phase] stw = util.time2sample(stw, self.sampling_rate) + 1 ltw = util.time2sample(ltw, self.sampling_rate) + 1 # Pre-process the data. The ObsPy functions operate by trace, so # will not break on gappy data (we haven't checked availability # yet) filtered_phase_waveforms = pre_process( phase_waveforms, self.sampling_rate, data.resample, data.upfactor, self.bandpass_filters[phase], data.starttime, data.endtime) # Loop through stations, check data availability for this phase, # and store this info, filtered waveforms and calculated onsets for station in data.stations: waveforms = filtered_phase_waveforms.select(station=station) available, av_dict = data.check_availability( waveforms, all_channels=self.all_channels, n_channels=self.channel_counts[phase], allow_gaps=self.allow_gaps, full_timespan=self.full_timespan, check_sampling_rate=True, sampling_rate=self.sampling_rate) availability[f"{station}_{phase}"] = available # If no data available, skip if available == 0: logging.info(f"\t\tNo {phase} onset for {station}.") continue # Check that all channels met the availability critera. If # not, remove this channel from the stream. for key, available in av_dict.items(): if available == 0: to_remove = waveforms.select(id=key) [waveforms.remove(tr) for tr in to_remove] # Pad with tiny floats so onset will be the correct length. # Note: this will only have an effect if allow_gaps=True or # full_timespan=False. Otherwise, there will be no gaps to pad. if self.allow_gaps or not self.full_timespan: # Square root to avoid floating point errors when value # is squared to compute the energy trace tiny = np.sqrt(np.finfo(float).tiny) # Apply another taper to remove transients from filtering - # this is within the pre- and post-pad for continuous data waveforms.taper(type="cosine", max_percentage=0.05) # Fill gaps waveforms.merge(method=1, fill_value=tiny) # Pad start/end; delta of +/-0.00001 is to avoid # occasional obspy weirdness. `nearest_sample` is # appropriate as data is at uniform sampling rate with # off-sample data corrected by util.shift_to_sample() waveforms.trim(starttime=data.starttime - 0.00001, endtime=data.endtime + 0.00001, pad=True, fill_value=tiny, nearest_sample=False) # Calculate onset and add to WaveForm data object; add filtered # waveforms that have passed the availability check to # WaveformData.filtered_waveforms onsets_dict.setdefault(station, {}).update( {phase: self._onset(waveforms, stw, ltw, log, timespan)}) onsets.append(onsets_dict[station][phase]) filtered_waveforms += waveforms logging.debug(filtered_waveforms.__str__(extended=True)) onsets = np.stack(onsets, axis=0) onset_data = OnsetData(onsets_dict, self.phases, self.channel_maps, filtered_waveforms, availability, data.starttime, data.endtime, self.sampling_rate) return onsets, onset_data
s_t = t - start_buff e_t = t + end_buff tr.trim(starttime=s_t, endtime=e_t) # deduct theoretical traveltime and start_buf from starttime if rel_time == 1: tr.stats.starttime = tr.stats.starttime - atime st_pickalign += tr except: pass print('After alignment and range selection - event: ' + str(len(st_pickalign)) + ' traces') #%% #print(st) # at length if verbose: print(st.__str__(extended=True)) if rel_time == 1: print(st_pickalign.__str__(extended=True)) #%% detrend, taper, filter st_pickalign.detrend(type='simple') st_pickalign.taper(taper_frac) st_pickalign.filter('bandpass', freqmin=freq_min, freqmax=freq_max, corners=2, zerophase=True) st_pickalign.taper(taper_frac) #%% Cull further by imposing SNR threshold on both traces stgood = Stream()
def pro3pair(eq_file1, eq_file2, stat_corr=1, simple_taper=0, skip_SNR=0, dphase='PKIKP', dphase2='PKiKP', dphase3='PKIKP', dphase4='PKiKP', rel_time=1, start_buff=-200, end_buff=500, plot_scale_fac=0.05, qual_threshold=0, corr_threshold=0.5, freq_min=1, freq_max=3, min_dist=0, max_dist=180, alt_statics=0, statics_file='nothing', ARRAY=0, ref_loc=0): # Parameters # ARRAY 0 is Hinet, 1 is LASA, 2 is NORSAR # start_buff = -50 # plots start Xs after PKIKP # end_buff = 200 # plots end Xs after PKIKP # plot_scale_fac = 0.5 # Bigger numbers make each trace amplitude bigger on plot # stat_corr = 1 # apply station static corrections # qual_threshold = 0.2 # minimum SNR # corr_threshold = 0.7 # minimum correlation in measuring shift to use station in static construction # dphase = 'PKIKP' # phase to be aligned # dphase2 = 'PKiKP' # another phase to have traveltime plotted # dphase3 = 'pPKiKP' # another phase to have traveltime plotted # dphase4 = 'pPKIKP' # another phase to have traveltime plotted #%% Set some parameters verbose = 0 # more output # rel_time = 1 # timing is relative to a chosen phase, otherwise relative to OT taper_frac = .05 #Fraction of window tapered on both ends signal_dur = 5. # signal length used in SNR calculation plot_tt = 1 # plot the traveltimes? do_decimate = 0 # 0 if no decimation desired #ref_loc = 0 # 1 if selecting stations within ref_rad of ref_lat and ref_lon # 0 if selecting stations by distance from earthquake if ref_loc == 1: if ARRAY == 0: ref_lat = 36.3 # °N, around middle of Japan ref_lon = 138.5 # °E ref_rad = 1.5 # ° radius (°) elif ARRAY == 1: ref_lat = 46.7 # °N keep only inner rings A-D ref_lon = -106.22 # °E ref_rad = 0.4 # ° radius (°) if rel_time == 0: # SNR requirement not implemented for unaligned traces qual_threshold = 0 # Plot with reduced velocity? red_plot = 0 red_dist = 55 red_time = 300 red_slow = 7.2 # seconds per degree #%% Import functions from obspy import UTCDateTime from obspy import Stream from obspy import read from obspy.geodetics import gps2dist_azimuth import numpy as np import os from obspy.taup import TauPyModel import matplotlib.pyplot as plt import time model = TauPyModel(model='iasp91') import sys # don't show any warnings import warnings if not sys.warnoptions: warnings.simplefilter("ignore") print('Running pro3a_sort_plot_pair') start_time_wc = time.time() #%% Get saved event info, also used to name files # event 2016-05-28T09:47:00.000 -56.241 -26.935 78 print('Opening ' + eq_file1) if ARRAY == 0: file = open(eq_file1, 'r') elif ARRAY == 1: file = open('EvLocs/' + eq_file1, 'r') lines = file.readlines() split_line = lines[0].split() # ids.append(split_line[0]) ignore label for now t1 = UTCDateTime(split_line[1]) date_label1 = split_line[1][0:10] year1 = split_line[1][0:4] ev_lat1 = float(split_line[2]) ev_lon1 = float(split_line[3]) ev_depth1 = float(split_line[4]) print('1st event: date_label ' + date_label1 + ' time ' + str(t1) + ' lat ' + str(ev_lat1) + ' lon ' + str(ev_lon1) + ' depth ' + str(ev_depth1)) print('Opening ' + eq_file2) if ARRAY == 0: file = open(eq_file2, 'r') elif ARRAY == 1: file = open('EvLocs/' + eq_file2, 'r') lines = file.readlines() split_line = lines[0].split() # ids.append(split_line[0]) ignore label for now t2 = UTCDateTime(split_line[1]) date_label2 = split_line[1][0:10] year2 = split_line[1][0:4] ev_lat2 = float(split_line[2]) ev_lon2 = float(split_line[3]) ev_depth2 = float(split_line[4]) print('2nd event: date_label ' + date_label2 + ' time ' + str(t2) + ' lat ' + str(ev_lat2) + ' lon ' + str(ev_lon2) + ' depth ' + str(ev_depth2)) #%% Get station location file if stat_corr == 1: # load static terms, only applies to Hinet and LASA if ARRAY == 0: if alt_statics == 0: # standard set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/hinet_sta_statics.txt' else: # custom set made by this event for this event sta_file = ( '/Users/vidale/Documents/GitHub/Array_codes/Files/' + 'HA' + date_label1[:10] + 'pro4_' + dphase + '.statics') elif ARRAY == 1: sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/L_sta_statics.txt' 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_dist = [] st_lats = [] st_lons = [] st_shift = [] st_corr = [] for ii in station_index: line = lines[ii] split_line = line.split() st_names.append(split_line[0]) st_dist.append(split_line[1]) st_lats.append(split_line[2]) st_lons.append(split_line[3]) st_shift.append(split_line[4]) st_corr.append(split_line[5]) else: # no static terms, always true for LASA or NORSAR if ARRAY == 0: # Hinet set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/hinet_sta.txt' elif ARRAY == 1: # LASA set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/LASA_sta.txt' else: # NORSAR set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/NORSAR_sta.txt' 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]) #%% Is taper too long compared to noise estimation window? totalt = end_buff - start_buff noise_time_skipped = taper_frac * totalt if simple_taper == 0: if noise_time_skipped >= 0.5 * (-start_buff): print( 'Specified taper of ' + str(taper_frac * totalt) + ' is not big enough compared to available noise estimation window ' + str(-start_buff - noise_time_skipped) + '. May not work well.') old_taper_frac = taper_frac taper_frac = -0.5 * start_buff / totalt print('Taper reset from ' + str(old_taper_frac * totalt) + ' to ' + str(taper_frac * totalt) + ' seconds.') #%% Load waveforms and decimate to 10 sps st1 = Stream() st2 = Stream() if ARRAY == 0: fname1 = 'HD' + date_label1 + '.mseed' fname2 = 'HD' + date_label2 + '.mseed' elif ARRAY == 1: fname1 = 'Mseed/HD' + date_label1 + '.mseed' fname2 = 'Mseed/HD' + date_label2 + '.mseed' st1 = read(fname1) st2 = read(fname2) if do_decimate != 0: st1.decimate(do_decimate) st2.decimate(do_decimate) print('1st trace has : ' + str(len(st1[0].data)) + ' time pts ') print('st1 has ' + str(len(st1)) + ' traces') print('st2 has ' + str(len(st2)) + ' traces') print('1st trace starts at ' + str(st1[0].stats.starttime) + ', event at ' + str(t1)) print('2nd trace starts at ' + str(st2[0].stats.starttime) + ', event at ' + str(t2)) #%% Select by distance, window and adjust start time to align picked times st_pickalign1 = Stream() st_pickalign2 = Stream() for tr in st1: # traces one by one, find lat-lon by searching entire inventory. Inefficient if float( year1 ) < 1970: # fix the damn 1969 -> 2069 bug in Gibbon's LASA data temp_t = str(tr.stats.starttime) temp_tt = '19' + temp_t[2:] tr.stats.starttime = UTCDateTime(temp_tt) 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]): # find station in inventory if stat_corr != 1 or float( st_corr[ii] ) > corr_threshold: # if using statics, reject low correlations stalat = float(st_lats[ii]) stalon = float( st_lons[ii] ) # look up lat & lon again to find distance if ref_loc == 1: ref_distance = gps2dist_azimuth( stalat, stalon, ref_lat, ref_lon) ref_dist = ref_distance[0] / (1000 * 111) distance = gps2dist_azimuth( stalat, stalon, ev_lat1, ev_lon1) # Get traveltimes again, hard to store tr.stats.distance = distance[0] # distance in km dist = distance[0] / (1000 * 111) if ref_loc != 1 and min_dist < dist and dist < max_dist: # select distance range from earthquake try: # print('Phase ' + dphase + ', depth ' + str(ev_depth1) + ' distance ' + str(dist)) arrivals = model.get_travel_times( source_depth_in_km=ev_depth1, distance_in_degree=dist, phase_list=[dphase]) atime = arrivals[0].time # print(dphase + ' arrival time is ' + str(atime)) if stat_corr == 1: # apply static station corrections tr.stats.starttime -= float(st_shift[ii]) if rel_time == 1: s_t = t1 + atime + start_buff e_t = t1 + atime + end_buff else: s_t = t1 + start_buff e_t = t1 + end_buff tr.trim(starttime=s_t, endtime=e_t) # deduct theoretical traveltime and start_buf from starttime if rel_time == 1: tr.stats.starttime -= atime st_pickalign1 += tr except: pass elif ref_loc == 1: if ref_dist < ref_rad: # alternatively, select based on distance from ref location try: arrivals = model.get_travel_times( source_depth_in_km=ev_depth1, distance_in_degree=dist, phase_list=[dphase]) atime = arrivals[0].time if stat_corr == 1: # apply static station corrections tr.stats.starttime -= float(st_shift[ii]) if rel_time == 1: s_t = t1 + atime + start_buff e_t = t1 + atime + end_buff else: s_t = t1 + start_buff e_t = t1 + end_buff tr.trim(starttime=s_t, endtime=e_t) # deduct theoretical traveltime and start_buf from starttime if rel_time == 1: tr.stats.starttime -= atime st_pickalign1 += tr except: pass # if len(tr.data) == 0: # print('Event 1 - empty window. Trace starts at ' + str(tr.stats.starttime) + ', event at ' + str(t1)) for tr in st2: # traces one by one if float( year2 ) < 1970: # fix the damn 1969 -> 2069 bug in Gibbon's LASA data temp_t = str(tr.stats.starttime) temp_tt = '19' + temp_t[2:] tr.stats.starttime = UTCDateTime(temp_tt) 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]): # find station in inventory if stat_corr != 1 or float( st_corr[ii] ) > corr_threshold: # if using statics, reject low correlations stalat = float(st_lats[ii]) stalon = float(st_lons[ii]) if ref_loc == 1: ref_distance = gps2dist_azimuth( stalat, stalon, ref_lat, ref_lon) ref_dist = ref_distance[0] / (1000 * 111) distance = gps2dist_azimuth( stalat, stalon, ev_lat2, ev_lon2) # Get traveltimes again, hard to store tr.stats.distance = distance[0] # distance in km dist = distance[0] / (1000 * 111) if ref_loc != 1 and min_dist < dist and dist < max_dist: # select distance range from earthquake try: arrivals = model.get_travel_times( source_depth_in_km=ev_depth2, distance_in_degree=dist, phase_list=[dphase]) atime = arrivals[0].time if stat_corr == 1: # apply static station corrections tr.stats.starttime -= float(st_shift[ii]) if rel_time == 1: s_t = t2 + atime + start_buff e_t = t2 + atime + end_buff else: s_t = t2 + start_buff e_t = t2 + end_buff tr.trim(starttime=s_t, endtime=e_t) # deduct theoretical traveltime and start_buf from starttime if rel_time == 1: tr.stats.starttime -= atime st_pickalign2 += tr except: pass elif ref_loc == 1: if ref_dist < ref_rad: # alternatively, select based on distance from ref location try: arrivals = model.get_travel_times( source_depth_in_km=ev_depth2, distance_in_degree=dist, phase_list=[dphase]) atime = arrivals[0].time if stat_corr == 1: # apply static station corrections tr.stats.starttime -= float(st_shift[ii]) if rel_time == 1: s_t = t2 + atime + start_buff e_t = t2 + atime + end_buff else: s_t = t2 + start_buff e_t = t2 + end_buff tr.trim(starttime=s_t, endtime=e_t) # deduct theoretical traveltime and start_buf from starttime if rel_time == 1: tr.stats.starttime -= atime st_pickalign2 += tr except: pass # if len(tr.data) == 0: # print('Event 2 - empty window. Trace starts at ' + str(tr.stats.starttime) + ', event at ' + str(t2)) print('After alignment and range selection: ' + str(len(st_pickalign1)) + ' traces') #%% #print(st) # at length if verbose: print(st1.__str__(extended=True)) print(st2.__str__(extended=True)) if rel_time == 1: print(st_pickalign1.__str__(extended=True)) print(st_pickalign2.__str__(extended=True)) #%% Detrend, taper, filter st_pickalign1.detrend(type='simple') st_pickalign2.detrend(type='simple') st_pickalign1.taper(taper_frac) st_pickalign2.taper(taper_frac) st_pickalign1.filter('bandpass', freqmin=freq_min, freqmax=freq_max, corners=4, zerophase=True) st_pickalign2.filter('bandpass', freqmin=freq_min, freqmax=freq_max, corners=4, zerophase=True) st_pickalign1.taper(taper_frac) st_pickalign2.taper(taper_frac) #%% Cull further by imposing SNR threshold on both traces st1good = Stream() st2good = Stream() for tr1 in st_pickalign1: for tr2 in st_pickalign2: if ((tr1.stats.network == tr2.stats.network) & (tr1.stats.station == tr2.stats.station)): if skip_SNR == 1: st1good += tr1 st2good += tr2 else: # estimate median noise t_noise1_start = int(len(tr1.data) * taper_frac) t_noise2_start = int(len(tr2.data) * taper_frac) t_noise1_end = int( len(tr1.data) * (-start_buff) / (end_buff - start_buff)) t_noise2_end = int( len(tr2.data) * (-start_buff) / (end_buff - start_buff)) noise1 = np.median( abs(tr1.data[t_noise1_start:t_noise1_end])) noise2 = np.median( abs(tr2.data[t_noise2_start:t_noise2_end])) # estimate median signal t_signal1_start = int( len(tr1.data) * (-start_buff) / (end_buff - start_buff)) t_signal2_start = int( len(tr2.data) * (-start_buff) / (end_buff - start_buff)) t_signal1_end = t_signal1_start + int( len(tr1.data) * signal_dur / (end_buff - start_buff)) t_signal2_end = t_signal2_start + int( len(tr2.data) * signal_dur / (end_buff - start_buff)) signal1 = np.median( abs(tr1.data[t_signal1_start:t_signal1_end])) signal2 = np.median( abs(tr2.data[t_signal2_start:t_signal2_end])) # test SNR SNR1 = signal1 / noise1 SNR2 = signal2 / noise2 if (SNR1 > qual_threshold and SNR2 > qual_threshold): st1good += tr1 st2good += tr2 if skip_SNR == 1: print('Matches (no SNR test): ' + str(len(st1good)) + ' traces') else: print('Match and above SNR threshold: ' + str(len(st1good)) + ' traces') #%% get station lat-lon, compute distance for plot for tr in st1good: for ii in station_index: if (tr.stats.station == st_names[ii]): # find station in inventory stalon = float( st_lons[ii]) # look up lat & lon again to find distance stalat = float(st_lats[ii]) distance = gps2dist_azimuth(stalat, stalon, ev_lat1, ev_lon1) tr.stats.distance = distance[0] # distance in km for tr in st2good: for ii in station_index: if (tr.stats.station == st_names[ii]): # find station in inventory stalon = float( st_lons[ii]) # look up lat & lon again to find distance stalat = float(st_lats[ii]) distance = gps2dist_azimuth(stalat, stalon, ev_lat2, ev_lon2) tr.stats.distance = distance[0] # distance in km print('Made it to here.') #%% # plot traces fig_index = 3 plt.close(fig_index) plt.figure(fig_index, figsize=(8, 8)) plt.xlim(start_buff, end_buff) plt.ylim(min_dist, max_dist) for tr in st1good: dist_offset = tr.stats.distance / (1000 * 111 ) # trying for approx degrees ttt = np.arange(len(tr.data)) * tr.stats.delta + (tr.stats.starttime - t1) if red_plot == 1: shift = red_time + (dist_offset - red_dist) * red_slow ttt = ttt - shift # These lines used to cause a crash in Spyder plt.plot(ttt, (tr.data - np.median(tr.data)) * plot_scale_fac / (tr.data.max() - tr.data.min()) + dist_offset, color='green') #plt.title(fname1) print('And made it to here?') for tr in st2good: dist_offset = tr.stats.distance / (1000 * 111 ) # trying for approx degrees ttt = np.arange(len(tr.data)) * tr.stats.delta + (tr.stats.starttime - t2) if red_plot == 1: shift = red_time + (dist_offset - red_dist) * red_slow ttt = ttt - shift ttt = ttt # These lines used to cause a crash in Spyder plt.plot(ttt, (tr.data - np.median(tr.data)) * plot_scale_fac / (tr.data.max() - tr.data.min()) + dist_offset, color='red') print('And made it to here.') #%% Plot traveltime curves if plot_tt: # first traveltime curve line_pts = 50 dist_vec = np.arange(min_dist, max_dist, (max_dist - min_dist) / line_pts) # distance grid time_vec1 = np.arange( min_dist, max_dist, (max_dist - min_dist) / line_pts) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times(source_depth_in_km=ev_depth1, distance_in_degree=dist_vec[i], phase_list=[dphase]) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase: time_vec1[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec1[i] = np.nan # second traveltime curve if dphase2 != 'no': time_vec2 = np.arange( min_dist, max_dist, (max_dist - min_dist) / line_pts) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times( source_depth_in_km=ev_depth1, distance_in_degree=dist_vec[i], phase_list=[dphase2]) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase2: time_vec2[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec2[i] = np.nan if rel_time == 1: time_vec2 = time_vec2 - time_vec1 plt.plot(time_vec2, dist_vec, color='orange') # third traveltime curve if dphase3 != 'no': time_vec3 = np.arange( min_dist, max_dist, (max_dist - min_dist) / line_pts) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times( source_depth_in_km=ev_depth1, distance_in_degree=dist_vec[i], phase_list=[dphase3]) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase3: time_vec3[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec3[i] = np.nan if rel_time == 1: time_vec3 = time_vec3 - time_vec1 plt.plot(time_vec3, dist_vec, color='yellow') # fourth traveltime curve if dphase4 != 'no': time_vec4 = np.arange( min_dist, max_dist, (max_dist - min_dist) / line_pts) # empty time grid of same length (filled with -1000) for i in range(0, line_pts): arrivals = model.get_travel_times( source_depth_in_km=ev_depth1, distance_in_degree=dist_vec[i], phase_list=[dphase4]) num_arrivals = len(arrivals) found_it = 0 for j in range(0, num_arrivals): if arrivals[j].name == dphase4: time_vec4[i] = arrivals[j].time found_it = 1 if found_it == 0: time_vec4[i] = np.nan if rel_time == 1: time_vec4 = time_vec4 - time_vec1 plt.plot(time_vec4, dist_vec, color='purple') if rel_time == 1: time_vec1 = time_vec1 - time_vec1 plt.plot(time_vec1, dist_vec, color='blue') plt.xlabel('Time (s)') plt.ylabel('Epicentral distance from event (°)') if ARRAY == 0: plt.title(dphase + ' for ' + fname1 + ' vs ' + fname2) elif ARRAY == 1: plt.title(dphase + ' for ' + fname1[8:18] + ' vs ' + fname2[8:18]) plt.show() #%% Save processed files if ARRAY == 0: fname1 = 'HD' + date_label1 + 'sel.mseed' fname2 = 'HD' + date_label2 + 'sel.mseed' elif ARRAY == 1: fname1 = 'Pro_Files/HD' + date_label1 + 'sel.mseed' fname2 = 'Pro_Files/HD' + date_label2 + 'sel.mseed' st1good.write(fname1, format='MSEED') st2good.write(fname2, format='MSEED') elapsed_time_wc = time.time() - start_time_wc print('This job took ' + str(elapsed_time_wc) + ' seconds') os.system('say "Done"')
def read_local(data_dir, coord_file, network, station, location, channel, starttime, endtime, merge=True): """ Read in waveforms from "local" 1-hour, IRIS-compliant miniSEED files, and output a :class:`~obspy.core.stream.Stream` with station/element coordinates attached. **NOTE 1** The expected naming convention for the miniSEED files is ``<network>.<station>.<location>.<channel>.<year>.<julian_day>.<hour>`` **NOTE 2** This function assumes that the response has been removed from the waveforms in the input miniSEED files. Args: data_dir (str): Directory containing miniSEED files coord_file (str): JSON file containing coordinates for local stations (full path required) network (str): SEED network code [wildcards (``*``, ``?``) accepted] station (str): SEED station code [wildcards (``*``, ``?``) accepted] location (str): SEED location code [wildcards (``*``, ``?``) accepted] channel (str): SEED channel code [wildcards (``*``, ``?``) accepted] starttime (:class:`~obspy.core.utcdatetime.UTCDateTime`): Start time for data request endtime (:class:`~obspy.core.utcdatetime.UTCDateTime`): End time for data request merge (bool): Toggle merging of :class:`~obspy.core.trace.Trace` objects with identical IDs Returns: :class:`~obspy.core.stream.Stream` containing gathered waveforms """ print('-----------------------------') print('GATHERING LOCAL MINISEED DATA') print('-----------------------------') # Take (hour) floor of starttime starttime_hr = UTCDateTime(starttime.year, starttime.month, starttime.day, starttime.hour) # Take (hour) floor of endtime - this ensures we check this miniSEED file endtime_hr = UTCDateTime(endtime.year, endtime.month, endtime.day, endtime.hour) # Define filename template template = f'{network}.{station}.{location}.{channel}.{{}}.{{}}.{{}}' # Initialize Stream object st_out = Stream() # Initialize the starting hour tmp_time = starttime_hr # Cycle forward in time, advancing hour by hour through miniSEED files while tmp_time <= endtime_hr: pattern = template.format(tmp_time.strftime('%Y'), tmp_time.strftime('%j'), tmp_time.strftime('%H')) files = glob.glob(os.path.join(data_dir, pattern)) for file in files: st_out += read(file) tmp_time += HR2SEC # Add an hour! if merge: st_out.merge() # Merge Traces with the same ID st_out.sort() # If the Stream is empty, then we can stop here if st_out.count() == 0: print('No data downloaded.') return st_out # Otherwise, show what the Stream contains print(st_out.__str__(extended=True)) # This syntax prints the WHOLE Stream # Add zeros to ensure all Traces have same length st_out.trim(starttime, endtime, pad=True, fill_value=0) # Replace numerical outliers with zeroes for tr in st_out: d0 = np.where(tr.data > OUTLIER_THRESHOLD)[0] if d0.any(): print(f'{len(d0)} data points in {tr.id} were outliers with ' f' values > {OUTLIER_THRESHOLD} and are now set to 0') tr.data[d0] = 0 print('Assigning coordinates...') # Assign coordinates by searching through user-supplied JSON file local_coords = load_json_file(coord_file) for tr in st_out: try: tr.stats.latitude, tr.stats.longitude,\ tr.stats.elevation = local_coords[tr.stats.station] except KeyError: print(f'No coordinates available for {tr.id}. Stopping.') raise print('Done') # Return the Stream with coordinates attached return st_out