#!/usr/bin/env python # coding: utf-8 #
#
#
#
ObsPy Tutorial
#
Handling Waveform Data
#
#
#
# image: User:Abbaszade656 / Wikimedia Commons / CC-BY-SA-4.0 # ## Workshop for the "Training in Network Management Systems and Analytical Tools for Seismic" # ### Baku, October 2018 # # Seismo-Live: http://seismo-live.org # # ##### Authors: # * Tobias Megies ([@megies](https://github.com/megies)) # * Lion Krischer ([@krischer](https://github.com/krischer)) # --- # ![](images/obspy_logo_full_524x179px.png) # In[ ]: get_ipython().run_line_magic('matplotlib', 'inline') from __future__ import print_function import matplotlib.pyplot as plt plt.style.use('ggplot') plt.rcParams['figure.figsize'] = 12, 8 # # # * read waveform data is returned as a **`Stream`** object. # In[ ]: from obspy import read st = read("./data/waveform_PFO.mseed", format="mseed") print(st) # - UNIX wildcards can be used to read multiple files simultaneously # - automatic file format detection, no need to worry about file formats # # - currently supported: **MiniSEED, SAC, SEG-Y, SEG-2, GSE1/2, Seisan, SH, DataMark, CSS, wav, y, Q, ASCII, Guralp Compressed Format, Kinemetrics EVT, K-NET&KiK formats, PDAS, Reftek 130, WIN (keeps growing...)** # - more file formats are included whenever a basic reading routine is provided (or e.g. sufficient documentation on data compression etc.) # - custom user-specific file formats can be added (through plug-ins) to be auto-discovered in a local ObsPy installation # In[ ]: from obspy import read st = read("./data/waveform_*") print(st) # - for MiniSEED files, only reading short portions of files without all of the file getting read into memory is supported (saves time and memory when working on large collections of big files) # In[ ]: from obspy import UTCDateTime t = UTCDateTime("2011-03-11T05:46:23.015400Z") st = read("./data/waveform_*", starttime=t + 10 * 60, endtime=t + 12 * 60) print(st) # #### The Stream Object # # - A Stream object is a collection of Trace objects # In[ ]: from obspy import read st = read("./data/waveform_PFO.mseed") print(type(st)) # In[ ]: print(st) # In[ ]: print(st.traces) # In[ ]: print(st[0]) # - More traces can be assembled using **`+`** operator (or using the `.append()` and `.extend()` methods) # In[ ]: st1 = read("./data/waveform_PFO.mseed") st2 = read("./data/waveform_PFO_synthetics.mseed") st = st1 + st2 print(st) st3 = read("./data/waveform_BFO_BHE.sac") st += st3 print(st) # - convenient (and nicely readable) looping over traces # In[ ]: for tr in st: print(tr.id) # - Stream is useful for applying the same processing to a larger number of different waveforms or to group Traces for processing (e.g. three components of one station in one Stream) # #### The Trace Object # # - a Trace object is a single, contiguous waveform data block (i.e. regularly spaced time series, no gaps) # - a Trace object contains a limited amount of metadata in a dictionary-like object (as **`Trace.stats`**) that fully describes the time series by specifying.. # * recording location/instrument (network, station, location and channel code) # * start time # * sampling rate # In[ ]: st = read("./data/waveform_PFO.mseed") tr = st[0] # get the first Trace in the Stream print(tr) # In[ ]: print(tr.stats) # - For custom applications it is sometimes necessary to directly manipulate the metadata of a Trace. # - The metadata of the Trace will **stay consistent**, as all values are derived from the starttime, the data and the sampling rate and are **updated automatically** # In[ ]: print(tr.stats.delta, "|", tr.stats.endtime) # In[ ]: tr.stats.sampling_rate = 5.0 print(tr.stats.delta, "|", tr.stats.endtime) # In[ ]: print(tr.stats.npts) # In[ ]: tr.data = tr.data[:100] print(tr.stats.npts, "|", tr.stats.endtime) # - convenience methods make basic manipulations simple # In[ ]: tr = read("./data/waveform_PFO.mseed")[0] tr.plot() # In[ ]: print(tr) tr.resample(sampling_rate=100.0) print(tr) # In[ ]: print(tr) tr.trim(tr.stats.starttime + 12 * 60, tr.stats.starttime + 14 * 60) print(tr) tr.plot() # In[ ]: tr.detrend("linear") tr.taper(max_percentage=0.05, type='cosine') tr.filter("lowpass", freq=0.1) tr.plot() # In[ ]: # try tr. for other methods defined for Trace get_ipython().run_line_magic('pinfo', 'tr.detrend') # - Raw data available as a [**`numpy.ndarray`**](http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html) (as **`Trace.data`**) # In[ ]: print(tr.data[:20]) # - Data can be directly modified e.g. .. # # ..by doing arithmetic operations (fast, handled in C by NumPy) # In[ ]: print(tr.data ** 2 + 0.5) # ..by using [**`numpy.ndarray`** builtin methods](http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html) (also done in C by NumPy) # In[ ]: print(tr.data.max()) print(tr.data.mean()) print(tr.data.ptp()) # try tr.data. for a list of numpy methods defined on ndarray # ..by using **`numpy`** functions (also done in C by NumPy) # In[ ]: import numpy as np print(np.abs(tr.data)) # you can try np. but there is a lot in there # try np.a # ..by feeding pointers to existing C/Fortran routines from inside Python! # # This is done internally in several places, e.g. for cross correlations, beamforming or in third-party filetype libraries like e.g. libmseed. # # - Trace objects can also be manually generated with data in a [**`numpy.ndarray`**](http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html) (e.g. when needing to parse waveforms from non-standard ascii files) # In[ ]: from obspy import Trace x = np.random.randint(-100, 100, 500) tr = Trace(data=x) tr.stats.station = "XYZ" tr.stats.starttime = UTCDateTime() tr.plot() # - Stream objects can be assembled from manually generated Traces # In[ ]: from obspy import Stream tr2 = Trace(data=np.random.randint(-300, 100, 1000)) tr2.stats.starttime = UTCDateTime() tr2.stats.sampling_rate = 10.0 st = Stream([tr, tr2]) st.plot() # #### Builtin methods defined on **`Stream`** / **`Trace`** # # - Most methods that work on a Trace object also work on a Stream object. They are simply executed for every trace. [See ObsPy documentation for an overview of available methods](http://docs.obspy.org/packages/autogen/obspy.core.stream.Stream.html) (or try **`st.`**). # - **`st.filter()`** - Filter all attached traces. # - **`st.trim()`** - Cut all traces. # - **`st.resample()`** / **`st.decimate()`** - Change the sampling rate. # - **`st.trigger()`** - Run triggering algorithms. # - **`st.plot()`** / **`st.spectrogram()`** - Visualize the data. # - **`st.attach_response()`**/**`st.remove_response()`**, **`st.simulate()`** - Instrument correction # - **`st.merge()`**, **`st.normalize()`**, **`st.detrend()`**, **`st.taper()`**, ... # - A **`Stream`** object can also be exported to many formats, so ObsPy can be used to convert between different file formats. # In[ ]: st = read("./data/waveform_*.sac") st.write("output_file.mseed", format="MSEED") get_ipython().system('ls -l output_file*') # # #### Trace Exercises # - Make an **`numpy.ndarray`** with zeros and (e.g. use **`numpy.zeros()`**) and put an ideal pulse somewhere in it # - initialize a **`Trace`** object with your data array # - Fill in some station information (e.g. network, station, ..) # - Print trace summary and plot the trace # - Change the sampling rate to 20 Hz # - Change the starttime of the trace to the start time of this session # - Print the trace summary and plot the trace again # In[ ]: # - Use **`tr.filter(...)`** and apply a lowpass filter with a corner frequency of 1 Hertz. # - Display the preview plot, there are a few seconds of zeros that we can cut off. # In[ ]: # - Use **`tr.trim(...)`** to remove some of the zeros at start and at the end # - show the preview plot again # In[ ]: # - Scale up the amplitudes of the trace by a factor of 500 # - Add standard normal gaussian noise to the trace (use [**`np.random.randn()`**](http://docs.scipy.org/doc/numpy/reference/generated/numpy.random.randn.html)) # - Display the preview plot again # In[ ]: # #### Stream Exercises # # - Read all Tohoku example earthquake data into a stream object ("./data/waveform\_\*") # - Print the stream summary # In[ ]: # - Use **`st.select()`** to only keep traces of station BFO in the stream. Show the preview plot. # In[ ]: # - trim the data to a 10 minute time window around the first arrival (just roughly looking at the preview plot) # - display the preview plot and spectrograms for the stream (with logarithmic frequency scale, use `wlen=50` for the spectrogram plot) # In[ ]: # - remove the linear trend from the data, apply a tapering and a lowpass at 0.1 Hertz # - show the preview plot again # In[ ]: