def test_linstack(self): """Test the utils.stacking.linstack function.""" # Generate synth data import numpy as np from obspy import Stream, Trace synth = Stream(Trace()) synth[0].data = np.zeros(200) synth[0].data[100] = 1.0 sine_x = np.arange(0, 10.0, 0.5) damped_sine = np.exp(-sine_x) * np.sin(2 * np.pi * sine_x) synth[0].data = np.convolve(synth[0].data, damped_sine) # Normalize: synth[0].data = synth[0].data / synth[0].data.max() maximum_synth = synth[0].data.max() RMS_max = np.sqrt(np.mean(np.square(synth[0].data))) streams = [synth.copy() for i in range(10)] stack = linstack(streams, normalize=True) # Check normalized amplitude is correct self.assertEqual(np.float32(stack[0].data.max()), np.float32(10 * maximum_synth / RMS_max)) stack = linstack(streams, normalize=False) # Check amplitude is preserved self.assertEqual(stack[0].data.max(), 10 * maximum_synth) # Check length is preserved self.assertEqual(len(synth[0].data), len(stack[0].data))
def main(data_dir, output_dir): # Get borehole geode info stats = pd.read_csv("walkaround_borehole_orientation_stats.csv") # Loop over stations/geodes for sta in ['BH001', 'BH002', 'BH004', 'BH005', 'BH006', 'BH008', 'BH011', 'BH012', 'BH013', 'BH014', 'BH015', 'BH016', 'BH017', 'BH018']: # geodes.Geode: print("Processing station %s" % sta) # Rotation matrix for correction inc_corr = - stats.loc[stats['geode'] == sta, 'inclination'].values[0] baz_corr = - stats.loc[stats['geode'] == sta, 'mean'].values[0] # Read data flist1 = glob(os.path.join(data_dir, "8O.%s..DP1*" % sta)) flist2 = glob(os.path.join(data_dir, "8O.%s..DP2*" % sta)) flist3 = glob(os.path.join(data_dir, "8O.%s..DP3*" % sta)) if not flist1 or not flist2 or not flist3: continue # Look for suffix file = os.path.split(flist1[0])[1] if file.find("unit") != -1: idx = file.find("unit") suffix = "_" + file[idx:].split(".")[0] else: suffix = "" st = Stream() st += read(os.path.join(data_dir, "8O.%s..DP1*" % sta), format="SAC")[0] st += read(os.path.join(data_dir, "8O.%s..DP2*" % sta), format="SAC")[0] st += read(os.path.join(data_dir, "8O.%s..DP3*" % sta), format="SAC")[0] if len(st) == 0: continue st.resample(500.0) starttime = st[0].stats.starttime endtime = st[0].stats.endtime # Save rotated coordinates st3 = st.copy() compN, compE, compZ = rotate_to_zne(st3[0].data, st3[1].data, st3[2].data, inc_corr, baz_corr) trN = SACTrace.from_obspy_trace(st3[0]) trN.data = compN trN.kcmpnm = "DPN" trN.write(os.path.join(output_dir, "8O.%s..DPN.%s_%s%s.sac" % (sta, starttime.strftime("%Y%m%d%H%M%S"), endtime.strftime("%Y%m%d%H%M%S"), suffix))) trE = SACTrace.from_obspy_trace(st3[1]) trE.data = compE trE.kcmpnm = "DPE" trE.write(os.path.join(output_dir, "8O.%s..DPE.%s_%s%s.sac" % (sta, starttime.strftime("%Y%m%d%H%M%S"), endtime.strftime("%Y%m%d%H%M%S"), suffix))) trZ = SACTrace.from_obspy_trace(st3[2]) trZ.data = compZ trZ.kcmpnm = "DPZ" trZ.write(os.path.join(output_dir, "8O.%s..DPZ.%s_%s%s.sac" % (sta, starttime.strftime("%Y%m%d%H%M%S"), endtime.strftime("%Y%m%d%H%M%S"), suffix)))
def get_test_data(): """ Generate a set of waveforms from GeoNet for use in subspace testing :return: List of cut templates with no filters applied :rtype: list """ from http.client import IncompleteRead from obspy import UTCDateTime from eqcorrscan.utils.catalog_utils import filter_picks from eqcorrscan.utils.clustering import catalog_cluster from obspy.clients.fdsn import Client client = Client("GEONET") cat = client.get_events(minlatitude=-40.98, maxlatitude=-40.85, minlongitude=175.4, maxlongitude=175.5, starttime=UTCDateTime(2016, 5, 11), endtime=UTCDateTime(2016, 5, 13)) cat = filter_picks(catalog=cat, top_n_picks=5) stachans = list( set([(pick.waveform_id.station_code, pick.waveform_id.channel_code) for event in cat for pick in event.picks])) clusters = catalog_cluster(catalog=cat, thresh=2, show=False, metric='distance') cluster = sorted(clusters, key=lambda c: len(c))[-1] client = Client('GEONET') design_set = [] st = Stream() for event in cluster: # This print is just in to force some output during long running test print("Downloading for event {0}".format(event.resource_id)) bulk_info = [] t1 = event.origins[0].time + 5 t2 = t1 + 15.1 for station, channel in stachans: bulk_info.append(('NZ', station, '10', channel[0:2] + '?', t1, t2)) st += client.get_waveforms_bulk(bulk=bulk_info) for event in cluster: t1 = event.origins[0].time + 5 t2 = t1 + 15 design_set.append(st.copy().trim(t1, t2)) t1 = UTCDateTime(2016, 5, 11, 19) t2 = UTCDateTime(2016, 5, 11, 20) bulk_info = [('NZ', stachan[0], '10', stachan[1][0:2] + '?', t1, t2) for stachan in stachans] st = Stream() for _bulk in bulk_info: try: st += client.get_waveforms(*_bulk) except IncompleteRead: print(f"Could not download {_bulk}") st.merge().detrend('simple').trim(starttime=t1, endtime=t2) return design_set, st
def setup_stream(self, stream: Stream, freqmin=3., freqmax=20., **kwargs): """ Setup the stream to be analyzed by the CNN. It must contain 3 channels. This method will perform a trim, detrend and bandpass filter to the Stream. If sample rate is not 100 Hz it will also interpolate it to 100 Hz. :param stream: An obspy.Stream with 3 channels inside. :param freqmin: Minimum frequency for the bandpass filter. Default=3Hz, filter should be set True for this to have an effect. :param freqmax: Maximum frequency for the bandpass filter. Default=20Hz, filter should be set True for this to have an effect. :keyword: :keyword resample: Interpolate Stream to self.resample_freq. Default=True. This will interpolate to a default frequency of 100Hz. Set to False if you want to avoid it. However, it's highly recommend to resample to 100Hz since the CNN is better trained for this sample rate. :keyword filter: It will apply a bandpass filter using freqmin and freqmax. Default=True. :keyword detrend: Detrend stream using linear. Default=True. :keyword trim: Trim stream usigin latest_start and earliest_stop from tracers. Default=True. :keyword copy: If true it will copy the stream otherwise the original stream will be modify. Default = True. Set False if you not gonna use the stream after. :return: """ copy_stream = kwargs.get("copy", True) trim_stream = kwargs.get("trim", True) detrend_stream = kwargs.get("detrend", True) filter_stream = kwargs.get("filter", True) resample_stream = kwargs.get("resample", True) self.__stream = stream.copy( ) if copy_stream else stream # use a copy of stream by default. self.validate_stream() self.__stream.sort(['channel']) if trim_stream: latest_start = np.amax( [tr.stats.starttime for tr in self.__stream]) earliest_stop = np.amin([tr.stats.endtime for tr in self.__stream]) self.__stream.trim(latest_start, earliest_stop) if detrend_stream: self.__stream.detrend(type='linear') # Filter data plays a big role on predict. if filter_stream: self.__stream.filter(type='bandpass', freqmin=freqmin, freqmax=freqmax) if resample_stream: # Works better if interpolate. # self.__stream.resample(100) # TODO test re sample. it gets more events. self.__stream.interpolate(self.resample_freq)
def trim_event_stream( stream: Stream, merge: Optional[int] = 1, copy: bool = True, trim_tolerance=None, required_len: Optional[float] = 0.95, ): """ Trim the waveforms to a common start time and end time. Uses latest start and earliest end for each trace, unless an abnormally short trace is found. Parameters ---------- stream : obspy.Stream The waveforms to trim merge : int, optional If not None, merge the waveforms with this method before trimming. See obspy waveforms docs for merge options copy : bool Copy the waveforms before altering it. trim_tolerance: The number of seconds the trimmed starttime or endtime can vary from the starttime/endtime in the current traces. required_len The fraction of the longest len a trace must have to be considered good. If any trace is shorter remove it. Returns ------- stream The merged waveforms """ if copy: stream = stream.copy() if merge is not None: # merge stream.merge(method=merge) stream = stream.split() # get a dataframe of start/end/duration and trace data = [(tr.stats.starttime.timestamp, tr.stats.endtime.timestamp, tr) for tr in stream] df = pd.DataFrame(data, columns=["start", "end", "trace"]) df["duration"] = df.end - df.start # get start and end times stream = _trim_stream(df, stream, required_len, trim_tolerance) # ensure each channel has exactly one trace or merge to create masked if not len(stream) == len({tr.id for tr in stream}): stream.merge(method=merge) return stream
def _stack(path, **kwargs): files = glob.glob(path + "/*") # stations = glob.glob(path + "/*") # print(stations) # for sta in stations: # files = glob.glob(sta + "/*") st = Stream() for file in files: try: tr = read(file)[0] except: continue st.append(tr) # get channels channels = [] for tr in st: chn = tr.stats.channel if chn not in channels: channels.append(chn) method = kwargs["stack"]["method"] power = kwargs["stack"]["power"] outpath = kwargs["io"]["outpath"] + "/1a_stack" try: os.makedirs(outpath) except: pass stream = st.copy() for chn in channels: st = stream.select(channel=chn) if method == "linear": tr = linear_stack(st, normalize=True) elif method == "PWS": tr = pws_stack(st, power, normalize=True) elif method == "bootstrap_linear" or method == "bootstrap_PWS": # note tr here is a stream containing two traces, mean and std tr = _bootstrap(st, normalize=True, **kwargs) filen = outpath + "/" + tr.id + "_%s.pkl" % method tr.write(filename=filen, format="PICKLE") return 0
def test_align(self): """Check that alignment does as expected.""" test_stream = Stream(read()[0]) # Shift it length = 15 st1 = test_stream.copy().trim(test_stream[0].stats.starttime + 3, test_stream[0].stats.starttime + 3 + length) st2 = test_stream.trim(test_stream[0].stats.starttime, test_stream[0].stats.starttime + length) aligned = subspace.align_design(design_set=[st1.copy(), st2.copy()], shift_len=5, reject=0.3, multiplex=False, plot=False) self.assertEqual(aligned[0][0].stats.starttime, aligned[1][0].stats.starttime)
def generate_G_matrix( self, st_GF: obspy.Stream, az: float, comp: str, slice: bool = False, tt: float = None, t_pre: float = None, t_post: float = None, ): st_in = st_GF.copy() if slice: st_in.trim( starttime=self.or_time + tt - t_pre, endtime=self.or_time + tt + t_post, ) G = _GreensFunctions.from_GF_get_G(st_in, az, comp) return G
def get_waveforms( stream: Stream, network: str = "*", station: str = "*", location: str = "*", channel: str = "*", starttime: Optional[UTC] = None, endtime: Optional[UTC] = None, ): """ A subset of the Client.get_waveforms method. Simply makes successive calls to Stream.select and Stream.trim under the hood. Matching is available on all str parameters. Parameters ---------- network The network code station The station code location Location code channel Channel code starttime Starttime for query endtime Endtime for query Returns ------- Stream """ stream = stream.copy() st = stream.select(network=network, station=station, location=location, channel=channel) st = st.trim(starttime=UTC(starttime or SMALL_UTC), endtime=UTC(endtime or BIG_UTC)) return st
def test_phase_weighted_stack(self): """Test the utils.stacking.PWS_stack.""" # Generate synth data import numpy as np from obspy import Stream, Trace synth = Stream(Trace()) synth[0].data = np.zeros(200) synth[0].data[100] = 1.0 sine_x = np.arange(0, 10.0, 0.5) damped_sine = np.exp(-sine_x) * np.sin(2 * np.pi * sine_x) synth[0].data = np.convolve(synth[0].data, damped_sine) # Normalize: synth[0].data = synth[0].data / synth[0].data.max() # maximum_synth = synth[0].data.max() # RMS_max = np.sqrt(np.mean(np.square(synth[0].data))) streams = [synth.copy() for i in range(10)] stack = PWS_stack(streams, weight=2, normalize=True) # Check length is preserved self.assertEqual(len(synth[0].data), len(stack[0].data))
def pre_picking_processing(self): # read Afile self.st, self.FirstStn, self.fileHeader = self.unpackAfile(self.A_File) # remove station not in nsta24 database self.checkNonDBsta(self.st, self.DB.db_nsta) # rotate OBS stations if not self.EEW_Mode: OBS_rotate(self.st, Config_File=self.config) # remove station in DONT_USE_LIST self.remove_sta_not_use(self.st, checklist=self.config['Misc']['CheckList_sta']) # remove station in addon block list self.remove_addon_block_list( self.st, blocklist=self.config['Misc']['Addon_BlockList']) # remove stations is outside velocty model self.Remove_Stn_Outside_Grid(self.st, nsta_db=self.DB.db_nsta) # de-mean without zeros self.demean_wo_zero() # copy trace for intensity use st_intensity_tmp = Stream() st_intensity_tmp += self.st.select(channel='Ch1') st_intensity_tmp += self.st.select(channel='Ch2') st_intensity_tmp += self.st.select(channel='Ch3') self.st_intensity = st_intensity_tmp.copy() # copy streams for calculate Magnitude self.stmag = self.st.copy() # remove no data Streams self.checkZeroGap_new(self.st) return self.st, self.st_intensity, self.stmag, self.FirstStn, self.fileHeader
def _process_streams(self, stream, pre_processed, process_cores=1, parallel=False, ignore_bad_data=False, select_used_chans=True): """ Process a stream based on the template parameters. """ if select_used_chans: template_stream = Stream() for tr in self.template.st: template_stream += stream.select(network=tr.stats.network, station=tr.stats.station, location=tr.stats.location, channel=tr.stats.channel) else: template_stream = stream if not pre_processed: processed_streams = _group_process(template_group=[self.template], cores=process_cores, parallel=parallel, stream=template_stream.copy(), daylong=False, ignore_length=False, overlap=0.0, ignore_bad_data=ignore_bad_data) processed_stream = Stream() for p in processed_streams: processed_stream += p processed_stream.merge(method=1) Logger.debug(processed_stream) else: processed_stream = stream.merge() return processed_stream
def test_coincidenceTriggerWithSimilarityChecking(self): """ Test network coincidence trigger with cross correlation similarity checking of given event templates. """ st = Stream() files = ["BW.UH1._.SHZ.D.2010.147.cut.slist.gz", "BW.UH2._.SHZ.D.2010.147.cut.slist.gz", "BW.UH3._.SHZ.D.2010.147.cut.slist.gz", "BW.UH3._.SHN.D.2010.147.cut.slist.gz", "BW.UH3._.SHE.D.2010.147.cut.slist.gz", "BW.UH4._.EHZ.D.2010.147.cut.slist.gz"] for filename in files: filename = os.path.join(self.path, filename) st += read(filename) # some prefiltering used for UH network st.filter('bandpass', freqmin=10, freqmax=20) # set up template event streams times = ["2010-05-27T16:24:33.095000", "2010-05-27T16:27:30.370000"] templ = {} for t in times: t = UTCDateTime(t) st_ = st.select(station="UH3").slice(t, t + 2.5).copy() templ.setdefault("UH3", []).append(st_) times = ["2010-05-27T16:27:30.574999"] for t in times: t = UTCDateTime(t) st_ = st.select(station="UH1").slice(t, t + 2.5).copy() templ.setdefault("UH1", []).append(st_) trace_ids = {"BW.UH1..SHZ": 1, "BW.UH2..SHZ": 1, "BW.UH3..SHZ": 1, "BW.UH4..EHZ": 1} similarity_thresholds = {"UH1": 0.8, "UH3": 0.7} with warnings.catch_warnings(record=True) as w: # avoid getting influenced by the warning filters getting set up # differently in obspy-runtests. # (e.g. depending on options "-v" and "-q") warnings.resetwarnings() trig = coincidence_trigger( "classicstalta", 5, 1, st.copy(), 4, sta=0.5, lta=10, trace_ids=trace_ids, event_templates=templ, similarity_threshold=similarity_thresholds) # two warnings get raised self.assertEqual(len(w), 2) # check floats in resulting dictionary separately self.assertAlmostEqual(trig[0].pop('duration'), 3.9600000381469727) self.assertAlmostEqual(trig[1].pop('duration'), 1.9900000095367432) self.assertAlmostEqual(trig[2].pop('duration'), 1.9200000762939453) self.assertAlmostEqual(trig[3].pop('duration'), 3.9200000762939453) self.assertAlmostEqual(trig[0]['similarity'].pop('UH1'), 0.94149447384) self.assertAlmostEqual(trig[0]['similarity'].pop('UH3'), 1) self.assertAlmostEqual(trig[1]['similarity'].pop('UH1'), 0.65228204570) self.assertAlmostEqual(trig[1]['similarity'].pop('UH3'), 0.72679293429) self.assertAlmostEqual(trig[2]['similarity'].pop('UH1'), 0.89404458774) self.assertAlmostEqual(trig[2]['similarity'].pop('UH3'), 0.74581409371) self.assertAlmostEqual(trig[3]['similarity'].pop('UH1'), 1) self.assertAlmostEqual(trig[3]['similarity'].pop('UH3'), 1) remaining_results = \ [{'coincidence_sum': 4.0, 'similarity': {}, 'stations': ['UH3', 'UH2', 'UH1', 'UH4'], 'time': UTCDateTime(2010, 5, 27, 16, 24, 33, 210000), 'trace_ids': ['BW.UH3..SHZ', 'BW.UH2..SHZ', 'BW.UH1..SHZ', 'BW.UH4..EHZ']}, {'coincidence_sum': 3.0, 'similarity': {}, 'stations': ['UH3', 'UH1', 'UH2'], 'time': UTCDateTime(2010, 5, 27, 16, 25, 26, 710000), 'trace_ids': ['BW.UH3..SHZ', 'BW.UH1..SHZ', 'BW.UH2..SHZ']}, {'coincidence_sum': 3.0, 'similarity': {}, 'stations': ['UH2', 'UH1', 'UH3'], 'time': UTCDateTime(2010, 5, 27, 16, 27, 2, 260000), 'trace_ids': ['BW.UH2..SHZ', 'BW.UH1..SHZ', 'BW.UH3..SHZ']}, {'coincidence_sum': 4.0, 'similarity': {}, 'stations': ['UH3', 'UH2', 'UH1', 'UH4'], 'time': UTCDateTime(2010, 5, 27, 16, 27, 30, 510000), 'trace_ids': ['BW.UH3..SHZ', 'BW.UH2..SHZ', 'BW.UH1..SHZ', 'BW.UH4..EHZ']}] self.assertEqual(trig, remaining_results)
def read_waveform_data(self, starttime, endtime, pre_pad=0., post_pad=0.): """ Read in waveform data from the archive between two times. Supports all formats currently supported by ObsPy, including: "MSEED", "SAC", "SEGY", "GSE2" . Optionally, read data with some pre- and post-pad, and for all stations in the archive - this will be stored in `data.raw_waveforms`, while `data.waveforms` will contain only data for selected stations between `starttime` and `endtime`. Parameters ---------- starttime : `obspy.UTCDateTime` object Timestamp from which to read waveform data. endtime : `obspy.UTCDateTime` object Timestamp up to which to read waveform data. pre_pad : float, optional Additional pre pad of data to read. Defaults to 0. post_pad : float, optional Additional post pad of data to read. Defaults to 0. Returns ------- data : :class:`~quakemigrate.io.data.WaveformData` object Object containing the waveform data read from the archive that satisfies the query. """ # Ensure pre-pad and post-pad are not negative. pre_pad = max(0., pre_pad) post_pad = max(0., post_pad) data = WaveformData(starttime=starttime, endtime=endtime, stations=self.stations, read_all_stations=self.read_all_stations, resample=self.resample, upfactor=self.upfactor, response_inv=self.response_inv, water_level=self.water_level, pre_filt=self.pre_filt, remove_full_response=self.remove_full_response, pre_pad=pre_pad, post_pad=post_pad) files = self._load_from_path(starttime - pre_pad, endtime + post_pad) st = Stream() try: first = next(files) files = chain([first], files) for file in files: file = str(file) try: read_start = starttime - pre_pad read_end = endtime + post_pad st += read(file, starttime=read_start, endtime=read_end, nearest_sample=True) except TypeError: logging.info(f"File not compatible with ObsPy - {file}") continue # Merge all traces with contiguous data, or overlapping data which # exactly matches (== st._cleanup(); i.e. no clobber) st.merge(method=-1) # Make copy of raw waveforms to output if requested data.raw_waveforms = st.copy() # Ensure data is timestamped "on-sample" (i.e. an integer number # of samples after midnight). Otherwise the data will be implicitly # shifted when it is used to calculate the onset function / # migrated. st = util.shift_to_sample(st, interpolate=self.interpolate) if self.read_all_stations: # Re-populate st with only stations in station file st_selected = Stream() for station in self.stations: st_selected += st.select(station=station) st = st_selected.copy() del st_selected if pre_pad != 0. or post_pad != 0.: # Trim data between start and end time for tr in st: tr.trim(starttime=starttime, endtime=endtime, nearest_sample=True) if not bool(tr): st.remove(tr) # Test if the stream is completely empty # (see __nonzero__ for `obspy.Stream` object) if not bool(st): raise util.DataGapException # Add cleaned stream to `waveforms` data.waveforms = st except StopIteration: raise util.ArchiveEmptyException return data
lines=file.readlines() split_line = lines[0].split() t2 = UTCDateTime(split_line[1]) date_label2 = split_line[1][0:10] fname1 = 'HD' + date_label1 + '_' + date_label2 + '_tshift.mseed' tshift = Stream() tshift = read(fname1) fname1 = 'HD' + date_label1 + '_' + date_label2 + '_amp_ratio.mseed' amp_ratio = Stream() amp_ratio = read(fname1) fname2 = 'HD' + date_label1 + '_' + date_label2 + '_amp_ave.mseed' amp_ave = Stream() amp_ave = read(fname2) tshift_full = tshift.copy() tshift.decimate(decimate_fac) amp_ratio.decimate(decimate_fac) amp_ave.decimate(decimate_fac) #print(f'len(tshift): ' {len(tshift):4d} 'len(tshift[0].data): ' {len(tshift[0].data:4d}) print(f'len(tshift): {len(tshift):4d}') print(f'len(amp_ave): {len(amp_ave):4d}') print(f'len(amp_ratio): {len(amp_ratio):4d}') nt = len(tshift[0].data) dt = tshift[0].stats.delta print(f'nt: {nt:6d}') #%% Make grid of slownesses slowR_n = int(1 + (slowR_hi - slowR_lo)/slow_delta) # number of slownesses
for master in detections: keep = True for slave in detections: if not master == slave and abs(master.detect_time - slave.detect_time) <= 1.0: # If the events are within 1s of each other then test which # was the 'best' match, strongest detection if not master.detect_val > slave.detect_val: keep = False break if keep: unique_detections.append(master) print("We made a total of " + str(len(unique_detections)) + " detections") for detection in unique_detections: print( "Detection at :" + str(detection.detect_time) + " for template " + detection.template_name + " with a cross-correlation sum of: " + str(detection.detect_val) ) # We can plot these too stplot = st.copy() template = templates[template_names.index(detection.template_name)] lags = sorted([tr.stats.starttime for tr in template]) maxlag = lags[-1] - lags[0] stplot.trim(starttime=detection.detect_time - 10, endtime=detection.detect_time + maxlag + 10) plotting.detection_multiplot(stplot, template, [detection.detect_time.datetime])
class WaveformPlotting(object): """ Class that provides several solutions for plotting large and small waveform data sets. .. warning:: This class should NOT be used directly, instead use the :meth:`~obspy.core.stream.Stream.plot` method of the ObsPy :class:`~obspy.core.stream.Stream` or :class:`~obspy.core.trace.Trace` objects. It uses matplotlib to plot the waveforms. """ def __init__(self, **kwargs): """ Checks some variables and maps the kwargs to class variables. """ self.kwargs = kwargs self.stream = kwargs.get('stream') # Check if it is a Stream or a Trace object. if isinstance(self.stream, Trace): self.stream = Stream([self.stream]) elif not isinstance(self.stream, Stream): msg = 'Plotting is only supported for Stream or Trace objects.' raise TypeError(msg) # Stream object should contain at least one Trace if len(self.stream) < 1: msg = "Empty stream object" raise IndexError(msg) self.stream = self.stream.copy() # Type of the plot. self.type = kwargs.get('type', 'normal') # Start- and endtimes of the plots. self.starttime = kwargs.get('starttime', None) self.endtime = kwargs.get('endtime', None) self.fig_obj = kwargs.get('fig', None) # If no times are given take the min/max values from the stream object. if not self.starttime: self.starttime = min([trace.stats.starttime for trace in self.stream]) if not self.endtime: self.endtime = max([trace.stats.endtime for trace in self.stream]) # Map stream object and slice just in case. self.stream.trim(self.starttime, self.endtime) # normalize times if self.type == 'relative': dt = self.starttime # fix plotting boundaries self.endtime = UTCDateTime(self.endtime - self.starttime) self.starttime = UTCDateTime(0) # fix stream times for tr in self.stream: tr.stats.starttime = UTCDateTime(tr.stats.starttime - dt) # Whether to use straight plotting or the fast minmax method. self.plotting_method = kwargs.get('method', 'fast') # Below that value the data points will be plotted normally. Above it # the data will be plotted using a different approach (details see # below). Can be overwritten by the above self.plotting_method kwarg. self.max_npts = 400000 # If automerge is enabled. Merge traces with the same id for the plot. self.automerge = kwargs.get('automerge', True) # If equal_scale is enabled all plots are equally scaled. self.equal_scale = kwargs.get('equal_scale', True) # Set default values. # The default value for the size is determined dynamically because # there might be more than one channel to plot. self.size = kwargs.get('size', None) # Values that will be used to calculate the size of the plot. self.default_width = 800 self.default_height_per_channel = 250 if not self.size: self.width = 800 # Check the kind of plot. if self.type == 'dayplot': self.height = 600 else: # One plot for each trace. if self.automerge: count = self.__getMergablesIds() count = len(count) else: count = len(self.stream) self.height = count * 250 else: self.width, self.height = self.size # Interval length in minutes for dayplot. self.interval = 60 * kwargs.get('interval', 15) # Scaling. self.vertical_scaling_range = kwargs.get('vertical_scaling_range', None) # Dots per inch of the plot. Might be useful for printing plots. self.dpi = kwargs.get('dpi', 100) # Color of the graph. if self.type == 'dayplot': self.color = kwargs.get('color', ('#B2000F', '#004C12', '#847200', '#0E01FF')) if isinstance(self.color, basestring): self.color = (self.color,) self.number_of_ticks = kwargs.get('number_of_ticks', None) else: self.color = kwargs.get('color', 'k') self.number_of_ticks = kwargs.get('number_of_ticks', 4) # Background, face and grid color. self.background_color = kwargs.get('bgcolor', 'w') self.face_color = kwargs.get('face_color', 'w') self.grid_color = kwargs.get('grid_color', 'black') self.grid_linewidth = kwargs.get('grid_linewidth', 0.5) self.grid_linestyle = kwargs.get('grid_linestyle', ':') # Transparency. Overwrites background and facecolor settings. self.transparent = kwargs.get('transparent', False) if self.transparent: self.background_color = None # Ticks. self.tick_format = kwargs.get('tick_format', '%H:%M:%S') self.tick_rotation = kwargs.get('tick_rotation', 0) # Whether or not to save a file. self.outfile = kwargs.get('outfile') self.handle = kwargs.get('handle') # File format of the resulting file. Usually defaults to PNG but might # be dependent on your matplotlib backend. self.format = kwargs.get('format') self.show = kwargs.get('show', True) self.block = kwargs.get('block', True) # plot parameters options self.x_labels_size = kwargs.get('x_labels_size', 8) self.y_labels_size = kwargs.get('y_labels_size', 8) self.title_size = kwargs.get('title_size', 10) self.linewidth = kwargs.get('linewidth', 0.4) self.linestyle = kwargs.get('linestyle', '-') self.subplots_adjust_left = kwargs.get('subplots_adjust_left', 0.12) self.subplots_adjust_right = kwargs.get('subplots_adjust_right', 0.88) self.subplots_adjust_top = kwargs.get('subplots_adjust_top', 0.95) self.subplots_adjust_bottom = kwargs.get('subplots_adjust_bottom', 0.1) self.right_vertical_labels = kwargs.get('right_vertical_labels', False) self.one_tick_per_line = kwargs.get('one_tick_per_line', False) self.show_y_UTC_label = kwargs.get('show_y_UTC_label', True) self.title = kwargs.get('title', self.stream[0].id) def __del__(self): """ Destructor closes the figure instance if it has been created by the class. """ if self.kwargs.get("fig", None) is None: plt.close() def __getMergeId(self, tr): tr_id = tr.id # don't merge normal traces with previews try: if tr.stats.preview: tr_id += 'preview' except KeyError: pass # don't merge traces with different processing steps try: if tr.stats.processing: tr_id += str(tr.stats.processing) except KeyError: pass return tr_id def __getMergablesIds(self): ids = [] for tr in self.stream: tr_id = self.__getMergeId(tr) if not tr_id in ids: ids.append(tr_id) return ids def plotWaveform(self, *args, **kwargs): """ Creates a graph of any given ObsPy Stream object. It either saves the image directly to the file system or returns an binary image string. For all color values you can use legit HTML names, HTML hex strings (e.g. '#eeefff') or you can pass an R , G , B tuple, where each of R , G , B are in the range [0, 1]. You can also use single letters for basic built-in colors ('b' = blue, 'g' = green, 'r' = red, 'c' = cyan, 'm' = magenta, 'y' = yellow, 'k' = black, 'w' = white) and gray shades can be given as a string encoding a float in the 0-1 range. """ # Setup the figure if not passed explicitly. if not self.fig_obj: self.__setupFigure() else: self.fig = self.fig_obj # Determine kind of plot and do the actual plotting. if self.type == 'dayplot': self.plotDay(*args, **kwargs) else: self.plot(*args, **kwargs) # Adjust the subplot so there is always a fixed margin on every side if self.type != 'dayplot': fract_y = 60.0 / self.height fract_y2 = 40.0 / self.height fract_x = 80.0 / self.width self.fig.subplots_adjust(top=1.0 - fract_y, bottom=fract_y2, left=fract_x, right=1.0 - fract_x / 2) self.fig.canvas.draw() # The following just serves as a unified way of saving and displaying # the plots. if not self.transparent: extra_args = {'dpi': self.dpi, 'facecolor': self.face_color, 'edgecolor': self.face_color} else: extra_args = {'dpi': self.dpi, 'transparent': self.transparent} if self.outfile: # If format is set use it. if self.format: self.fig.savefig(self.outfile, format=self.format, **extra_args) # Otherwise use format from self.outfile or default to PNG. else: self.fig.savefig(self.outfile, **extra_args) else: # Return an binary imagestring if not self.outfile but self.format. if self.format: imgdata = StringIO.StringIO() self.fig.savefig(imgdata, format=self.format, **extra_args) imgdata.seek(0) return imgdata.read() elif self.handle: return self.fig else: if not self.fig_obj and self.show: try: plt.show(block=self.block) except: plt.show() def plot(self, *args, **kwargs): """ Plot the Traces showing one graph per Trace. Plots the whole time series for self.max_npts points and less. For more points it plots minmax values. """ stream_new = [] # Just remove empty traces. if not self.automerge: for tr in self.stream: stream_new.append([]) if len(tr.data): stream_new[-1].append(tr) else: # Generate sorted list of traces (no copy) # Sort order, id, starttime, endtime ids = self.__getMergablesIds() for id in ids: stream_new.append([]) for tr in self.stream: tr_id = self.__getMergeId(tr) if tr_id == id: # does not copy the elements of the data array tr_ref = copy(tr) # Trim does nothing if times are outside if self.starttime >= tr_ref.stats.endtime or \ self.endtime <= tr_ref.stats.starttime: continue if tr_ref.data.size: stream_new[-1].append(tr_ref) # delete if empty list if not len(stream_new[-1]): stream_new.pop() continue stream_new[-1].sort(key=lambda x: x.stats.endtime) stream_new[-1].sort(key=lambda x: x.stats.starttime) # If everything is lost in the process raise an Exception. if not len(stream_new): raise Exception("Nothing to plot") # Create helper variable to track ids and min/max/mean values. self.stats = [] # Loop over each Trace and call the appropriate plotting method. self.axis = [] for _i, tr in enumerate(stream_new): # Each trace needs to have the same sampling rate. sampling_rates = set([_tr.stats.sampling_rate for _tr in tr]) if len(sampling_rates) > 1: msg = "All traces with the same id need to have the same " + \ "sampling rate." raise Exception(msg) sampling_rate = sampling_rates.pop() if self.background_color: ax = self.fig.add_subplot(len(stream_new), 1, _i + 1, axisbg=self.background_color) else: ax = self.fig.add_subplot(len(stream_new), 1, _i + 1) self.axis.append(ax) # XXX: Also enable the minmax plotting for previews. if self.plotting_method != 'full' and \ ((self.endtime - self.starttime) * sampling_rate > self.max_npts): self.__plotMinMax(stream_new[_i], ax, *args, **kwargs) else: self.__plotStraight(stream_new[_i], ax, *args, **kwargs) # Set ticks. self.__plotSetXTicks() self.__plotSetYTicks() @deprecated_keywords({'swap_time_axis': None}) def plotDay(self, *args, **kwargs): """ Extend the seismogram. """ # Merge and trim to pad. self.stream.merge() if len(self.stream) != 1: msg = "All traces need to be of the same id for a dayplot" raise ValueError(msg) self.stream.trim(self.starttime, self.endtime, pad=True) # Get minmax array. self.__dayplotGetMinMaxValues(self, *args, **kwargs) # Normalize array self.__dayplotNormalizeValues(self, *args, **kwargs) # Get timezone information. If none is given, use local time. self.time_offset = kwargs.get('time_offset', round((UTCDateTime(datetime.now()) - UTCDateTime()) / 3600.0, 2)) self.timezone = kwargs.get('timezone', 'local time') # Try to guess how many steps are needed to advance one full time unit. self.repeat = None intervals = self.extreme_values.shape[0] if self.interval < 60 and 60 % self.interval == 0: self.repeat = 60 / self.interval elif self.interval < 1800 and 3600 % self.interval == 0: self.repeat = 3600 / self.interval # Otherwise use a maximum value of 10. else: if intervals >= 10: self.repeat = 10 else: self.repeat = intervals # Create axis to plot on. if self.background_color: ax = self.fig.add_subplot(1, 1, 1, axisbg=self.background_color) else: ax = self.fig.add_subplot(1, 1, 1) # Adjust the subplots self.fig.subplots_adjust(left=self.subplots_adjust_left, right=self.subplots_adjust_right, top=self.subplots_adjust_top, bottom=self.subplots_adjust_bottom) # Create x_value_array. aranged_array = np.arange(self.width) x_values = np.empty(2 * self.width) x_values[0::2] = aranged_array x_values[1::2] = aranged_array intervals = self.extreme_values.shape[0] # Loop over each step. for _i in xrange(intervals): # Create offset array. y_values = np.ma.empty(self.width * 2) y_values.fill(intervals - (_i + 1)) # Add min and max values. y_values[0::2] += self.extreme_values[_i, :, 0] y_values[1::2] += self.extreme_values[_i, :, 1] # Plot the values. ax.plot(x_values, y_values, color=self.color[_i % len(self.color)], linewidth=self.linewidth, linestyle=self.linestyle) # Plot the scale, if required. scale_unit = kwargs.get("data_unit", None) if scale_unit is not None: self._plotDayplotScale(unit=scale_unit) # Set ranges. ax.set_xlim(0, self.width - 1) ax.set_ylim(-0.3, intervals + 0.3) self.axis = [ax] # Set ticks. self.__dayplotSetYTicks(*args, **kwargs) self.__dayplotSetXTicks(*args, **kwargs) # Choose to show grid but only on the x axis. self.fig.axes[0].grid(color=self.grid_color, linestyle=self.grid_linestyle, linewidth=self.grid_linewidth) self.fig.axes[0].yaxis.grid(False) # Set the title of the plot. self.fig.suptitle(self.title, fontsize=self.title_size) # Now try to plot some events. events = kwargs.get("events", []) # Potentially download some events with the help of obspy.neries. if "min_magnitude" in events: try: from obspy.neries import Client c = Client() events = c.getEvents(min_datetime=self.starttime, max_datetime=self.endtime, format="catalog", min_magnitude=events["min_magnitude"]) except Exception, e: msg = "Could not download the events because of '%s: %s'." % \ (e.__class__.__name__, e.message) warnings.warn(msg) if events: for event in events: self._plotEvent(event)
def test_coincidence_trigger_with_similarity_checking(self): """ Test network coincidence trigger with cross correlation similarity checking of given event templates. """ st = Stream() files = ["BW.UH1._.SHZ.D.2010.147.cut.slist.gz", "BW.UH2._.SHZ.D.2010.147.cut.slist.gz", "BW.UH3._.SHZ.D.2010.147.cut.slist.gz", "BW.UH3._.SHN.D.2010.147.cut.slist.gz", "BW.UH3._.SHE.D.2010.147.cut.slist.gz", "BW.UH4._.EHZ.D.2010.147.cut.slist.gz"] for filename in files: filename = os.path.join(self.path, filename) st += read(filename) # some prefiltering used for UH network st.filter('bandpass', freqmin=10, freqmax=20) # set up template event streams times = ["2010-05-27T16:24:33.095000", "2010-05-27T16:27:30.370000"] templ = {} for t in times: t = UTCDateTime(t) st_ = st.select(station="UH3").slice(t, t + 2.5).copy() templ.setdefault("UH3", []).append(st_) times = ["2010-05-27T16:27:30.574999"] for t in times: t = UTCDateTime(t) st_ = st.select(station="UH1").slice(t, t + 2.5).copy() templ.setdefault("UH1", []).append(st_) # add another template with different SEED ID, it should be ignored # (this can happen when using many templates over a long time period # and instrument changes over time) st_ = st_.copy() for tr in st_: tr.stats.channel = 'X' + tr.stats.channel[1:] templ['UH1'].insert(0, st_) trace_ids = {"BW.UH1..SHZ": 1, "BW.UH2..SHZ": 1, "BW.UH3..SHZ": 1, "BW.UH4..EHZ": 1} similarity_thresholds = {"UH1": 0.8, "UH3": 0.7} with warnings.catch_warnings(record=True) as w: # avoid getting influenced by the warning filters getting set up # differently in obspy-runtests. # (e.g. depending on options "-v" and "-q") warnings.resetwarnings() trig = coincidence_trigger( "classicstalta", 5, 1, st.copy(), 4, sta=0.5, lta=10, trace_ids=trace_ids, event_templates=templ, similarity_threshold=similarity_thresholds) # four warnings get raised self.assertEqual(len(w), 4) self.assertEqual( str(w[0].message), "At least one trace's ID was not found in the trace ID list and " "was disregarded (BW.UH3..SHN)") self.assertEqual( str(w[1].message), "At least one trace's ID was not found in the trace ID list and " "was disregarded (BW.UH3..SHE)") self.assertEqual( str(w[2].message), 'Skipping trace BW.UH1..XHZ in template correlation (not present ' 'in stream to check).') self.assertEqual( str(w[3].message), "Skipping template(s) for station 'UH1': No common SEED IDs when " "comparing template (BW.UH1..XHZ) and data streams (BW.UH1..SHZ, " "BW.UH2..SHZ, BW.UH3..SHE, BW.UH3..SHN, BW.UH3..SHZ, " "BW.UH4..EHZ).") # check floats in resulting dictionary separately self.assertAlmostEqual(trig[0].pop('duration'), 3.96, places=6) self.assertAlmostEqual(trig[1].pop('duration'), 1.99, places=6) self.assertAlmostEqual(trig[2].pop('duration'), 1.92, places=6) self.assertAlmostEqual(trig[3].pop('duration'), 3.92, places=6) self.assertAlmostEqual(trig[0]['similarity'].pop('UH1'), 0.94149447384, places=6) self.assertAlmostEqual(trig[0]['similarity'].pop('UH3'), 1, places=6) self.assertAlmostEqual(trig[1]['similarity'].pop('UH1'), 0.65228204570, places=6) self.assertAlmostEqual(trig[1]['similarity'].pop('UH3'), 0.72679293429, places=6) self.assertAlmostEqual(trig[2]['similarity'].pop('UH1'), 0.89404458774, places=6) self.assertAlmostEqual(trig[2]['similarity'].pop('UH3'), 0.74581409371, places=6) self.assertAlmostEqual(trig[3]['similarity'].pop('UH1'), 1, places=6) self.assertAlmostEqual(trig[3]['similarity'].pop('UH3'), 1, places=6) remaining_results = \ [{'coincidence_sum': 4.0, 'similarity': {}, 'stations': ['UH3', 'UH2', 'UH1', 'UH4'], 'time': UTCDateTime(2010, 5, 27, 16, 24, 33, 210000), 'trace_ids': ['BW.UH3..SHZ', 'BW.UH2..SHZ', 'BW.UH1..SHZ', 'BW.UH4..EHZ']}, {'coincidence_sum': 3.0, 'similarity': {}, 'stations': ['UH3', 'UH1', 'UH2'], 'time': UTCDateTime(2010, 5, 27, 16, 25, 26, 710000), 'trace_ids': ['BW.UH3..SHZ', 'BW.UH1..SHZ', 'BW.UH2..SHZ']}, {'coincidence_sum': 3.0, 'similarity': {}, 'stations': ['UH2', 'UH1', 'UH3'], 'time': UTCDateTime(2010, 5, 27, 16, 27, 2, 260000), 'trace_ids': ['BW.UH2..SHZ', 'BW.UH1..SHZ', 'BW.UH3..SHZ']}, {'coincidence_sum': 4.0, 'similarity': {}, 'stations': ['UH3', 'UH2', 'UH1', 'UH4'], 'time': UTCDateTime(2010, 5, 27, 16, 27, 30, 510000), 'trace_ids': ['BW.UH3..SHZ', 'BW.UH2..SHZ', 'BW.UH1..SHZ', 'BW.UH4..EHZ']}] self.assertEqual(trig, remaining_results)
def test_coincidenceTriggerWithSimilarityChecking(self): """ Test network coincidence trigger with cross correlation similarity checking of given event templates. """ st = Stream() files = [ "BW.UH1._.SHZ.D.2010.147.cut.slist.gz", "BW.UH2._.SHZ.D.2010.147.cut.slist.gz", "BW.UH3._.SHZ.D.2010.147.cut.slist.gz", "BW.UH3._.SHN.D.2010.147.cut.slist.gz", "BW.UH3._.SHE.D.2010.147.cut.slist.gz", "BW.UH4._.EHZ.D.2010.147.cut.slist.gz" ] for filename in files: filename = os.path.join(self.path, filename) st += read(filename) # some prefiltering used for UH network st.filter('bandpass', freqmin=10, freqmax=20) # set up template event streams times = ["2010-05-27T16:24:33.095000", "2010-05-27T16:27:30.370000"] templ = {} for t in times: t = UTCDateTime(t) st_ = st.select(station="UH3").slice(t, t + 2.5).copy() templ.setdefault("UH3", []).append(st_) times = ["2010-05-27T16:27:30.574999"] for t in times: t = UTCDateTime(t) st_ = st.select(station="UH1").slice(t, t + 2.5).copy() templ.setdefault("UH1", []).append(st_) trace_ids = { "BW.UH1..SHZ": 1, "BW.UH2..SHZ": 1, "BW.UH3..SHZ": 1, "BW.UH4..EHZ": 1 } similarity_thresholds = {"UH1": 0.8, "UH3": 0.7} trig = coincidenceTrigger("classicstalta", 5, 1, st.copy(), 4, sta=0.5, lta=10, trace_ids=trace_ids, event_templates=templ, similarity_threshold=similarity_thresholds) # check floats in resulting dictionary separately self.assertAlmostEqual(trig[0].pop('duration'), 3.9600000381469727) self.assertAlmostEqual(trig[1].pop('duration'), 1.9900000095367432) self.assertAlmostEqual(trig[2].pop('duration'), 1.9200000762939453) self.assertAlmostEqual(trig[3].pop('duration'), 3.9200000762939453) self.assertAlmostEqual(trig[0]['similarity'].pop('UH1'), 0.94149447384) self.assertAlmostEqual(trig[0]['similarity'].pop('UH3'), 1) self.assertAlmostEqual(trig[1]['similarity'].pop('UH1'), 0.65228204570) self.assertAlmostEqual(trig[1]['similarity'].pop('UH3'), 0.72679293429) self.assertAlmostEqual(trig[2]['similarity'].pop('UH1'), 0.89404458774) self.assertAlmostEqual(trig[2]['similarity'].pop('UH3'), 0.74581409371) self.assertAlmostEqual(trig[3]['similarity'].pop('UH1'), 1) self.assertAlmostEqual(trig[3]['similarity'].pop('UH3'), 1) remaining_results = \ [{'coincidence_sum': 4.0, 'similarity': {}, 'stations': ['UH3', 'UH2', 'UH1', 'UH4'], 'time': UTCDateTime(2010, 5, 27, 16, 24, 33, 210000), 'trace_ids': ['BW.UH3..SHZ', 'BW.UH2..SHZ', 'BW.UH1..SHZ', 'BW.UH4..EHZ']}, {'coincidence_sum': 3.0, 'similarity': {}, 'stations': ['UH3', 'UH1', 'UH2'], 'time': UTCDateTime(2010, 5, 27, 16, 25, 26, 710000), 'trace_ids': ['BW.UH3..SHZ', 'BW.UH1..SHZ', 'BW.UH2..SHZ']}, {'coincidence_sum': 3.0, 'similarity': {}, 'stations': ['UH2', 'UH1', 'UH3'], 'time': UTCDateTime(2010, 5, 27, 16, 27, 2, 260000), 'trace_ids': ['BW.UH2..SHZ', 'BW.UH1..SHZ', 'BW.UH3..SHZ']}, {'coincidence_sum': 4.0, 'similarity': {}, 'stations': ['UH3', 'UH2', 'UH1', 'UH4'], 'time': UTCDateTime(2010, 5, 27, 16, 27, 30, 510000), 'trace_ids': ['BW.UH3..SHZ', 'BW.UH2..SHZ', 'BW.UH1..SHZ', 'BW.UH4..EHZ']}] self.assertTrue(trig == remaining_results)
def run_tutorial(plot=False, multiplex=True, return_streams=False, cores=4, verbose=False): """ Run the tutorial. :return: detections """ client = Client("GEONET", debug=verbose) cat = client.get_events(minlatitude=-40.98, maxlatitude=-40.85, minlongitude=175.4, maxlongitude=175.5, starttime=UTCDateTime(2016, 5, 1), endtime=UTCDateTime(2016, 5, 20)) print(f"Downloaded a catalog of {len(cat)} events") # This gives us a catalog of events - it takes a while to download all # the information, so give it a bit! # We will generate a five station, multi-channel detector. cat = filter_picks(catalog=cat, top_n_picks=5) stachans = list( set([(pick.waveform_id.station_code, pick.waveform_id.channel_code) for event in cat for pick in event.picks])) # In this tutorial we will only work on one cluster, defined spatially. # You can work on multiple clusters, or try to whole set. clusters = catalog_cluster(catalog=cat, metric="distance", thresh=2, show=False) # We will work on the largest cluster cluster = sorted(clusters, key=lambda c: len(c))[-1] # This cluster contains 32 events, we will now download and trim the # waveforms. Note that each chanel must start at the same time and be the # same length for multiplexing. If not multiplexing EQcorrscan will # maintain the individual differences in time between channels and delay # the detection statistics by that amount before stacking and detection. client = Client('GEONET') design_set = [] st = Stream() for event in cluster: print(f"Downloading for event {event.resource_id.id}") bulk_info = [] t1 = event.origins[0].time t2 = t1 + 25.1 # Have to download extra data, otherwise GeoNet will # trim wherever suits. t1 -= 0.1 for station, channel in stachans: try: st += client.get_waveforms('NZ', station, '*', channel[0:2] + '?', t1, t2) except IncompleteRead: print(f"Could not download for {station} {channel}") print(f"Downloaded {len(st)} channels") for event in cluster: t1 = event.origins[0].time t2 = t1 + 25 design_set.append(st.copy().trim(t1, t2)) # Construction of the detector will process the traces, then align them, # before multiplexing. print("Making detector") detector = subspace.Detector() detector.construct(streams=design_set, lowcut=2.0, highcut=9.0, filt_order=4, sampling_rate=20, multiplex=multiplex, name='Wairarapa1', align=True, reject=0.2, shift_len=6, plot=plot).partition(9) print("Constructed Detector") if plot: detector.plot() # We also want the continuous stream to detect in. t1 = UTCDateTime(2016, 5, 11, 19) t2 = UTCDateTime(2016, 5, 11, 20) # We are going to look in a single hour just to minimize cost, but you can # run for much longer. bulk_info = [('NZ', stachan[0], '*', stachan[1][0] + '?' + stachan[1][-1], t1, t2) for stachan in detector.stachans] print("Downloading continuous data") st = client.get_waveforms_bulk(bulk_info) st.merge().detrend('simple').trim(starttime=t1, endtime=t2) # We set a very low threshold because the detector is not that great, we # haven't aligned it particularly well - however, at this threshold we make # two real detections. print("Computing detections") detections, det_streams = detector.detect(st=st, threshold=0.4, trig_int=2, extract_detections=True, cores=cores) if return_streams: return detections, det_streams else: return detections
def p_compare2(event_no, traces1, traces2, start_buff, end_buff, taper, taper_frac, plot_scale_fac, filt, freq_min, freq_max, min_dist, max_dist, norm_each, dist_norm, basin_width, basin, CI_only, component1, component2, fig_inc): # component 1 is E, 2 is N, 3 is Z # H is cvmhy, H_simp is cvmhn # S is cvms426-223, S_simp is cvms400-100 # norm_each == 1 scales the peak of each trace to the same size # otherwise, the amplitude is normalized to 10 km, with the scale set with plot_scale_fac 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 matplotlib.pyplot as plt import time import sys # don't show any warnings import warnings if not sys.warnoptions: warnings.simplefilter("ignore") start_time_wc = time.time() # Box limits Lon_min = -118.8 Lat_min = 33.5 Lon_max = -117.5 Lat_max = 34.6 if event_no == '15481673': # no 29 event_name = 'LaHa2014' elif event_no == '10410337': # no 14 event_name = 'Ingl2009' elif event_no == '14383980': # no 29 event_name = 'Chin2008' elif event_no == '14312160': # no 29 event_name = 'Chat2007' elif event_no == '9703873': # no 29 event_name = 'BaHa2001' else: print('Number does not correspond to a valid event') sys.exit() verbose = False # print every distance of a station if component2 == 'same': component2 = component1 #%% find event details for origin time, lat, lon ev_file = '/Users/vidale/Documents/Research/BasinsLA/My_initial_effort/Compare/event_list.txt' file_ev = open(ev_file, 'r') for line in file_ev: # pull numbers off all the lines split_line = line.split() event = split_line[0] if event == event_no: ev_lat = float(split_line[3]) ev_lon = float(split_line[2]) t1 = UTCDateTime(split_line[5]) date_label = split_line[5][0:10] year1 = split_line[5][0:4] print(event_name + ': ev_no ' + event_no + ' ' + str(t1) + ' Lat-Lon ' + str(ev_lat) + ' ' + str(ev_lon)) #%% find event details for origin time, lat, lon badt_file = '/Users/vidale/Documents/Research/BasinsLA/My_initial_effort/Compare/bad_trace_no.txt' file_badt = open(badt_file, 'r') badt_lines = file_badt.readlines() badt_event = [] badt_station = [] badt_compo = [] for line in badt_lines: # pull numbers off all the lines split_line = line.split() badt_event.append(split_line[0]) badt_station.append(split_line[1]) badt_compo.append(split_line[2]) print(str(len(badt_event)) + ' bad traces in list') #%% Open station location file sta_file = '/Users/vidale/Documents/Research/BasinsLA/My_initial_effort/Compare/stations.txt' file_st = open(sta_file, 'r') line = file_st.readline() # read first line to skip header information lines = file_st.readlines() # print(str(len(lines)) + ' stations read from ' + sta_file) # Load station coords into arrays, many more stations than used st_num = [] st_netw = [] st_name = [] st_dist = [] st_az = [] st_baz = [] st_lat = [] st_lon = [] for line in lines: split_line = line.split() st_num.append(split_line[0]) st_netw.append(split_line[2]) st_name.append(split_line[3]) st_lat.append(split_line[4]) st_lon.append(split_line[5]) distance = gps2dist_azimuth(ev_lat, ev_lon, float( split_line[4]), float(split_line[5])) # Get traveltime and azimuth # print('Event ' + str(ev_lat) + ' ' + str(ev_lon) + ' station ' + split_line[4] + ' ' + split_line[5] + ' distance ' + str(distance[0])) st_dist.append(distance[0] / 1000.) # distance st_az.append(distance[1]) # azimuth st_baz.append(distance[2]) # back-azimuth print('number of stations in list is ' + str(len(st_num))) #%% Load data and synthetic waveforms sgrams1 = Stream() if traces1 == 'B_Data': fname_datZ = '/Users/vidale/Documents/Research/BasinsLA/B_Data/' + event_name + '/allData_mseed/' + component1 + '.mseed' elif traces1 == 'CVM_S4': fname_datZ = '/Users/vidale/Documents/Research/BasinsLA/B_Syn/' + event_name + '/CVM_S4/' + component1 + '.mseed' elif traces1 == 'CVM_H': fname_datZ = '/Users/vidale/Documents/Research/BasinsLA/B_Syn/' + event_name + '/CVM_H/' + component1 + '.mseed' sgrams1 = read(fname_datZ) len1_in = len(sgrams1) print(traces1 + ' has ' + str(len1_in) + ' traces') if len(sgrams1) == 0: print('No stations found in traces1, quit now!') sys.exit() print('1st gather, 1st data trace has : ' + str(len(sgrams1[0].data)) + ' time pts ') print('1st gather, 1st trace starts at ' + str(sgrams1[0].stats.starttime) + ', event at ' + str(t1)) if traces2 != 'no': sgrams2 = Stream() if traces2 == 'B_Data': fname_datZ = '/Users/vidale/Documents/Research/BasinsLA/B_Data/' + event_name + '/allData_mseed/' + component2 + '.mseed' elif traces2 == 'CVM_S4': fname_datZ = '/Users/vidale/Documents/Research/BasinsLA/B_Syn/' + event_name + '/CVM_S4/' + component2 + '.mseed' elif traces2 == 'CVM_H': fname_datZ = '/Users/vidale/Documents/Research/BasinsLA/B_Syn/' + event_name + '/CVM_H/' + component2 + '.mseed' sgrams2 = read(fname_datZ) # if traces2 == 'Ricardo_syn': # lenH = len(sgrams2) # correct Hercules units from from meters to cm # for ii in range(lenH): # assume trace count of all synthetic files is same # sgrams2[ii].data = 100 * sgrams2[ii].data if traces2 != 'no': len2_in = len(sgrams2) print(traces2 + ' has ' + str(len2_in) + ' traces') if len(sgrams2) == 0: print('No stations found in traces1, quit now!') sys.exit() print('2nd gather, 1st trace has : ' + str(len(sgrams2[0].data)) + ' time pts ') print('2nd gather, 1st trace starts at ' + str(sgrams2[0].stats.starttime) + ', event at ' + str(t1)) #%% Keep only data that is not on list of bad traces # either individual components or A for all components sgrams1_good = Stream() for tr in sgrams1: # examine traces one by one do_write = 1 for ii in range(len(badt_event)): if event_no == badt_event[ii] and tr.stats.station == badt_station[ ii]: # find station in inventory if badt_compo[ii] == 'Z' or badt_compo[ii] == 'A': do_write = 0 if do_write == 1: sgrams1_good += tr print('After rejecting listed bad traces, ' + str(len(sgrams1_good)) + ' out of ' + str(len1_in) + ' traces remain in 1st gather.') if traces2 != 'no' or '': sgrams2_good = Stream() for tr in sgrams2: # examine traces one by one do_write = 1 for ii in range(len(badt_event)): if event_no == badt_event[ ii] and tr.stats.station == badt_station[ ii]: # find station in inventory if badt_compo[ii] == 'Z' or badt_compo[ii] == 'A': do_write = 0 if do_write == 1: sgrams2_good += tr print('After rejecting listed bad traces, ' + str(len(sgrams2_good)) + ' out of ' + str(len2_in) + ' traces remain in 2nd gather.') if len(sgrams1_good) == 0: print('No common stations found, quit now!') sys.exit() #%% Keep only common traces if traces2 != 'no': sgrams1_comm = Stream() sgrams2_comm = Stream() for tr1 in sgrams1_good: # examine traces one by one found_it = 0 if tr1.stats.station in st_name: # find station in station list found_it = 1 ii = st_name.index(tr1.stats.station) for tr2 in sgrams2_good: # examine traces one by one if tr2.stats.station == tr1.stats.station: # find station in station list in_CI = (tr1.stats.network == 'CI' or tr2.stats.network == 'CI') if in_CI == True or CI_only == False: sgrams1_comm += tr1 sgrams2_comm += tr2 if found_it == 0: print('Not on station list! station ' + tr1.stats.station + ' network ' + tr1.stats.network) print('1st, 2nd sets of seismograms have ' + str(len(sgrams1_comm)) + ' and ' + str(len(sgrams2_comm)) + ' common stations') else: sgrams1_comm = Stream() sgrams1_comm = sgrams1_good.copy() #%% Select data and syn, only need one, don't need 2nd gather for map # select data by distance (and azimuth?), and cull synthetics to match data sgrams1_select = Stream() for tr in sgrams1_comm: # examine traces one by one if (tr.stats.network == 'CI' or CI_only == False or traces1 == 'CVM_H' or traces1 == 'CVM_S4'): found_it = 0 for ii in range( len(st_name)): # find matching entry in station roster if tr.stats.station == st_name[ ii]: # find station in inventory found_it = 1 flat = float(st_lat[ii]) flon = float(st_lon[ii]) if st_dist[ii] < max_dist and (flat < Lat_max and flat > Lat_min and flon < Lon_max and flon > Lon_min): # exclude stations too far away or outside the map box # basin is roughly within 15 km of these 3 stations distance = gps2dist_azimuth(33.99053, -118.36171, float(flat), float(flon)) # station BHP dist1 = distance[0] / 1000 # convert m to km distance = gps2dist_azimuth(33.88110, -118.17568, float(flat), float(flon)) # station LTP dist2 = distance[0] / 1000 distance = gps2dist_azimuth(33.80776, -117.98116, float(flat), float(flon)) # station BRE dist3 = distance[0] / 1000 # print(tr.stats.station + ' ' + str(dist1) + ' ' + str(dist2) + ' ' + str(dist3)) # keep stations only within X km of basin axis if basin == False or (dist1 < basin_width) or ( dist2 < basin_width) or (dist3 < basin_width): # print('selected: ' + tr.stats.station) tr.stats.distance = st_dist[ii] tr.stats.stla = st_lat[ii] tr.stats.stlo = st_lon[ii] sgrams1_select += tr # if found_it == 0: # print('Not on station list! station ' + tr.stats.station + ' network ' + tr.stats.network) print('Sgrams 1: Within ' + str(max_dist) + ' km distance and optional basin culling, ' + str(len(sgrams1_select)) + ' traces remain for plotting.') if traces2 != 'no': sgrams2_select = Stream() for tr in sgrams2_comm: # examine traces one by one if (tr.stats.network == 'CI' or CI_only == False or traces2 == 'CVM_H' or traces2 == 'CVM_S4'): found_it = 0 for ii in range( len(st_name)): # find matching entry in station roster if tr.stats.station == st_name[ ii]: # find station in inventory found_it = 1 flat = float(st_lat[ii]) flon = float(st_lon[ii]) if st_dist[ii] < max_dist and (flat < Lat_max and flat > Lat_min and flon < Lon_max and flon > Lon_min): # exclude stations too far away or outside the map box # basin is roughly within 15 km of these 3 stations distance = gps2dist_azimuth( 33.99053, -118.36171, float(flat), float(flon)) # station BHP dist1 = distance[0] / 1000 # convert m to km distance = gps2dist_azimuth( 33.88110, -118.17568, float(flat), float(flon)) # station LTP dist2 = distance[0] / 1000 distance = gps2dist_azimuth( 33.80776, -117.98116, float(flat), float(flon)) # station BRE dist3 = distance[0] / 1000 # print(tr.stats.station + ' ' + str(dist1) + ' ' + str(dist2) + ' ' + str(dist3)) if basin == False or (dist1 < basin_width) or ( dist2 < basin_width ) or ( dist3 < basin_width ): # keep stations only within X km of basin axis # print('selected: ' + tr.stats.station) tr.stats.distance = st_dist[ii] tr.stats.stla = st_lat[ii] tr.stats.stlo = st_lon[ii] sgrams2_select += tr # if found_it == 0: # print('Not on station list! station ' + tr.stats.station + ' network ' + tr.stats.network) print('Sgrams 2: Within ' + str(max_dist) + ' km distance and optional basin culling, ' + str(len(sgrams2_select)) + ' traces remain for plotting.') if len(sgrams1_select) == 0: print('No common stations found, quit now!') sys.exit() #%% detrend, taper, filter if taper: sgrams1_select.taper(taper_frac) if traces2 != 'no': sgrams2_select.taper(taper_frac) if filt: sgrams1_select.filter('bandpass', freqmin=freq_min, freqmax=freq_max, corners=4, zerophase=True) if traces2 != 'no': sgrams2_select.filter('bandpass', freqmin=freq_min, freqmax=freq_max, corners=4, zerophase=True) if taper: sgrams1_select.taper(taper_frac) if traces2 != 'no': sgrams2_select.taper(taper_frac) #%% plot traces, different windows in case multiple components are to be compared if component1 == 'Z': fig_index = 13 + fig_inc elif component1 == 'N': fig_index = 14 + fig_inc elif component1 == 'E': fig_index = 15 + fig_inc elif component1 == 'R': fig_index = 16 + fig_inc elif component1 == 'T': fig_index = 17 + fig_inc plt.close(fig_index) plt.figure(fig_index, figsize=(10, 8)) plt.xlim(start_buff, end_buff) plt.ylim(min_dist, max_dist) # find max of absolute amplitude maxD1 = 0 for tr in sgrams1_select: tr_max = max(abs(tr.data)) if tr_max > maxD1: maxD1 = tr_max if traces2 != 'no': maxD2 = 0 for tr in sgrams2_select: tr_max = max(abs(tr.data)) if tr_max > maxD2: maxD2 = tr_max maxD = max(maxD1, maxD2) # print('Max data and H, S, H_simp, and S_simp synthetics are ' + str(maxD) + ' ' + str(maxSH) + ' ' + str(maxSS)+ ' ' + str(maxSH_simp)+ ' ' + str(maxSS_simp)) # find max normalized to 10 km distance (i.e., amp divided by distance, assumes 1/R amp fall-off) maxD_N = 0 for tr in sgrams1_select: if verbose == True: print(f'Distance is {tr.stats.distance:6.4f}' + ' ' + tr.stats.station) tr_max = max(abs(tr.data)) * tr.stats.distance if tr_max > maxD_N: maxD_N = tr_max print(f'Peak amp of trace1 is {maxD1:6.4f}') if traces2 != 'no': print(f'Peak amp of trace2 is {maxD2:6.4f}') print(f'Peak amp of both traces is {maxD:6.4f}') print(f'Normed amp of data is {maxD_N:6.4f}') plot_fac = plot_scale_fac * (max_dist - min_dist) / maxD_N for tr in sgrams1_select: dist_offset = tr.stats.distance # km if dist_offset > min_dist and dist_offset < max_dist: pnum = max(abs(tr.data)) printee2 = f'amp {pnum:8.3f} {tr.stats.network} {tr.stats.station}' plt.text(s=printee2, x=end_buff * 0.8, y=dist_offset + (max_dist - min_dist) * 0.02, color='black' ) #label traces and note amplitude (cm, cm/s, cm/s^^2) if traces1 == 'CVM_H' or traces1 == 'CVM_S4': tr.stats.starttime = t1 ttt = np.arange(len(tr.data)) * tr.stats.delta + ( tr.stats.starttime - t1) color_p = 'red' if traces2 == 'no': color_p = 'black' if norm_each: plt.plot(ttt, (tr.data - np.median(tr.data)) * plot_scale_fac / (tr.data.max() - tr.data.min()) + dist_offset, color=color_p) elif dist_norm == True: plt.plot(ttt, (tr.data * plot_fac * tr.stats.distance) + dist_offset, color=color_p) else: plt.plot(ttt, (tr.data * plot_fac) + dist_offset, color=color_p) if traces2 != 'no': for tr in sgrams2_select: dist_offset = tr.stats.distance if dist_offset > min_dist and dist_offset < max_dist: if traces2 == 'CVM_H' or traces2 == 'CVM_S4': tr.stats.starttime = t1 ttt = np.arange(len(tr.data)) * tr.stats.delta + ( tr.stats.starttime - t1) if norm_each: plt.plot(ttt, (tr.data - np.median(tr.data)) * plot_scale_fac / (tr.data.max() - tr.data.min()) + dist_offset, color='green') elif dist_norm == True: plt.plot(ttt, (tr.data * plot_fac * tr.stats.distance) + dist_offset, color='green') else: plt.plot(ttt, (tr.data * plot_fac) + dist_offset, color='green') plt.xlabel('Time (s)') plt.ylabel('Epicentral distance from event (km)') # plt.title(fname1[8:18] + ' vs ' + fname2[8:18]) if traces2 != 'no': plt.title(date_label + ' red ' + traces1 + ' ' + component1 + ' green ' + traces2 + ' ' + component2) else: plt.title(date_label + ' ' + traces1 + ' ' + component1) plt.show() elapsed_time_wc = time.time() - start_time_wc print('This job took ' + str(elapsed_time_wc) + ' seconds') os.system('say "Done"')
def make_template(df, sampling_rate, filter=[0.2, 8], tcs_length=[1, 9]): ''' download template by event ID original function by: Amanda Thomas modified by: Tim Lin -merge data with interpolate data point 2020.09 -add multiple attemps when download has unexpected issue 2020.09 ''' from obspy.clients.fdsn import Client from libcomcat.search import get_event_by_id from libcomcat.dataframes import get_phase_dataframe from obspy import Stream import time client = Client("IRIS") # make templates regional = df['Regional'] eventid = regional + str(df['ID']) detail = get_event_by_id(eventid, includesuperseded=True) OT = UTCDateTime(detail.time) #event origin time phases = get_phase_dataframe(detail, catalog=regional) phases = phases[phases['Status'] == 'manual'] phases = phases[~phases. duplicated(keep='first', subset=['Channel', 'Phase'])] st = Stream() tr = Stream() sav_net_sta_comp = [] sav_phase = [] sav_arr = [] sav_travel = [] All_info = {} for ii in range(len(phases)): elems = phases.iloc[ii]['Channel'].split('.') net = elems[0] sta = elems[1] comp = elems[2] location = elems[3] if location == '--': location = '' #sometime the location use -- instead of empty str Phase = phases.iloc[ii][ 'Phase'] #P or S wave, save this info for further relocation #phase = phases.iloc[ii]['Phase'] arr = UTCDateTime(phases.iloc[ii]['Arrival Time']) #print(int(np.round(arr.microsecond/(1/sampling_rate*10**6))*1/sampling_rate*10**6)==1000000) if int( np.round(arr.microsecond / (1 / sampling_rate * 10**6)) * 1 / sampling_rate * 10**6) == 1000000: arr.microsecond = 0 #arr.second=arr.second+1 arr = arr + 1 #print('arrival Time after arrangement',arr) else: arr.microsecond = int( np.round(arr.microsecond / (1 / sampling_rate * 10**6)) * 1 / sampling_rate * 10**6) t1 = arr - tcs_length[0] t2 = arr + tcs_length[1] #try: i_attempt = 0 #reset number of attempt tr_exist = False while i_attempt < 5: try: #tr = client.get_waveforms(net, sta, "*", comp, t1-2, t2+2) #Be careful, this may query more than 1 trace channel i.e. '00','01'... tr = client.get_waveforms( net, sta, location, comp, t1 - 2, t2 + 2 ) #Be careful, this may query more than 1 trace channel i.e. '00','01'... tr_exist = True break except: #print('Attempts %d'%(i_attempt),net, sta, "*", comp, t1-2, t2+2) time.sleep(2) #wait 2 sec and try again later... i_attempt += 1 continue if not (tr_exist): #some unexpected error when attemp to client.get_waveforms #print("No data for "+net+" "+sta+" "+comp+" "+str(t1)+" "+str(t2)) continue else: #print("Data available:",net, sta, location, comp, t1-2, t2+2) tr.merge(method=1, interpolation_samples=-1, fill_value='interpolate') tr.detrend('linear') tr.trim(starttime=t1 - 2, endtime=t2 + 2, nearest_sample=1, pad=1, fill_value=0) if filter: tr.filter("bandpass", freqmin=filter[0], freqmax=filter[1]) tr.interpolate(sampling_rate=sampling_rate, starttime=t1, method='linear') tr.trim(starttime=t1, endtime=t2, nearest_sample=1, pad=1, fill_value=0) assert len( tr) == 1, 'Unexpecting error when query:%s %s %s %s %s %s' % ( net, sta, location, comp, t1 - 2, t2 + 2) #tr[0].stats.location = tr[0].stats.location+'.'+Phase st += tr.copy() #save name, time and "phase" info for later relocation #.ms only gives starttime (know arrival time) but not P or S wave sav_net_sta_comp.append(net + '.' + sta + '.' + comp + '.' + location) sav_phase.append(Phase) #tmp_arrT = arr.strftime('%Y-%m-%dT%H:%M:%S.%f') #arrival time in isoformat i.e. 2018-05-05T17:44:18 or 2018-05-05T17:44:23.960000 tmp_arrT = arr.strftime( '%Y-%m-%dT%H:%M:%S.%f')[:-4] #accuracy to 0.01 sec #if len(tmp_arrT)==26: # tmp_arrT = tmp_arrT[:-4] #print('Time:',tmp_arrT) sav_arr.append(tmp_arrT) sav_travel.append(arr - OT) #travel time (relative to the origin) #=========IMPORTANT NOTE!!! the order of these array are NOT necessarily the same as st===================================================== #=========When there are both P and S data in the same net.sta.chn.loc, the order will be messed up when write in .ms file================== #=========Use these info as extra caution=================================================================================================== #=========2020.11.12 Update: When calling bulk_make_template will will be runing additional re-order(All_info_reorder) to all the All_info== #=========================so the data generated from download_tools.bulk_make_template will be ready to use (the right order as .ms file)=== All_info['net_sta_comp'] = np.array(sav_net_sta_comp) All_info['phase'] = np.array(sav_phase) All_info['arrival'] = np.array(sav_arr) #absolute arrival time All_info['travel'] = np.array(sav_travel) #phase travel time (sec) All_info['OT_template'] = OT.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-4] All_info['tcs_length'] = tcs_length All_info['filter'] = filter All_info['eqloc'] = [df['Lon'], df['Lat'], df['Depth']] All_info['eqmag'] = df['Magnitude'] All_info['evid'] = eventid return st, All_info
def _prep_data_for_correlation(stream, templates, template_names=None, force_stream_epoch=True): """ Check that all channels are the same length and that all channels have data for both template and stream. Works in place on data - will cut to shortest length :param stream: Stream to compare data to :param templates: List of streams that will be forced to have the same channels as stream :param template_names: List of strings same length as templates :type force_stream_epoch: bool :param force_stream_epoch: Whether to force all channels in stream to cover the same time period :return: stream, templates, template_names (if template_names given) """ n_templates = len(templates) template_samp_rates = { tr.stats.sampling_rate for template in templates for tr in template} stream_samp_rates = {tr.stats.sampling_rate for tr in stream} samp_rates = template_samp_rates.union(stream_samp_rates) assert len(samp_rates) == 1, "Sampling rates differ" samp_rate = samp_rates.pop() out_stream = Stream() named = True if template_names is None: named = False template_names = range(n_templates) # Work out shapes. stream_start = min([tr.stats.starttime for tr in stream]) stream_end = max([tr.stats.endtime for tr in stream]) if force_stream_epoch: stream_length = int(samp_rate * (stream_end - stream_start)) + 1 else: stream_length = max([tr.stats.npts for tr in stream]) template_length = { tr.stats.npts for template in templates for tr in template} assert len(template_length) == 1, "Template traces not all the same length" template_length = template_length.pop() stream_ids = {tr.id for tr in stream} # Need to ensure that a channel can be in the template multiple times. template_ids = {stream_id: [] for stream_id in stream_ids} for template in templates: # Only include those in the stream. channels_in_template = { tr.id for tr in template}.intersection(stream_ids) for channel in channels_in_template: template_ids[channel].append(len(template.select(id=channel))) template_ids = {key: max(value) for key, value in template_ids.items() if len(value) > 0} seed_ids = sorted( [key.split('.') + [i] for key, value in template_ids.items() for i in range(value)]) seed_ids = [('.'.join(seed_id[0:-1]), seed_id[-1]) for seed_id in seed_ids] for channel_number, seed_id in enumerate(template_ids.keys()): stream_data = np.zeros(stream_length, dtype=np.float32) stream_channel = stream.select(id=seed_id) if len(stream_channel) > 1: raise NotImplementedError( "Multiple channels in continuous data for {0}".format(seed_id)) stream_channel = stream_channel[0] if stream_channel.stats.npts == stream_length: stream_data = stream_channel.data else: Logger.info('Data for {0} is not as long as needed, ' 'padding'.format(stream_channel.id)) if force_stream_epoch: start_pad = int(samp_rate * ( stream_channel.stats.starttime - stream_start)) end_pad = stream_length - ( start_pad + stream_channel.stats.npts) # In some cases there will be one sample missing when sampling # time-stamps are not set consistently between channels, this # results in start_pad and end_pad being len==0 if start_pad == 0 and end_pad == 0: Logger.debug("Start and end pad are both zero, padding " "at one end") if (stream_channel.stats.starttime - stream_start) > ( stream_end - stream_channel.stats.endtime): start_pad = int( stream_length - stream_channel.stats.npts) else: end_pad = int( stream_length - stream_channel.stats.npts) stream_channel.stats.starttime -= (start_pad / samp_rate) else: start_pad = 0 end_pad = stream_length - stream_channel.stats.npts if end_pad == 0: stream_data[start_pad:] = stream_channel.data else: stream_data[start_pad:-end_pad] = stream_channel.data header = stream_channel.stats.copy() header.npts = stream_length out_stream += Trace(data=stream_data, header=header) # Initialize nan template for speed. nan_channel = np.full(template_length, np.nan, dtype=np.float32) nan_template = Stream() for _seed_id in seed_ids: net, sta, loc, chan = _seed_id[0].split('.') nan_template += Trace(header=Stats({ 'network': net, 'station': sta, 'location': loc, 'channel': chan, 'starttime': UTCDateTime(), 'npts': template_length, 'sampling_rate': samp_rate})) # Remove templates with no matching channels filt = np.ones(len(template_names)).astype(bool) for i, template in enumerate(templates): template_ids = {tr.id for tr in template} if len(template_ids.intersection(stream_ids)) == 0: filt[i] = 0 _out = dict(zip( [_tn for _tn, _filt in zip(template_names, filt) if _filt], [_t for _t, _filt in zip(templates, filt) if _filt])) if len(_out) != len(templates): Logger.debug("Some templates not used due to no matching channels") # Fill out the templates with nan channels for template_name, template in _out.items(): template_starttime = min([tr.stats.starttime for tr in template]) out_template = nan_template.copy() for channel_number, _seed_id in enumerate(seed_ids): seed_id, channel_index = _seed_id template_channel = template.select(id=seed_id) if len(template_channel) <= channel_index: out_template[channel_number].data = nan_channel out_template[channel_number].stats.starttime = \ template_starttime else: out_template[channel_number] = template_channel[channel_index] _out.update({template_name: out_template}) out_templates = list(_out.values()) out_template_names = list(_out.keys()) if named: return out_stream, out_templates, out_template_names return out_stream, out_templates
ntemp=read(path+sta[k]+'.HNN.mseed') ntemp.trim(starttime=time_epi) ntemp[0].data=(ntemp[0].data/1e6)/9.81 etemp=read(path+sta[k]+'.HNE.mseed') etemp.trim(starttime=time_epi) etemp[0].data=(etemp[0].data/1e6)/9.81 ztemp=read(path+sta[k]+'.HNZ.mseed') ztemp.trim(starttime=time_epi) ztemp[0].data=(ztemp[0].data/1e6)/9.81 n+=ntemp[0] e+=etemp[0] z+=ztemp[0] #Make time series of pga npga=n.copy() epga=e.copy() zpga=z.copy() N=n[0].stats.npts for ksta in range(len(sta)): for ktime in range(N): if ktime==0: npga[ksta].data[ktime]=0 epga[ksta].data[ktime]=0 zpga[ksta].data[ktime]=0 else: npga[ksta].data[ktime]=abs(n[ksta].data[0:ktime]).max() epga[ksta].data[ktime]=abs(e[ksta].data[0:ktime]).max() zpga[ksta].data[ktime]=abs(z[ksta].data[0:ktime]).max() #Decimate time series to 2Hz
def paracorr(par): """Computation of noise correlation functions Compute noise correlation functions according to specifications parameter dictionary ``par``. This function is most conviniently used as a python program passing the parameter file as argument. This use is explained in the tutorial on correlation. The processing is performed in the following sequence * Data is read in typically day-long chunks ideally contained in a single file to speed up reading time * Preprocessing on the long sequences to avoid dominating influence of perturbing signals if processed in shorter chunks. * dividing these long sequences into shorter ones (typically an hour) * time domain preprocessing * frequency domain preprocessing * correlation * if ``direct_output`` is present data is directly writen by individual processes * optionally rotation of correlation tensor into ZRT system (not possible in combination with direct output * combination of correlation traces of subsequent time segments in correlation matrices * optionally delete traces of individual time segments :type par: dict :param par: processing parameters """ # initialize MPI comm = MPI.COMM_WORLD rank = comm.Get_rank() psize = comm.Get_size() # set up the logger logger = logging.getLogger('paracorr') hdlr = logging.FileHandler(os.path.join(par['log_dir'],'%s_paracorr_%d.log' % ( par['execution_start'],rank))) formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') hdlr.setFormatter(formatter) logger.addHandler(hdlr) logger.setLevel(logging.DEBUG) comb_list = combine_station_channels(par['net']['stations'], par['net']['channels'],par['co']['combination_method']) sttimes = datetime_list(par['co']['read_start'], par['co']['read_end'], inc=par['co']['read_inc']) ## loop over 24hrs/whole days time_inc = datetime.timedelta(seconds=par['co']['read_len']) lle_df = lat_lon_ele_load(par['net']['coordinate_file']) res_dir = par['co']['res_dir'] station_list = par['net']['stations'] channel_list = par['net']['channels'] program_start = UTCDateTime() # bulid a dictionary that caches the streams that reside in the same file stream_cache = {} for station in par['net']['stations']: stream_cache.update({station:{}}) for channel in par['net']['channels']: stream_cache[station].update({channel:Stream().append(Trace())}) # mapping of stations to processes pmap = (np.arange(len(station_list))*psize)/len(station_list) # indecies for stations to be worked on by each process st_ind = np.where(pmap == rank)[0] # number of subdivision of read length if 'subdivision' in par['co'].keys(): nsub = int(np.ceil((float(par['co']['read_len']) - par['co']['subdivision']['corr_len']) /par['co']['subdivision']['corr_inc'])+1) else: nsub = 1 # loop over times pathname = os.path.join(res_dir, correlation_subdir_name(sttimes[0])) print '\nrank %d of %d' % (rank,psize) logger.debug('Rank %d of %d Beginning execution.' % (rank,psize)) for sttime in sttimes: if rank == 0: print "\n>>> Working on %s at %s:" % (sttime,UTCDateTime()) logger.debug("\n>>> Working on %s at %s:" % (sttime,UTCDateTime())) usttime = UTCDateTime(sttime) # fill cache and extract current stream: only done by process 0 cst = Stream() # loop over stations different stations for every process for this_ind in st_ind: station = station_list[this_ind] tst = Stream() for channel in channel_list: print channel, station try: if ((len(stream_cache[station][channel])==0) or (not ((stream_cache[station][channel][0].stats['starttime']<=usttime) & (stream_cache[station][channel][0].stats['endtime']>=(usttime+par['co']['read_len']))))): stream_cache[station][channel] = read_from_filesystem('%s.*.%s' %(station, channel), sttime, sttime+time_inc, par['net']['fss'], trim=False) if not stream_cache[station][channel]: logger.warning("%s %s at %s: No trace read." % (station, channel, sttime)) continue samp_flag = False for tr in stream_cache[station][channel]: if tr.stats.sampling_rate != par['co']['sampling_rate']: samp_flag = True if samp_flag: logger.warning("%s %s at %s: Mismatching sampling rate." % (station, channel, sttime)) stream_cache[station][channel] = Stream() continue if par['co']['decimation'] > 1: sst = stream_cache[station][channel].split() sst.decimate(par['co']['decimation']) stream_cache[station][channel] = deepcopy(sst.merge()) ttst = stream_cache[station][channel].copy().trim(starttime=usttime, endtime=usttime+par['co']['read_len']) get_valid_traces(ttst) tst +=ttst except: logger.warning("%s %s at %s: %s" % (station, channel, sttime, sys.exc_info()[0])) cst += tst cst = stream_add_lat_lon_ele(cst,lle_df) # initial preprocessing on long time series if 'preProcessing' in par['co'].keys(): for procStep in par['co']['preProcessing']: cst = procStep['function'](cst,**procStep['args']) # create output path pathname = os.path.join(par['co']['res_dir'],correlation_subdir_name(sttime)) if rank == 0: create_path(pathname) # broadcast every station to every process st = Stream() for pind in range(psize): pst = Stream() if rank == pind: pst = deepcopy(cst) pst = comm.bcast(pst, root=pind) st += pst ## do correlations if len(st) == 0: logger.warning("%s: No traces to correlate." % (sttime)) else: targs = deepcopy(par['co']['corr_args']) if 'direct_output' in targs.keys(): targs['direct_output']['base_dir'] = pathname # loop over subdivisions for subn in range(nsub): if nsub > 1: sub_st = st.copy().trim(starttime=UTCDateTime(sttime)+ subn*par['co']['subdivision']['corr_inc'], endtime=UTCDateTime(sttime)+subn*par['co']['subdivision']['corr_inc']+ par['co']['subdivision']['corr_len']) get_valid_traces(sub_st) else: sub_st = st targs['combinations'] = select_available_combinations(sub_st,comb_list,targs) if len(targs['combinations']) == 0: continue cst = px.stream_pxcorr(sub_st,targs,comm=comm) # if 'direct_output' in targs.keys() cst is empty and the # following will not be executed if cst: if par['co']['rotation']: rcst = px.rotate_multi_corr_stream(cst) else: rcst = cst # distributed writing # mapping of stations to processes pmap = (np.arange(len(rcst))*psize)/len(rcst) # indecies for stations to be worked on by each process tr_ind = np.where(pmap == rank)[0] logger.debug('Process %d starting to write %d traces to %s.' % (rank,len(tr_ind),pathname)) this_st = Stream() for this_ind in tr_ind: this_st.append(rcst[this_ind]) convert_to_matlab(this_st,'trace',pathname) # if there is a subdivision of read traces comm.barrier() if ('subdivision' in par['co']) and (rank == 0): logger.debug('combining subdivisions') # combine traces to matrix corr_mat_create_from_traces(pathname, pathname, delete_trace_files=True) if par['co']['subdivision']['recombine_subdivision']: flist = dir_read(pathname,'mat__*.mat') for fl in flist: try: mat = mat_to_ndarray(fl) tr = corr_mat_extract_trace(mat,method='norm_mean') save_dict_to_matlab_file(fl.replace('mat__','tr__'),tr) if par['co']['subdivision']['delete_subdivisions']: os.remove(fl) except: pass program_end = UTCDateTime() print 'rank %d execution time' % rank, program_end-program_start logger.debug('Rank %d of %d End execution.' % (rank,psize))
def run_tutorial(plot=False, process_len=3600, num_cores=cpu_count(), **kwargs): """Main function to run the tutorial dataset.""" # First we want to load our templates template_names = glob.glob('tutorial_template_*.ms') if len(template_names) == 0: raise IOError('Template files not found, have you run the template ' + 'creation tutorial?') templates = [read(template_name) for template_name in template_names] # Work out what stations we have and get the data for them stations = [] for template in templates: for tr in template: stations.append((tr.stats.station, tr.stats.channel)) # Get a unique list of stations stations = list(set(stations)) # We will loop through the data chunks at a time, these chunks can be any # size, in general we have used 1 day as our standard, but this can be # as short as five minutes (for MAD thresholds) or shorter for other # threshold metrics. However the chunk size should be the same as your # template process_len. # You should test different parameters!!! start_time = UTCDateTime(2016, 1, 4) end_time = UTCDateTime(2016, 1, 5) chunks = [] chunk_start = start_time while chunk_start < end_time: chunk_end = chunk_start + process_len if chunk_end > end_time: chunk_end = end_time chunks.append((chunk_start, chunk_end)) chunk_start += process_len unique_detections = [] # Set up a client to access the GeoNet database client = Client("GEONET") # Note that these chunks do not rely on each other, and could be paralleled # on multiple nodes of a distributed cluster, see the SLURM tutorial for # an example of this. for t1, t2 in chunks: # Generate the bulk information to query the GeoNet database bulk_info = [] for station in stations: bulk_info.append(('NZ', station[0], '*', station[1][0] + 'H' + station[1][-1], t1, t2)) # Note this will take a little while. print('Downloading seismic data, this may take a while') st = client.get_waveforms_bulk(bulk_info) # Merge the stream, it will be downloaded in chunks st.merge() # Pre-process the data to set frequency band and sampling rate # Note that this is, and MUST BE the same as the parameters used for # the template creation. print('Processing the seismic data') st = pre_processing.shortproc(st, lowcut=2.0, highcut=9.0, filt_order=4, samp_rate=20.0, num_cores=num_cores, starttime=t1, endtime=t2) # Convert from list to stream st = Stream(st) # Now we can conduct the matched-filter detection detections = match_filter.match_filter(template_names=template_names, template_list=templates, st=st, threshold=8.0, threshold_type='MAD', trig_int=6.0, plotvar=plot, plotdir='.', cores=num_cores, plot_format='png', **kwargs) # Now lets try and work out how many unique events we have just to # compare with the GeoNet catalog of 20 events on this day in this # sequence for master in detections: keep = True for slave in detections: if not master == slave and abs(master.detect_time - slave.detect_time) <= 1.0: # If the events are within 1s of each other then test which # was the 'best' match, strongest detection if not master.detect_val > slave.detect_val: keep = False print('Removed detection at %s with cccsum %s' % (master.detect_time, master.detect_val)) print('Keeping detection at %s with cccsum %s' % (slave.detect_time, slave.detect_val)) break if keep: unique_detections.append(master) print('Detection at :' + str(master.detect_time) + ' for template ' + master.template_name + ' with a cross-correlation sum of: ' + str(master.detect_val)) # We can plot these too if plot: stplot = st.copy() template = templates[template_names.index( master.template_name)] lags = sorted([tr.stats.starttime for tr in template]) maxlag = lags[-1] - lags[0] stplot.trim(starttime=master.detect_time - 10, endtime=master.detect_time + maxlag + 10) plotting.detection_multiplot(stplot, template, [master.detect_time.datetime]) print('We made a total of ' + str(len(unique_detections)) + ' detections') return unique_detections
def plot_compare3(event_no, traces1, traces2, traces3, start_buff, end_buff, taper, taper_frac, plot_scale_fac, filt, freq_min, freq_max, only_common, min_dist, max_dist, basin_width, basin, basin_box, norm_each, dist_norm, CI_only, component1, component2, component3, fig_inc, shift1, shift2, shift3): # component 1 is E, 2 is N, 3 is Z # H is cvmhy, H_simp is cvmhn # S is cvms426-223, S_simp is cvms400-100 # norm_each == 1 scales the peak of each trace to the same size # otherwise, the amplitude is normalized to 10 km, with the scale set with plot_scale_fac from obspy import UTCDateTime from obspy import Stream from obspy import read from obspy.geodetics import gps2dist_azimuth from termcolor import colored import numpy as np import os import sys import matplotlib.pyplot as plt import time import warnings # if not sys.warnoptions: # warnings.simplefilter("ignore") start_time_wc = time.time() print(colored('Running plot_compare_two', 'cyan')) # Box limits Lon_min = -118.8 Lat_min = 33.3 Lon_max = -117.5 Lat_max = 34.6 if basin_box: Lon_min = -118.3 Lat_min = 33.75 Lon_max = -118.0 Lat_max = 34.05 if basin_box and basin: print('Invoking both basin and basin_box parameter, RETHINK!') sys.exit() if event_no == '15481673': # no 29 event_dir = 'LaHa2014' elif event_no == '10410337': # no 14 event_dir = 'Ingl2009' elif event_no == '14383980': # no 29 event_dir = 'Chin2008' elif event_no == '14312160': # no 29 event_dir = 'Chat2007' elif event_no == '9703873': # no 29 event_dir = 'BeHi2001' else: print('Number does not correspond to a valid event') sys.exit() verbose = False # print every distance of a station if component2 == 'same': component2 = component1 component3 = component1 #%% Look up origin time, lat, lon ev_file = '/Users/vidale/Documents/GitHub/LAB/Quakes/Files/event_list.txt' file_ev = open(ev_file, 'r') for line in file_ev: # pull numbers off all the lines split_line = line.split() event = split_line[0] if event == event_no: ev_lat = float(split_line[3]) ev_lon = float(split_line[2]) t1 = UTCDateTime(split_line[5]) date_label = split_line[5][0:10] year1 = split_line[5][0:4] print(event_dir + ': ev_no ' + event_no + ' ' + str(t1) + ' Lat-Lon ' + str(ev_lat) + ' ' + str(ev_lon)) #%% Read in bad traces file badt_file = '/Users/vidale/Documents/GitHub/LAB/Quakes/Files/bad_traces.txt' file_badt = open(badt_file, 'r') badt_lines = file_badt.readlines() badt_event = [] badt_station = [] badt_compo = [] for line in badt_lines: # pull numbers off all the lines split_line = line.split() badt_event.append(split_line[0]) badt_station.append(split_line[1]) badt_compo.append(split_line[2]) print(str(len(badt_event)) + ' bad traces in list') #%% Read in station location file sta_file = '/Users/vidale/Documents/GitHub/LAB/Quakes/Files/stations.txt' file_st = open(sta_file, 'r') line = file_st.readline() # read first line to skip header information lines = file_st.readlines() print(str(len(lines)) + ' stations read from ' + sta_file) # Load station names and coords into arrays st_netw = [] st_name = [] st_lat = [] st_lon = [] # Calculated values st_dist = [] st_az = [] st_baz = [] for line in lines: split_line = line.split() st_netw.append(split_line[0]) st_name.append(split_line[1]) st_lat.append(split_line[2]) st_lon.append(split_line[3]) distance = gps2dist_azimuth(ev_lat, ev_lon, float( split_line[2]), float(split_line[3])) # Get distance and azimuth st_dist.append(distance[0] / 1000.) # distance st_az.append(distance[1]) # azimuth st_baz.append(distance[2]) # back-azimuth print('Number of stations in list is ' + str(len(st_name))) #%% Load data and synthetic waveforms sgrams1 = Stream() fname = '/Users/vidale/Documents/Research/BasinsLA/Seis/' + event_dir + '/' + traces1 + '/' + component1 + '.mseed' print(fname + ' is 1st file to be read') sgrams1 = read(fname) len1_in = len(sgrams1) print(colored(traces1 + ' has ' + str(len1_in) + ' traces', 'magenta')) if len(sgrams1) == 0: print('No stations found in traces1, quit now!') sys.exit() print('1st gather, 1st data trace has : ' + str(len(sgrams1[0].data)) + ' time pts ') print('1st gather, 1st trace starts at ' + str(sgrams1[0].stats.starttime) + ', event at ' + str(t1)) if traces2 != 'no': sgrams2 = Stream() fname = '/Users/vidale/Documents/Research/BasinsLA/Seis/' + event_dir + '/' + traces2 + '/' + component2 + '.mseed' print(fname + ' is 2nd file to be read') sgrams2 = read(fname) len2_in = len(sgrams2) print(colored(traces2 + ' has ' + str(len2_in) + ' traces', 'magenta')) print('2nd gather, 1st data trace has : ' + str(len(sgrams2[0].data)) + ' time pts ') print('2nd gather, 1st trace starts at ' + str(sgrams2[0].stats.starttime)) if traces3 != 'no': sgrams3 = Stream() fname = '/Users/vidale/Documents/Research/BasinsLA/Seis/' + event_dir + '/' + traces3 + '/' + component3 + '.mseed' print(fname + ' is 3rd file to be read') sgrams3 = read(fname) len3_in = len(sgrams3) print(colored(traces3 + ' has ' + str(len3_in) + ' traces', 'magenta')) print('3rd gather, 1st data trace has : ' + str(len(sgrams3[0].data)) + ' time pts ') print('3rd gather, 1st trace starts at ' + str(sgrams3[0].stats.starttime)) if shift1 != 0: # added to shift traces, done just before plotting for inscrutable reasons print( colored( 'BE AWARE! trace1 gets shifted earlier ' + str(shift1) + ' seconds!', 'magenta')) if traces2 != 'no': if shift2 != 0: print( colored( 'BE AWARE! trace2 gets shifted earlier ' + str(shift2) + ' seconds!', 'magenta')) if traces3 != 'no': if shift3 != 0: print( colored( 'BE AWARE! trace3 gets shifted earlier ' + str(shift3) + ' seconds!', 'magenta')) # if src_name1 == 'Ric' and traces1 == 'data': # added to easily correct the 0.4s shift Ricardo's data needs # print('trace1 gets 0.33s Ric shift') # shift1 += 0.33 # print('src_name2 and traces2 are ' + src_name2 + ' ' + traces2) # if traces2 != 'no' and src_name2 == 'Ric' and traces2 == 'data': # print('trace2 gets 0.33s Ric shift') # shift2 += 0.33 # if traces3 != 'no' and src_name3 == 'Ric' and traces3 == 'data': # print('trace3 gets 0.33s Ric shift') # shift3 += 0.33 # Ricardo's synthetics need an amp correction # if src_name1 == 'Ric' and (traces1 == 'CVM_S' or traces1 == 'CVM_H'): # Hercules factor of 100 # print('trace1 gets Ric syn 100X mult') # lenS = len(sgrams1) # for ii in range(lenS): # lendata = len(sgrams1[ii].data) # for iii in range(lendata): # sgrams1[ii].data[iii] *= 100. # if traces2 != 'no': # if src_name2 == 'Ric' and (traces2 == 'CVM_S' or traces2 == 'CVM_H'): # print('trace2 gets Ric syn 100X mult') # lenS = len(sgrams2) # for ii in range(lenS): # lendata = len(sgrams2[ii].data) # for iii in range(lendata): # sgrams2[ii].data[iii] *= 100. # if traces3 != 'no': # if src_name3 == 'Ric' and (traces3 == 'CVM_S' or traces3 == 'CVM_H'): # print('trace3 gets Ric syn 100X mult') # lenS = len(sgrams3) # for ii in range(lenS): # lendata = len(sgrams3[ii].data) # for iii in range(lendata): # sgrams3[ii].data[iii] *= 100. if traces2 != 'no': len2_in = len(sgrams2) print(traces2 + ' has ' + str(len2_in) + ' traces') if len(sgrams2) == 0: print('No stations found in traces2, quit now!') sys.exit() print('2nd gather, 1st trace has : ' + str(len(sgrams2[0].data)) + ' time pts ') print('2nd gather, 1st trace starts at ' + str(sgrams2[0].stats.starttime) + ', event at ' + str(t1)) if traces3 != 'no': len3_in = len(sgrams3) print(traces3 + ' has ' + str(len3_in) + ' traces') if len(sgrams3) == 0: print('No stations found in traces3, quit now!') sys.exit() print('3rd gather, 1st trace has : ' + str(len(sgrams3[0].data)) + ' time pts ') print('3rd gather, 1st trace starts at ' + str(sgrams3[0].stats.starttime) + ', event at ' + str(t1)) #%% Keep only data that is not on list of bad traces # either individual components or A for all components sgrams1_good = Stream() for tr in sgrams1: # examine traces one by one do_write = 1 for ii in range(len(badt_event)): if event_no == badt_event[ii] and tr.stats.station == badt_station[ ii]: # find station in inventory if badt_compo[ii] == 'Z' or badt_compo[ii] == 'A': do_write = 0 if do_write == 1: sgrams1_good += tr print('After rejecting listed bad traces, ' + str(len(sgrams1_good)) + ' out of ' + str(len1_in) + ' traces remain in 1st gather.') if traces2 != 'no': sgrams2_good = Stream() for tr in sgrams2: # examine traces one by one do_write = 1 for ii in range(len(badt_event)): if event_no == badt_event[ ii] and tr.stats.station == badt_station[ ii]: # find station in inventory if badt_compo[ii] == 'Z' or badt_compo[ii] == 'A': do_write = 0 if do_write == 1: sgrams2_good += tr print('After rejecting listed bad traces, ' + str(len(sgrams2_good)) + ' out of ' + str(len2_in) + ' traces remain in 2nd gather.') if traces3 != 'no': sgrams3_good = Stream() for tr in sgrams3: # examine traces one by one do_write = 1 for ii in range(len(badt_event)): if event_no == badt_event[ ii] and tr.stats.station == badt_station[ ii]: # find station in inventory if badt_compo[ii] == 'Z' or badt_compo[ii] == 'A': do_write = 0 if do_write == 1: sgrams3_good += tr print('After rejecting listed bad traces, ' + str(len(sgrams3_good)) + ' out of ' + str(len3_in) + ' traces remain in 2nd gather.') if len(sgrams1_good) == 0: print('No seismogram found for traces1, quit now!') sys.exit() #%% test for traces not in station list, but really want to test stations that meet all inclusion criteria # if traces2 != 'no': # for tr1 in sgrams1_good: # examine traces one by one # if tr1.stats.station not in st_name: # is station in station list? # if traces1 == 'Data': # print('Wasted data trace, not on station list! station ' + tr1.stats.station + ' network ' + tr1.stats.network) # else: # print('Wasted syn trace, not on station list! station ' + tr1.stats.station + ' network ' + tr1.stats.network) #%% Keep only common traces sgrams1_comm = Stream() sgrams2_comm = Stream() sgrams3_comm = Stream() if only_common == True: if traces2 != 'no': for tr1 in sgrams1_good: # examine traces one by one if tr1.stats.station in st_name: # find station in station list ii = st_name.index(tr1.stats.station) for tr2 in sgrams2_good: # examine traces one by one if tr2.stats.station == tr1.stats.station: # find station in station list if traces3 != 'no': for tr3 in sgrams3_good: # examine traces one by one if tr3.stats.station == tr1.stats.station: # find station in station list in_CI = (tr1.stats.network == 'CI') if in_CI == True or CI_only == False: sgrams1_comm += tr1 sgrams2_comm += tr2 sgrams3_comm += tr3 else: in_CI = (tr1.stats.network == 'CI') if in_CI == True or CI_only == False: sgrams1_comm += tr1 sgrams2_comm += tr2 print('1st, 2nd, 3rd sets of seismograms have ' + str(len(sgrams1_comm)) + ', ' + str(len(sgrams2_comm)) + ', and ' + str(len(sgrams3_comm)) + ' common stations') else: sgrams1_comm = Stream() sgrams1_comm = sgrams1_good.copy() else: sgrams1_comm = sgrams1_good if traces2 != 'no': sgrams2_comm = sgrams2_good if traces3 != 'no': sgrams3_comm = sgrams3_good print('Not common: 1st, 2nd, 3rd sets of seismograms have ' + str(len(sgrams1_comm)) + ', ' + str(len(sgrams2_comm)) + ', and ' + str(len(sgrams3_comm))) #%% Select data and syn, only need one, don't need 2nd gather for map # select data by distance (and azimuth?), and cull synthetics to match data sgrams1_select = Stream() for tr in sgrams1_comm: # examine traces one by one if (tr.stats.network == 'CI' or CI_only == False or traces1 == 'CVM_H' or traces1 == 'CVM_S' or traces1 == 'Jon_CVM_S'): found_it = 0 for ii in range( len(st_name)): # find matching entry in station roster if tr.stats.station == st_name[ ii]: # find station in inventory found_it = 1 flat = float(st_lat[ii]) flon = float(st_lon[ii]) if verbose == True: print(tr.stats.station + ' X Lat ' + str(flat) + ' Lon ' + str(flon)) if (st_dist[ii] < max_dist and st_dist[ii] > min_dist ) and (flat < Lat_max and flat > Lat_min and flon < Lon_max and flon > Lon_min): # exclude stations too far away or outside the map box # basin is roughly within 15 km of these 3 stations distance = gps2dist_azimuth(33.99053, -118.36171, float(flat), float(flon)) # station BHP dist1 = distance[0] / 1000 # convert m to km distance = gps2dist_azimuth(33.88110, -118.17568, float(flat), float(flon)) # station LTP dist2 = distance[0] / 1000 distance = gps2dist_azimuth(33.80776, -117.98116, float(flat), float(flon)) # station BRE dist3 = distance[0] / 1000 if verbose == True: print(tr.stats.station + ' Lat ' + str(flat) + ' Lon ' + str(flon)) # print(tr.stats.station + ' ' + str(dist1) + ' ' + str(dist2) + ' ' + str(dist3)) # keep stations only within X km of basin axis if (basin == False) or (dist1 < basin_width) or ( dist2 < basin_width) or (dist3 < basin_width): # print('selected: ' + tr.stats.station) tr.stats.distance = st_dist[ii] tr.stats.stla = st_lat[ii] tr.stats.stlo = st_lon[ii] sgrams1_select += tr # if found_it == 0: # print('Not on station list! station ' + tr.stats.station + ' network ' + tr.stats.network) print('Sgrams 1: Within ' + str(max_dist) + ' km distance and optional basin culling, ' + str(len(sgrams1_select)) + ' traces remain for plotting.') if traces2 != 'no': sgrams2_select = Stream() for tr in sgrams2_comm: # examine traces one by one if (tr.stats.network == 'CI' or CI_only == False or traces2 == 'CVM_H' or traces2 == 'CVM_S' or traces2 == 'Jon_CVM_S'): found_it = 0 for ii in range( len(st_name)): # find matching entry in station roster if tr.stats.station == st_name[ ii]: # find station in inventory found_it = 1 flat = float(st_lat[ii]) flon = float(st_lon[ii]) if (st_dist[ii] < max_dist and st_dist[ii] > min_dist ) and (flat < Lat_max and flat > Lat_min and flon < Lon_max and flon > Lon_min): # exclude stations too far away or outside the map box # basin is roughly within 15 km of these 3 stations distance = gps2dist_azimuth( 33.99053, -118.36171, float(flat), float(flon)) # station BHP dist1 = distance[0] / 1000 # convert m to km distance = gps2dist_azimuth( 33.88110, -118.17568, float(flat), float(flon)) # station LTP dist2 = distance[0] / 1000 distance = gps2dist_azimuth( 33.80776, -117.98116, float(flat), float(flon)) # station BRE dist3 = distance[0] / 1000 # print(tr.stats.station + ' ' + str(dist1) + ' ' + str(dist2) + ' ' + str(dist3)) if (basin == False) or (dist1 < basin_width) or ( dist2 < basin_width ) or ( dist3 < basin_width ): # keep stations only within X km of basin axis # print('selected: ' + tr.stats.station) tr.stats.distance = st_dist[ii] tr.stats.stla = st_lat[ii] tr.stats.stlo = st_lon[ii] sgrams2_select += tr # if found_it == 0: # print('Not on station list! station ' + tr.stats.station + ' network ' + tr.stats.network) print('Sgrams 2: Within ' + str(max_dist) + ' km distance and optional basin culling, ' + str(len(sgrams2_select)) + ' traces remain for plotting.') if len(sgrams1_select) == 0: print('No common stations found, quit now!') sys.exit() if traces3 != 'no': sgrams3_select = Stream() for tr in sgrams3_comm: # examine traces one by one if (tr.stats.network == 'CI' or CI_only == False or traces3 == 'CVM_H' or traces3 == 'CVM_S' or traces3 == 'Jon_CVM_S'): found_it = 0 for ii in range( len(st_name)): # find matching entry in station roster if tr.stats.station == st_name[ ii]: # find station in inventory found_it = 1 flat = float(st_lat[ii]) flon = float(st_lon[ii]) if (st_dist[ii] < max_dist and st_dist[ii] > min_dist ) and (flat < Lat_max and flat > Lat_min and flon < Lon_max and flon > Lon_min): # exclude stations too far away or outside the map box # basin is roughly within 15 km of these 3 stations distance = gps2dist_azimuth( 33.99053, -118.36171, float(flat), float(flon)) # station BHP dist1 = distance[0] / 1000 # convert m to km distance = gps2dist_azimuth( 33.88110, -118.17568, float(flat), float(flon)) # station LTP dist2 = distance[0] / 1000 distance = gps2dist_azimuth( 33.80776, -117.98116, float(flat), float(flon)) # station BRE dist3 = distance[0] / 1000 # print(tr.stats.station + ' ' + str(dist1) + ' ' + str(dist2) + ' ' + str(dist3)) if (basin == False) or (dist1 < basin_width) or ( dist2 < basin_width ) or ( dist3 < basin_width ): # keep stations only within X km of basin axis # print('selected: ' + tr.stats.station) tr.stats.distance = st_dist[ii] tr.stats.stla = st_lat[ii] tr.stats.stlo = st_lon[ii] sgrams3_select += tr # if found_it == 0: # print('Not on station list! station ' + tr.stats.station + ' network ' + tr.stats.network) print('Sgrams 3: Within ' + str(max_dist) + ' km distance and optional basin culling, ' + str(len(sgrams3_select)) + ' traces remain for plotting.') if len(sgrams1_select) == 0: print('No common stations found, quit now!') sys.exit() #%% detrend, taper, filter if taper: sgrams1_select.taper(taper_frac) if traces2 != 'no': sgrams2_select.taper(taper_frac) if traces3 != 'no': sgrams3_select.taper(taper_frac) if filt: sgrams1_select.filter('bandpass', freqmin=freq_min, freqmax=freq_max, corners=4, zerophase=True) if traces2 != 'no': sgrams2_select.filter('bandpass', freqmin=freq_min, freqmax=freq_max, corners=4, zerophase=True) if traces3 != 'no': sgrams3_select.filter('bandpass', freqmin=freq_min, freqmax=freq_max, corners=4, zerophase=True) if taper: sgrams1_select.taper(taper_frac) if traces2 != 'no': sgrams2_select.taper(taper_frac) if traces3 != 'no': sgrams3_select.taper(taper_frac) #%% plot traces, different windows in case multiple components are to be compared if component1 == 'Z': fig_index = 13 + fig_inc elif component1 == 'N': fig_index = 14 + fig_inc elif component1 == 'E': fig_index = 15 + fig_inc elif component1 == 'R': fig_index = 16 + fig_inc elif component1 == 'T': fig_index = 17 + fig_inc plt.close(fig_index) plt.figure(fig_index, figsize=(12, 12)) plt.xlim(start_buff, end_buff) plt.ylim(min_dist, max_dist) # find max of absolute amplitude maxD1 = 0 for tr in sgrams1_select: tr_max = max(abs(tr.data)) if tr_max > maxD1: maxD = tr_max if traces2 != 'no': maxD2 = 0 for tr in sgrams2_select: tr_max = max(abs(tr.data)) if tr_max > maxD2: maxD2 = tr_max maxD = max(maxD, maxD2) if traces3 != 'no': maxD3 = 0 for tr in sgrams3_select: tr_max = max(abs(tr.data)) if tr_max > maxD3: maxD3 = tr_max maxD = max(maxD, maxD3) # find max normalized to 10 km distance (i.e., amp divided by distance, assumes 1/R amp fall-off) maxD_N = 0 for tr in sgrams1_select: if verbose == True: print(f'Distance is {tr.stats.distance:6.4f}' + ' ' + tr.stats.station) tr_max = max(abs(tr.data)) * tr.stats.distance if tr_max > maxD_N: maxD_N = tr_max print(f'Peak amp of trace1 is {maxD1:6.4f}') if traces2 != 'no': print(f'Peak amp of trace2 is {maxD2:6.4f}') print(f'Peak amp of both traces is {maxD:6.4f}') if traces3 != 'no': print(f'Peak amp of trace3 is {maxD3:6.4f}') print(f'Peak amp of both traces is {maxD:6.4f}') print(f'Normed amp of data is {maxD_N:6.4f}') plot_fac = plot_scale_fac * (max_dist - min_dist) / maxD_N for tr in sgrams1_select: dist_offset = tr.stats.distance # km if dist_offset > min_dist and dist_offset < max_dist: # distance range to plot pnum = max(abs(tr.data)) printee2 = f'{tr.stats.station} {pnum:5.3f} cm/s' plt.text(s=printee2, x=end_buff * 0.7, y=dist_offset + (max_dist - min_dist) * 0.02, color='black' ) #label traces and note amplitude (cm, cm/s, cm/s^^2) if traces1 == 'CVM_H' or traces1 == 'CVM_S' or traces1 == 'Jon_CVM_S': tr.stats.starttime = t1 # print('1st gather: trace ' + tr.stats.station + 'starts at ' + str(tr.stats.starttime)) ttt = np.arange(len(tr.data)) * tr.stats.delta + ( tr.stats.starttime - t1) - shift1 # impose manual shift of shift1 color_p = 'red' if traces2 == 'no': color_p = 'black' if norm_each: plt.plot(ttt, (tr.data - np.median(tr.data)) * plot_scale_fac / (tr.data.max() - tr.data.min()) + dist_offset, color=color_p) elif dist_norm == True: # normalized to 20 km distance plt.plot(ttt, (tr.data * plot_fac * (tr.stats.distance / 20.)) + dist_offset, color=color_p) else: plt.plot(ttt, (tr.data * plot_fac) + dist_offset, color=color_p) if traces2 != 'no': for tr in sgrams2_select: dist_offset = tr.stats.distance if dist_offset > min_dist and dist_offset < max_dist: if traces2 == 'CVM_H' or traces2 == 'CVM_S' or traces2 == 'Jon_CVM_S': tr.stats.starttime = t1 # print('2nd gather: trace ' + tr.stats.station + 'starts at ' + str(tr.stats.starttime)) ttt = np.arange(len(tr.data)) * tr.stats.delta + ( tr.stats.starttime - t1) - shift2 # impose manual shift of shift2 seconds if norm_each: plt.plot(ttt, (tr.data - np.median(tr.data)) * plot_scale_fac / (tr.data.max() - tr.data.min()) + dist_offset, color='green') elif dist_norm == True: # normalized to 20 km distance plt.plot(ttt, (tr.data * plot_fac * (tr.stats.distance / 20.)) + dist_offset, color='green') else: plt.plot(ttt, (tr.data * plot_fac) + dist_offset, color='green') if traces3 != 'no': for tr in sgrams3_select: dist_offset = tr.stats.distance if dist_offset > min_dist and dist_offset < max_dist: if traces3 == 'CVM_H' or traces3 == 'CVM_S' or traces3 == 'Jon_CVM_S': tr.stats.starttime = t1 # print('3rd gather: trace ' + tr.stats.station + 'starts at ' + str(tr.stats.starttime)) ttt = np.arange(len(tr.data)) * tr.stats.delta + ( tr.stats.starttime - t1) - shift3 # impose manual shift of shift2 seconds if norm_each: plt.plot(ttt, (tr.data - np.median(tr.data)) * plot_scale_fac / (tr.data.max() - tr.data.min()) + dist_offset, color='blue') elif dist_norm == True: # normalized to 20 km distance plt.plot(ttt, (tr.data * plot_fac * (tr.stats.distance / 20.)) + dist_offset, color='blue') else: plt.plot(ttt, (tr.data * plot_fac) + dist_offset, color='blue') plt.xlabel('Time (s)') plt.ylabel('Epicentral distance from event (km)') # plt.title(fname1[8:18] + ' vs ' + fname2[8:18] + ' vs ' + fname3[8:18]) if traces2 != 'no': if traces3 != 'no': plt.title(event_dir + ' R: ' + traces1 + ' ' + component1 + ' Green: ' + traces2 + ' ' + component2 + ' Blue: ' + traces3 + ' ' + component3) else: plt.title(event_dir + ' Red: ' + traces1 + ' ' + component1 + ' Green: ' + traces2 + ' ' + component2) else: plt.title(event_dir + ' ' + traces1 + ' ' + component1) plt.show() elapsed_time_wc = time.time() - start_time_wc print('This job took ' + str(elapsed_time_wc) + ' seconds') os.system('say "Done"')
def waveforms_matrix(home,project_name,fault_name,rupture_name,station_file,GF_list, model_name,run_name,epicenter,time_epi,integrate,tsunami,hot_start, resample,beta,rupture_speed,num_windows,dt,NFFT): ''' To supplant waveforms() it needs to include resmapling and all that jazz... This routine will take synthetics and apply a slip dsitribution. It will delay each subfault by the appropriate rupture time and linearly superimpose all of them. Output will be one sac waveform file per direction of motion (NEU) for each station defined in the station_file. Depending on the specified rake angle at each subfault the code will compute the contribution to dip and strike slip directions. It will also compute the moment at that subfault and scale it according to the unit amount of momeent (1e15 N-m) IN: home: Home directory project_name: Name of the problem rupture_name: Name of rupture description file station_file: File with coordinates of stations model_Name: Name of Earth structure model file integrate: =0 if you want output to be velocity, =1 if you want output to de displacement OUT: Nothing ''' from numpy import loadtxt,genfromtxt,allclose,vstack,deg2rad,array,sin,cos,where,zeros,arange from obspy import read,Stream,Trace from string import rjust import datetime import gc from mudpy.inverse import getG from linecache import getline from os import remove print 'Solving for kinematic problem' #Output where? outpath=home+project_name+'/output/forward_models/' logpath=home+project_name+'/logs/' log='' #Time for log file now=datetime.datetime.now() now=now.strftime('%b-%d-%H%M') #load source #source=loadtxt(home+project_name+'/forward_models/'+rupture_name,ndmin=2) #Load stations station_file=home+project_name+'/data/station_info/'+station_file staname=genfromtxt(station_file,dtype="S6",usecols=0) #Now load rupture model mss=genfromtxt(home+project_name+'/forward_models/'+rupture_name,usecols=8) mds=genfromtxt(home+project_name+'/forward_models/'+rupture_name,usecols=9) m=zeros(2*len(mss)) i=arange(0,2*len(mss),2) m[i]=mss i=arange(1,2*len(mds),2) m[i]=mds #What am I processing v or d ? if integrate==1: vord='disp' else: vord='vel' #Load gflist gfsta=genfromtxt(home+project_name+'/data/station_info/'+GF_list,usecols=0,skip_header=1,dtype='S6') #Loop over stations for ksta in range(hot_start,len(staname)): print 'Working on station '+staname[ksta]+' ('+str(ksta+1)+'/'+str(len(staname))+')' #Initalize output n=Stream() e=Stream() z=Stream() sta=staname[ksta] #Make dummy data ndummy=Stream(Trace()) ndummy[0].data=zeros(int(NFFT)) ndummy[0].stats.delta=dt ndummy[0].stats.starttime=time_epi edummy=ndummy.copy() udummy=ndummy.copy() ndummy.write(home+project_name+'/data/waveforms/'+sta+'.'+vord+'.n',format='SAC') edummy.write(home+project_name+'/data/waveforms/'+sta+'.'+vord+'.e',format='SAC') udummy.write(home+project_name+'/data/waveforms/'+sta+'.'+vord+'.u',format='SAC') #Extract only one station from GF_list file ista=int(where(gfsta==staname[ksta])[0])+2 #Make mini GF_file tmpgf='tmpfwd.gflist' try: remove(home+project_name+'/data/station_info/'+tmpgf) except: pass gflist=getline(home+project_name+'/data/station_info/'+GF_list,ista) f=open(home+project_name+'/data/station_info/'+tmpgf,'w') f.write('# Headers\n') f.write(gflist) f.close() #Get matrix for one station to all sources G_from_file=False G_name='tmpfwd' G=getG(home,project_name,fault_name,model_name,tmpgf,G_from_file,G_name,epicenter, rupture_speed,num_windows,decimate=None,bandpass=None,tsunami=False) # Matrix multiply and separate data streams d=G.dot(m) n=ndummy.copy() e=edummy.copy() u=udummy.copy() ncut=len(d)/3 n[0].data=d[0:ncut] e[0].data=d[ncut:2*ncut] u[0].data=d[2*ncut:3*ncut] # Write to file n.write(home+project_name+'/output/forward_models/'+run_name+'.'+gfsta[ksta]+'.'+vord+'.n',format='SAC') e.write(home+project_name+'/output/forward_models/'+run_name+'.'+gfsta[ksta]+'.'+vord+'.e',format='SAC') u.write(home+project_name+'/output/forward_models/'+run_name+'.'+gfsta[ksta]+'.'+vord+'.u',format='SAC')
def obtain_timeshifts(st): '''function to compute time shift of overlapping files ''' '''st: obspy stream containing one station channel pair ''' # check if all traces are from same station/channel for tr in st: if tr.stats.station == st[0].stats.station: pass else: raise NameError("Traces in st recorded by different stations") if tr.stats.channel == st[0].stats.channel: pass else: raise NameError("Traces in st recorded on different channels") # empty stream for time corrected traces; add first trace cor = Stream() # "take_previous" determines if first trace is subject to syncronization to previous mseed file take_previous = False # check if previous mseed file is available try: stn = st[0].stats.station chn = st[0].stats.channel stime = st[0].stats.starttime prev = read("/scratch/flindner/G7Jul01/MSEED/4D.%s..%s.%04d%02d%02d%02d*" \ % (stn, chn, stime.year, stime.month, stime.day, stime.hour), starttime=stime - 5) cor += prev[0] # available -> synchronize take_previous = True except: pass # in case of no sync to previous file, start with timeshift between first and second trace if take_previous == False: cor += st[0] st = st[1:] warnings.warn("Synchronization to previous MSEED file failed!") # empty list for dts dts = [] # add next trace for i, tr in enumerate(st): cor += tr # helper stream - overlap helper = cor.copy() t1 = helper[1].stats.starttime t2 = helper[0].stats.endtime helper.trim(t1, t2) delta = helper[0].stats.delta npts = helper[0].stats.npts # even number of samples if (npts % 2) != 0: helper.trim(t1, t2-delta) delta = helper[0].stats.delta # cross-correlation in order to obtain timeshift xcorr = np.correlate(helper[0].data, helper[1].data, "same") xcorr = Trace(data=xcorr) xcorr.stats.delta = delta xcorr.resample(1000) ndelta = xcorr.stats.delta nnpts = xcorr.stats.npts dt = (np.argmax(xcorr.data) - nnpts/2.) * ndelta dt = round(dt, 3) # add estimated time shift to second trace cor[1].stats.starttime += dt # test if time series are perfectly aligned check = True count = 0 while check: start = cor[1].stats.starttime end = cor[0].stats.endtime test = cor.slice(start, end) eq_st = False if round(test[0].stats.starttime.timestamp, 4) == round(test[1].stats.starttime.timestamp, 4): eq_st = True eq_ar = np.array_equal(test[0].data, test[1].data) if eq_ar == True and eq_st == True: # if perfectly aligned, quit testing loop check = False if count == 0: dts.append(dt) # if not perfectly aligned, undo time shift obtained by xcorr and shift manually else: if count == 0: # undo time shift obtained from xcorr cor[1].stats.starttime -= dt mshift = int((end - start) / delta) + 1000 cor[1].stats.starttime -= mshift*delta elif count > 0: cor[1].stats.starttime += delta cor[1].stats.starttime.microsecond = np.round(cor[1].stats.starttime.microsecond, 3) count += 1 # in case of take_previous == True, remove previous file from stream (was just needed for time sync) if i==0 and take_previous: for tr in cor.select(network="4D"): cor.remove(tr) # time shift obtained from shifting if count > 1: dt = (-mshift + count - 2) * delta dts.append(dt) cor.merge(method=1) # check number of written times shifts dts = np.asarray(dts) if len(dts) != len(st): raise ValueError("Number of time shifts is not correct") return dts, take_previous
def test_coincidence_trigger(self): """ Test network coincidence trigger. """ st = Stream() files = ["BW.UH1._.SHZ.D.2010.147.cut.slist.gz", "BW.UH2._.SHZ.D.2010.147.cut.slist.gz", "BW.UH3._.SHZ.D.2010.147.cut.slist.gz", "BW.UH4._.EHZ.D.2010.147.cut.slist.gz"] for filename in files: filename = os.path.join(self.path, filename) st += read(filename) # some prefiltering used for UH network st.filter('bandpass', freqmin=10, freqmax=20) # 1. no weighting, no stations specified, good settings # => 3 events, no false triggers # for the first test we make some additional tests regarding types res = coincidence_trigger("recstalta", 3.5, 1, st.copy(), 3, sta=0.5, lta=10) self.assertTrue(isinstance(res, list)) self.assertEqual(len(res), 3) expected_keys = ['time', 'coincidence_sum', 'duration', 'stations', 'trace_ids'] expected_types = [UTCDateTime, float, float, list, list] for item in res: self.assertTrue(isinstance(item, dict)) for key, _type in zip(expected_keys, expected_types): self.assertIn(key, item) self.assertTrue(isinstance(item[key], _type)) self.assertGreater(res[0]['time'], UTCDateTime("2010-05-27T16:24:31")) self.assertTrue(res[0]['time'] < UTCDateTime("2010-05-27T16:24:35")) self.assertTrue(4.2 < res[0]['duration'] < 4.8) self.assertEqual(res[0]['stations'], ['UH3', 'UH2', 'UH1', 'UH4']) self.assertEqual(res[0]['coincidence_sum'], 4) self.assertGreater(res[1]['time'], UTCDateTime("2010-05-27T16:26:59")) self.assertTrue(res[1]['time'] < UTCDateTime("2010-05-27T16:27:03")) self.assertTrue(3.2 < res[1]['duration'] < 3.7) self.assertEqual(res[1]['stations'], ['UH2', 'UH3', 'UH1']) self.assertEqual(res[1]['coincidence_sum'], 3) self.assertGreater(res[2]['time'], UTCDateTime("2010-05-27T16:27:27")) self.assertTrue(res[2]['time'] < UTCDateTime("2010-05-27T16:27:33")) self.assertTrue(4.2 < res[2]['duration'] < 4.4) self.assertEqual(res[2]['stations'], ['UH3', 'UH2', 'UH1', 'UH4']) self.assertEqual(res[2]['coincidence_sum'], 4) # 2. no weighting, station selection # => 2 events, no false triggers trace_ids = ['BW.UH1..SHZ', 'BW.UH3..SHZ', 'BW.UH4..EHZ'] # raises "UserWarning: At least one trace's ID was not found" with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', UserWarning) re = coincidence_trigger("recstalta", 3.5, 1, st.copy(), 3, trace_ids=trace_ids, sta=0.5, lta=10) self.assertEqual(len(w), 1) self.assertIn("At least one trace's ID was not", str(w[0])) self.assertEqual(len(re), 2) self.assertGreater(re[0]['time'], UTCDateTime("2010-05-27T16:24:31")) self.assertTrue(re[0]['time'] < UTCDateTime("2010-05-27T16:24:35")) self.assertTrue(4.2 < re[0]['duration'] < 4.8) self.assertEqual(re[0]['stations'], ['UH3', 'UH1', 'UH4']) self.assertEqual(re[0]['coincidence_sum'], 3) self.assertGreater(re[1]['time'], UTCDateTime("2010-05-27T16:27:27")) self.assertTrue(re[1]['time'] < UTCDateTime("2010-05-27T16:27:33")) self.assertTrue(4.2 < re[1]['duration'] < 4.4) self.assertEqual(re[1]['stations'], ['UH3', 'UH1', 'UH4']) self.assertEqual(re[1]['coincidence_sum'], 3) # 3. weighting, station selection # => 3 events, no false triggers trace_ids = {'BW.UH1..SHZ': 0.4, 'BW.UH2..SHZ': 0.35, 'BW.UH3..SHZ': 0.4, 'BW.UH4..EHZ': 0.25} res = coincidence_trigger("recstalta", 3.5, 1, st.copy(), 1.0, trace_ids=trace_ids, sta=0.5, lta=10) self.assertEqual(len(res), 3) self.assertGreater(res[0]['time'], UTCDateTime("2010-05-27T16:24:31")) self.assertTrue(res[0]['time'] < UTCDateTime("2010-05-27T16:24:35")) self.assertTrue(4.2 < res[0]['duration'] < 4.8) self.assertEqual(res[0]['stations'], ['UH3', 'UH2', 'UH1', 'UH4']) self.assertEqual(res[0]['coincidence_sum'], 1.4) self.assertGreater(res[1]['time'], UTCDateTime("2010-05-27T16:26:59")) self.assertTrue(res[1]['time'] < UTCDateTime("2010-05-27T16:27:03")) self.assertTrue(3.2 < res[1]['duration'] < 3.7) self.assertEqual(res[1]['stations'], ['UH2', 'UH3', 'UH1']) self.assertEqual(res[1]['coincidence_sum'], 1.15) self.assertGreater(res[2]['time'], UTCDateTime("2010-05-27T16:27:27")) self.assertTrue(res[2]['time'] < UTCDateTime("2010-05-27T16:27:33")) self.assertTrue(4.2 < res[2]['duration'] < 4.4) self.assertEqual(res[2]['stations'], ['UH3', 'UH2', 'UH1', 'UH4']) self.assertEqual(res[2]['coincidence_sum'], 1.4) # 4. weighting, station selection, max_len # => 2 events, no false triggers, small event does not overlap anymore trace_ids = {'BW.UH1..SHZ': 0.6, 'BW.UH2..SHZ': 0.6} # raises "UserWarning: At least one trace's ID was not found" with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', UserWarning) re = coincidence_trigger("recstalta", 3.5, 1, st.copy(), 1.2, trace_ids=trace_ids, max_trigger_length=0.13, sta=0.5, lta=10) self.assertEqual(len(w), 2) self.assertIn("At least one trace's ID was not", str(w[0])) self.assertIn("At least one trace's ID was not", str(w[1])) self.assertEqual(len(re), 2) self.assertGreater(re[0]['time'], UTCDateTime("2010-05-27T16:24:31")) self.assertTrue(re[0]['time'] < UTCDateTime("2010-05-27T16:24:35")) self.assertTrue(0.2 < re[0]['duration'] < 0.3) self.assertEqual(re[0]['stations'], ['UH2', 'UH1']) self.assertEqual(re[0]['coincidence_sum'], 1.2) self.assertGreater(re[1]['time'], UTCDateTime("2010-05-27T16:27:27")) self.assertTrue(re[1]['time'] < UTCDateTime("2010-05-27T16:27:33")) self.assertTrue(0.18 < re[1]['duration'] < 0.2) self.assertEqual(re[1]['stations'], ['UH2', 'UH1']) self.assertEqual(re[1]['coincidence_sum'], 1.2) # 5. station selection, extremely sensitive settings # => 4 events, 1 false triggers # raises "UserWarning: At least one trace's ID was not found" with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', UserWarning) res = coincidence_trigger("recstalta", 2.5, 1, st.copy(), 2, trace_ids=['BW.UH1..SHZ', 'BW.UH3..SHZ'], sta=0.3, lta=5) self.assertEqual(len(w), 2) self.assertIn("At least one trace's ID was not", str(w[0])) self.assertIn("At least one trace's ID was not", str(w[1])) self.assertEqual(len(res), 5) self.assertGreater(res[3]['time'], UTCDateTime("2010-05-27T16:27:01")) self.assertTrue(res[3]['time'] < UTCDateTime("2010-05-27T16:27:02")) self.assertTrue(1.5 < res[3]['duration'] < 1.7) self.assertEqual(res[3]['stations'], ['UH3', 'UH1']) self.assertEqual(res[3]['coincidence_sum'], 2.0) # 6. same as 5, gappy stream # => same as 5 (almost, duration of 1 event changes by 0.02s) st2 = st.copy() tr1 = st2.pop(0) t1 = tr1.stats.starttime t2 = tr1.stats.endtime td = t2 - t1 tr1a = tr1.slice(starttime=t1, endtime=t1 + 0.45 * td) tr1b = tr1.slice(starttime=t1 + 0.6 * td, endtime=t1 + 0.94 * td) st2.insert(1, tr1a) st2.insert(3, tr1b) with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', UserWarning) res = coincidence_trigger("recstalta", 2.5, 1, st2, 2, trace_ids=['BW.UH1..SHZ', 'BW.UH3..SHZ'], sta=0.3, lta=5) self.assertEqual(len(w), 2) self.assertIn("At least one trace's ID was not", str(w[0])) self.assertIn("At least one trace's ID was not", str(w[1])) self.assertEqual(len(res), 5) self.assertGreater(res[3]['time'], UTCDateTime("2010-05-27T16:27:01")) self.assertTrue(res[3]['time'] < UTCDateTime("2010-05-27T16:27:02")) self.assertTrue(1.5 < res[3]['duration'] < 1.7) self.assertEqual(res[3]['stations'], ['UH3', 'UH1']) self.assertEqual(res[3]['coincidence_sum'], 2.0) # 7. same as 3 but modify input trace ids and check output of trace_ids # and other additional information with ``details=True`` st2 = st.copy() st2[0].stats.network = "XX" st2[1].stats.location = "99" st2[1].stats.network = "" st2[1].stats.location = "99" st2[1].stats.channel = "" st2[2].stats.channel = "EHN" st2[3].stats.network = "" st2[3].stats.channel = "" st2[3].stats.station = "" trace_ids = {'XX.UH1..SHZ': 0.4, '.UH2.99.': 0.35, 'BW.UH3..EHN': 0.4, '...': 0.25} res = coincidence_trigger("recstalta", 3.5, 1, st2, 1.0, trace_ids=trace_ids, details=True, sta=0.5, lta=10) self.assertEqual(len(res), 3) self.assertGreater(res[0]['time'], UTCDateTime("2010-05-27T16:24:31")) self.assertTrue(res[0]['time'] < UTCDateTime("2010-05-27T16:24:35")) self.assertTrue(4.2 < res[0]['duration'] < 4.8) self.assertEqual(res[0]['stations'], ['UH3', 'UH2', 'UH1', '']) self.assertEqual(res[0]['trace_ids'][0], st2[2].id) self.assertEqual(res[0]['trace_ids'][1], st2[1].id) self.assertEqual(res[0]['trace_ids'][2], st2[0].id) self.assertEqual(res[0]['trace_ids'][3], st2[3].id) self.assertEqual(res[0]['coincidence_sum'], 1.4) self.assertGreater(res[1]['time'], UTCDateTime("2010-05-27T16:26:59")) self.assertTrue(res[1]['time'] < UTCDateTime("2010-05-27T16:27:03")) self.assertTrue(3.2 < res[1]['duration'] < 3.7) self.assertEqual(res[1]['stations'], ['UH2', 'UH3', 'UH1']) self.assertEqual(res[1]['trace_ids'][0], st2[1].id) self.assertEqual(res[1]['trace_ids'][1], st2[2].id) self.assertEqual(res[1]['trace_ids'][2], st2[0].id) self.assertEqual(res[1]['coincidence_sum'], 1.15) self.assertGreater(res[2]['time'], UTCDateTime("2010-05-27T16:27:27")) self.assertTrue(res[2]['time'] < UTCDateTime("2010-05-27T16:27:33")) self.assertTrue(4.2 < res[2]['duration'] < 4.4) self.assertEqual(res[2]['stations'], ['UH3', 'UH2', 'UH1', '']) self.assertEqual(res[2]['trace_ids'][0], st2[2].id) self.assertEqual(res[2]['trace_ids'][1], st2[1].id) self.assertEqual(res[2]['trace_ids'][2], st2[0].id) self.assertEqual(res[2]['trace_ids'][3], st2[3].id) self.assertEqual(res[2]['coincidence_sum'], 1.4) expected_keys = ['cft_peak_wmean', 'cft_std_wmean', 'cft_peaks', 'cft_stds'] expected_types = [float, float, list, list] for item in res: for key, _type in zip(expected_keys, expected_types): self.assertIn(key, item) self.assertTrue(isinstance(item[key], _type)) # check some of the detailed info ev = res[-1] self.assertAlmostEqual(ev['cft_peak_wmean'], 18.101139518271076) self.assertAlmostEqual(ev['cft_std_wmean'], 4.800051726246676) self.assertAlmostEqual(ev['cft_peaks'][0], 18.985548683223936) self.assertAlmostEqual(ev['cft_peaks'][1], 16.852175794415011) self.assertAlmostEqual(ev['cft_peaks'][2], 18.64005853900883) self.assertAlmostEqual(ev['cft_peaks'][3], 17.572363634564621) self.assertAlmostEqual(ev['cft_stds'][0], 4.8909448258821362) self.assertAlmostEqual(ev['cft_stds'][1], 4.4446373508521804) self.assertAlmostEqual(ev['cft_stds'][2], 5.3499401252675964) self.assertAlmostEqual(ev['cft_stds'][3], 4.2723814539487703)
class RFData(object): """ A RFData object contains Class attributes that associate station information with a single event (i.e., earthquake) metadata, corresponding raw and rotated seismograms and receiver functions. Note ---- The object is initialized with the ``sta`` field only, and other attributes are added to the object as the analysis proceeds. Parameters ---------- sta : object Object containing station information - from :mod:`~stdb` database. meta : :class:`~rfpy.rfdata.Meta` Object of metadata information for single event (initially set to None) data : :class:`~obspy.core.Stream` Stream object containing the three-component seismograms (either un-rotated or rotated by the method :func:`~rfpy.rfdata.rotate`) """ def __init__(self, sta): # Load example data if initializing empty object if sta == 'demo' or sta == 'Demo': print("Uploading demo data - station NY.MMPY") import os import pickle sta = pickle.load( open( os.path.join(os.path.dirname(__file__), "examples/data", "MMPY.pkl"), 'rb'))['NY.MMPY'] # Attributes from parameters self.sta = sta # Initialize meta and data objects as None self.meta = None self.data = None def add_event(self, event, gacmin=30., gacmax=90., phase='P', returned=False): """ Adds event metadata to RFData object, including travel time info of P wave. Parameters ---------- event : :class:`~obspy.core.event` Event XML object returned : bool Whether or not to return the ``accept`` attribute Returns ------- accept : bool Whether or not the object is accepted for further analysis """ from obspy.geodetics.base import gps2dist_azimuth as epi from obspy.geodetics import kilometer2degrees as k2d from obspy.taup import TauPyModel from obspy.core.event.event import Event if event == 'demo' or event == 'Demo': from obspy.clients.fdsn import Client from obspy.core import UTCDateTime client = Client() # Get catalogue using deployment start and end event = client.get_events( starttime=UTCDateTime('2014-06-30T19:00:00'), endtime=UTCDateTime('2014-06-30T21:00:00'), minmagnitude=6.0, maxmagnitude=6.5)[0] print(event.short_str()) if not isinstance(event, Event): raise (Exception("Event has incorrect type")) # Store as object attributes self.meta = Meta(sta=self.sta, event=event, gacmin=gacmin, gacmax=gacmax, phase=phase) if returned: return self.meta.accept def add_data(self, stream, returned=False, new_sr=5.): """ Adds stream as object attribute Parameters ---------- stream : :class:`~obspy.core.Stream` Stream container for NEZ seismograms returned : bool Whether or not to return the ``accept`` attribute Attributes ---------- zne_data : :class:`~obspy.core.Stream` Stream container for NEZ seismograms Returns ------- accept : bool Whether or not the object is accepted for further analysis """ if not self.meta: raise (Exception("No meta data available - aborting")) if not self.meta.accept: return # Load demo data if stream == 'demo' or stream == 'Demo': import os import pickle file = open( os.path.join(os.path.dirname(__file__), "examples/data", "ZNE_Data.pkl"), "rb") stream = pickle.load(file) print(stream) if not isinstance(stream, Stream): raise (Exception("Event has incorrect type")) try: self.data = stream if not np.allclose([tr.stats.npts for tr in stream[1:]], stream[0].stats.npts): self.meta.accept = False # Filter Traces if not stream[0].stats.sampling_rate == new_sr: self.data.filter('lowpass', freq=0.5 * new_sr, corners=2, zerophase=True) self.data.resample(new_sr, no_filter=True) except: print("Error: Not all channels are available") self.meta.accept = False if returned: return self.meta.accept def download_data(self, client, stdata=[], ndval=np.nan, new_sr=5., dts=120., returned=False, verbose=False): """ Downloads seismograms based on event origin time and P phase arrival. Parameters ---------- client : :class:`~obspy.client.fdsn.Client` Client object ndval : float Fill in value for missing data new_sr : float New sampling rate (Hz) dts : float Time duration (sec) stdata : List Station list returned : bool Whether or not to return the ``accept`` attribute Returns ------- accept : bool Whether or not the object is accepted for further analysis Attributes ---------- data : :class:`~obspy.core.Stream` Stream containing :class:`~obspy.core.Trace` objects """ if self.meta is None: raise (Exception("Requires event data as attribute - aborting")) if not self.meta.accept: return # Define start time for request tstart = self.meta.time + self.meta.ttime - dts tend = self.meta.time + self.meta.ttime + dts # Get waveforms print("* Requesting Waveforms: ") print("* Startime: " + tstart.strftime("%Y-%m-%d %H:%M:%S")) print("* Endtime: " + tend.strftime("%Y-%m-%d %H:%M:%S")) # Download data err, stream = utils.download_data(client=client, sta=self.sta, start=tstart, end=tend, stdata=stdata, ndval=ndval, new_sr=new_sr, verbose=verbose) # Store as attributes with traces in dictionary try: trE = stream.select(component='E')[0] trN = stream.select(component='N')[0] trZ = stream.select(component='Z')[0] self.data = Stream(traces=[trZ, trN, trE]) # Filter Traces and resample self.data.filter('lowpass', freq=0.5 * new_sr, corners=2, zerophase=True) self.data.resample(new_sr, no_filter=True) # If there is no ZNE, perhaps there is Z12? except: try: tr1 = stream.select(component='1')[0] tr2 = stream.select(component='2')[0] trZ = stream.select(component='Z')[0] self.data = Stream(traces=[trZ, tr1, tr2]) # Filter Traces and resample self.data.filter('lowpass', freq=0.5 * new_sr, corners=2, zerophase=True) self.data.resample(new_sr, no_filter=True) # Save Z12 components in case it's necessary for later self.dataZ12 = self.data.copy() # Rotate from Z12 to ZNE using StDb azcorr attribute self.rotate(align='ZNE') except: self.meta.accept = False if returned: return self.meta.accept def rotate(self, vp=None, vs=None, align=None): """ Rotates 3-component seismograms from vertical (Z), east (E) and north (N) to longitudinal (L), radial (Q) and tangential (T) components of motion. Note that the method 'rotate' from ``obspy.core.stream.Stream`` is used for the rotation ``'ZNE->ZRT'`` and ``'ZNE->LQT'``. Rotation ``'ZNE->PVH'`` is implemented separately here due to different conventions. Parameters ---------- vp : float P-wave velocity at surface (km/s) vs : float S-wave velocity at surface (km/s) align : str Alignment of coordinate system for rotation ('ZRT', 'LQT', or 'PVH') Returns ------- rotated : bool Whether or not the object has been rotated """ if not self.meta.accept: return if self.meta.rotated: print("Data have been rotated already - continuing") return # Use default values from meta data if arguments are not specified if not align: align = self.meta.align if align == 'ZNE': from obspy.signal.rotate import rotate2zne # Copy traces trZ = self.data.select(component='Z')[0].copy() trN = self.data.select(component='1')[0].copy() trE = self.data.select(component='2')[0].copy() azim = self.sta.azcorr # Try EBS with left handed system Z, N, E = rotate2zne(trZ.data, 0., -90., trN.data, azim, 0., trE.data, azim + 90., 0.) # Z, N, E = rotate2zne(trZ.data, 0., -90., trN.data, # azim, 0., trE.data, azim+90., 0.) trN.data = N trE.data = E # Update stats of streams trN.stats.channel = trN.stats.channel[:-1] + 'N' trE.stats.channel = trE.stats.channel[:-1] + 'E' self.data = Stream(traces=[trZ, trN, trE]) elif align == 'ZRT': self.data.rotate('NE->RT', back_azimuth=self.meta.baz) self.meta.align = align self.meta.rotated = True elif align == 'LQT': self.data.rotate('ZNE->LQT', back_azimuth=self.meta.baz, inclination=self.meta.inc) for tr in self.data: if tr.stats.channel.endswith('Q'): tr.data = -tr.data self.meta.align = align self.meta.rotated = True elif align == 'PVH': # First rotate to ZRT self.data.rotate('NE->RT', back_azimuth=self.meta.baz) # Copy traces trP = self.data.select(component='Z')[0].copy() trV = self.data.select(component='R')[0].copy() trH = self.data.select(component='T')[0].copy() slow = self.meta.slow if not vp: vp = self.meta.vp if not vs: vs = self.meta.vs # Vertical slownesses # P vertical slowness qp = np.sqrt(1. / vp / vp - slow * slow) # S vertical slowness qs = np.sqrt(1. / vs / vs - slow * slow) # Elements of rotation matrix m11 = slow * vs * vs / vp m12 = -(1. - 2. * vs * vs * slow * slow) / (2. * vp * qp) m21 = (1. - 2. * vs * vs * slow * slow) / (2. * vs * qs) m22 = slow * vs # Rotation matrix rot = np.array([[-m11, m12], [-m21, m22]]) # Vector of Radial and Vertical r_z = np.array([trV.data, trP.data]) # Rotation vec = np.dot(rot, r_z) # Extract P and SV, SH components trP.data = vec[0, :] trV.data = vec[1, :] trH.data = -trH.data / 2. # Update stats of streams trP.stats.channel = trP.stats.channel[:-1] + 'P' trV.stats.channel = trV.stats.channel[:-1] + 'V' trH.stats.channel = trH.stats.channel[:-1] + 'H' # Over-write data attribute self.data = Stream(traces=[trP, trV, trH]) self.meta.align = align self.meta.rotated = True else: raise (Exception("incorrect 'align' argument")) def calc_snr(self, dt=30., fmin=0.05, fmax=1.): """ Calculates signal-to-noise ratio on either Z, L or P component Parameters ---------- dt : float Duration (sec) fmin : float Minimum frequency corner for SNR filter (Hz) fmax : float Maximum frequency corner for SNR filter (Hz) Attributes ---------- snr : float Signal-to-noise ratio on vertical component (dB) snrh : float Signal-to-noise ratio on radial component (dB) """ if not self.meta.accept: return if self.meta.snr: print("SNR already calculated - continuing") return t1 = self.meta.time + self.meta.ttime # SNR for dominant component ('Z', 'L' or 'P') comp = self.meta.align[0] # Copy trace to signal and noise traces trSig = self.data.select(component=comp)[0].copy() trNze = self.data.select(component=comp)[0].copy() trSig.detrend().taper(max_percentage=0.05) trNze.detrend().taper(max_percentage=0.05) # Filter between 0.1 and 1.0 (dominant P wave frequencies) trSig.filter('bandpass', freqmin=fmin, freqmax=fmax, corners=2, zerophase=True) trNze.filter('bandpass', freqmin=fmin, freqmax=fmax, corners=2, zerophase=True) # Trim around P-wave arrival trSig.trim(t1, t1 + dt) trNze.trim(t1 - dt, t1) # Calculate root mean square (RMS) srms = np.sqrt(np.mean(np.square(trSig.data))) nrms = np.sqrt(np.mean(np.square(trNze.data))) # Calculate signal/noise ratio in dB self.meta.snr = 10 * np.log10(srms * srms / nrms / nrms) # SNR for radial component ('R', 'Q' or 'V') comp = self.meta.align[1] # Copy trace to signal and noise traces trSig = self.data.select(component=comp)[0].copy() trNze = self.data.select(component=comp)[0].copy() trSig.detrend().taper(max_percentage=0.05) trNze.detrend().taper(max_percentage=0.05) # Filter between 0.1 and 1.0 (dominant P wave frequencies) trSig.filter('bandpass', freqmin=fmin, freqmax=fmax, corners=2, zerophase=True) trNze.filter('bandpass', freqmin=fmin, freqmax=fmax, corners=2, zerophase=True) # Trim around P-wave arrival trSig.trim(t1, t1 + dt) trNze.trim(t1 - dt, t1) # Calculate root mean square (RMS) srms = np.sqrt(np.mean(np.square(trSig.data))) nrms = np.sqrt(np.mean(np.square(trNze.data))) # Calculate signal/noise ratio in dB self.meta.snrh = 10 * np.log10(srms * srms / nrms / nrms) def deconvolve(self, phase='P', vp=None, vs=None, align=None, method='wiener', pre_filt=None, gfilt=None, wlevel=0.01): """ Deconvolves three-compoent data using one component as the source wavelet. The source component is always taken as the dominant compressional component, which can be either 'Z', 'L', or 'P'. Parameters ---------- vp : float P-wave velocity at surface (km/s) vs : float S-wave velocity at surface (km/s) align : str Alignment of coordinate system for rotation ('ZRT', 'LQT', or 'PVH') method : str Method for deconvolution. Options are 'wiener' or 'multitaper' gfilt : float Center frequency of Gaussian filter (Hz). wlevel : float Water level used in ``method='water'``. Attributes ---------- rf : :class:`~obspy.core.Stream` Stream containing the receiver function traces """ if not self.meta.accept: return def _npow2(x): return 1 if x == 0 else 2**(x - 1).bit_length() def _pad(array, n): tmp = np.zeros(n) tmp[:array.shape[0]] = array return tmp def _gauss_filt(dt, nft, f0): df = 1. / (nft * dt) nft21 = int(0.5 * nft + 1) f = df * np.arange(nft21) w = 2. * np.pi * f gauss = np.zeros(nft) gauss[:nft21] = np.exp(-0.25 * (w / f0)**2.) / dt gauss[nft21:] = np.flip(gauss[1:nft21 - 1]) return gauss def _decon(parent, daughter1, daughter2, noise, nn, method): # Get length, zero padding parameters and frequencies dt = parent.stats.delta # Wiener or Water level deconvolution if method == 'wiener' or method == 'water': # npad = _npow2(nn*2) npad = nn freqs = np.fft.fftfreq(npad, d=dt) # Fourier transform Fp = np.fft.fft(parent.data, n=npad) Fd1 = np.fft.fft(daughter1.data, n=npad) Fd2 = np.fft.fft(daughter2.data, n=npad) Fn = np.fft.fft(noise.data, n=npad) # Auto and cross spectra Spp = np.real(Fp * np.conjugate(Fp)) Sd1p = Fd1 * np.conjugate(Fp) Sd2p = Fd2 * np.conjugate(Fp) Snn = np.real(Fn * np.conjugate(Fn)) # Final processing depends on method if method == 'wiener': Sdenom = Spp + Snn elif method == 'water': phi = np.amax(Spp) * wlevel Sdenom = Spp Sdenom[Sdenom < phi] = phi # Multitaper deconvolution elif method == 'multitaper': from spectrum import dpss npad = nn # Re-check length and pad with zeros if necessary if not np.allclose([ tr.stats.npts for tr in [parent, daughter1, daughter2, noise] ], npad): parent.data = _pad(parent.data, npad) daughter1.data = _pad(daughter1.data, npad) daughter2.data = _pad(daughter2.data, npad) noise.data = _pad(noise.data, npad) freqs = np.fft.fftfreq(npad, d=dt) NW = 2.5 Kmax = int(NW * 2 - 2) [tapers, eigenvalues] = dpss(npad, NW, Kmax) # Get multitaper spectrum of data Fp = np.fft.fft(np.multiply(tapers.transpose(), parent.data)) Fd1 = np.fft.fft( np.multiply(tapers.transpose(), daughter1.data)) Fd2 = np.fft.fft( np.multiply(tapers.transpose(), daughter2.data)) Fn = np.fft.fft(np.multiply(tapers.transpose(), noise.data)) # Auto and cross spectra Spp = np.sum(np.real(Fp * np.conjugate(Fp)), axis=0) Sd1p = np.sum(Fd1 * np.conjugate(Fp), axis=0) Sd2p = np.sum(Fd2 * np.conjugate(Fp), axis=0) Snn = np.sum(np.real(Fn * np.conjugate(Fn)), axis=0) # Denominator Sdenom = Spp + Snn else: print("Method not implemented") pass # Apply Gaussian filter? if gfilt: gauss = _gauss_filt(dt, npad, gfilt) gnorm = np.sum(gauss) * (freqs[1] - freqs[0]) * dt else: gauss = np.ones(npad) gnorm = 1. # Copy traces rfp = parent.copy() rfd1 = daughter1.copy() rfd2 = daughter2.copy() # Spectral division and inverse transform rfp.data = np.fft.fftshift( np.real(np.fft.ifft(gauss * Spp / Sdenom)) / gnorm) rfd1.data = np.fft.fftshift( np.real(np.fft.ifft(gauss * Sd1p / Sdenom)) / gnorm) rfd2.data = np.fft.fftshift( np.real(np.fft.ifft(gauss * Sd2p / Sdenom)) / gnorm) # rfd1.data = np.fft.fftshift(np.real(np.fft.ifft( # gauss*Sd1p/Sdenom))/np.amax(rfp.data)/gnorm) # rfd2.data = np.fft.fftshift(np.real(np.fft.ifft( # gauss*Sd2p/Sdenom))/np.amax(rfp.data)/gnorm) return rfp, rfd1, rfd2 if not self.meta.rotated: print("Warning: Data have not been rotated yet - rotating now") self.rotate(vp=vp, vs=vs, align=align) if not self.meta.snr: print("Warning: SNR has not been calculated - " + "calculating now using default") self.calc_snr() if hasattr(self, 'rf'): print("Warning: Data have been deconvolved already - passing") return # Get the name of components (order is critical here) cL = self.meta.align[0] cQ = self.meta.align[1] cT = self.meta.align[2] # Define signal and noise trL = self.data.select(component=cL)[0].copy() trQ = self.data.select(component=cQ)[0].copy() trT = self.data.select(component=cT)[0].copy() trNl = self.data.select(component=cL)[0].copy() trNq = self.data.select(component=cQ)[0].copy() if phase == 'P' or 'PP': # Get signal length (i.e., seismogram to deconvolve) from trace length dts = len(trL.data) * trL.stats.delta / 2. nn = int(round((dts - 5.) * trL.stats.sampling_rate)) + 1 # Crop traces for signal (-5. to dts-10 sec) trL.trim(self.meta.time + self.meta.ttime - 5., self.meta.time + self.meta.ttime + dts - 10., nearest_sample=False, pad=nn, fill_value=0.) trQ.trim(self.meta.time + self.meta.ttime - 5., self.meta.time + self.meta.ttime + dts - 10., nearest_sample=False, pad=nn, fill_value=0.) trT.trim(self.meta.time + self.meta.ttime - 5., self.meta.time + self.meta.ttime + dts - 10., nearest_sample=False, pad=nn, fill_value=0.) # Crop trace for noise (-dts to -5 sec) trNl.trim(self.meta.time + self.meta.ttime - dts, self.meta.time + self.meta.ttime - 5., nearest_sample=False, pad=nn, fill_value=0.) trNq.trim(self.meta.time + self.meta.ttime - dts, self.meta.time + self.meta.ttime - 5., nearest_sample=False, pad=nn, fill_value=0.) elif phase == 'S' or 'SKS': # Get signal length (i.e., seismogram to deconvolve) from trace length dts = len(trL.data) * trL.stats.delta / 2. # Crop traces for signal (-5. to dts-10 sec) trL.trim(self.meta.time + self.meta.ttime + 25. - dts / 2., self.meta.time + self.meta.ttime + 25.) trQ.trim(self.meta.time + self.meta.ttime + 25. - dts / 2., self.meta.time + self.meta.ttime + 25.) trT.trim(self.meta.time + self.meta.ttime + 25. - dts / 2., self.meta.time + self.meta.ttime + 25.) # Crop trace for noise (-dts to -5 sec) trNl.trim(self.meta.time + self.meta.ttime - dts, self.meta.time + self.meta.ttime - dts / 2.) trNq.trim(self.meta.time + self.meta.ttime - dts, self.meta.time + self.meta.ttime - dts / 2.) # Demean, detrend, taper, demean, detrend trL.detrend('demean').detrend('linear').taper(max_percentage=0.05, max_length=2.) trQ.detrend('demean').detrend('linear').taper(max_percentage=0.05, max_length=2.) trT.detrend('demean').detrend('linear').taper(max_percentage=0.05, max_length=2.) trNl.detrend('demean').detrend('linear').taper(max_percentage=0.05, max_length=2.) trNq.detrend('demean').detrend('linear').taper(max_percentage=0.05, max_length=2.) trL.detrend('demean').detrend('linear') trQ.detrend('demean').detrend('linear') trT.detrend('demean').detrend('linear') trNl.detrend('demean').detrend('linear') trNq.detrend('demean').detrend('linear') # Stream(traces=[trL, trQ, trT, trNl, trNq]).plot(size=(600,300)) # Pre-filter waveforms before deconvolution if pre_filt: trL.filter('bandpass', freqmin=pre_filt[0], freqmax=pre_filt[1], corners=2, zerophase=True) trQ.filter('bandpass', freqmin=pre_filt[0], freqmax=pre_filt[1], corners=2, zerophase=True) trT.filter('bandpass', freqmin=pre_filt[0], freqmax=pre_filt[1], corners=2, zerophase=True) trNl.filter('bandpass', freqmin=pre_filt[0], freqmax=pre_filt[1], corners=2, zerophase=True) trNq.filter('bandpass', freqmin=pre_filt[0], freqmax=pre_filt[1], corners=2, zerophase=True) # Stream(traces=[trL, trQ, trT, trNl, trNq]).plot(size=(600,300)) # Demean, detrend, taper, demean, detrend trL.detrend('linear').taper(max_percentage=0.05, max_length=2.).detrend('linear') trQ.detrend('linear').taper(max_percentage=0.05, max_length=2.).detrend('linear') trT.detrend('linear').taper(max_percentage=0.05, max_length=2.).detrend('linear') trNl.detrend('linear').taper(max_percentage=0.05, max_length=2.).detrend('linear') trNq.detrend('linear').taper(max_percentage=0.05, max_length=2.).detrend('linear') # Stream(traces=[trL, trQ, trT, trNl, trNq]).plot(size=(600,300)) # Deconvolve if phase == 'P' or 'PP': rfL, rfQ, rfT = _decon(trL, trQ, trT, trNl, nn, method) elif phase == 'S' or 'SKS': rfQ, rfL, rfT = _decon(trQ, trL, trT, trNq, nn, method) # Stream(traces=[rfQ, rfL, rfT]).plot(size=(600,300)) # Update stats of streams rfL.stats.channel = 'RF' + self.meta.align[0] rfQ.stats.channel = 'RF' + self.meta.align[1] rfT.stats.channel = 'RF' + self.meta.align[2] self.rf = Stream(traces=[rfL, rfQ, rfT]) def calc_cc(self): if not self.meta.accept: return if not hasattr(self, 'rf'): raise (Exception("Warning: Receiver functions are not available")) obs_L = self.data[0].copy() obs_Q = self.data[1].copy() obs_rfQ = self.rf[1].copy() sr = obs_L.stats.sampling_rate # Filter using SNR bandpass obs_L.detrend().taper(max_percentage=0.05, max_length=2.) obs_Q.detrend().taper(max_percentage=0.05, max_length=2.) obs_L.filter('bandpass', freqmin=0.05, freqmax=1.) obs_Q.filter('bandpass', freqmin=0.05, freqmax=1.) obs_rfQ.filter('bandpass', freqmin=0.05, freqmax=1.) # Convolve L with rfQ to obtain predicted Q pred_Q = obs_Q.copy() pred_Q.stats.channel = 'PRR' ind1 = int((len(obs_rfQ.data) / 2.)) ind2 = ind1 + len(obs_L.data) pred_Q.data = np.convolve(obs_L.data, obs_rfQ.data, mode='full')[ind1:ind2] # trim all traces from 0 to 20. sec following P-wave (fftshift first) obs_L.data = np.fft.fftshift(obs_L.data)[0:int(sr * 10.)] obs_Q.data = np.fft.fftshift(obs_Q.data)[0:int(sr * 10.)] pred_Q.data = np.fft.fftshift(pred_Q.data)[0:int(sr * 10.)] # Get cross correlation coefficient between observed and predicted Q self.meta.cc = np.corrcoef(obs_Q.data, pred_Q.data)[0][1] # print(self.meta.cc) # test = Stream(traces=[obs_L, obs_Q, pred_Q]) # test.plot() def to_stream(self): """ Method to switch from RFData object to Stream object. This allows easier manipulation of the receiver functions for post-processing. """ if not self.meta.accept: return def _add_rfstats(trace): trace.stats.snr = self.meta.snr trace.stats.snrh = self.meta.snrh trace.stats.cc = self.meta.cc trace.stats.slow = self.meta.slow trace.stats.baz = self.meta.baz trace.stats.gac = self.meta.gac trace.stats.stlo = self.sta.longitude trace.stats.stla = self.sta.latitude trace.stats.evlo = self.meta.lon trace.stats.evla = self.meta.lat trace.stats.vp = self.meta.vp trace.stats.vs = self.meta.vs trace.stats.phase = self.meta.phase trace.stats.is_rf = True nn = self.rf[0].stats.npts sr = self.rf[0].stats.sampling_rate trace.stats.taxis = np.arange(-nn / 2., nn / 2.) / sr return trace if not hasattr(self, 'rf'): raise (Exception("Warning: Receiver functions are not available")) stream = self.rf for tr in stream: tr = _add_rfstats(tr) return stream def save(self, file): """ Saves RFData object to file Parameters ---------- file : str File name for RFData object """ import pickle output = open(file, 'wb') pickle.dump(self, output) output.close()
def pro5stack2d(eq_num, slow_delta=0.0005, slowR_lo=-0.1, slowR_hi=0.1, slowT_lo=-0.1, slowT_hi=0.1, start_buff=-50, end_buff=50, norm=1, ARRAY=0, NS=False, decimate_fac=0, ref_loc=0, ref_lat=36.3, ref_lon=138.5, stack_option=1): from obspy import UTCDateTime from obspy import Stream, Trace from obspy import read from obspy.geodetics import gps2dist_azimuth import numpy as np import os from scipy.signal import hilbert import math import time import sys # don't show any warnings import warnings from termcolor import colored print(colored('Running pro5b_stack2d', 'cyan')) env_stack = 0 # flag to stack envelopes instead of oscillating seismograms start_time_wc = time.time() fname = '/Users/vidale/Documents/Research/IC/EvLocs/event' + str( eq_num) + '.txt' file = open(fname, 'r') lines = file.readlines() split_line = lines[0].split() # ids.append(split_line[0]) ignore label for now t = UTCDateTime(split_line[1]) date_label = split_line[1][0:10] ev_lat = float(split_line[2]) ev_lon = float(split_line[3]) # ev_depth = float( split_line[4]) if not sys.warnoptions: warnings.simplefilter("ignore") #%% Get location file if ARRAY == 0: # Hinet set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_hinet.txt' if ref_loc == 0: ref_lat = 36.3 ref_lon = 138.5 elif ARRAY == 1: # LASA set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_LASA.txt' if ref_loc == 0: ref_lat = 46.69 ref_lon = -106.22 elif ARRAY == 2: # China set and center sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/sta_ch.txt' if ref_loc == 0: ref_lat = 38 # °N ref_lon = 104.5 # °E 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() #%% Input parameters # date_label = '2018-04-02' # date for filename fname = 'HD' + date_label + 'sel.mseed' goto = '/Users/vidale/Documents/Research/IC/Pro_Files' os.chdir(goto) st = Stream() st = read(fname) print('Read in: ' + str(len(st)) + ' traces') nt = len(st[0].data) dt = st[0].stats.delta print('First trace has : ' + str(nt) + ' time pts, time sampling of ' + str(dt) + ' and thus duration of ' + str((nt - 1) * dt)) #%% Make grid of slownesses slowR_n = int( round(1 + (slowR_hi - slowR_lo) / slow_delta)) # number of slownesses slowT_n = int( round(1 + (slowT_hi - slowT_lo) / slow_delta)) # number of slownesses stack_nt = int( round(1 + ((end_buff - start_buff) / dt))) # number of time points # In English, stack_slows = range(slow_n) * slow_delta - slow_lo a1R = range(slowR_n) a1T = range(slowT_n) stack_Rslows = [(x * slow_delta + slowR_lo) for x in a1R] stack_Tslows = [(x * slow_delta + slowT_lo) for x in a1T] # testing slownesses in indexing print( str(slowR_n) + ' radial slownesses, ' + str(slowT_n) + ' trans slownesses, ') print('Radial slownesses 0' + ' ' + str(stack_Rslows[0]) + ' ' 'end' + ' ' + str(stack_Rslows[-1])) print('Transverse slownesses 1' + ' ' + str(stack_Tslows[0]) + ' ' 'end' + ' ' + str(stack_Tslows[-1])) #%% Build empty Stack array stack = Stream() tr = Trace() tr.stats.delta = dt tr.stats.starttime = t + start_buff tr.stats.npts = stack_nt tr.stats.network = 'stack' tr.stats.channel = 'BHZ' tr.data = np.zeros(stack_nt) done = 0 for stackR_one in stack_Rslows: for stackT_one in stack_Tslows: tr1 = tr.copy() tr1.stats.station = str(int(round(done))) stack.extend([tr1]) done += 1 # Only need to compute ref location to event distance once ref_dist_az = gps2dist_azimuth(ev_lat, ev_lon, ref_lat, ref_lon) ref_back_az = ref_dist_az[2] #%% select by distance, window and adjust start time to align picked times done = 0 if env_stack == 1: for tr in st: # #convert oscillating seismograms to envelopes tr.data = np.abs(hilbert(tr.data)) for tr in st: # traces one by one, find lat-lon by searching entire inventory. Inefficient but cheap if tr.stats.station in st_names: # find station in station list ii = st_names.index(tr.stats.station) if norm == 1: tr.normalize() # trace divided abs(max of trace) stalat = float(st_lats[ii]) stalon = float( st_lons[ii]) # use lat & lon to find distance and back-az rel_dist_az = gps2dist_azimuth(stalat, stalon, ref_lat, ref_lon) rel_dist = rel_dist_az[0] / 1000 # km rel_back_az = rel_dist_az[1] # radians if NS == False: del_distR = rel_dist * math.cos( (rel_back_az - ref_back_az) * math.pi / 180) del_distT = rel_dist * math.sin( (rel_back_az - ref_back_az) * math.pi / 180) # North and east else: del_distR = rel_dist * math.cos(rel_back_az * math.pi / 180) del_distT = rel_dist * math.sin(rel_back_az * math.pi / 180) for slowR_i in range( slowR_n): # for this station, loop over radial slownesses for slowT_i in range( slowT_n): # loop over transverse slownesses time_lag = del_distR * stack_Rslows[ slowR_i] # time shift due to radial slowness time_lag += del_distT * stack_Tslows[ slowT_i] # time shift due to transverse slowness time_correction = ((t - tr.stats.starttime) + (time_lag + start_buff)) / dt indx = int(round(slowR_i * slowT_n + slowT_i)) # could do a little better by sampling finer, 20 sps?, before applying statics in pro3 if stack_option == 0: # my old inefficient method for it in range( stack_nt): # check points one at a time it_in = int(round(it + time_correction)) if it_in >= 0 and it_in < nt - 1: # does data lie within seismogram? stack[indx].data[it] += tr[it_in] if stack_option == 1: # Wei's much faster method arr = tr.data nshift = round(time_correction) if time_correction < 0: nshift = nshift - 1 if nshift <= 0: nbeg1 = -nshift nend1 = stack_nt nbeg2 = 0 nend2 = stack_nt + nshift elif nshift > 0: nbeg1 = 0 nend1 = stack_nt - nshift nbeg2 = nshift nend2 = stack_nt if nend1 >= 0 and nbeg1 <= stack_nt: stack[indx].data[nbeg1:nend1] += arr[nbeg2:nend2] done += 1 if done % 100 == 0: print('Done stacking ' + str(done) + ' out of ' + str(len(st)) + ' stations.') else: print(tr.stats.station + ' not found in station list') #%% take envelope, decimate envelope stack_raw = stack.copy() for slowR_i in range(slowR_n): # loop over radial slownesses for slowT_i in range(slowT_n): # loop over transverse slownesses indx = slowR_i * slowT_n + slowT_i stack[indx].data = np.abs(hilbert(stack[indx].data)) if decimate_fac != 0: stack[indx].decimate(decimate_fac, no_filter=True) #%% Save processed files fname = 'HD' + date_label + '_2dstack_env.mseed' stack.write(fname, format='MSEED') fname = 'HD' + date_label + '_2dstack.mseed' stack_raw.write(fname, format='MSEED') elapsed_time_wc = time.time() - start_time_wc print(f'This job took {elapsed_time_wc:.1f} seconds') os.system('say "Done"')
def run_tutorial(plot=False): """Main function to run the tutorial dataset.""" from eqcorrscan.utils import pre_processing from eqcorrscan.utils import plotting from eqcorrscan.core import match_filter import glob # This import section copes with namespace changes between obspy versions import obspy if int(obspy.__version__.split('.')[0]) >= 1: from obspy.clients.fdsn import Client else: from obspy.fdsn import Client from obspy import UTCDateTime, Stream, read # First we want to load our templates template_names = glob.glob('tutorial_template_*.ms') if len(template_names) == 0: raise IOError('Template files not found, have you run the template ' + 'creation tutorial?') templates = [read(template_name) for template_name in template_names] # Work out what stations we have and get the data for them stations = [] for template in templates: for tr in template: stations.append((tr.stats.station, tr.stats.channel)) # Get a unique list of stations stations = list(set(stations)) # We are going to look for detections on the day of our template, however, to # generalize, we will write a loop through the days between our templates, in # this case that is only one day. template_days = [] for template in templates: template_days.append(template[0].stats.starttime.date) template_days = sorted(template_days) kdays = (template_days[-1] - template_days[0]).days + 1 unique_detections = [] for i in range(kdays): t1 = UTCDateTime(template_days[0]) + (86400 * i) t2 = t1 + 86400 # Generate the bulk information to query the GeoNet database bulk_info = [] for station in stations: bulk_info.append(('NZ', station[0], '*', station[1][0] + 'H' + station[1][-1], t1, t2)) # Set up a client to access the GeoNet database client = Client("GEONET") # Note this will take a little while. print('Downloading seismic data, this may take a while') st = client.get_waveforms_bulk(bulk_info) # Merge the stream, it will be downloaded in chunks st.merge(fill_value='interpolate') # Work out what data we actually have to cope with possible lost data stations = list(set([tr.stats.station for tr in st])) # Set how many cores we want to parallel across, we will set this to four # as this is the number of templates, if your machine has fewer than four # cores/CPUs the multiprocessing will wait until there is a free core. # Setting this to be higher than the number of templates will have no # increase in speed as only detections for each template are computed in # parallel. It may also slow your processing by using more memory than # needed, to the extent that swap may be filled. ncores = 4 # Pre-process the data to set frequency band and sampling rate # Note that this is, and MUST BE the same as the parameters used for the # template creation. print('Processing the seismic data') st = pre_processing.dayproc(st, lowcut=2.0, highcut=9.0, filt_order=4, samp_rate=20.0, debug=0, starttime=t1, num_cores=ncores) # Convert from list to stream st = Stream(st) # Now we can conduct the matched-filter detection detections = match_filter.match_filter(template_names=template_names, template_list=templates, st=st, threshold=8.0, threshold_type='MAD', trig_int=6.0, plotvar=plot, plotdir='.', cores=ncores, tempdir=False, debug=1, plot_format='jpg') # Now lets try and work out how many unique events we have just to compare # with the GeoNet catalog of 20 events on this day in this sequence for master in detections: keep = True for slave in detections: if not master == slave and\ abs(master.detect_time - slave.detect_time) <= 1.0: # If the events are within 1s of each other then test which # was the 'best' match, strongest detection if not master.detect_val > slave.detect_val: keep = False break if keep: unique_detections.append(master) print('We made a total of ' + str(len(unique_detections)) + ' detections') for detection in unique_detections: print('Detection at :' + str(detection.detect_time) + ' for template ' + detection.template_name + ' with a cross-correlation sum of: ' + str(detection.detect_val)) # We can plot these too if plot: stplot = st.copy() template = templates[template_names.index(detection.template_name)] lags = sorted([tr.stats.starttime for tr in template]) maxlag = lags[-1] - lags[0] stplot.trim(starttime=detection.detect_time - 10, endtime=detection.detect_time + maxlag + 10) plotting.detection_multiplot(stplot, template, [detection.detect_time.datetime]) return unique_detections
ref2_st = st_ref2[0].data #print("here 0") #%% #client = Client('138.253.112.23', 16022) # ip, port - ip's 138.253.113.19 or 138.253.112.23 t1 = UTCDateTime(2019, 10, 1, 0, 0, 0) #the format is year:day_of_the_year:month t2 = t1 + 24 * 60 * 60 sts = Stream() sts = client.get_waveforms(net, sta, loc, cha, t1, t2) #print("here 1") sts.detrend(type='linear') sts.detrend(type='demean') sts1 = sts.copy() sts2 = sts.copy() sts3 = sts.copy() fb1 = sts1.filter(type='bandpass', freqmin=12, freqmax=22) #fb1.plot(type = 'dayplot',interval=15,starttime=t1, endtime=t2) fb2 = sts2.filter(type='bandpass', freqmin=0.5, freqmax=22) fb2.plot(type='dayplot', interval=30, starttime=t1, endtime=t2) fb3 = sts3.filter(type='bandpass', freqmin=0.5, freqmax=5) #fb3.plot(type = 'dayplot',interval=60,starttime=t1, endtime=t2) #%% trace_high = fb1[0]
def pro5stack2d(eq_file, plot_scale_fac=0.05, slow_delta=0.0005, slowR_lo=-0.1, slowR_hi=0.1, slowT_lo=-0.1, slowT_hi=0.1, start_buff=-50, end_buff=50, norm=1, global_norm_plot=1, ARRAY=0, NS=0, decimate_fac=0): from obspy import UTCDateTime from obspy import Stream, Trace from obspy import read from obspy.geodetics import gps2dist_azimuth import numpy as np import os from scipy.signal import hilbert import math import time import sys # don't show any warnings import warnings print('Running pro5b_stack2d') start_time_wc = time.time() if ARRAY == 0: file = open(eq_file, 'r') elif ARRAY == 1: goto = '/Users/vidale/Documents/PyCode/LASA/EvLocs' os.chdir(goto) file = open(eq_file, 'r') lines = file.readlines() split_line = lines[0].split() # ids.append(split_line[0]) ignore label for now t = UTCDateTime(split_line[1]) date_label = split_line[1][0:10] ev_lat = float(split_line[2]) ev_lon = float(split_line[3]) # ev_depth = float( split_line[4]) if not sys.warnoptions: warnings.simplefilter("ignore") #%% Get location file if ARRAY == 0: # Hinet set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/hinet_sta.txt' ref_lat = 36.3 ref_lon = 138.5 else: # LASA set sta_file = '/Users/vidale/Documents/GitHub/Array_codes/Files/LASA_sta.txt' ref_lat = 46.69 ref_lon = -106.22 with open(sta_file, 'r') as file: lines = file.readlines() print(str(len(lines)) + ' stations read from ' + sta_file) # Load station coords into arrays station_index = range(len(lines)) st_names = [] st_lats = [] st_lons = [] for ii in station_index: line = lines[ii] split_line = line.split() st_names.append(split_line[0]) st_lats.append(split_line[1]) st_lons.append(split_line[2]) #%% Input parameters # date_label = '2018-04-02' # date for filename fname = 'HD' + date_label + 'sel.mseed' if ARRAY == 1: goto = '/Users/vidale/Documents/PyCode/LASA/Pro_Files' os.chdir(goto) st = Stream() st = read(fname) print('Read in: ' + str(len(st)) + ' traces') nt = len(st[0].data) dt = st[0].stats.delta print('First trace has : ' + str(nt) + ' time pts, time sampling of ' + str(dt) + ' and thus duration of ' + str((nt - 1) * dt)) #%% Make grid of slownesses slowR_n = int(1 + (slowR_hi - slowR_lo) / slow_delta) # number of slownesses slowT_n = int(1 + (slowT_hi - slowT_lo) / slow_delta) # number of slownesses stack_nt = int(1 + ((end_buff - start_buff) / dt)) # number of time points # In English, stack_slows = range(slow_n) * slow_delta - slow_lo a1R = range(slowR_n) a1T = range(slowT_n) stack_Rslows = [(x * slow_delta + slowR_lo) for x in a1R] stack_Tslows = [(x * slow_delta + slowT_lo) for x in a1T] print( str(slowR_n) + ' radial slownesses, ' + str(slowT_n) + ' trans slownesses, ') #%% Build empty Stack array stack = Stream() tr = Trace() tr.stats.delta = dt tr.stats.starttime = t + start_buff tr.stats.npts = stack_nt tr.stats.network = 'stack' tr.stats.channel = 'BHZ' tr.data = np.zeros(stack_nt) done = 0 for stackR_one in stack_Rslows: for stackT_one in stack_Tslows: tr1 = tr.copy() tr1.stats.station = str(int(done)) stack.extend([tr1]) done += 1 # Only need to compute ref location to event distance once ref_dist_az = gps2dist_azimuth(ev_lat, ev_lon, ref_lat, ref_lon) # ref_dist = ref_dist_az[0]/1000 # km ref_back_az = ref_dist_az[2] # print(f'Ref location {ref_lat:.4f} , {ref_lon:.4f}, event location {ev_lat:.4f} {ev_lon:.4f} ref_back_az {ref_back_az:.1f}°') #%% select by distance, window and adjust start time to align picked times done = 0 for tr in st: # traces one by one, find lat-lon by searching entire inventory. Inefficient but cheap for ii in station_index: if ARRAY == 0: # have to chop off last letter, always 'h' this_name = st_names[ii] this_name_truc = this_name[0:5] name_truc_cap = this_name_truc.upper() elif ARRAY == 1: name_truc_cap = st_names[ii] if (tr.stats.station == name_truc_cap ): # find station in inventory # if (tr.stats.station == st_names[ii]): # found station in inventory if norm == 1: tr.normalize() # trace divided abs(max of trace) stalat = float(st_lats[ii]) stalon = float( st_lons[ii]) # use lat & lon to find distance and back-az rel_dist_az = gps2dist_azimuth(stalat, stalon, ref_lat, ref_lon) rel_dist = rel_dist_az[0] / 1000 # km rel_back_az = rel_dist_az[1] # radians # print(f'Sta lat-lon {stalat:.4f} {stalon:.4f}') if NS == 0: del_distR = rel_dist * math.cos( (rel_back_az - ref_back_az) * math.pi / 180) del_distT = rel_dist * math.sin( (rel_back_az - ref_back_az) * math.pi / 180) # North and east else: del_distR = rel_dist * math.cos( rel_back_az * math.pi / 180) del_distT = rel_dist * math.sin( rel_back_az * math.pi / 180) for slowR_i in range( slowR_n ): # for this station, loop over radial slownesses for slowT_i in range( slowT_n): # loop over transverse slownesses time_lag = del_distR * stack_Rslows[ slowR_i] # time shift due to radial slowness time_lag += del_distT * stack_Tslows[ slowT_i] # time shift due to transverse slowness time_correction = ((t - tr.stats.starttime) + (time_lag + start_buff)) / dt indx = int(slowR_i * slowT_n + slowT_i) for it in range( stack_nt): # check points one at a time it_in = int(it + time_correction) if it_in >= 0 and it_in < nt - 2: # does data lie within seismogram? # should be 1, not 2, but 2 prevents the problem "index XX is out of bounds for axis 0 with size XX" stack[indx].data[it] += tr[it_in] done += 1 if done % 20 == 0: print('Done stacking ' + str(done) + ' out of ' + str(len(st)) + ' stations.') #%% take envelope, decimate envelope stack_raw = stack.copy() for slowR_i in range(slowR_n): # loop over radial slownesses for slowT_i in range(slowT_n): # loop over transverse slownesses indx = slowR_i * slowT_n + slowT_i stack[indx].data = np.abs(hilbert(stack[indx].data)) if decimate_fac != 0: stack[indx].decimate(decimate_fac) #%% Save processed files fname = 'HD' + date_label + '_2dstack_env.mseed' stack.write(fname, format='MSEED') fname = 'HD' + date_label + '_2dstack.mseed' stack_raw.write(fname, format='MSEED') elapsed_time_wc = time.time() - start_time_wc print('This job took ' + str(elapsed_time_wc) + ' seconds') os.system('say "Done"')
def run_tutorial(plot=False): """Main function to run the tutorial dataset.""" from eqcorrscan.utils import pre_processing from eqcorrscan.utils import plotting from eqcorrscan.core import match_filter import glob from multiprocessing import cpu_count # This import section copes with namespace changes between obspy versions import obspy if int(obspy.__version__.split('.')[0]) >= 1: from obspy.clients.fdsn import Client else: from obspy.fdsn import Client from obspy import UTCDateTime, Stream, read # First we want to load our templates template_names = glob.glob('tutorial_template_*.ms') if len(template_names) == 0: raise IOError('Template files not found, have you run the template ' + 'creation tutorial?') templates = [read(template_name) for template_name in template_names] # Work out what stations we have and get the data for them stations = [] for template in templates: for tr in template: stations.append((tr.stats.station, tr.stats.channel)) # Get a unique list of stations stations = list(set(stations)) # We will loop through the data chunks at a time, these chunks can be any # size, in general we have used 1 day as our standard, but this can be # as short as five minutes (for MAD thresholds) or shorter for other # threshold metrics. However the chunk size should be the same as your # template process_len. # You should test different parameters!!! start_time = UTCDateTime(2016, 1, 4) end_time = UTCDateTime(2016, 1, 5) process_len = 3600 chunks = [] chunk_start = start_time while chunk_start < end_time: chunk_end = chunk_start + process_len if chunk_end > end_time: chunk_end = end_time chunks.append((chunk_start, chunk_end)) chunk_start += process_len unique_detections = [] detections = [] # Set up a client to access the GeoNet database client = Client("GEONET") # Note that these chunks do not rely on each other, and could be paralleled # on multiple nodes of a distributed cluster, see the SLURM tutorial for # an example of this. for t1, t2 in chunks: # Generate the bulk information to query the GeoNet database bulk_info = [] for station in stations: bulk_info.append(('NZ', station[0], '*', station[1][0] + 'H' + station[1][-1], t1, t2)) # Note this will take a little while. print('Downloading seismic data, this may take a while') st = client.get_waveforms_bulk(bulk_info) # Merge the stream, it will be downloaded in chunks st.merge(fill_value='interpolate') # Set how many cores we want to parallel across, we will set this to four # as this is the number of templates, if your machine has fewer than four # cores/CPUs the multiprocessing will wait until there is a free core. # Setting this to be higher than the number of templates will have no # increase in speed as only detections for each template are computed in # parallel. It may also slow your processing by using more memory than # needed, to the extent that swap may be filled. if cpu_count() < 4: ncores = cpu_count() else: ncores = 4 # Pre-process the data to set frequency band and sampling rate # Note that this is, and MUST BE the same as the parameters used for the # template creation. print('Processing the seismic data') st = pre_processing.shortproc(st, lowcut=2.0, highcut=9.0, filt_order=4, samp_rate=20.0, debug=2, num_cores=ncores, starttime=t1, endtime=t2) # Convert from list to stream st = Stream(st) # Now we can conduct the matched-filter detection detections += match_filter.match_filter(template_names=template_names, template_list=templates, st=st, threshold=8.0, threshold_type='MAD', trig_int=6.0, plotvar=plot, plotdir='.', cores=ncores, tempdir=False, debug=1, plot_format='jpg') # Now lets try and work out how many unique events we have just to compare # with the GeoNet catalog of 20 events on this day in this sequence for master in detections: keep = True for slave in detections: if not master == slave and\ abs(master.detect_time - slave.detect_time) <= 1.0: # If the events are within 1s of each other then test which # was the 'best' match, strongest detection if not master.detect_val > slave.detect_val: keep = False break if keep: unique_detections.append(master) print('We made a total of ' + str(len(unique_detections)) + ' detections') for detection in unique_detections: print('Detection at :' + str(detection.detect_time) + ' for template ' + detection.template_name + ' with a cross-correlation sum of: ' + str(detection.detect_val)) # We can plot these too if plot: stplot = st.copy() template = templates[template_names.index(detection.template_name)] lags = sorted([tr.stats.starttime for tr in template]) maxlag = lags[-1] - lags[0] stplot.trim(starttime=detection.detect_time - 10, endtime=detection.detect_time + maxlag + 10) plotting.detection_multiplot(stplot, template, [detection.detect_time.datetime]) return unique_detections
#Get station to hypocenter delta distance delta = locations2degrees(coords[k, 1], coords[k, 0], epicenter[1], epicenter[0]) #Get pwave travel-time to site tt = getTravelTimes(delta, epicenter[2]) tp = timedelta(seconds=float64(tt[0]['time'])) #Trim n[0].trim(starttime=time_epi + tp - tmin, endtime=time_epi + tp + tmax) e[0].trim(starttime=time_epi + tp - tmin, endtime=time_epi + tp + tmax) u[0].trim(starttime=time_epi + tp - tmin, endtime=time_epi + tp + tmax) #Remove first epoch n[0].data = n[0].data - n[0].data[0] e[0].data = e[0].data - e[0].data[0] u[0].data = u[0].data - u[0].data[0] #Create velocity time series (copy displacememnts then replace data) nvel = n.copy() evel = e.copy() uvel = u.copy() nvel[0].data = diff(n[0].data) / n[0].stats.delta evel[0].data = diff(e[0].data) / e[0].stats.delta uvel[0].data = diff(u[0].data) / u[0].stats.delta #Write displacememnts to file n.write(path + 'trim/' + sta + '.LXN.sac', format='SAC') e.write(path + 'trim/' + sta + '.LXE.sac', format='SAC') u.write(path + 'trim/' + sta + '.LXZ.sac', format='SAC') #Write velocity to file nvel.write(path + 'trim/' + sta + '.LYN.sac', format='SAC') evel.write(path + 'trim/' + sta + '.LYE.sac', format='SAC') uvel.write(path + 'trim/' + sta + '.LYZ.sac', format='SAC') if make_plots: