[refactor] major refactoring of Magnitude objects finished

now the changed usage of the Magnitude object has to be implemented into autoPyLoT and QtPyLoT (pending)
This commit is contained in:
Sebastian Wehling-Benatelli 2016-09-27 13:57:14 +02:00
parent d4481e4acd
commit 405402ffdc

View File

@ -39,39 +39,52 @@ class Magnitude(object):
self._stream = stream self._stream = stream
self._magnitudes = dict() self._magnitudes = dict()
def __str__(self): def __str__(self):
print('number of stations used: {0}\n'.format(len(self.magnitudes.values()))) print('number of stations used: {0}\n'.format(len(self.magnitudes.values())))
print('\tstation\tmagnitude') print('\tstation\tmagnitude')
for s, m in self.magnitudes.items(): print('\t{0}\t{1}'.format(s, m)) for s, m in self.magnitudes.items(): print('\t{0}\t{1}'.format(s, m))
def __nonzero__(self):
return bool(self.magnitudes)
@property @property
def plot_flag(self): def plot_flag(self):
return self._plot_flag return self._plot_flag
@plot_flag.setter @plot_flag.setter
def plot_flag(self, value): def plot_flag(self, value):
self._plot_flag = value self._plot_flag = value
@property @property
def stream(self): def stream(self):
return self._stream return self._stream
@stream.setter @stream.setter
def stream(self, value): def stream(self, value):
self._stream = value self._stream = value
@property @property
def event(self): def event(self):
return self._event return self._event
@property @property
def arrivals(self): def arrivals(self):
return self._event.origins[0].arrivals return self._event.origins[0].arrivals
@property @property
def magnitudes(self): def magnitudes(self):
return self._magnitudes return self._magnitudes
@magnitudes.setter @magnitudes.setter
def magnitudes(self, value): def magnitudes(self, value):
""" """
@ -84,169 +97,22 @@ class Magnitude(object):
station, magnitude = value station, magnitude = value
self._magnitudes[station] = magnitude self._magnitudes[station] = magnitude
def get(self): def get(self):
return self.magnitudes return self.magnitudes
class Magnitude(object):
'''
Superclass for calculating Wood-Anderson peak-to-peak
amplitudes, local magnitudes, source spectra, seismic moments
and moment magnitudes.
'''
def __init__(self, wfstream, t0, pwin, iplot, NLLocfile=None, \ def net_magnitude(self):
picks=None, rho=None, vp=None, Qp=None, invdir=None): if self:
''' return np.median([M["mag"] for M in self.magnitudes.values()])
:param: wfstream return None
:type: `~obspy.core.stream.Stream
:param: t0, onset time, P- or S phase
:type: float
:param: pwin, pick window [t0 t0+pwin] to get maximum
peak-to-peak amplitude (WApp) or to calculate
source spectrum (DCfc) around P onset
:type: float
:param: iplot, no. of figure window for plotting interims results
:type: integer
:param: NLLocfile, name and full path to NLLoc-location file
needed when calling class MoMw
:type: string
:param: picks, dictionary containing picking results
:type: dictionary
:param: rho [kg/], rock density, parameter from autoPyLoT.in
:type: integer
:param: vp [m/s], P-velocity
:param: integer
:param: invdir, name and path to inventory or dataless-SEED file
:type: string
'''
assert isinstance(wfstream, Stream), "%s is not a stream object" % str(wfstream)
self._stream = wfstream
self._invdir = invdir
self._t0 = t0
self._pwin = pwin
self._iplot = iplot
self.setNLLocfile(NLLocfile)
self.setrho(rho)
self.setpicks(picks)
self.setvp(vp)
self.setQp(Qp)
self.calcwapp()
self.calcsourcespec()
self.run_calcMoMw()
@property
def stream(self):
return self._stream
@stream.setter
def stream(self, wfstream):
self._stream = wfstream
@property
def t0(self):
return self._t0
@t0.setter
def t0(self, value):
self._t0 = value
@property
def invdir(self):
return self._invdir
@invdir.setter
def invdir(self, value):
self._invdir = value
@property
def pwin(self):
return self._pwin
@pwin.setter
def pwin(self, value):
self._pwin = value
@property
def plot_flag(self):
return self.iplot
@plot_flag.setter
def plot_flag(self, value):
self._iplot = value
def setNLLocfile(self, NLLocfile):
self.NLLocfile = NLLocfile
def getNLLocfile(self):
return self.NLLocfile
def setrho(self, rho):
self.rho = rho
def getrho(self):
return self.rho
def setvp(self, vp):
self.vp = vp
def getvp(self):
return self.vp
def setQp(self, Qp):
self.Qp = Qp
def getQp(self):
return self.Qp
def setpicks(self, picks):
self.picks = picks
def getpicks(self):
return self.picks
def getwapp(self):
return self.wapp
def getw0(self):
return self.w0
def getfc(self):
return self.fc
def get_metadata(self):
return read_metadata(self.invdir)
def plot(self):
pass
def getpicdic(self):
return self.picdic
def calcwapp(self):
self.wapp = None
def calcsourcespec(self):
self.sourcespek = None
def run_calcMoMw(self):
self.pickdic = None
class RichterMagnitude(Magnitude): class RichterMagnitude(Magnitude):
''' """
Method to derive peak-to-peak amplitude as seen on a Wood-Anderson- Method to derive peak-to-peak amplitude as seen on a Wood-Anderson-
seismograph. Has to be derived from instrument corrected traces! seismograph. Has to be derived from instrument corrected traces!
''' """
# poles, zeros and sensitivity of WA seismograph # poles, zeros and sensitivity of WA seismograph
# (see Uhrhammer & Collins, 1990, BSSA, pp. 702-716) # (see Uhrhammer & Collins, 1990, BSSA, pp. 702-716)
@ -257,29 +123,23 @@ class RichterMagnitude(Magnitude):
'sensitivity': 1 'sensitivity': 1
} }
def __init__(self, stream, event, t0, calc_win, verbosity=False): def __init__(self, stream, event, calc_win, verbosity=False):
super(RichterMagnitude, self).__init__(stream, event, verbosity) super(RichterMagnitude, self).__init__(stream, event, verbosity)
self._t0 = t0
self._calc_win = calc_win self._calc_win = calc_win
@property
def t0(self):
return self._t0
@t0.setter
def t0(self, value):
self._t0 = value
@property @property
def calc_win(self): def calc_win(self):
return self._calc_win return self._calc_win
@calc_win.setter @calc_win.setter
def calc_win(self, value): def calc_win(self, value):
self._calc_win = value self._calc_win = value
def peak_to_peak(self, st):
def peak_to_peak(self, st, t0):
# simulate Wood-Anderson response # simulate Wood-Anderson response
st.simulate(paz_remove=None, paz_simulate=self._paz) st.simulate(paz_remove=None, paz_simulate=self._paz)
@ -303,7 +163,7 @@ class RichterMagnitude(Magnitude):
# get time array # get time array
th = np.arange(0, len(sqH) * dt, dt) th = np.arange(0, len(sqH) * dt, dt)
# get maximum peak within pick window # get maximum peak within pick window
iwin = getsignalwin(th, self.t0 - stime, self.calc_win) iwin = getsignalwin(th, t0 - stime, self.calc_win)
wapp = np.max(sqH[iwin]) wapp = np.max(sqH[iwin])
if self._verbosity: if self._verbosity:
print("Determined Wood-Anderson peak-to-peak amplitude: {0} " print("Determined Wood-Anderson peak-to-peak amplitude: {0} "
@ -315,7 +175,7 @@ class RichterMagnitude(Magnitude):
f = plt.figure(2) f = plt.figure(2)
plt.plot(th, sqH) plt.plot(th, sqH)
plt.plot(th[iwin], sqH[iwin], 'g') plt.plot(th[iwin], sqH[iwin], 'g')
plt.plot([self.t0, self.t0], [0, max(sqH)], 'r', linewidth=2) plt.plot([t0, t0], [0, max(sqH)], 'r', linewidth=2)
plt.title('Station %s, RMS Horizontal Traces, WA-peak-to-peak=%4.1f mm' \ plt.title('Station %s, RMS Horizontal Traces, WA-peak-to-peak=%4.1f mm' \
% (st[0].stats.station, wapp)) % (st[0].stats.station, wapp))
plt.xlabel('Time [s]') plt.xlabel('Time [s]')
@ -326,6 +186,7 @@ class RichterMagnitude(Magnitude):
return wapp return wapp
def get(self): def get(self):
for a in self.arrivals: for a in self.arrivals:
if a.phase not in 'sS': if a.phase not in 'sS':
@ -334,18 +195,20 @@ class RichterMagnitude(Magnitude):
station = pick.waveform_id.station_code station = pick.waveform_id.station_code
wf = select_for_phase(self.stream.select( wf = select_for_phase(self.stream.select(
station=station), a.phase) station=station), a.phase)
if not wf:
print('WARNING: no waveform data found for station {0}'.format(
station))
continue
delta = degrees2kilometers(a.distance) delta = degrees2kilometers(a.distance)
wapp = self.peak_to_peak(wf) onset = pick.time
wapp = self.peak_to_peak(wf, onset)
# using standard Gutenberg-Richter relation # using standard Gutenberg-Richter relation
# TODO make the ML calculation more flexible by allowing # TODO make the ML calculation more flexible by allowing
# use of custom relation functions # use of custom relation functions
mag = np.log10(wapp) + richter_magnitude_scaling(delta) mag = dict(mag=np.log10(wapp) + richter_magnitude_scaling(delta))
self.magnitudes = (station, mag) self.magnitudes = (station, mag)
return self.magnitudes return self.magnitudes
def net_magnitude(self):
return np.median([M for M in self.magnitudes.values()])
class MomentMagnitude(Magnitude): class MomentMagnitude(Magnitude):
''' '''
@ -357,6 +220,29 @@ class MomentMagnitude(Magnitude):
corresponding moment magntiude Mw. corresponding moment magntiude Mw.
''' '''
def __init__(self, stream, event, vp, Qp, density, verbosity=False):
super(MomentMagnitude, self).__init__(stream, event)
self._vp = vp
self._Qp = Qp
self._density = density
@property
def p_velocity(self):
return self._vp
@property
def p_attenuation(self):
return self._Qp
@property
def rock_density(self):
return self._density
def run_calcMoMw(self): def run_calcMoMw(self):
picks = self.getpicks() picks = self.getpicks()
@ -405,6 +291,32 @@ class MomentMagnitude(Magnitude):
self.picdic = picks self.picdic = picks
def get(self):
for a in self.arrivals:
if a.phase not in 'pP':
continue
pick = a.pick_id.get_referred_object()
station = pick.waveform_id.station_code
wf = select_for_phase(self.stream.select(
station=station), a.phase)
if not wf:
continue
onset = pick.time
distance = degrees2kilometers(a.distance)
azimuth = a.azimuth
incidence = a.takeoff_angle
w0, fc = calcsourcespec(wf, onset, self.p_velocity, distance, azimuth,
incidence, self.p_attenuation)
if w0 is None or fc is None:
print("WARNING: insufficient frequency information")
continue
wf = select_for_phase(wf, "P")
M0, Mw = calcMoMw(wf, w0, self.rock_density, self.p_velocity, distance)
mag = dict(w0=w0, fc=fc, M0=M0, mag=Mw)
self.magnitudes = (station, mag)
return self.magnitudes
def calc_woodanderson_pp(st, metadata, T0, win=10., verbosity=False): def calc_woodanderson_pp(st, metadata, T0, win=10., verbosity=False):
if verbosity: if verbosity:
print ("Getting Wood-Anderson peak-to-peak amplitude ...") print ("Getting Wood-Anderson peak-to-peak amplitude ...")
@ -491,8 +403,8 @@ def calcMoMw(wfstream, w0, rho, vp, delta):
return Mo, Mw return Mo, Mw
def calcsourcespec(wfstream, onset, metadata, vp, delta, azimuth, incidence, def calcsourcespec(wfstream, onset, vp, delta, azimuth, incidence,
qp, iplot): qp, iplot=0):
''' '''
Subfunction to calculate the source spectrum and to derive from that the plateau Subfunction to calculate the source spectrum and to derive from that the plateau
(usually called omega0) and the corner frequency assuming Aki's omega-square (usually called omega0) and the corner frequency assuming Aki's omega-square
@ -500,16 +412,12 @@ def calcsourcespec(wfstream, onset, metadata, vp, delta, azimuth, incidence,
thus restitution and integration necessary! Integrated traces are rotated thus restitution and integration necessary! Integrated traces are rotated
into ray-coordinate system ZNE => LQT using Obspy's rotate modul! into ray-coordinate system ZNE => LQT using Obspy's rotate modul!
:param: wfstream :param: wfstream (corrected for instrument)
:type: `~obspy.core.stream.Stream` :type: `~obspy.core.stream.Stream`
:param: onset, P-phase onset time :param: onset, P-phase onset time
:type: float :type: float
:param: metadata, tuple or list containing type of inventory and either
list of files or inventory object
:type: tuple or list
:param: vp, Vp-wave velocity :param: vp, Vp-wave velocity
:type: float :type: float
@ -533,30 +441,26 @@ def calcsourcespec(wfstream, onset, metadata, vp, delta, azimuth, incidence,
# get Q value # get Q value
Q, A = qp Q, A = qp
delta = delta * 1000 # hypocentral distance in [m] dist = delta * 1000 # hypocentral distance in [m]
fc = None fc = None
w0 = None w0 = None
wf_copy = wfstream.copy()
invtype, inventory = metadata zdat = select_for_phase(wfstream, "P")
dt = zdat[0].stats.delta
freq = zdat[0].stats.sampling_rate
# trim traces to common range (for rotation)
trstart, trend = common_range(wfstream)
wfstream.trim(trstart, trend)
[cordat, restflag] = restitute_data(wf_copy, invtype, inventory)
if restflag is True:
zdat = cordat.select(component="Z")
if len(zdat) == 0:
zdat = cordat.select(component="3")
cordat_copy = cordat.copy()
# get equal time stamps and lengths of traces
# necessary for rotation of traces
trstart, trend = common_range(cordat_copy)
cordat_copy.trim(trstart, trend)
try:
# rotate into LQT (ray-coordindate-) system using Obspy's rotate # rotate into LQT (ray-coordindate-) system using Obspy's rotate
# L: P-wave direction # L: P-wave direction
# Q: SV-wave direction # Q: SV-wave direction
# T: SH-wave direction # T: SH-wave direction
LQT = cordat_copy.rotate('ZNE->LQT', azimuth, incidence) LQT = wfstream.rotate('ZNE->LQT', azimuth, incidence)
ldat = LQT.select(component="L") ldat = LQT.select(component="L")
if len(ldat) == 0: if len(ldat) == 0:
# if horizontal channels are 2 and 3 # if horizontal channels are 2 and 3
@ -567,22 +471,19 @@ def calcsourcespec(wfstream, onset, metadata, vp, delta, azimuth, incidence,
ldat = LQT.select(component="Z") ldat = LQT.select(component="Z")
# integrate to displacement # integrate to displacement
# unrotated vertical component (for copmarison) # unrotated vertical component (for comparison)
inttrz = signal.detrend(integrate.cumtrapz(zdat[0].data, None, inttrz = signal.detrend(integrate.cumtrapz(zdat[0].data, None, dt))
zdat[0].stats.delta))
# rotated component Z => L # rotated component Z => L
Ldat = signal.detrend(integrate.cumtrapz(ldat[0].data, None, Ldat = signal.detrend(integrate.cumtrapz(ldat[0].data, None, dt))
ldat[0].stats.delta))
# get window after P pulse for # get window after P pulse for
# calculating source spectrum # calculating source spectrum
tstart = UTCDateTime(zdat[0].stats.starttime) rel_onset = onset - trstart
tonset = onset.timestamp - tstart.timestamp impickP = int(rel_onset * freq)
impickP = tonset * zdat[0].stats.sampling_rate
wfzc = Ldat[impickP: len(Ldat) - 1] wfzc = Ldat[impickP: len(Ldat) - 1]
# get time array # get time array
t = np.arange(0, len(inttrz) * zdat[0].stats.delta, \ t = np.arange(0, len(inttrz) * dt, dt)
zdat[0].stats.delta)
# calculate spectrum using only first cycles of # calculate spectrum using only first cycles of
# waveform after P onset! # waveform after P onset!
zc = crossings_nonzero_all(wfzc) zc = crossings_nonzero_all(wfzc)
@ -594,21 +495,21 @@ def calcsourcespec(wfstream, onset, metadata, vp, delta, azimuth, incidence,
else: else:
plotflag = 1 plotflag = 1
index = min([3, len(zc) - 1]) index = min([3, len(zc) - 1])
calcwin = (zc[index] - zc[0]) * zdat[0].stats.delta calcwin = (zc[index] - zc[0]) * dt
iwin = getsignalwin(t, tonset, calcwin) iwin = getsignalwin(t, rel_onset, calcwin)
xdat = Ldat[iwin] xdat = Ldat[iwin]
# fft # fft
fny = zdat[0].stats.sampling_rate / 2 fny = freq / 2
l = len(xdat) / zdat[0].stats.sampling_rate l = len(xdat) / freq
# number of fft bins after Bath # number of fft bins after Bath
n = zdat[0].stats.sampling_rate * l n = freq * l
# find next power of 2 of data length # find next power of 2 of data length
m = pow(2, np.ceil(np.log(len(xdat)) / np.log(2))) m = pow(2, np.ceil(np.log(len(xdat)) / np.log(2)))
N = int(np.power(m, 2)) N = int(np.power(m, 2))
y = zdat[0].stats.delta * np.fft.fft(xdat, N) y = dt * np.fft.fft(xdat, N)
Y = abs(y[: N / 2]) Y = abs(y[: N / 2])
L = (N - 1) / zdat[0].stats.sampling_rate L = (N - 1) / freq
f = np.arange(0, fny, 1 / L) f = np.arange(0, fny, 1 / L)
# remove zero-frequency and frequencies above # remove zero-frequency and frequencies above
@ -620,7 +521,7 @@ def calcsourcespec(wfstream, onset, metadata, vp, delta, azimuth, incidence,
# correction for attenuation # correction for attenuation
wa = 2 * np.pi * F # angular frequency wa = 2 * np.pi * F # angular frequency
D = np.exp((wa * delta) / (2 * vp * Q * F ** A)) D = np.exp((wa * dist) / (2 * vp * Q * F ** A))
YYcor = YY.real * D YYcor = YY.real * D
# get plateau (DC value) and corner frequency # get plateau (DC value) and corner frequency
@ -633,8 +534,7 @@ def calcsourcespec(wfstream, onset, metadata, vp, delta, azimuth, incidence,
# use of implicit scipy otimization function # use of implicit scipy otimization function
fit = synthsourcespec(F, w0in, Fcin) fit = synthsourcespec(F, w0in, Fcin)
[optspecfit, _] = curve_fit(synthsourcespec, F, YYcor, [w0in, [optspecfit, _] = curve_fit(synthsourcespec, F, YYcor, [w0in, Fcin])
Fcin])
w01 = optspecfit[0] w01 = optspecfit[0]
fc1 = optspecfit[1] fc1 = optspecfit[1]
print ("calcsourcespec: Determined w0-value: %e m/Hz, \n" print ("calcsourcespec: Determined w0-value: %e m/Hz, \n"
@ -649,13 +549,9 @@ def calcsourcespec(wfstream, onset, metadata, vp, delta, azimuth, incidence,
fc = np.median([fc1, fc2]) fc = np.median([fc1, fc2])
print("calcsourcespec: Using w0-value = %e m/Hz and fc = %f Hz" % (w0, fc)) print("calcsourcespec: Using w0-value = %e m/Hz and fc = %f Hz" % (w0, fc))
except TypeError as er:
raise TypeError('''{0}'''.format(er))
if iplot > 1: if iplot > 1:
f1 = plt.figure() f1 = plt.figure()
tLdat = np.arange(0, len(Ldat) * zdat[0].stats.delta, \ tLdat = np.arange(0, len(Ldat) * dt, dt)
zdat[0].stats.delta)
plt.subplot(2, 1, 1) plt.subplot(2, 1, 1)
# show displacement in mm # show displacement in mm
p1, = plt.plot(t, np.multiply(inttrz, 1000), 'k') p1, = plt.plot(t, np.multiply(inttrz, 1000), 'k')
@ -847,9 +743,17 @@ def calc_moment_magnitude(e, wf, metadata, vp, Qp, rho):
continue continue
onset = pick.time onset = pick.time
dist = degrees2kilometers(a.distance) dist = degrees2kilometers(a.distance)
w0, fc = calcsourcespec(wf, onset, metadata, vp, dist, a.azimuth, a.takeoff_angle, Qp, 0) invtype, inventory = metadata
[corr_wf, rest_flag] = restitute_data(wf, invtype, inventory)
if not rest_flag:
print("WARNING: data for {0} could not be corrected".format(
station))
continue
w0, fc = calcsourcespec(corr_wf, onset, vp, dist, a.azimuth,
a.takeoff_angle, Qp, 0)
if w0 is None or fc is None: if w0 is None or fc is None:
continue continue
wf = select_for_phase(corr_wf, "P")
station_mag = calcMoMw(wf, w0, rho, vp, dist) station_mag = calcMoMw(wf, w0, rho, vp, dist)
mags[station] = station_mag mags[station] = station_mag
mag = np.median([M[1] for M in mags.values()]) mag = np.median([M[1] for M in mags.values()])