Add all notebooks for part ii of the lecture. (#13)

Reviewed-on: #13
Co-authored-by: Janis Heuel <janis.heuel@ruhr-uni-bochum.de>
Co-committed-by: Janis Heuel <janis.heuel@ruhr-uni-bochum.de>
This commit was merged in pull request #13.
This commit is contained in:
2021-06-26 16:15:46 +02:00
committed by Kasper D. Fischer
parent 1a077cb628
commit a47d87bc9b
26 changed files with 3091 additions and 1 deletions

View File

@@ -0,0 +1,35 @@
import numpy as np
def movingWindowAnalysis(data,winfun,nwin,shift,exp):
"""
Performs moving window analysis of a time series.
data: data array
winfun: name of the window function to be called
nwin: number of window samples (power of 2)
shift: displacement of moving window in samples
exp: exponent for taking power of spectrum
"""
fwin = winfun(nwin) # compute window values
npts = len(data) # number of total samples
nseg = int((npts-nwin)/shift)+1 # total number of expected data segment
mwa = np.zeros((nwin//2+1,nseg)) # array for result (rfft returns N/2+1 samples)
wa = 0 # start index of data segment
we = nwin # end index of data segment
jseg = 0 # initialize data segment counter
while we < npts: # loop over segments
seg = data[wa:we]*fwin # multiply data segment with window
seg = seg-seg.mean() # subtract mean value of segment
ftseg = np.abs(np.fft.rfft(seg)) # abs value of Fourier transform
maxft = np.amax(ftseg) # max value of Fourier transform
ftseg = ftseg/maxft+1.e-10 # normalize spectrum to its maximum, remove zeros
mwa[:,jseg] = np.power(ftseg,exp) # assign values to the matrix
wa = wa+shift # move window start by shift
we = we+shift # move window end by shift
jseg = jseg+1 # increase segment counter
return nseg,mwa # return number of segments and moving window matrix
#------------------------------------------------------------------------------------
#
def hann(nw):
arg = 2.*np.pi*np.arange(0,nw)/nw # argument of cosine
fwin = 0.5*(1.-np.cos(arg)) # Hann window
return fwin

View File

@@ -0,0 +1,47 @@
import numpy as np
def multipleFilterAnalysis(data,alfa,cfreq,dt,ndec):
"""
Perform a multiple filter analysis of data.
data: Array of detrended and demeaned data whose length is power of 2
alfa: Width parameter of Gaussian bandpass filter
cfreq: Array of center frequencies of Gaussian filter
dt: sampling interval of data
ndec: decimation factor for instantaneous amplitude output
"""
npts = len(data)
nd = int(pow(2, np.ceil(np.log(npts)/np.log(2)))) # find next higher power of 2 of npts
ftd = np.fft.rfft(data,nd) # Fourier transform of entire data set (pos. freq.)
# data are padded with zeros since npts <= nd
freq = np.fft.rfftfreq(nd,dt) # Fourier frequencies (positive frequencies)
nt = int(np.ceil(npts/ndec))
mfa = np.zeros((len(cfreq),nt)) # numpy array for MFA result
for jf,cf in enumerate(cfreq):
hg = np.exp(-alfa*((freq-cf)/cf)**2) # Gaussian filter (use f instead of omega here)
fk = hg*ftd # multiply FT of data with filter
qk = np.complex(0,1)*fk # FT of Hilbert transform
ftk = np.fft.irfft(fk) # filtered data
qtk = np.fft.irfft(qk) # Hilbert transform of filtered data
at = np.sqrt(ftk**2+qtk**2) # instantaneous amplitude
mfa[jf,:] = at[0:npts:ndec] # store decimated original result
return mfa
#-----------------------------------------------------------------------
# normalize multiple filter result either along time or frequency axis
def normalizeMFT(mfa,mode,exp):
"""
Normalize the result of the mutiple filtering operation.
mfa: array with instantaneous amplitudes versus frequency (mfa(f,t))
mode: normalization mode: if 'time', normalize along time axis
else normalize along frequency axis
exp: exponent for modifying inst amp using a power less than 1
"""
nf,nt = mfa.shape
if mode == 'time':
for jf in range(0,nf):
mfamax = np.amax(mfa[jf,:])
mfa[jf,:] = np.power(mfa[jf,:]/mfamax+1.e-10,exp)
else:
for jt in range(0,nt):
mfamax = np.amax(mfa[:,jt])
mfa[:,jt] = np.power(mfa[:,jt]/mfamax+1.e-10,exp)
return mfa

View File

@@ -0,0 +1,268 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Two-station method\n",
"Here we compute the phase velocity form seismograms of two stations lying on a common great circle. We make use of the fact that the phase of the Fourier tranform of the cross-correlation of the two seismograms is equal to the phase difference between the surface wave trains from which we calclate the phase velocity."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"# Preparation: load packages, set some basic options \n",
"%matplotlib inline\n",
"from obspy import UTCDateTime\n",
"from obspy.clients.fdsn import Client\n",
"from obspy.geodetics import locations2degrees\n",
"import numpy as np\n",
"import matplotlib.pylab as plt\n",
"from multipleFilter import multipleFilterAnalysis, normalizeMFT \n",
"plt.style.use('ggplot')\n",
"plt.rcParams['figure.figsize'] = 15, 4\n",
"plt.rcParams['lines.linewidth'] = 0.5"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"# Get data from earthquake (Gulf of Alaska, m=7.9, d = 24 km)\n",
"client = Client(\"BGR\")\n",
"t1 = UTCDateTime(\"2018-01-23T09:31:42.000\")\n",
"st1raw = client.get_waveforms(\"GR\",\"TNS\",\"\",\"LHZ\",t1,t1 + 2 * 3600, # data of station TNS of GRSN\n",
" attach_response = True)\n",
"inv1 = client.get_stations(network=\"GR\",station=\"TNS\",\n",
" channel='LHZ',level=\"channel\",starttime=t1) # inventory info about TNS\n",
"client = Client(\"GFZ\")\n",
"st2raw = client.get_waveforms(\"GE\",\"STU\",\"\",\"LHZ\",t1,t1 + 2 * 3600, # data of station STU of GEOFON\n",
" attach_response = True)\n",
"inv2 = client.get_stations(network=\"GE\",station=\"STU\",\n",
" channel='LHZ',level=\"channel\",starttime=t1) # inventory info about STU"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"st1 = st1raw.copy() # copy data\n",
"st2 = st2raw.copy()\n",
"st1.detrend('linear') # detrend\n",
"st2.detrend('linear')\n",
"st1.remove_response(output='VEL',zero_mean = True, # remove response (pre-filter is important)\n",
" pre_filt = (0.001, 0.005, 0.4, 0.5))\n",
"st2.remove_response(output='VEL',zero_mean = True, # remove response(pre-filter is important)\n",
" pre_filt = (0.001, 0.005, 0.4, 0.5))\n",
"st1.plot() # plot data\n",
"st2.plot()\n",
"print(st1)\n",
"print(st2)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"co = inv1.get_coordinates(\"GR.TNS..LHZ\") # extract coordinates from inventory\n",
"tns = (co[\"longitude\"],co[\"latitude\"])\n",
"co = inv2.get_coordinates(\"GE.STU..LHZ\")\n",
"stu = (co[\"longitude\"],co[\"latitude\"])\n",
"event = (-149.09,56.05) # coordinates of event\n",
"dis = locations2degrees(tns[1],tns[0],stu[1],stu[0])*np.pi/180.*6371. # epicentral distance\n",
"print(\"Distance in km: \",dis)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<img src=\"../images/phase_velocity_map.png\">"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"tw1 = UTCDateTime(\"2018-01-23T10:05:00.000\") # cut seismograms to surface wave train\n",
"tw2 = UTCDateTime(\"2018-01-23T10:16:00.000\")\n",
"st1.trim(tw1,tw2)\n",
"st2.trim(tw1,tw2)\n",
"st1.plot()\n",
"st2.plot()\n",
"npts = st1[0].stats.npts\n",
"dt = st1[0].stats.delta\n",
"print(\"Number of samples: \",npts,\"Sampling interval: \",dt)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"cc = np.correlate(st2[0],st1[0],mode=\"full\") # Cross correlation: cc(k)=sum_n a(n+k)b(n)\n",
"tm = np.arange(-(npts-1),npts)*dt # includes negative lags as well\n",
"plt.plot(tm,cc,'-k')\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
},
"scrolled": true
},
"outputs": [],
"source": [
"ns = 256 # limit length of cross correlation to first ns points\n",
"ccp = cc[npts-1:npts-1+ns] # cross correlation for positive lag\n",
"print(len(ccp))\n",
"tm = np.arange(0,ns)*dt # associated time values\n",
"plt.plot(tm,ccp,'-k')\n",
"plt.xlabel(\"Time\")\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"nd = int(pow(2, np.ceil(np.log(ns)/np.log(2)))) # Fourier transfrom of the cross correlation\n",
"fcp = np.fft.rfft(ccp,nd)\n",
"freq = np.fft.rfftfreq(nd,dt)\n",
"print(nd,dt,len(freq),freq[0:4])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"fmax = 0.1 # compute phase up to maximum frequency fmax\n",
"fny = 1./(2.*dt)\n",
"jmax = int(fmax/fny*(nd//2-1))\n",
"print(jmax)\n",
"spec = fcp[0:jmax]\n",
"frs = freq[0:jmax]\n",
"phase = np.unwrap(np.angle(spec)) # unwrap the phase owing to 2*pi multiples"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"plt.subplot(311)\n",
"plt.plot(frs,np.abs(spec),\"-k\") # plot abs of spectrum\n",
"plt.subplot(312)\n",
"plt.plot(frs,np.angle(spec),\"-k\") # plot phase of spectrum\n",
"plt.subplot(313)\n",
"plt.plot(frs,phase,\"-k\") # plot unwrapped phase\n",
"plt.xlabel(\"Frequency [Hz]\")\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"j1 = int(jmax*0.1)\n",
"j2 = int(jmax*0.6)\n",
"phavel = -2.*np.pi*frs[j1:j2]*dis/phase[j1:j2] # calculate phase velocity and plot\n",
"plt.plot(frs[j1:j2],phavel,'-k')\n",
"plt.xlabel(\"Frequency [Hz]\")\n",
"plt.ylabel(\"Phase velocity [km/s]\")\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.7"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@@ -0,0 +1,256 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Surface Wave Velocity (Single Station Approach)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"# Preparation: load packages, set some basic options \n",
"%matplotlib inline\n",
"from obspy import UTCDateTime\n",
"from obspy.clients.fdsn import Client\n",
"from obspy.clients.fdsn import URL_MAPPINGS\n",
"import numpy as np\n",
"import matplotlib.pylab as plt\n",
"from movingWindow import movingWindowAnalysis, hann # import the moving window analysis routines\n",
"from multipleFilter import multipleFilterAnalysis, normalizeMFT # import the multiple filter analysis routines\n",
"plt.style.use('ggplot')\n",
"plt.rcParams['figure.figsize'] = 15, 4\n",
"plt.rcParams['lines.linewidth'] = 0.5"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Data from data archive using obspy webfdsn client\n",
"FDSN stands for the International Federal of Digital Seismic Networks (www.fdsn.org). Citation from their web site: \"The International Federation of Digital Seismograph Networks (FDSN) is a global organization. Its membership is comprised of groups responsible for the installation and maintenance of seismographs either within their geographic borders or globally. Membership in the FDSN is open to all organizations that operate more than one broadband station. Members agree to coordinate station siting and provide free and open access to their data. This cooperation helps scientists all over the world to further the advancement of earth science and particularly the study of global seismic activity.\"\n",
"BGR (Bundesanstalt für Geowissenschaften und Rohstoffe) operates broadband stations in Germany and operates a data archive. It is member of the FDSN. That is why we can freely download data from them.\n",
"There are other data archives around the world which are also member of the FDSN and allow opn access to their data (see OBSPY documentation "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"for key in sorted(URL_MAPPINGS.keys()): # eine Liste der Archive\n",
" print(\"{0:<7} {1}\".format(key, URL_MAPPINGS[key]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Get waveforms for the Tohoku earthquake for station TNS of the German Regional Seismic Network."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"client = Client(\"BGR\") # data archive from which data are fetched\n",
"t1 = UTCDateTime(\"2018-01-14T09:19:00.000\") # desired start time of data\n",
"stc = client.get_waveforms(\"GR\", \"TNS\", \"\", \"LHZ\", t1, # use fdsn web client to download data\n",
" t1 + 7200., attach_response=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"stc.detrend('linear') # take out linear trend\n",
"stc.plot()\n",
"print(stc)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"st = stc.copy() # proceed with copy to avoid repeated downloading\n",
"ta = UTCDateTime(\"2018-01-14T10:05:00.000000Z\") # cut to surface wave train\n",
"te = UTCDateTime(\"2018-01-14T10:22:00.000000Z\")\n",
"st.trim(ta,te)\n",
"st.taper(0.05,\"hann\") # apply a taper to bring signal to zero at ends\n",
"st.plot() # dispersion is now well visible\n",
"print(st)\n",
"tr = st[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Moving window approach"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"wlen = 256.0 # window length in sec\n",
"npts = tr.stats.npts # number of samples in the trace\n",
"dt = tr.stats.delta # sample interval\n",
"nwin = int(wlen/dt) # nuber of samples in window\n",
"nwin = int(pow(2, np.ceil(np.log(nwin)/np.log(2)))) # find next higher power of 2 of nwin\n",
"shift = nwin//8\n",
"tshift = shift*dt\n",
"nseg,mwa = movingWindowAnalysis(tr.data,hann,nwin,shift,2.0) # compute spectrogram, exponent=2.0\n",
"freq = np.fft.rfftfreq(nwin,dt) # Fourier frequencies\n",
"print(\"Frequency range [Hz]: \",freq[0],freq[-1])\n",
"print(\"Number of frequency samples: \",len(freq))\n",
"print(\"Window length in samples: \",nwin)\n",
"print(\"Window shift in samples: \",shift)\n",
"print(\"Number of segments: \",nseg)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"# plot the result\n",
"st.plot() # plot seismogram again\n",
"fmax = 0.08 # maximum frequency to be plotted\n",
"fmin = 0.02\n",
"nf = len(freq)-1 # number of frequencies\n",
"jfmax = int(fmax/freq[-1]*nf) # max index for plotting \n",
"jfmin = int(fmin/freq[-1]*nf) # min index for plotting \n",
"extent = (0.5*wlen,0.5*wlen+nseg*tshift,fmin,fmax) # extent of matrix in true time and frequency\n",
"asp = 0.5*nseg*tshift/(fmax-fmin)\n",
"fg = plt.figure(figsize = [12.5,12])\n",
"plt.imshow(mwa[jfmin:jfmax,:],extent = extent, aspect = asp, \n",
" origin = \"lower\", interpolation = \"bicubic\") # plotting with interpolation\n",
"plt.xlabel('Window center time [s]')\n",
"plt.ylabel('Frequency [Hz]')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Observe the change of frequency content with time from 0.03 Hz initially to 0.06 Hz towards the end of the record. Given a epicentral distance $d$, we could transform the time axis into a group velocity axis via $U=d/t$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Multiple filtering approach"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"alfa = 50.0 # filter width parameter\n",
"cfreq = np.linspace(fmin,fmax,100) # array of filter center frequencies\n",
"mfa = multipleFilterAnalysis(tr.data,alfa,cfreq,dt,1)\n",
"mfa = normalizeMFT(mfa,'freq',2.0)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"st.plot()\n",
"fg = plt.figure(figsize = [12.5,12])\n",
"extent = (0.,npts*dt,fmin,fmax) # extent of matrix in true time and frequency\n",
"asp = 0.5*npts*dt/(fmax-fmin)\n",
"plt.imshow(mfa,extent = extent, aspect = asp, \n",
" origin = \"lower\", interpolation = \"bicubic\") # plotting\n",
"plt.xlabel('Time [s]')\n",
"plt.ylabel('Frequency [Hz]')\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.7"
}
},
"nbformat": 4,
"nbformat_minor": 4
}