diff --git a/QtPyLoT.py b/QtPyLoT.py index 3eeac515..f0cc571f 100755 --- a/QtPyLoT.py +++ b/QtPyLoT.py @@ -23,10 +23,11 @@ https://www.iconfinder.com/iconsets/flavour (http://www.gnu.org/copyleft/lesser.html) """ -import matplotlib import os import sys +import matplotlib + matplotlib.use('Qt4Agg') matplotlib.rcParams['backend.qt4'] = 'PySide' @@ -37,24 +38,22 @@ from PySide.QtGui import QMainWindow, QInputDialog, QIcon, QFileDialog, \ QDialog, QErrorMessage, QApplication, QPixmap, QMessageBox, QSplashScreen, \ QActionGroup, QListWidget, QDockWidget, QLineEdit import numpy as np -import subprocess from obspy import UTCDateTime -from obspy.geodetics import degrees2kilometers -from obspy.core.event import Magnitude -from pylot.core.analysis.magnitude import calcsourcespec, calcMoMw +from pylot.core.analysis.magnitude import RichterMagnitude, MomentMagnitude from pylot.core.io.data import Data from pylot.core.io.inputs import FilterOptions, AutoPickParameter from pylot.core.pick.autopick import autopickevent from pylot.core.pick.compare import Comparison +from pylot.core.pick.utils import symmetrize_error from pylot.core.io.phases import picksdict_from_picks import pylot.core.loc.nll as nll from pylot.core.util.defaults import FILTERDEFAULTS, COMPNAME_MAP, \ AUTOMATIC_DEFAULTS from pylot.core.util.errors import FormatError, DatastructureError, \ - OverwriteError + OverwriteError, ProcessingError from pylot.core.util.connection import checkurl -from pylot.core.util.dataprocessing import read_metadata +from pylot.core.util.dataprocessing import read_metadata, restitute_data from pylot.core.util.utils import fnConstructor, getLogin, \ full_range from pylot.core.io.location import create_creation_info, create_event @@ -124,6 +123,11 @@ class MainWindow(QMainWindow): # load and display waveform data self.dirty = False self.load_data() + finv = settings.value("inventoryFile", None) + if finv is not None: + self._metadata = read_metadata(finv) + else: + self._metadata = None if self.loadWaveformData(): self.updateFilterOptions() else: @@ -368,6 +372,17 @@ class MainWindow(QMainWindow): self.setCentralWidget(_widget) + + @property + def metadata(self): + return self._metadata + + + @metadata.setter + def metadata(self, value): + self._metadata = value + + def updateFileMenu(self): self.fileMenu.clear() @@ -911,6 +926,8 @@ class MainWindow(QMainWindow): epp = picks['epp'] - stime lpp = picks['lpp'] - stime spe = picks['spe'] + if not spe: + spe = symmetrize_error(mpp - epp, lpp - mpp) if picktype == 'manual': ax.fill_between([epp, lpp], ylims[0], ylims[1], @@ -984,53 +1001,55 @@ class MainWindow(QMainWindow): os.remove(phasepath) self.get_data().applyEVTData(lt.read_location(locpath), type='event') - self.get_data().get_evt_data().magnitudes.append(self.calc_magnitude()) + self.get_data().applyEVTData(self.calc_magnitude(), type='event') - def calc_magnitude(self): - e = self.get_data().get_evt_data() + + def calc_magnitude(self, type='ML'): + def set_inv(settings): + fninv, _ = QFileDialog.getOpenFileName(self, self.tr( + "Select inventory..."), self.tr("Select file")) + if not fninv: + return False + ans = QMessageBox.question(self, self.tr("Make default..."), + self.tr( + "New inventory filename set.\n" + \ + "Do you want to make it the default value?"), + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No) + if ans == QMessageBox.Yes: + settings.setValue("inventoryFile", fninv) + settings.sync() + self.metadata = read_metadata(fninv) + return True + settings = QSettings() - if e.origins: - o = e.origins[0] - mags = dict() - fninv = settings.value("inventoryFile", None) - if fninv is None: - fninv, _ = QFileDialog.getOpenFileName(self, self.tr( - "Select inventory..."), self.tr("Select file")) - ans = QMessageBox.question(self, self.tr("Make default..."), - self.tr( - "New inventory filename set.\n" + \ - "Do you want to make it the default value?"), - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No) - if ans == QMessageBox.Yes: - settings.setValue("inventoryFile", fninv) - settings.sync() - metadata = read_metadata(fninv) - for a in o.arrivals: - if a.phase in 'sS': - continue - pick = a.pick_id.get_referred_object() - station = pick.waveform_id.station_code - wf = self.get_data().getWFData().select(station=station) - if not wf: - continue - onset = pick.time - dist = degrees2kilometers(a.distance) - w0, fc = calcsourcespec(wf, onset, metadata, self.inputs.get('vp'), dist, - a.azimuth, a.takeoff_angle, - self.inputs.get('Qp'), 0) - if w0 is None or fc is None: - continue - station_mag = calcMoMw(wf, w0, self.inputs.get('rho'), - self.inputs.get('vp'), dist) - mags[station] = station_mag - mag = np.median([M[1] for M in mags.values()]) - # give some information on the processing - print('number of stations used: {0}\n'.format(len(mags.values()))) - print('stations used:\n') - for s in mags.keys(): print('\t{0}'.format(s)) + fninv = settings.value("inventoryFile", None) - return Magnitude(mag=mag, magnitude_type='Mw') + if fninv is None and not self.metadata: + if not set_inv(settings): + return None + elif fninv is not None and not self.metadata: + ans = QMessageBox.question(self, self.tr("Use default..."), + self.tr( + "Do you want to use the default value?"), + QMessageBox.Yes | QMessageBox.No, + QMessageBox.Yes) + if ans == QMessageBox.No: + if not set_inv(settings): + return None + else: + self.metadata = read_metadata(fninv) + + wf_copy = self.get_data().getWFData().copy() + [corr_wf, rest_flag] = restitute_data(wf_copy, *self.metadata) + if not rest_flag: + raise ProcessingError('Restitution of waveform data failed!') + if type == 'ML': + local_mag = RichterMagnitude(corr_wf, self.get_data().get_evt_data(), self.inputs.get('sstop'), verbosity = True) + return local_mag.updated_event() + elif type == 'Mw': + moment_mag = MomentMagnitude(corr_wf, self.get_data().get_evt_data(), self.inputs.get('vp'), self.inputs.get('Qp'), self.inputs.get('rho'), verbosity = True) + return moment_mag.updated_event() else: return None diff --git a/autoPyLoT.py b/autoPyLoT.py index 01b430ab..8c81b51d 100755 --- a/autoPyLoT.py +++ b/autoPyLoT.py @@ -5,17 +5,18 @@ from __future__ import print_function import argparse import glob -import string import os -import numpy as np +from obspy import read_events -from pylot.core.analysis.magnitude import M0Mw +import pylot.core.loc.hsat as hsat +import pylot.core.loc.nll as nll +from pylot.core.analysis.magnitude import MomentMagnitude, RichterMagnitude from pylot.core.io.data import Data from pylot.core.io.inputs import AutoPickParameter -import pylot.core.loc.nll as nll -import pylot.core.loc.hsat as hsat from pylot.core.pick.autopick import autopickevent, iteratepicker +from pylot.core.util.dataprocessing import restitute_data, read_metadata, \ + remove_underscores from pylot.core.util.structure import DATASTRUCTURE from pylot.core.util.version import get_git_version as _getVersionString @@ -54,6 +55,7 @@ def autoPyLoT(inputfile): data = Data() + evt = None # getting information on data structure if parameter.hasParam('datastructure'): @@ -97,139 +99,26 @@ def autoPyLoT(inputfile): print("!!No source parameter estimation possible!!") print(" !!! ") - # multiple event processing - # read each event in database datapath = datastructure.expandDataPath() if not parameter.hasParam('eventID'): - for event in [events for events in glob.glob(os.path.join(datapath, '*')) if os.path.isdir(events)]: - data.setWFData(glob.glob(os.path.join(datapath, event, '*'))) - print('Working on event %s' % event) - print(data) - wfdat = data.getWFData() # all available streams - ########################################################## - # !automated picking starts here! - picks = autopickevent(wfdat, parameter) - finalpicks = picks - ########################################################## - # locating - if locflag == 1: - # write phases to NLLoc-phase file - nll.export(picks, phasefile) - - # For locating the event the NLLoc-control file has to be modified! - evID = event[string.rfind(event, "/") + 1: len(events) - 1] - nllocout = '%s_%s' % (evID, nllocoutpatter) - # create comment line for NLLoc-control file - nll.modify_inputs(ctrf, nllocroot, nllocout, phasef, - ttpat) - - # locate the event - nll.locate(ctrfile) - - # !iterative picking if traces remained unpicked or occupied with bad picks! - # get theoretical onset times for picks with weights >= 4 - # in order to reprocess them using smaller time windows around theoretical onset - # get stations with bad onsets - badpicks = [] - for key in picks: - if picks[key]['P']['weight'] >= 4 or picks[key]['S']['weight'] >= 4: - badpicks.append([key, picks[key]['P']['mpp']]) - - if len(badpicks) == 0: - print("autoPyLoT: No bad onsets found, thus no iterative picking necessary!") - # get NLLoc-location file - locsearch = '%s/loc/%s.????????.??????.grid?.loc.hyp' % (nllocroot, nllocout) - if len(glob.glob(locsearch)) > 0: - # get latest NLLoc-location file if several are available - nllocfile = max(glob.glob(locsearch), key=os.path.getctime) - # calculating seismic moment Mo and moment magnitude Mw - finalpicks = M0Mw(wfdat, None, None, parameter.get('iplot'), \ - nllocfile, picks, parameter.get('rho'), \ - parameter.get('vp'), parameter.get('Qp'), \ - parameter.get('invdir')) - else: - print("autoPyLoT: No NLLoc-location file available!") - print("No source parameter estimation possible!") - else: - # get theoretical P-onset times from NLLoc-location file - locsearch = '%s/loc/%s.????????.??????.grid?.loc.hyp' % (nllocroot, nllocout) - if len(glob.glob(locsearch)) > 0: - # get latest file if several are available - nllocfile = max(glob.glob(locsearch), key=os.path.getctime) - nlloccounter = 0 - while len(badpicks) > 0 and nlloccounter <= maxnumit: - nlloccounter += 1 - if nlloccounter > maxnumit: - print("autoPyLoT: Number of maximum iterations reached, stop iterative picking!") - break - print("autoPyLoT: Starting with iteration No. %d ..." % nlloccounter) - picks = iteratepicker(wfdat, nllocfile, picks, badpicks, parameter) - # write phases to NLLoc-phase file - nll.export(picks, phasefile) - # remove actual NLLoc-location file to keep only the last - os.remove(nllocfile) - # locate the event - nll.locate(ctrfile) - print("autoPyLoT: Iteration No. %d finished." % nlloccounter) - # get updated NLLoc-location file - nllocfile = max(glob.glob(locsearch), key=os.path.getctime) - # check for bad picks - badpicks = [] - for key in picks: - if picks[key]['P']['weight'] >= 4 or picks[key]['S']['weight'] >= 4: - badpicks.append([key, picks[key]['P']['mpp']]) - print("autoPyLoT: After iteration No. %d: %d bad onsets found ..." % (nlloccounter, \ - len(badpicks))) - if len(badpicks) == 0: - print("autoPyLoT: No more bad onsets found, stop iterative picking!") - nlloccounter = maxnumit - - # calculating seismic moment Mo and moment magnitude Mw - finalpicks = M0Mw(wfdat, None, None, parameter.get('iplot'), \ - nllocfile, picks, parameter.get('rho'), \ - parameter.get('vp'), parameter.get('Qp'), \ - parameter.get('invdir')) - # get network moment magntiude - netMw = [] - for key in finalpicks.getpicdic(): - if finalpicks.getpicdic()[key]['P']['Mw'] is not None: - netMw.append(finalpicks.getpicdic()[key]['P']['Mw']) - netMw = np.median(netMw) - print("Network moment magnitude: %4.1f" % netMw) - else: - print("autoPyLoT: No NLLoc-location file available! Stop iteration!") - ########################################################## - # write phase files for various location routines - # HYPO71 - hypo71file = '%s/autoPyLoT_HYPO71.pha' % event - if hasattr(finalpicks, 'getpicdic') and finalpicks.getpicdic() is not None: - hsat.export(finalpicks.getpicdic(), hypo71file) - data.applyEVTData(finalpicks.getpicdic()) - else: - hsat.export(picks, hypo71file) - data.applyEVTData(picks) - fnqml = '%s/autoPyLoT' % event - data.exportEvent(fnqml) - - endsplash = '''------------------------------------------\n' - -----Finished event %s!-----\n' - ------------------------------------------'''.format \ - (version=_getVersionString()) % evID - print(endsplash) - if locflag == 0: - print("autoPyLoT was running in non-location mode!") - - # single event processing + # multiple event processing + # read each event in database + events = [events for events in glob.glob(os.path.join(datapath, '*')) if os.path.isdir(events)] else: - data.setWFData(glob.glob(os.path.join(datapath, parameter.get('eventID'), '*'))) - print("Working on event {0}".format(parameter.get('eventID'))) + # single event processing + events = glob.glob(os.path.join(datapath, parameter.get('eventID'))) + for event in events: + data.setWFData(glob.glob(os.path.join(datapath, event, '*'))) + evID = os.path.split(event)[-1] + print('Working on event %s' % event) print(data) - wfdat = data.getWFData() # all available streams + wfdat = remove_underscores(wfdat) + metadata = read_metadata(parameter.get('invdir')) + corr_dat, rest_flag = restitute_data(wfdat.copy(), *metadata) ########################################################## # !automated picking starts here! picks = autopickevent(wfdat, parameter) - finalpicks = picks ########################################################## # locating if locflag == 1: @@ -237,12 +126,14 @@ def autoPyLoT(inputfile): nll.export(picks, phasefile) # For locating the event the NLLoc-control file has to be modified! - nllocout = '%s_%s' % (parameter.get('eventID'), nllocoutpatter) + nllocout = '%s_%s' % (evID, nllocoutpatter) # create comment line for NLLoc-control file - nll.modify_inputs(ctrf, nllocroot, nllocout, phasef, ttpat) + nll.modify_inputs(ctrf, nllocroot, nllocout, phasef, + ttpat) # locate the event nll.locate(ctrfile) + # !iterative picking if traces remained unpicked or occupied with bad picks! # get theoretical onset times for picks with weights >= 4 # in order to reprocess them using smaller time windows around theoretical onset @@ -252,18 +143,30 @@ def autoPyLoT(inputfile): if picks[key]['P']['weight'] >= 4 or picks[key]['S']['weight'] >= 4: badpicks.append([key, picks[key]['P']['mpp']]) + # TODO keep code DRY (Don't Repeat Yourself) the following part is written twice + # suggestion: delete block and modify the later similar block to work properly + if len(badpicks) == 0: print("autoPyLoT: No bad onsets found, thus no iterative picking necessary!") # get NLLoc-location file locsearch = '%s/loc/%s.????????.??????.grid?.loc.hyp' % (nllocroot, nllocout) if len(glob.glob(locsearch)) > 0: - # get latest NLLOc-location file if several are available + # get latest NLLoc-location file if several are available nllocfile = max(glob.glob(locsearch), key=os.path.getctime) + evt = read_events(nllocfile)[0] # calculating seismic moment Mo and moment magnitude Mw - finalpicks = M0Mw(wfdat, None, None, parameter.get('iplot'), \ - nllocfile, picks, parameter.get('rho'), \ - parameter.get('vp'), parameter.get('Qp'), \ - parameter.get('invdir')) + moment_mag = MomentMagnitude(corr_dat, evt, parameter.get('vp'), + parameter.get('Qp'), + parameter.get('rho'), True, 0) + # update pick with moment property values (w0, fc, Mo) + for station, props in moment_mag.moment_props.items(): + picks[station]['P'].update(props) + evt = moment_mag.updated_event() + local_mag = RichterMagnitude(corr_dat, evt, + parameter.get('sstop'), True, 0) + for station, amplitude in local_mag.amplitudes.items(): + picks[station]['S']['Ao'] = amplitude.generic_amplitude + evt = local_mag.updated_event() else: print("autoPyLoT: No NLLoc-location file available!") print("No source parameter estimation possible!") @@ -300,38 +203,39 @@ def autoPyLoT(inputfile): if len(badpicks) == 0: print("autoPyLoT: No more bad onsets found, stop iterative picking!") nlloccounter = maxnumit - + evt = read_events(nllocfile)[0] # calculating seismic moment Mo and moment magnitude Mw - finalpicks = M0Mw(wfdat, None, None, parameter.get('iplot'), \ - nllocfile, picks, parameter.get('rho'), \ - parameter.get('vp'), parameter.get('Qp'), \ - parameter.get('invdir')) - # get network moment magntiude - netMw = [] - for key in finalpicks.getpicdic(): - if finalpicks.getpicdic()[key]['P']['Mw'] is not None: - netMw.append(finalpicks.getpicdic()[key]['P']['Mw']) - netMw = np.median(netMw) - print("Network moment magnitude: %4.1f" % netMw) + moment_mag = MomentMagnitude(corr_dat, evt, parameter.get('vp'), + parameter.get('Qp'), + parameter.get('rho'), True, 0) + # update pick with moment property values (w0, fc, Mo) + for station, props in moment_mag.moment_props.items(): + picks[station]['P'].update(props) + evt = moment_mag.updated_event() + local_mag = RichterMagnitude(corr_dat, evt, + parameter.get('sstop'), True, 0) + for station, amplitude in local_mag.amplitudes.items(): + picks[station]['S']['Ao'] = amplitude.generic_amplitude + evt = local_mag.updated_event() + net_mw = moment_mag.net_magnitude() + print("Network moment magnitude: %4.1f" % net_mw.mag) else: print("autoPyLoT: No NLLoc-location file available! Stop iteration!") ########################################################## # write phase files for various location routines # HYPO71 - hypo71file = '%s/%s/autoPyLoT_HYPO71.pha' % (datapath, parameter.get('eventID')) - if hasattr(finalpicks, 'getpicdic') and finalpicks.getpicdic() is not None: - hsat.export(finalpicks.getpicdic(), hypo71file) - data.applyEVTData(finalpicks.getpicdic()) - else: - hsat.export(picks, hypo71file) - data.applyEVTData(picks) - fnqml = '%s/%s/autoPyLoT' % (datapath, parameter.get('eventID')) + hypo71file = '%s/autoPyLoT_HYPO71.pha' % event + hsat.export(picks, hypo71file) + data.applyEVTData(picks) + if evt is not None: + data.applyEVTData(evt, 'event') + fnqml = '%s/autoPyLoT' % event data.exportEvent(fnqml) endsplash = '''------------------------------------------\n' -----Finished event %s!-----\n' ------------------------------------------'''.format \ - (version=_getVersionString()) % parameter.get('eventID') + (version=_getVersionString()) % evID print(endsplash) if locflag == 0: print("autoPyLoT was running in non-location mode!") diff --git a/pylot/RELEASE-VERSION b/pylot/RELEASE-VERSION index 30ada269..4c41ac0c 100644 --- a/pylot/RELEASE-VERSION +++ b/pylot/RELEASE-VERSION @@ -1 +1 @@ -55a58-dirty +5f92-dirty diff --git a/pylot/core/active/seismicArrayPreparation.py b/pylot/core/active/seismicArrayPreparation.py index 976c37f3..907e9161 100644 --- a/pylot/core/active/seismicArrayPreparation.py +++ b/pylot/core/active/seismicArrayPreparation.py @@ -3,6 +3,41 @@ import sys import numpy as np from scipy.interpolate import griddata +def readMygridNlayers(filename): + infile = open(filename, 'r') + nlayers = len(infile.readlines()) / 2 + infile.close() + + return nlayers + +def readMygrid(filename): + ztop = []; + zbot = []; + vtop = []; + vbot = [] + infile = open(filename, 'r') + nlayers = readMygridNlayers(filename) + + print('\nreadMygrid: Reading file %s.' % filename) + for index in range(nlayers): + line1 = infile.readline() + line2 = infile.readline() + ztop.append(float(line1.split()[0])) + vtop.append(float(line1.split()[1])) + zbot.append(float(line2.split()[0])) + vbot.append(float(line2.split()[1])) + print('Layer %s:\n[Top: v = %s [km/s], z = %s [m]]' + '\n[Bot: v = %s [km/s], z = %s [m]]' + % (index + 1, vtop[index], ztop[index], + vbot[index], zbot[index])) + + if not ztop[0] == 0: + print('ERROR: there must be a velocity set for z = 0 in the file %s' % filename) + print('e.g.:\n0 0.33\n-5 1.0\netc.') + + infile.close() + return ztop, zbot, vtop, vbot + class SeisArray(object): ''' @@ -714,41 +749,6 @@ class SeisArray(object): # rad = angle / 180 * PI # return rad - def readMygridNlayers(filename): - infile = open(filename, 'r') - nlayers = len(infile.readlines()) / 2 - infile.close() - - return nlayers - - def readMygrid(filename): - ztop = []; - zbot = []; - vtop = []; - vbot = [] - infile = open(filename, 'r') - nlayers = readMygridNlayers(filename) - - print('\nreadMygrid: Reading file %s.' % filename) - for index in range(nlayers): - line1 = infile.readline() - line2 = infile.readline() - ztop.append(float(line1.split()[0])) - vtop.append(float(line1.split()[1])) - zbot.append(float(line2.split()[0])) - vbot.append(float(line2.split()[1])) - print('Layer %s:\n[Top: v = %s [km/s], z = %s [m]]' - '\n[Bot: v = %s [km/s], z = %s [m]]' - % (index + 1, vtop[index], ztop[index], - vbot[index], zbot[index])) - - if not ztop[0] == 0: - print('ERROR: there must be a velocity set for z = 0 in the file %s' % filename) - print('e.g.:\n0 0.33\n-5 1.0\netc.') - - infile.close() - return ztop, zbot, vtop, vbot - R = 6371. vmin = 0.34 decm = 0.3 # diagonal elements of the covariance matrix (grid3dg's default value is 0.3) diff --git a/pylot/core/analysis/magnitude.py b/pylot/core/analysis/magnitude.py index 525b43b9..5a057b6f 100644 --- a/pylot/core/analysis/magnitude.py +++ b/pylot/core/analysis/magnitude.py @@ -6,214 +6,261 @@ Created autumn/winter 2015. :author: Ludger Küperkoch / MAGS2 EP3 working group """ import os + import matplotlib.pyplot as plt import numpy as np -from obspy.core import Stream, UTCDateTime -from pylot.core.pick.utils import getsignalwin, crossings_nonzero_all -from pylot.core.util.utils import getPatternLine -from scipy.optimize import curve_fit +import obspy.core.event as ope +from obspy.geodetics import degrees2kilometers from scipy import integrate, signal -from pylot.core.io.data import Data -from pylot.core.util.dataprocessing import restitute_data, read_metadata +from scipy.optimize import curve_fit + +from pylot.core.pick.utils import getsignalwin, crossings_nonzero_all, \ + select_for_phase from pylot.core.util.utils import common_range, fit_curve + def richter_magnitude_scaling(delta): relation = np.loadtxt(os.path.join(os.path.expanduser('~'), - '.pylot', 'richter_scaling.data')) + '.pylot', 'richter_scaling.data')) # prepare spline interpolation to calculate return value - func, params = fit_curve(relation[:,0], relation[:, 1]) + func, params = fit_curve(relation[:, 0], relation[:, 1]) return func(delta, params) + class Magnitude(object): - ''' - Superclass for calculating Wood-Anderson peak-to-peak - amplitudes, local magnitudes, source spectra, seismic moments - and moment magnitudes. - ''' + """ + Base class object for Magnitude calculation within PyLoT. + """ - def __init__(self, wfstream, To, pwin, iplot, NLLocfile=None, \ - picks=None, rho=None, vp=None, Qp=None, invdir=None): - ''' - :param: wfstream - :type: `~obspy.core.stream.Stream + def __init__(self, stream, event, verbosity=False, iplot=0): + self._type = "M" + self._plot_flag = iplot + self._verbosity = verbosity + self._event = event + self._stream = stream + self._magnitudes = dict() - :param: To, onset time, P- or S phase - :type: float + def __str__(self): + print( + 'number of stations used: {0}\n'.format(len(self.magnitudes.values()))) + print('\tstation\tmagnitude') + for s, m in self.magnitudes.items(): print('\t{0}\t{1}'.format(s, m)) - :param: pwin, pick window [To To+pwin] to get maximum - peak-to-peak amplitude (WApp) or to calculate - source spectrum (DCfc) around P onset - :type: float + def __nonzero__(self): + return bool(self.magnitudes) - :param: iplot, no. of figure window for plotting interims results - :type: integer + @property + def type(self): + return self._type - :param: NLLocfile, name and full path to NLLoc-location file - needed when calling class MoMw - :type: string + @property + def plot_flag(self): + return self._plot_flag - :param: picks, dictionary containing picking results - :type: dictionary + @plot_flag.setter + def plot_flag(self, value): + self._plot_flag = value - :param: rho [kg/m³], rock density, parameter from autoPyLoT.in - :type: integer + @property + def verbose(self): + return self._verbosity - :param: vp [m/s], P-velocity - :param: integer + @verbose.setter + def verbose(self, value): + if not isinstance(value, bool): + print('WARNING: only boolean values accepted...\n') + value = bool(value) + self._verbosity = value - :param: invdir, name and path to inventory or dataless-SEED file - :type: string - ''' + @property + def stream(self): + return self._stream - assert isinstance(wfstream, Stream), "%s is not a stream object" % str(wfstream) + @stream.setter + def stream(self, value): + self._stream = value - self.setwfstream(wfstream) - self.setTo(To) - self.setpwin(pwin) - self.setiplot(iplot) - self.setNLLocfile(NLLocfile) - self.setrho(rho) - self.setpicks(picks) - self.setvp(vp) - self.setQp(Qp) - self.setinvdir(invdir) - self.calcwapp() - self.calcsourcespec() - self.run_calcMoMw() + @property + def event(self): + return self._event - def getwfstream(self): - return self.wfstream + @property + def origin_id(self): + return self._event.origins[0].resource_id - def setwfstream(self, wfstream): - self.wfstream = wfstream + @property + def arrivals(self): + return self._event.origins[0].arrivals - def getTo(self): - return self.To + @property + def magnitudes(self): + return self._magnitudes - def setTo(self, To): - self.To = To + @magnitudes.setter + def magnitudes(self, value): + """ + takes a tuple and saves the key value pair to private + attribute _magnitudes + :param value: station, magnitude value pair + :type value: tuple or list + :return: + """ + station, magnitude = value + self._magnitudes[station] = magnitude - def getpwin(self): - return self.pwin + def calc(self): + pass - def setpwin(self, pwin): - self.pwin = pwin + def updated_event(self): + self.event.magnitudes.append(self.net_magnitude()) + return self.event - def getiplot(self): - return self.iplot - - def setiplot(self, iplot): - self.iplot = iplot - - 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 setinvdir(self, invdir): - self.invdir = invdir - - def get_metadata(self): - return read_metadata(self.invdir) - - 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 + def net_magnitude(self): + if self: + # TODO if an average Magnitude instead of the median is calculated + # StationMagnitudeContributions should be added to the returned + # Magnitude object + # mag_error => weights (magnitude error estimate from peak_to_peak, calcsourcespec?) + # weights => StationMagnitdeContribution + mag = ope.Magnitude( + mag=np.median([M.mag for M in self.magnitudes.values()]), + magnitude_type=self.type, + origin_id=self.origin_id, + station_count=len(self.magnitudes), + azimuthal_gap=self.origin_id.get_referred_object().quality.azimuthal_gap) + return mag + return None -class WApp(Magnitude): - ''' +class RichterMagnitude(Magnitude): + """ Method to derive peak-to-peak amplitude as seen on a Wood-Anderson- seismograph. Has to be derived from instrument corrected traces! - ''' + """ - def calcwapp(self): - print ("Getting Wood-Anderson peak-to-peak amplitude ...") - print ("Simulating Wood-Anderson seismograph ...") + # poles, zeros and sensitivity of WA seismograph + # (see Uhrhammer & Collins, 1990, BSSA, pp. 702-716) + _paz = { + 'poles': [5.6089 - 5.4978j, -5.6089 - 5.4978j], + 'zeros': [0j, 0j], + 'gain': 2080, + 'sensitivity': 1 + } - self.wapp = None - stream = self.getwfstream() + _amplitudes = dict() - # poles, zeros and sensitivity of WA seismograph - # (see Uhrhammer & Collins, 1990, BSSA, pp. 702-716) - paz_wa = { - 'poles': [5.6089 - 5.4978j, -5.6089 - 5.4978j], - 'zeros': [0j, 0j], - 'gain': 2080, - 'sensitivity': 1} + def __init__(self, stream, event, calc_win, verbosity=False, iplot=0): + super(RichterMagnitude, self).__init__(stream, event, verbosity, iplot) - stream.simulate(paz_remove=None, paz_simulate=paz_wa) + self._calc_win = calc_win + self._type = 'ML' + self.calc() + + @property + def calc_win(self): + return self._calc_win + + @calc_win.setter + def calc_win(self, value): + self._calc_win = value + + @property + def amplitudes(self): + return self._amplitudes + + @amplitudes.setter + def amplitudes(self, value): + station, a0 = value + self._amplitudes[station] = a0 + + def peak_to_peak(self, st, t0): + + # simulate Wood-Anderson response + st.simulate(paz_remove=None, paz_simulate=self._paz) + + # trim waveform to common range + stime, etime = common_range(st) + st.trim(stime, etime) + + # get time delta from waveform data + dt = st[0].stats.delta + + power = [np.power(tr.data, 2) for tr in st if tr.stats.channel[-1] not + in 'Z3'] + if len(power) != 2: + raise ValueError('Wood-Anderson amplitude defintion only valid for ' + 'two horizontals: {0} given'.format(len(power))) + power_sum = power[0] + power[1] + # + sqH = np.sqrt(power_sum) - trH1 = stream[0].data - trH2 = stream[1].data - ilen = min([len(trH1), len(trH2)]) - # get RMS of both horizontal components - sqH = np.sqrt(np.power(trH1[0:ilen], 2) + np.power(trH2[0:ilen], 2)) # get time array - th = np.arange(0, len(sqH) * stream[0].stats.delta, stream[0].stats.delta) + th = np.arange(0, len(sqH) * dt, dt) # get maximum peak within pick window - iwin = getsignalwin(th, self.getTo(), self.getpwin()) - self.wapp = np.max(sqH[iwin]) - print ("Determined Wood-Anderson peak-to-peak amplitude: %f mm") % self.wapp + iwin = getsignalwin(th, t0 - stime, self.calc_win) + wapp = np.max(sqH[iwin]) + if self.verbose: + print("Determined Wood-Anderson peak-to-peak amplitude: {0} " + "mm".format(wapp)) - if self.getiplot() > 1: - stream.plot() + # check for plot flag (for debugging only) + if self.plot_flag > 1: + st.plot() f = plt.figure(2) plt.plot(th, sqH) plt.plot(th[iwin], sqH[iwin], 'g') - plt.plot([self.getTo(), self.getTo()], [0, max(sqH)], 'r', linewidth=2) - plt.title('Station %s, RMS Horizontal Traces, WA-peak-to-peak=%4.1f mm' \ - % (stream[0].stats.station, self.wapp)) + plt.plot([t0, t0], [0, max(sqH)], 'r', linewidth=2) + plt.title( + 'Station %s, RMS Horizontal Traces, WA-peak-to-peak=%4.1f mm' \ + % (st[0].stats.station, wapp)) plt.xlabel('Time [s]') plt.ylabel('Displacement [mm]') plt.show() raw_input() plt.close(f) + return wapp -class M0Mw(Magnitude): + def calc(self): + for a in self.arrivals: + if a.phase not in 'sS': + 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: + if self.verbose: + print( + 'WARNING: no waveform data found for station {0}'.format( + station)) + continue + delta = degrees2kilometers(a.distance) + onset = pick.time + a0 = self.peak_to_peak(wf, onset) + amplitude = ope.Amplitude(generic_amplitude=a0 * 1e-3) + amplitude.unit = 'm' + amplitude.category = 'point' + amplitude.waveform_id = pick.waveform_id + amplitude.magnitude_hint = self.type + amplitude.pick_id = pick.resource_id + amplitude.type = 'AML' + self.event.amplitudes.append(amplitude) + self.amplitudes = (station, amplitude) + # using standard Gutenberg-Richter relation + # TODO make the ML calculation more flexible by allowing + # use of custom relation functions + magnitude = ope.StationMagnitude( + mag=np.log10(a0) + richter_magnitude_scaling(delta)) + magnitude.origin_id = self.origin_id + magnitude.waveform_id = pick.waveform_id + magnitude.amplitude_id = amplitude.resource_id + magnitude.station_magnitude_type = self.type + self.event.station_magnitudes.append(magnitude) + self.magnitudes = (station, magnitude) + + +class MomentMagnitude(Magnitude): ''' Method to calculate seismic moment Mo and moment magnitude Mw. Requires results of class calcsourcespec for calculating plateau w0 @@ -223,55 +270,82 @@ class M0Mw(Magnitude): corresponding moment magntiude Mw. ''' - def run_calcMoMw(self): + _props = dict() - picks = self.getpicks() - nllocfile = self.getNLLocfile() - wfdat = self.getwfstream() - self.picdic = None + def __init__(self, stream, event, vp, Qp, density, verbosity=False, + iplot=False): + super(MomentMagnitude, self).__init__(stream, event, verbosity, iplot) - for key in picks: - if picks[key]['P']['weight'] < 4: - # select waveform - selwf = wfdat.select(station=key) - if len(key) > 4: - Ppattern = '%s ? ? ? P' % key - elif len(key) == 4: - Ppattern = '%s ? ? ? P' % key - elif len(key) < 4: - Ppattern = '%s ? ? ? P' % key - nllocline = getPatternLine(nllocfile, Ppattern) - # get hypocentral distance, station azimuth and - # angle of incidence from NLLoc-location file - delta = float(nllocline.split(None)[21]) - az = float(nllocline.split(None)[22]) - inc = float(nllocline.split(None)[24]) - # call subfunction to estimate source spectrum - # and to derive w0 and fc - [w0, fc] = calcsourcespec(selwf, picks[key]['P']['mpp'], \ - self.get_metadata(), self.getvp(), delta, az, \ - inc, self.getQp(), self.getiplot()) + self._vp = vp + self._Qp = Qp + self._density = density + self._type = 'Mw' + self.calc() - if w0 is not None: - # call subfunction to calculate Mo and Mw - zdat = selwf.select(component="Z") - if len(zdat) == 0: # check for other components - zdat = selwf.select(component="3") - [Mo, Mw] = calcMoMw(zdat, w0, self.getrho(), self.getvp(), - delta) - else: - Mo = None - Mw = None + @property + def p_velocity(self): + return self._vp - # add w0, fc, Mo and Mw to dictionary - picks[key]['P']['w0'] = w0 - picks[key]['P']['fc'] = fc - picks[key]['P']['Mo'] = Mo - picks[key]['P']['Mw'] = Mw - self.picdic = picks + @property + def p_attenuation(self): + return self._Qp + + @property + def rock_density(self): + return self._density + + @property + def moment_props(self): + return self._props + + @moment_props.setter + def moment_props(self, value): + station, props = value + self._props[station] = props + + @property + def seismic_moment(self): + return self._m0 + + @seismic_moment.setter + def seismic_moment(self, value): + self._m0 = value + + def calc(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, + self.plot_flag, self.verbose) + if w0 is None or fc is None: + if self.verbose: + print("WARNING: insufficient frequency information") + continue + wf = select_for_phase(wf, "P") + m0, mw = calcMoMw(wf, w0, self.rock_density, self.p_velocity, + distance, self.verbose) + self.moment_props = (station, dict(w0=w0, fc=fc, Mo=m0)) + magnitude = ope.StationMagnitude(mag=mw) + magnitude.origin_id = self.origin_id + magnitude.waveform_id = pick.waveform_id + magnitude.station_magnitude_type = self.type + self.event.station_magnitudes.append(magnitude) + self.magnitudes = (station, magnitude) -def calcMoMw(wfstream, w0, rho, vp, delta): +def calcMoMw(wfstream, w0, rho, vp, delta, verbosity=False): ''' Subfunction of run_calcMoMw to calculate individual seismic moments and corresponding moment magnitudes. @@ -295,11 +369,14 @@ def calcMoMw(wfstream, w0, rho, vp, delta): tr = wfstream[0] delta = delta * 1000 # hypocentral distance in [m] - print("calcMoMw: Calculating seismic moment Mo and moment magnitude Mw for station %s ..." \ - % tr.stats.station) + if verbosity: + print( + "calcMoMw: Calculating seismic moment Mo and moment magnitude Mw for station {0} ...".format( + tr.stats.station)) # additional common parameters for calculating Mo - rP = 2 / np.sqrt(15) # average radiation pattern of P waves (Aki & Richards, 1980) + rP = 2 / np.sqrt( + 15) # average radiation pattern of P waves (Aki & Richards, 1980) freesurf = 2.0 # free surface correction, assuming vertical incidence Mo = w0 * 4 * np.pi * rho * np.power(vp, 3) * delta / (rP * freesurf) @@ -307,13 +384,16 @@ def calcMoMw(wfstream, w0, rho, vp, delta): # Mw = np.log10(Mo * 1e07) * 2 / 3 - 10.7 # after Hanks & Kanamori (1979), defined for [dyn*cm]! Mw = np.log10(Mo) * 2 / 3 - 6.7 # for metric units - print("calcMoMw: Calculated seismic moment Mo = %e Nm => Mw = %3.1f " % (Mo, Mw)) + if verbosity: + print( + "calcMoMw: Calculated seismic moment Mo = {0} Nm => Mw = {1:3.1f} ".format( + Mo, Mw)) return Mo, Mw -def calcsourcespec(wfstream, onset, metadata, vp, delta, azimuth, incidence, - qp, iplot): +def calcsourcespec(wfstream, onset, vp, delta, azimuth, incidence, + qp, iplot=0, verbosity=False): ''' 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 @@ -321,16 +401,12 @@ def calcsourcespec(wfstream, onset, metadata, vp, delta, azimuth, incidence, thus restitution and integration necessary! Integrated traces are rotated into ray-coordinate system ZNE => LQT using Obspy's rotate modul! - :param: wfstream + :param: wfstream (corrected for instrument) :type: `~obspy.core.stream.Stream` :param: onset, P-phase onset time :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 :type: float @@ -349,169 +425,162 @@ def calcsourcespec(wfstream, onset, metadata, vp, delta, azimuth, incidence, :param: iplot, show results (iplot>1) or not (iplot<1) :type: integer ''' - print ("Calculating source spectrum ....") + if verbosity: + print ("Calculating source spectrum ....") # get Q value Q, A = qp - delta = delta * 1000 # hypocentral distance in [m] + dist = delta * 1000 # hypocentral distance in [m] fc = None w0 = None - data = Data() - wf_copy = wfstream.copy() - invtype, inventory = metadata + zdat = select_for_phase(wfstream, "P") - [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 - # L: P-wave direction - # Q: SV-wave direction - # T: SH-wave direction - LQT = cordat_copy.rotate('ZNE->LQT', azimuth, incidence) - ldat = LQT.select(component="L") - if len(ldat) == 0: - # if horizontal channels are 2 and 3 - # no azimuth information is available and thus no - # rotation is possible! - print("calcsourcespec: Azimuth information is missing, " - "no rotation of components possible!") - ldat = LQT.select(component="Z") + dt = zdat[0].stats.delta - # integrate to displacement - # unrotated vertical component (for copmarison) - inttrz = signal.detrend(integrate.cumtrapz(zdat[0].data, None, - zdat[0].stats.delta)) - # rotated component Z => L - Ldat = signal.detrend(integrate.cumtrapz(ldat[0].data, None, - ldat[0].stats.delta)) + freq = zdat[0].stats.sampling_rate - # get window after P pulse for - # calculating source spectrum - tstart = UTCDateTime(zdat[0].stats.starttime) - tonset = onset.timestamp - tstart.timestamp - impickP = tonset * zdat[0].stats.sampling_rate - wfzc = Ldat[impickP: len(Ldat) - 1] - # get time array - t = np.arange(0, len(inttrz) * zdat[0].stats.delta, \ - zdat[0].stats.delta) - # calculate spectrum using only first cycles of - # waveform after P onset! - zc = crossings_nonzero_all(wfzc) - if np.size(zc) == 0 or len(zc) <= 3: - print ("calcsourcespec: Something is wrong with the waveform, " - "no zero crossings derived!") - print ("No calculation of source spectrum possible!") - plotflag = 0 - else: - plotflag = 1 - index = min([3, len(zc) - 1]) - calcwin = (zc[index] - zc[0]) * zdat[0].stats.delta - iwin = getsignalwin(t, tonset, calcwin) - xdat = Ldat[iwin] + # trim traces to common range (for rotation) + trstart, trend = common_range(wfstream) + wfstream.trim(trstart, trend) - # fft - fny = zdat[0].stats.sampling_rate / 2 - l = len(xdat) / zdat[0].stats.sampling_rate - # number of fft bins after Bath - n = zdat[0].stats.sampling_rate * l - # find next power of 2 of data length - m = pow(2, np.ceil(np.log(len(xdat)) / np.log(2))) - N = int(np.power(m, 2)) - y = zdat[0].stats.delta * np.fft.fft(xdat, N) - Y = abs(y[: N / 2]) - L = (N - 1) / zdat[0].stats.sampling_rate - f = np.arange(0, fny, 1 / L) + # rotate into LQT (ray-coordindate-) system using Obspy's rotate + # L: P-wave direction + # Q: SV-wave direction + # T: SH-wave direction + LQT = wfstream.rotate('ZNE->LQT', azimuth, incidence) + ldat = LQT.select(component="L") + if len(ldat) == 0: + # if horizontal channels are 2 and 3 + # no azimuth information is available and thus no + # rotation is possible! + if verbosity: + print("calcsourcespec: Azimuth information is missing, " + "no rotation of components possible!") + ldat = LQT.select(component="Z") - # remove zero-frequency and frequencies above - # corner frequency of seismometer (assumed - # to be 100 Hz) - fi = np.where((f >= 1) & (f < 100)) - F = f[fi] - YY = Y[fi] + # integrate to displacement + # unrotated vertical component (for comparison) + inttrz = signal.detrend(integrate.cumtrapz(zdat[0].data, None, dt)) - # correction for attenuation - wa = 2 * np.pi * F # angular frequency - D = np.exp((wa * delta) / (2 * vp * Q * F ** A)) - YYcor = YY.real * D + # rotated component Z => L + Ldat = signal.detrend(integrate.cumtrapz(ldat[0].data, None, dt)) - # get plateau (DC value) and corner frequency - # initial guess of plateau - w0in = np.mean(YYcor[0:100]) - # initial guess of corner frequency - # where spectral level reached 50% of flat level - iin = np.where(YYcor >= 0.5 * w0in) - Fcin = F[iin[0][np.size(iin) - 1]] + # get window after P pulse for + # calculating source spectrum + rel_onset = onset - trstart + impickP = int(rel_onset * freq) + wfzc = Ldat[impickP: len(Ldat) - 1] + # get time array + t = np.arange(0, len(inttrz) * dt, dt) + # calculate spectrum using only first cycles of + # waveform after P onset! + zc = crossings_nonzero_all(wfzc) + if np.size(zc) == 0 or len(zc) <= 3: + if verbosity: + print ("calcsourcespec: Something is wrong with the waveform, " + "no zero crossings derived!\n") + print ("No calculation of source spectrum possible!") + plotflag = 0 + else: + plotflag = 1 + index = min([3, len(zc) - 1]) + calcwin = (zc[index] - zc[0]) * dt + iwin = getsignalwin(t, rel_onset, calcwin) + xdat = Ldat[iwin] - # use of implicit scipy otimization function - fit = synthsourcespec(F, w0in, Fcin) - [optspecfit, _] = curve_fit(synthsourcespec, F, YYcor, [w0in, - Fcin]) - w01 = optspecfit[0] - fc1 = optspecfit[1] - print ("calcsourcespec: Determined w0-value: %e m/Hz, \n" - "Determined corner frequency: %f Hz" % (w01, fc1)) + # fft + fny = freq / 2 + l = len(xdat) / freq + # number of fft bins after Bath + n = freq * l + # find next power of 2 of data length + m = pow(2, np.ceil(np.log(len(xdat)) / np.log(2))) + N = int(np.power(m, 2)) + y = dt * np.fft.fft(xdat, N) + Y = abs(y[: N / 2]) + L = (N - 1) / freq + f = np.arange(0, fny, 1 / L) - # use of conventional fitting - [w02, fc2] = fitSourceModel(F, YYcor, Fcin, iplot) + # remove zero-frequency and frequencies above + # corner frequency of seismometer (assumed + # to be 100 Hz) + fi = np.where((f >= 1) & (f < 100)) + F = f[fi] + YY = Y[fi] - # get w0 and fc as median of both - # source spectrum fits - w0 = np.median([w01, w02]) - fc = np.median([fc1, fc2]) - print("calcsourcespec: Using w0-value = %e m/Hz and fc = %f Hz" % (w0, fc)) + # correction for attenuation + wa = 2 * np.pi * F # angular frequency + D = np.exp((wa * dist) / (2 * vp * Q * F ** A)) + YYcor = YY.real * D - except TypeError as er: - raise TypeError('''{0}'''.format(er)) + # get plateau (DC value) and corner frequency + # initial guess of plateau + w0in = np.mean(YYcor[0:100]) + # initial guess of corner frequency + # where spectral level reached 50% of flat level + iin = np.where(YYcor >= 0.5 * w0in) + Fcin = F[iin[0][np.size(iin) - 1]] - if iplot > 1: - f1 = plt.figure() - tLdat = np.arange(0, len(Ldat) * zdat[0].stats.delta, \ - zdat[0].stats.delta) - plt.subplot(2, 1, 1) - # show displacement in mm - p1, = plt.plot(t, np.multiply(inttrz, 1000), 'k') - p2, = plt.plot(tLdat, np.multiply(Ldat, 1000)) - plt.legend([p1, p2], ['Displacement', 'Rotated Displacement']) - if plotflag == 1: - plt.plot(t[iwin], np.multiply(xdat, 1000), 'g') - plt.title('Seismogram and P Pulse, Station %s-%s' \ - % (zdat[0].stats.station, zdat[0].stats.channel)) - else: - plt.title('Seismogram, Station %s-%s' \ - % (zdat[0].stats.station, zdat[0].stats.channel)) - plt.xlabel('Time since %s' % zdat[0].stats.starttime) - plt.ylabel('Displacement [mm]') + # use of implicit scipy otimization function + fit = synthsourcespec(F, w0in, Fcin) + [optspecfit, _] = curve_fit(synthsourcespec, F, YYcor, [w0in, Fcin]) + w01 = optspecfit[0] + fc1 = optspecfit[1] + if verbosity: + print ("calcsourcespec: Determined w0-value: %e m/Hz, \n" + "Determined corner frequency: %f Hz" % (w01, fc1)) - if plotflag == 1: - plt.subplot(2, 1, 2) - p1, = plt.loglog(f, Y.real, 'k') - p2, = plt.loglog(F, YY.real) - p3, = plt.loglog(F, YYcor, 'r') - p4, = plt.loglog(F, fit, 'g') - plt.loglog([fc, fc], [w0 / 100, w0], 'g') - plt.legend([p1, p2, p3, p4], ['Raw Spectrum', \ - 'Used Raw Spectrum', \ - 'Q-Corrected Spectrum', \ - 'Fit to Spectrum']) - plt.title('Source Spectrum from P Pulse, w0=%e m/Hz, fc=%6.2f Hz' \ - % (w0, fc)) - plt.xlabel('Frequency [Hz]') - plt.ylabel('Amplitude [m/Hz]') - plt.grid() - plt.show() - raw_input() - plt.close(f1) + # use of conventional fitting + [w02, fc2] = fitSourceModel(F, YYcor, Fcin, iplot, verbosity) + + # get w0 and fc as median of both + # source spectrum fits + w0 = np.median([w01, w02]) + fc = np.median([fc1, fc2]) + if verbosity: + print("calcsourcespec: Using w0-value = %e m/Hz and fc = %f Hz" % ( + w0, fc)) + + if iplot > 1: + f1 = plt.figure() + tLdat = np.arange(0, len(Ldat) * dt, dt) + plt.subplot(2, 1, 1) + # show displacement in mm + p1, = plt.plot(t, np.multiply(inttrz, 1000), 'k') + p2, = plt.plot(tLdat, np.multiply(Ldat, 1000)) + plt.legend([p1, p2], ['Displacement', 'Rotated Displacement']) + if plotflag == 1: + plt.plot(t[iwin], np.multiply(xdat, 1000), 'g') + plt.title('Seismogram and P Pulse, Station %s-%s' \ + % (zdat[0].stats.station, zdat[0].stats.channel)) + else: + plt.title('Seismogram, Station %s-%s' \ + % (zdat[0].stats.station, zdat[0].stats.channel)) + plt.xlabel('Time since %s' % zdat[0].stats.starttime) + plt.ylabel('Displacement [mm]') + + if plotflag == 1: + plt.subplot(2, 1, 2) + p1, = plt.loglog(f, Y.real, 'k') + p2, = plt.loglog(F, YY.real) + p3, = plt.loglog(F, YYcor, 'r') + p4, = plt.loglog(F, fit, 'g') + plt.loglog([fc, fc], [w0 / 100, w0], 'g') + plt.legend([p1, p2, p3, p4], ['Raw Spectrum', \ + 'Used Raw Spectrum', \ + 'Q-Corrected Spectrum', \ + 'Fit to Spectrum']) + plt.title('Source Spectrum from P Pulse, w0=%e m/Hz, fc=%6.2f Hz' \ + % (w0, fc)) + plt.xlabel('Frequency [Hz]') + plt.ylabel('Amplitude [m/Hz]') + plt.grid() + plt.show() + raw_input() + plt.close(f1) return w0, fc @@ -537,7 +606,7 @@ def synthsourcespec(f, omega0, fcorner): return ssp -def fitSourceModel(f, S, fc0, iplot): +def fitSourceModel(f, S, fc0, iplot, verbosity=False): ''' Calculates synthetic source spectrum by varying corner frequency fc. Returns best approximated plateau omega0 and corner frequency, i.e. with least @@ -591,9 +660,9 @@ def fitSourceModel(f, S, fc0, iplot): elif len(STD) == 0: fc = fc0 w0 = max(S) - - print("fitSourceModel: best fc: %fHz, best w0: %e m/Hz" \ - % (fc, w0)) + if verbosity: + print( + "fitSourceModel: best fc: {0} Hz, best w0: {1} m/Hz".format(fc, w0)) if iplot > 1: plt.figure(iplot) diff --git a/pylot/core/io/phases.py b/pylot/core/io/phases.py index d3064f6f..7871f4bb 100644 --- a/pylot/core/io/phases.py +++ b/pylot/core/io/phases.py @@ -15,6 +15,24 @@ from pylot.core.pick.utils import select_for_phase from pylot.core.util.utils import getOwner, full_range, four_digits +def add_amplitudes(event, amplitudes): + amplitude_list = [] + for pick in event.picks: + try: + a0 = amplitudes[pick.waveform_id.station_code] + amplitude = ope.Amplitude(generic_amplitude=a0 * 1e-3) + amplitude.unit = 'm' + amplitude.category = 'point' + amplitude.waveform_id = pick.waveform_id + amplitude.magnitude_hint = 'ML' + amplitude.pick_id = pick.resource_id + amplitude.type = 'AML' + amplitude_list.append(amplitude) + except KeyError: + continue + event.amplitudes = amplitude_list + return event + def readPILOTEvent(phasfn=None, locfn=None, authority_id='RUB', **kwargs): """ readPILOTEvent - function @@ -549,4 +567,4 @@ def merge_picks(event, picks): if p.waveform_id.station_code == station and p.phase_hint == phase: p.time, p.time_errors = time, err del time, err, phase, station - return event \ No newline at end of file + return event diff --git a/pylot/core/pick/autopick.py b/pylot/core/pick/autopick.py index 3b42eb1a..35111071 100755 --- a/pylot/core/pick/autopick.py +++ b/pylot/core/pick/autopick.py @@ -17,10 +17,8 @@ from pylot.core.pick.charfuns import CharacteristicFunction from pylot.core.pick.charfuns import HOScf, AICcf, ARZcf, ARHcf, AR3Ccf from pylot.core.pick.utils import checksignallength, checkZ4S, earllatepicker, \ getSNR, fmpicker, checkPonsets, wadaticheck -from pylot.core.util.dataprocessing import restitute_data, read_metadata from pylot.core.util.utils import getPatternLine from pylot.core.io.data import Data -from pylot.core.analysis.magnitude import WApp def autopickevent(data, param): @@ -121,8 +119,6 @@ def autopickstation(wfstream, pickparam, verbose=False): # parameter to check for spuriously picked S onset zfac = pickparam.get('zfac') # path to inventory-, dataless- or resp-files - invdir = pickparam.get('invdir') - invtype, inventory = read_metadata(invdir) # initialize output Pweight = 4 # weight for P onset @@ -565,24 +561,6 @@ def autopickstation(wfstream, pickparam, verbose=False): # re-create stream object including both horizontal components hdat = edat.copy() hdat += ndat - h_copy = hdat.copy() - [cordat, restflag] = restitute_data(h_copy, invtype, inventory) - # calculate WA-peak-to-peak amplitude - # using subclass WApp of superclass Magnitude - if restflag: - if Sweight < 4: - wapp = WApp(cordat, mpickS, mpickP + sstop, iplot) - else: - # use larger window for getting peak-to-peak amplitude - # as the S pick is quite unsure - wapp = WApp(cordat, mpickP, mpickP + sstop + - (0.5 * (mpickP + sstop)), iplot) - - Ao = wapp.getwapp() - else: - print("Go on processing data without source parameter " - "determination!") - else: msg = 'Bad initial (AIC) S-pick, skipping this onset!\n' \ 'AIC-SNR={0}, AIC-Slope={1}counts/s\n' \ @@ -602,16 +580,6 @@ def autopickstation(wfstream, pickparam, verbose=False): # re-create stream object including both horizontal components hdat = edat.copy() hdat += ndat - h_copy = hdat.copy() - [cordat, restflag] = restitute_data(h_copy, invtype, inventory) - if restflag == 1: - # calculate WA-peak-to-peak amplitude - # using subclass WApp of superclass Magnitude - wapp = WApp(cordat, mpickP, mpickP + sstop + (0.5 * (mpickP - + sstop)), - iplot) - Ao = wapp.getwapp() - else: print('autopickstation: No horizontal component data available or ' \ 'bad P onset, skipping S picking!') diff --git a/pylot/core/pick/utils.py b/pylot/core/pick/utils.py index 09405fb1..20c2227c 100644 --- a/pylot/core/pick/utils.py +++ b/pylot/core/pick/utils.py @@ -103,7 +103,7 @@ def earllatepicker(X, nfac, TSNR, Pick1, iplot=None, stealth_mode=False): # by weighting latest possible pick two times earliest possible pick diffti_tl = LPick - Pick1 diffti_te = Pick1 - EPick - PickError = (diffti_te + 2 * diffti_tl) / 3 + PickError = symmetrize_error(diffti_te, diffti_tl) if iplot > 1: p = plt.figure(iplot) @@ -325,6 +325,17 @@ def crossings_nonzero_all(data): return ((pos[:-1] & npos[1:]) | (npos[:-1] & pos[1:])).nonzero()[0] +def symmetrize_error(dte, dtl): + """ + takes earliest and latest possible pick and returns the symmetrized pick + uncertainty value + :param dte: relative lower uncertainty + :param dtl: relative upper uncertainty + :return: symmetrized error + """ + return (dte + 2 * dtl) / 3 + + def getSNR(X, TSNR, t1, tracenum=0): ''' Function to calculate SNR of certain part of seismogram relative to diff --git a/pylot/core/util/dataprocessing.py b/pylot/core/util/dataprocessing.py index e27b0c78..79a417b4 100644 --- a/pylot/core/util/dataprocessing.py +++ b/pylot/core/util/dataprocessing.py @@ -216,12 +216,8 @@ def restitute_data(data, invtype, inobj, unit='VEL', force=False): for tr in data: seed_id = tr.get_id() # check, whether this trace has already been corrected - # TODO read actual value of processing key in Trace's stats instead - # of just looking for thr key: if processing is setit doesn't - # necessarily mean that the trace has been corrected so far but only - # processed in some way, e.g. normalized if 'processing' in tr.stats.keys() \ - and np.all(['remove' in p for p in tr.stats.processing]) \ + and np.any(['remove' in p for p in tr.stats.processing]) \ and not force: print("Trace {0} has already been corrected!".format(seed_id)) continue @@ -254,7 +250,7 @@ def restitute_data(data, invtype, inobj, unit='VEL', force=False): finv = invlist[0] inventory = read_inventory(finv, format='STATIONXML') else: - restflag.append(False) + data.remove(tr) continue # apply restitution to data try: @@ -270,7 +266,9 @@ def restitute_data(data, invtype, inobj, unit='VEL', force=False): if msg0 not in e.message or msg1 not in e.message: raise else: - restflag.append(False) + # restitution done to copies of data thus deleting traces + # that failed should not be a problem + data.remove(tr) continue restflag.append(True) # check if ALL traces could be restituted, take care of large datasets diff --git a/pylot/core/util/errors.py b/pylot/core/util/errors.py index a6015e55..8805b488 100644 --- a/pylot/core/util/errors.py +++ b/pylot/core/util/errors.py @@ -24,3 +24,6 @@ class OverwriteError(IOError): class ParameterError(Exception): pass + +class ProcessingError(RuntimeError): + pass diff --git a/pylot/core/util/widgets.py b/pylot/core/util/widgets.py index 056c4553..fc20e0c1 100644 --- a/pylot/core/util/widgets.py +++ b/pylot/core/util/widgets.py @@ -33,8 +33,8 @@ from pylot.core.pick.compare import Comparison from pylot.core.util.defaults import OUTPUTFORMATS, FILTERDEFAULTS, LOCTOOLS, \ COMPPOSITION_MAP from pylot.core.util.utils import prepTimeAxis, full_range, scaleWFData, \ - demeanTrace, isSorted, findComboBoxIndex, clims, find_horizontals - + demeanTrace, isSorted, findComboBoxIndex, clims +import icons_rc def getDataType(parent): type = QInputDialog().getItem(parent, "Select phases type", "Type:", @@ -1377,7 +1377,9 @@ class LocalisationTab(PropTab): def selectDirectory(self, edit): selected_directory = QFileDialog.getExistingDirectory() - edit.setText(selected_directory) + # check if string is empty + if selected_directory: + edit.setText(selected_directory) def getValues(self): loctool = self.locToolComboBox.currentText()