Compare commits
89
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e434bda993 | ||
|
|
c0c3cbbd7b | ||
|
|
76d4ec290c | ||
|
|
63dac0fff6 | ||
|
|
b15cfe2e1d | ||
|
|
29a1e4ebe6 | ||
|
|
50cabb0038 | ||
|
|
8dd5789b0c | ||
|
|
c4aeab0d89 | ||
|
|
ef69fc429f | ||
|
|
c8f9c1c33a | ||
|
|
59f2c4b46f | ||
|
|
8eb958c91b | ||
|
|
6b7f297d7a | ||
|
|
70d5c2d621 | ||
|
|
7393201b90 | ||
|
|
d02f74ab10 | ||
|
|
0a5f5f0817 | ||
|
|
db976e5ea9 | ||
|
|
c021ca19d7 | ||
|
|
356988e71d | ||
|
|
ad62284e0e | ||
|
|
0fb0b0f11c | ||
|
|
e3dd4a4e28 | ||
|
|
2bbb84190c | ||
|
|
fb32d5e0c5 | ||
|
|
f043401cc0 | ||
|
|
eb15382b5f | ||
|
|
7fbc3bc5ae | ||
|
|
221743fe20 | ||
|
|
554afc5a81 | ||
|
|
c79e886d77 | ||
|
|
76f2d5d972 | ||
|
|
2d08fd029d | ||
|
|
8f22d438d3 | ||
|
|
93b7de3baa | ||
|
|
05642e775b | ||
|
|
47205ca493 | ||
|
|
5c7f0b56eb | ||
|
|
c574031931 | ||
|
|
e1a0fde619 | ||
|
|
48d196df11 | ||
|
|
6cc9cb4a96 | ||
|
|
5eab686445 | ||
|
|
b12e7937ac | ||
|
|
78f2dbcab2 | ||
|
|
8b95c7a0fe | ||
|
|
f03ace75e7 | ||
|
|
9c78471d20 | ||
|
|
09d2fb1022 | ||
|
|
3cae6d3a78 | ||
|
|
2e85d083a3 | ||
|
|
ba4e6cfe50 | ||
|
|
1f16d01648 | ||
|
|
3069e7d526 | ||
|
|
a9aeb7aaa3 | ||
|
|
b9adb182ad | ||
|
|
a823eb2440 | ||
|
|
486e3dc9c3 | ||
|
|
8d356050d7 | ||
|
|
43cab3767f | ||
|
|
a1f6c5ffca | ||
|
|
e4e7afa996 | ||
|
|
0634d24814 | ||
|
|
43c2b97b3d | ||
|
|
8d94440e77 | ||
|
|
66b7dea706 | ||
|
|
ebf6d4806a | ||
|
|
207d0b3a6f | ||
|
|
3b3bbc29d1 | ||
|
|
0c3fca9299 | ||
|
|
2d33a60421 | ||
|
|
a8c6f4c972 | ||
|
|
0d91f9e3fe | ||
|
|
494d281d61 | ||
|
|
5ef427ec12 | ||
|
|
29cf978782 | ||
|
|
091449819c | ||
|
|
cd9c139349 | ||
|
|
084fb10cea | ||
|
|
dde9520879 | ||
|
|
7847f40a35 | ||
|
|
2c188432a1 | ||
|
|
86dc0f5436 | ||
|
|
83ba63a3fd | ||
|
|
3392100206 | ||
|
|
401265eb3a | ||
|
|
dd685d5d5e | ||
|
|
3cd17ff364 |
@@ -24,10 +24,10 @@ It is highly recommended to use Anaconda for a simple creation of a Python insta
|
||||
|
||||
Afterwards run (from the PyLoT main directory where the files *requirements.txt* and *pylot.yml* are located)
|
||||
|
||||
conda create --name pylot_38 --file requirements.txt
|
||||
conda env create -f pylot.yml
|
||||
or
|
||||
|
||||
conda env create -f pylot.yml
|
||||
conda create --name pylot_38 --file requirements.txt
|
||||
|
||||
to create a new Anaconda environment called "pylot_38".
|
||||
|
||||
@@ -99,13 +99,13 @@ We hope to solve these with the next release.
|
||||
|
||||
## Staff
|
||||
|
||||
Original author(s): L. Kueperkoch, S. Wehling-Benatelli, M. Bischoff (PILOT)
|
||||
Original author(s): M. Rische, S. Wehling-Benatelli, L. Kueperkoch, M. Bischoff (PILOT)
|
||||
|
||||
Developer(s): S. Wehling-Benatelli, L. Kueperkoch, K. Olbert, M. Bischoff, C. Wollin, M. Rische, M. Paffrath
|
||||
Developer(s): S. Wehling-Benatelli, M. Paffrath, L. Kueperkoch, K. Olbert, M. Bischoff, C. Wollin, M. Rische, D. Arnold, K. Cökerim, S. Zimmermann
|
||||
|
||||
Others: A. Bruestle, T. Meier, W. Friederich
|
||||
|
||||
|
||||
[ObsPy]: http://github.com/obspy/obspy/wiki
|
||||
|
||||
September 2017
|
||||
April 2022
|
||||
|
||||
+11
-15
@@ -28,7 +28,7 @@ from pylot.core.util.dataprocessing import restitute_data, Metadata
|
||||
from pylot.core.util.defaults import SEPARATOR
|
||||
from pylot.core.util.event import Event
|
||||
from pylot.core.util.structure import DATASTRUCTURE
|
||||
from pylot.core.util.utils import get_None, trim_station_components, check4gapsAndRemove, check4doubled, \
|
||||
from pylot.core.util.utils import get_none, trim_station_components, check4gapsAndRemove, check4doubled, \
|
||||
check4rotated
|
||||
from pylot.core.util.version import get_git_version as _getVersionString
|
||||
|
||||
@@ -91,9 +91,9 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
|
||||
sp=sp_info)
|
||||
print(splash)
|
||||
|
||||
parameter = get_None(parameter)
|
||||
inputfile = get_None(inputfile)
|
||||
eventid = get_None(eventid)
|
||||
parameter = get_none(parameter)
|
||||
inputfile = get_none(inputfile)
|
||||
eventid = get_none(eventid)
|
||||
|
||||
fig_dict = None
|
||||
fig_dict_wadatijack = None
|
||||
@@ -119,13 +119,9 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
|
||||
obspyDMT_wfpath = input_dict['obspyDMT_wfpath']
|
||||
|
||||
if not parameter:
|
||||
if inputfile:
|
||||
parameter = PylotParameter(inputfile)
|
||||
# iplot = parameter['iplot']
|
||||
else:
|
||||
infile = os.path.join(os.path.expanduser('~'), '.pylot', 'pylot.in')
|
||||
print('Using default input file {}'.format(infile))
|
||||
parameter = PylotParameter(infile)
|
||||
if not inputfile:
|
||||
print('Using default input parameter')
|
||||
parameter = PylotParameter(inputfile)
|
||||
else:
|
||||
if not type(parameter) == PylotParameter:
|
||||
print('Wrong input type for parameter: {}'.format(type(parameter)))
|
||||
@@ -154,7 +150,7 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
|
||||
datastructure.setExpandFields(exf)
|
||||
|
||||
# check if default location routine NLLoc is available and all stations are used
|
||||
if get_None(parameter['nllocbin']) and station == 'all':
|
||||
if get_none(parameter['nllocbin']) and station == 'all':
|
||||
locflag = 1
|
||||
# get NLLoc-root path
|
||||
nllocroot = parameter.get('nllocroot')
|
||||
@@ -247,7 +243,7 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
|
||||
pylot_event = Event(eventpath) # event should be path to event directory
|
||||
data.setEvtData(pylot_event)
|
||||
if fnames == 'None':
|
||||
data.setWFData(glob.glob(os.path.join(datapath, event_datapath, '*')))
|
||||
data.set_wf_data(glob.glob(os.path.join(datapath, event_datapath, '*')))
|
||||
# the following is necessary because within
|
||||
# multiple event processing no event ID is provided
|
||||
# in autopylot.in
|
||||
@@ -262,7 +258,7 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
|
||||
now.minute)
|
||||
parameter.setParam(eventID=eventID)
|
||||
else:
|
||||
data.setWFData(fnames)
|
||||
data.set_wf_data(fnames)
|
||||
|
||||
eventpath = events[0]
|
||||
# now = datetime.datetime.now()
|
||||
@@ -272,7 +268,7 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
|
||||
# now.hour,
|
||||
# now.minute)
|
||||
parameter.setParam(eventID=eventid)
|
||||
wfdat = data.getWFData() # all available streams
|
||||
wfdat = data.get_wf_data() # all available streams
|
||||
if not station == 'all':
|
||||
wfdat = wfdat.select(station=station)
|
||||
if not wfdat:
|
||||
|
||||
+5
-3
@@ -3,8 +3,10 @@
|
||||
#$ -l low
|
||||
#$ -cwd
|
||||
#$ -pe smp 40
|
||||
#$ -l mem=2G
|
||||
#$ -l h_vmem=2G
|
||||
##$ -l mem=3G
|
||||
#$ -l h_vmem=6G
|
||||
#$ -l os=*stretch
|
||||
|
||||
python ./autoPyLoT.py -i /home/marcel/.pylot/pylot_alparray_mantle_corr_stack_0.03-0.5.in -dmt processed -c $NSLOTS
|
||||
conda activate pylot_311
|
||||
|
||||
python ./autoPyLoT.py -i /home/marcel/.pylot/pylot_adriaarray.in -c 20 -dmt processed
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
+109772
-109081
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ dependencies:
|
||||
- numpy=1.22.3
|
||||
- obspy=1.3.0
|
||||
- pyqtgraph=0.12.4
|
||||
- pyside2=5.13.2
|
||||
- pyside2>=5.13.2
|
||||
- python=3.8.12
|
||||
- qt=5.12.9
|
||||
- qt>=5.12.9
|
||||
- scipy=1.8.0
|
||||
@@ -144,6 +144,10 @@ class Magnitude(object):
|
||||
azimuthal_gap=self.origin_id.get_referred_object().quality.azimuthal_gap)
|
||||
else:
|
||||
# no scaling necessary
|
||||
# Temporary fix needs rework
|
||||
if (len(self.magnitudes.keys()) == 0):
|
||||
print("Error in local magnitude calculation ")
|
||||
return None
|
||||
mag = ope.Magnitude(
|
||||
mag=np.median([M.mag for M in self.magnitudes.values()]),
|
||||
magnitude_type=self.type,
|
||||
@@ -414,6 +418,10 @@ class MomentMagnitude(Magnitude):
|
||||
distance = degrees2kilometers(a.distance)
|
||||
azimuth = a.azimuth
|
||||
incidence = a.takeoff_angle
|
||||
if not 0. <= incidence <= 360.:
|
||||
if self.verbose:
|
||||
print(f'WARNING: Incidence angle outside bounds - {incidence}')
|
||||
return
|
||||
w0, fc = calcsourcespec(scopy, onset, self.p_velocity, distance,
|
||||
azimuth, incidence, self.p_attenuation,
|
||||
self.plot_flag, self.verbose)
|
||||
@@ -432,6 +440,40 @@ class MomentMagnitude(Magnitude):
|
||||
self.event.station_magnitudes.append(magnitude)
|
||||
self.magnitudes = (station, magnitude)
|
||||
|
||||
# WIP JG
|
||||
def getSourceSpec(self):
|
||||
for a in self.arrivals:
|
||||
if a.phase not in 'pP':
|
||||
continue
|
||||
# make sure calculating Mo only from reliable onsets
|
||||
# NLLoc: time_weight = 0 => do not use onset!
|
||||
if a.time_weight == 0:
|
||||
continue
|
||||
pick = a.pick_id.get_referred_object()
|
||||
station = pick.waveform_id.station_code
|
||||
if len(self.stream) <= 2:
|
||||
print("Station:" '{0}'.format(station))
|
||||
print("WARNING: No instrument corrected data available,"
|
||||
" no magnitude calculation possible! Go on.")
|
||||
continue
|
||||
wf = self.stream.select(station=station)
|
||||
if not wf:
|
||||
continue
|
||||
try:
|
||||
scopy = wf.copy()
|
||||
except AssertionError:
|
||||
print("WARNING: Something's wrong with the data,"
|
||||
"station {},"
|
||||
"no calculation of moment magnitude possible! Go on.".format(station))
|
||||
continue
|
||||
onset = pick.time
|
||||
distance = degrees2kilometers(a.distance)
|
||||
azimuth = a.azimuth
|
||||
incidence = a.takeoff_angle
|
||||
w0, fc, plt = calcsourcespec(scopy, onset, self.p_velocity, distance,
|
||||
azimuth, incidence, self.p_attenuation,
|
||||
3, self.verbose)
|
||||
return w0, fc, plt
|
||||
|
||||
def calcMoMw(wfstream, w0, rho, vp, delta, verbosity=False):
|
||||
'''
|
||||
@@ -602,7 +644,7 @@ def calcsourcespec(wfstream, onset, vp, delta, azimuth, incidence,
|
||||
N = min(int(np.power(m, 2)), 16384)
|
||||
# N = int(np.power(m, 2))
|
||||
y = dt * np.fft.fft(xdat, N)
|
||||
Y = abs(y[: N / 2])
|
||||
Y = abs(y[: int(N / 2)])
|
||||
L = (N - 1) / freq
|
||||
f = np.arange(0, fny, 1 / L)
|
||||
|
||||
@@ -679,6 +721,8 @@ def calcsourcespec(wfstream, onset, vp, delta, azimuth, incidence,
|
||||
plt.xlabel('Frequency [Hz]')
|
||||
plt.ylabel('Amplitude [m/Hz]')
|
||||
plt.grid()
|
||||
if iplot == 3:
|
||||
return w0, Fc, plt
|
||||
plt.show()
|
||||
try:
|
||||
input()
|
||||
|
||||
+41
-612
@@ -1,624 +1,69 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import copy
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Union
|
||||
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
from obspy import read_events
|
||||
from obspy.core import read, Stream, UTCDateTime
|
||||
from obspy.core.event import Event as ObsPyEvent
|
||||
from obspy.io.sac import SacIOError
|
||||
from obspy import UTCDateTime
|
||||
|
||||
import pylot.core.loc.focmec as focmec
|
||||
import pylot.core.loc.hypodd as hypodd
|
||||
import pylot.core.loc.velest as velest
|
||||
from pylot.core.io.phases import readPILOTEvent, picks_from_picksdict, \
|
||||
picksdict_from_pilot, merge_picks, PylotParameter
|
||||
from pylot.core.util.errors import FormatError, OverwriteError
|
||||
from pylot.core.util.event import Event
|
||||
from pylot.core.util.obspyDMT_interface import qml_from_obspyDMT
|
||||
from pylot.core.util.utils import fnConstructor, full_range, check4rotated, \
|
||||
check4gapsAndMerge, trim_station_components
|
||||
from pylot.core.io.event import EventData
|
||||
from pylot.core.io.waveformdata import WaveformData
|
||||
from pylot.core.util.dataprocessing import Metadata
|
||||
|
||||
|
||||
class Data(object):
|
||||
"""
|
||||
Data container with attributes wfdata holding ~obspy.core.stream.
|
||||
|
||||
:type parent: PySide2.QtWidgets.QWidget object, optional
|
||||
:param parent: A PySide2.QtWidgets.QWidget object utilized when
|
||||
called by a GUI to display a PySide2.QtWidgets.QMessageBox instead of printing
|
||||
to standard out.
|
||||
:type evtdata: ~obspy.core.event.Event object, optional
|
||||
:param evtdata ~obspy.core.event.Event object containing all derived or
|
||||
loaded event. Container object holding, e.g. phase arrivals, etc.
|
||||
"""
|
||||
@dataclass
|
||||
class Data:
|
||||
event_data: EventData = field(default_factory=EventData)
|
||||
waveform_data: WaveformData = field(default_factory=WaveformData)
|
||||
metadata: Metadata = field(default_factory=Metadata)
|
||||
_parent: Union[None, 'QtWidgets.QWidget'] = None
|
||||
|
||||
def __init__(self, parent=None, evtdata=None):
|
||||
self._parent = parent
|
||||
if self.getParent():
|
||||
self.comp = parent.getComponent()
|
||||
else:
|
||||
self.comp = 'Z'
|
||||
self.wfdata = Stream()
|
||||
self._new = False
|
||||
if isinstance(evtdata, ObsPyEvent) or isinstance(evtdata, Event):
|
||||
pass
|
||||
elif isinstance(evtdata, dict):
|
||||
evt = readPILOTEvent(**evtdata)
|
||||
evtdata = evt
|
||||
elif isinstance(evtdata, str):
|
||||
try:
|
||||
cat = read_events(evtdata)
|
||||
if len(cat) != 1:
|
||||
raise ValueError('ambiguous event information for file: '
|
||||
'{file}'.format(file=evtdata))
|
||||
evtdata = cat[0]
|
||||
except TypeError as e:
|
||||
if 'Unknown format for file' in e.message:
|
||||
if 'PHASES' in evtdata:
|
||||
picks = picksdict_from_pilot(evtdata)
|
||||
evtdata = ObsPyEvent()
|
||||
evtdata.picks = picks_from_picksdict(picks)
|
||||
elif 'LOC' in evtdata:
|
||||
raise NotImplementedError('PILOT location information '
|
||||
'read support not yet '
|
||||
'implemeted.')
|
||||
elif 'event.pkl' in evtdata:
|
||||
evtdata = qml_from_obspyDMT(evtdata)
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
raise e
|
||||
else: # create an empty Event object
|
||||
self.setNew()
|
||||
evtdata = ObsPyEvent()
|
||||
evtdata.picks = []
|
||||
self.evtdata = evtdata
|
||||
self.wforiginal = None
|
||||
self.cuttimes = None
|
||||
self.dirty = False
|
||||
self.processed = None
|
||||
self.event_data = EventData(evtdata)
|
||||
self.waveform_data = WaveformData()
|
||||
|
||||
def __str__(self):
|
||||
return str(self.wfdata)
|
||||
return str(self.waveform_data.wfdata)
|
||||
|
||||
def __add__(self, other):
|
||||
assert isinstance(other, Data), "operands must be of same type 'Data'"
|
||||
rs_id = self.get_evt_data().get('resource_id')
|
||||
rs_id_other = other.get_evt_data().get('resource_id')
|
||||
if other.isNew() and not self.isNew():
|
||||
picks_to_add = other.get_evt_data().picks
|
||||
old_picks = self.get_evt_data().picks
|
||||
wf_ids_old = [pick.waveform_id for pick in old_picks]
|
||||
for new_pick in picks_to_add:
|
||||
wf_id = new_pick.waveform_id
|
||||
if wf_id in wf_ids_old:
|
||||
for old_pick in old_picks:
|
||||
comparison = [old_pick.waveform_id == new_pick.waveform_id,
|
||||
old_pick.phase_hint == new_pick.phase_hint,
|
||||
old_pick.method_id == new_pick.method_id]
|
||||
if all(comparison):
|
||||
del (old_pick)
|
||||
old_picks.append(new_pick)
|
||||
elif not other.isNew() and self.isNew():
|
||||
new = other + self
|
||||
self.evtdata = new.get_evt_data()
|
||||
elif self.isNew() and other.isNew():
|
||||
if not isinstance(other, Data):
|
||||
raise TypeError("Operands must be of type 'Data'")
|
||||
if self.event_data.is_new() and other.event_data.is_new():
|
||||
pass
|
||||
elif rs_id == rs_id_other:
|
||||
other.setNew()
|
||||
elif other.event_data.is_new():
|
||||
new_picks = other.event_data.evtdata.picks
|
||||
old_picks = self.event_data.evtdata.picks
|
||||
old_picks.extend([pick for pick in new_picks if pick not in old_picks])
|
||||
elif self.event_data.is_new():
|
||||
return other + self
|
||||
elif self.event_data.get_id() == other.event_data.get_id():
|
||||
other.event_data.set_new()
|
||||
return self + other
|
||||
else:
|
||||
raise ValueError("both Data objects have differing "
|
||||
"unique Event identifiers")
|
||||
raise ValueError("Both Data objects have differing unique Event identifiers")
|
||||
return self
|
||||
|
||||
def getPicksStr(self):
|
||||
"""
|
||||
Return picks in event data
|
||||
:return: picks seperated by newlines
|
||||
:rtype: str
|
||||
"""
|
||||
picks_str = ''
|
||||
for pick in self.get_evt_data().picks:
|
||||
picks_str += str(pick) + '\n'
|
||||
return picks_str
|
||||
|
||||
def getParent(self):
|
||||
"""
|
||||
Get PySide.QtGui.QWidget parent object
|
||||
"""
|
||||
def get_parent(self):
|
||||
return self._parent
|
||||
|
||||
def isNew(self):
|
||||
return self._new
|
||||
def filter_wf_data(self, **kwargs):
|
||||
self.waveform_data.wfdata.detrend('linear')
|
||||
self.waveform_data.wfdata.taper(0.02, type='cosine')
|
||||
self.waveform_data.wfdata.filter(**kwargs)
|
||||
self.waveform_data.dirty = True
|
||||
|
||||
def setNew(self):
|
||||
self._new = True
|
||||
def set_wf_data(self, fnames: List[str], fnames_alt: List[str] = None, check_rotated=False, metadata=None, tstart=0, tstop=0):
|
||||
return self.waveform_data.load_waveforms(fnames, fnames_alt, check_rotated, metadata, tstart, tstop)
|
||||
|
||||
def getCutTimes(self):
|
||||
"""
|
||||
Returns earliest start and latest end of all waveform data
|
||||
:return: minimum start time and maximum end time as a tuple
|
||||
:rtype: (UTCDateTime, UTCDateTime)
|
||||
"""
|
||||
if self.cuttimes is None:
|
||||
self.updateCutTimes()
|
||||
return self.cuttimes
|
||||
def reset_wf_data(self):
|
||||
self.waveform_data.reset()
|
||||
|
||||
def updateCutTimes(self):
|
||||
"""
|
||||
Update cuttimes to contain earliest start and latest end time
|
||||
of all waveform data
|
||||
:rtype: None
|
||||
"""
|
||||
self.cuttimes = full_range(self.getWFData())
|
||||
def get_wf_data(self):
|
||||
return self.waveform_data.wfdata
|
||||
|
||||
def getEventFileName(self):
|
||||
ID = self.getID()
|
||||
# handle forbidden filenames especially on windows systems
|
||||
return fnConstructor(str(ID))
|
||||
|
||||
def checkEvent(self, event, fcheck, forceOverwrite=False):
|
||||
"""
|
||||
Check information in supplied event and own event and replace with own
|
||||
information if no other information are given or forced by forceOverwrite
|
||||
:param event: Event that supplies information for comparison
|
||||
:type event: pylot.core.util.event.Event
|
||||
:param fcheck: check and delete existing information
|
||||
can be a str or a list of strings of ['manual', 'auto', 'origin', 'magnitude']
|
||||
:type fcheck: str, [str]
|
||||
:param forceOverwrite: Set to true to force overwrite own information. If false,
|
||||
supplied information from event is only used if there is no own information in that
|
||||
category (given in fcheck: manual, auto, origin, magnitude)
|
||||
:type forceOverwrite: bool
|
||||
:return:
|
||||
:rtype: None
|
||||
"""
|
||||
if 'origin' in fcheck:
|
||||
self.replaceOrigin(event, forceOverwrite)
|
||||
if 'magnitude' in fcheck:
|
||||
self.replaceMagnitude(event, forceOverwrite)
|
||||
if 'auto' in fcheck:
|
||||
self.replacePicks(event, 'auto')
|
||||
if 'manual' in fcheck:
|
||||
self.replacePicks(event, 'manual')
|
||||
|
||||
def replaceOrigin(self, event, forceOverwrite=False):
|
||||
"""
|
||||
Replace own origin with the one supplied in event if own origin is not
|
||||
existing or forced by forceOverwrite = True
|
||||
:param event: Event that supplies information for comparison
|
||||
:type event: pylot.core.util.event.Event
|
||||
:param forceOverwrite: always replace own information with supplied one if true
|
||||
:type forceOverwrite: bool
|
||||
:return:
|
||||
:rtype: None
|
||||
"""
|
||||
if self.get_evt_data().origins or forceOverwrite:
|
||||
if event.origins:
|
||||
print("Found origin, replace it by new origin.")
|
||||
event.origins = self.get_evt_data().origins
|
||||
|
||||
def replaceMagnitude(self, event, forceOverwrite=False):
|
||||
"""
|
||||
Replace own magnitude with the one supplied in event if own magnitude is not
|
||||
existing or forced by forceOverwrite = True
|
||||
:param event: Event that supplies information for comparison
|
||||
:type event: pylot.core.util.event.Event
|
||||
:param forceOverwrite: always replace own information with supplied one if true
|
||||
:type forceOverwrite: bool
|
||||
:return:
|
||||
:rtype: None
|
||||
"""
|
||||
if self.get_evt_data().magnitudes or forceOverwrite:
|
||||
if event.magnitudes:
|
||||
print("Found magnitude, replace it by new magnitude")
|
||||
event.magnitudes = self.get_evt_data().magnitudes
|
||||
|
||||
def replacePicks(self, event, picktype):
|
||||
"""
|
||||
Replace picks in event with own picks
|
||||
:param event: Event that supplies information for comparison
|
||||
:type event: pylot.core.util.event.Event
|
||||
:param picktype: 'auto' or 'manual' picks
|
||||
:type picktype: str
|
||||
:return:
|
||||
:rtype: None
|
||||
"""
|
||||
checkflag = 1
|
||||
picks = event.picks
|
||||
# remove existing picks
|
||||
for j, pick in reversed(list(enumerate(picks))):
|
||||
try:
|
||||
if picktype in str(pick.method_id.id):
|
||||
picks.pop(j)
|
||||
checkflag = 2
|
||||
except AttributeError as e:
|
||||
msg = '{}'.format(e)
|
||||
print(e)
|
||||
checkflag = 0
|
||||
if checkflag > 0:
|
||||
if checkflag == 1:
|
||||
print("Write new %s picks to catalog." % picktype)
|
||||
if checkflag == 2:
|
||||
print("Found %s pick(s), remove them and append new picks to catalog." % picktype)
|
||||
|
||||
# append new picks
|
||||
for pick in self.get_evt_data().picks:
|
||||
if picktype in str(pick.method_id.id):
|
||||
picks.append(pick)
|
||||
|
||||
def exportEvent(self, fnout, fnext='.xml', fcheck='auto', upperErrors=None):
|
||||
"""
|
||||
Export event to file
|
||||
:param fnout: basename of file
|
||||
:param fnext: file extensions xml, cnv, obs, focmec, or/and pha
|
||||
:param fcheck: check and delete existing information
|
||||
can be a str or a list of strings of ['manual', 'auto', 'origin', 'magnitude']
|
||||
"""
|
||||
from pylot.core.util.defaults import OUTPUTFORMATS
|
||||
|
||||
if not type(fcheck) == list:
|
||||
fcheck = [fcheck]
|
||||
|
||||
try:
|
||||
evtformat = OUTPUTFORMATS[fnext]
|
||||
except KeyError as e:
|
||||
errmsg = '{0}; selected file extension {1} not ' \
|
||||
'supported'.format(e, fnext)
|
||||
raise FormatError(errmsg)
|
||||
|
||||
if hasattr(self.get_evt_data(), 'notes'):
|
||||
try:
|
||||
with open(os.path.join(os.path.dirname(fnout), 'notes.txt'), 'w') as notes_file:
|
||||
notes_file.write(self.get_evt_data().notes)
|
||||
except Exception as e:
|
||||
print('Warning: Could not save notes.txt: ', str(e))
|
||||
|
||||
# check for already existing xml-file
|
||||
if fnext == '.xml':
|
||||
if os.path.isfile(fnout + fnext):
|
||||
print("xml-file already exists! Check content ...")
|
||||
cat = read_events(fnout + fnext)
|
||||
if len(cat) > 1:
|
||||
raise IOError('Ambigious event information in file {}'.format(fnout + fnext))
|
||||
if len(cat) < 1:
|
||||
raise IOError('No event information in file {}'.format(fnout + fnext))
|
||||
event = cat[0]
|
||||
if not event.resource_id == self.get_evt_data().resource_id:
|
||||
QMessageBox.warning(self, 'Warning', 'Different resource IDs!')
|
||||
return
|
||||
self.checkEvent(event, fcheck)
|
||||
self.setEvtData(event)
|
||||
|
||||
self.get_evt_data().write(fnout + fnext, format=evtformat)
|
||||
|
||||
# try exporting event
|
||||
else:
|
||||
evtdata_org = self.get_evt_data()
|
||||
picks = evtdata_org.picks
|
||||
eventpath = evtdata_org.path
|
||||
picks_copy = copy.deepcopy(picks)
|
||||
evtdata_copy = Event(eventpath)
|
||||
evtdata_copy.picks = picks_copy
|
||||
|
||||
# check for stations picked automatically as well as manually
|
||||
# Prefer manual picks!
|
||||
for i in range(len(picks)):
|
||||
if picks[i].method_id == 'manual':
|
||||
mstation = picks[i].waveform_id.station_code
|
||||
mstation_ext = mstation + '_'
|
||||
for k in range(len(picks_copy)):
|
||||
if ((picks_copy[k].waveform_id.station_code == mstation) or
|
||||
(picks_copy[k].waveform_id.station_code == mstation_ext)) and \
|
||||
(picks_copy[k].method_id == 'auto'):
|
||||
del picks_copy[k]
|
||||
break
|
||||
lendiff = len(picks) - len(picks_copy)
|
||||
if lendiff != 0:
|
||||
print("Manual as well as automatic picks available. Prefered the {} manual ones!".format(lendiff))
|
||||
|
||||
if upperErrors:
|
||||
# check for pick uncertainties exceeding adjusted upper errors
|
||||
# Picks with larger uncertainties will not be saved in output file!
|
||||
for j in range(len(picks)):
|
||||
for i in range(len(picks_copy)):
|
||||
if picks_copy[i].phase_hint[0] == 'P':
|
||||
if (picks_copy[i].time_errors['upper_uncertainty'] >= upperErrors[0]) or \
|
||||
(picks_copy[i].time_errors['uncertainty'] is None):
|
||||
print("Uncertainty exceeds or equal adjusted upper time error!")
|
||||
print("Adjusted uncertainty: {}".format(upperErrors[0]))
|
||||
print("Pick uncertainty: {}".format(picks_copy[i].time_errors['uncertainty']))
|
||||
print("{1} P-Pick of station {0} will not be saved in outputfile".format(
|
||||
picks_copy[i].waveform_id.station_code,
|
||||
picks_copy[i].method_id))
|
||||
print("#")
|
||||
del picks_copy[i]
|
||||
break
|
||||
if picks_copy[i].phase_hint[0] == 'S':
|
||||
if (picks_copy[i].time_errors['upper_uncertainty'] >= upperErrors[1]) or \
|
||||
(picks_copy[i].time_errors['uncertainty'] is None):
|
||||
print("Uncertainty exceeds or equal adjusted upper time error!")
|
||||
print("Adjusted uncertainty: {}".format(upperErrors[1]))
|
||||
print("Pick uncertainty: {}".format(picks_copy[i].time_errors['uncertainty']))
|
||||
print("{1} S-Pick of station {0} will not be saved in outputfile".format(
|
||||
picks_copy[i].waveform_id.station_code,
|
||||
picks_copy[i].method_id))
|
||||
print("#")
|
||||
del picks_copy[i]
|
||||
break
|
||||
|
||||
if fnext == '.obs':
|
||||
try:
|
||||
evtdata_copy.write(fnout + fnext, format=evtformat)
|
||||
# write header afterwards
|
||||
evid = str(evtdata_org.resource_id).split('/')[1]
|
||||
header = '# EQEVENT: Label: EQ%s Loc: X 0.00 Y 0.00 Z 10.00 OT 0.00 \n' % evid
|
||||
nllocfile = open(fnout + fnext)
|
||||
l = nllocfile.readlines()
|
||||
# Adding A0/Generic Amplitude to .obs file
|
||||
# l2 = []
|
||||
# for li in l:
|
||||
# for amp in evtdata_org.amplitudes:
|
||||
# if amp.waveform_id.station_code == li[0:5].strip():
|
||||
# li = li[0:64] + '{:0.2e}'.format(amp.generic_amplitude) + li[73:-1] + '\n'
|
||||
# l2.append(li)
|
||||
# l = l2
|
||||
nllocfile.close()
|
||||
l.insert(0, header)
|
||||
nllocfile = open(fnout + fnext, 'w')
|
||||
nllocfile.write("".join(l))
|
||||
nllocfile.close()
|
||||
except KeyError as e:
|
||||
raise KeyError('''{0} export format
|
||||
not implemented: {1}'''.format(evtformat, e))
|
||||
if fnext == '.cnv':
|
||||
try:
|
||||
velest.export(picks_copy, fnout + fnext, eventinfo=self.get_evt_data())
|
||||
except KeyError as e:
|
||||
raise KeyError('''{0} export format
|
||||
not implemented: {1}'''.format(evtformat, e))
|
||||
if fnext == '_focmec.in':
|
||||
try:
|
||||
infile = os.path.join(os.path.expanduser('~'), '.pylot', 'pylot.in')
|
||||
print('Using default input file {}'.format(infile))
|
||||
parameter = PylotParameter(infile)
|
||||
focmec.export(picks_copy, fnout + fnext, parameter, eventinfo=self.get_evt_data())
|
||||
except KeyError as e:
|
||||
raise KeyError('''{0} export format
|
||||
not implemented: {1}'''.format(evtformat, e))
|
||||
if fnext == '.pha':
|
||||
try:
|
||||
infile = os.path.join(os.path.expanduser('~'), '.pylot', 'pylot.in')
|
||||
print('Using default input file {}'.format(infile))
|
||||
parameter = PylotParameter(infile)
|
||||
hypodd.export(picks_copy, fnout + fnext, parameter, eventinfo=self.get_evt_data())
|
||||
except KeyError as e:
|
||||
raise KeyError('''{0} export format
|
||||
not implemented: {1}'''.format(evtformat, e))
|
||||
|
||||
def getComp(self):
|
||||
"""
|
||||
Get component (ZNE)
|
||||
"""
|
||||
return self.comp
|
||||
|
||||
def getID(self):
|
||||
"""
|
||||
Get unique resource id
|
||||
"""
|
||||
try:
|
||||
return self.evtdata.get('resource_id').id
|
||||
except:
|
||||
return None
|
||||
|
||||
def filterWFData(self, kwargs):
|
||||
"""
|
||||
Filter waveform data
|
||||
:param kwargs: arguments to pass through to filter function
|
||||
"""
|
||||
data = self.getWFData()
|
||||
data.detrend('linear')
|
||||
data.taper(0.02, type='cosine')
|
||||
data.filter(**kwargs)
|
||||
self.dirty = True
|
||||
|
||||
def setWFData(self, fnames, fnames_syn=None, checkRotated=False, metadata=None, tstart=0, tstop=0):
|
||||
"""
|
||||
Clear current waveform data and set given waveform data
|
||||
:param fnames: waveform data names to append
|
||||
:type fnames: list
|
||||
"""
|
||||
self.wfdata = Stream()
|
||||
self.wforiginal = None
|
||||
self.wfsyn = Stream()
|
||||
if tstart == tstop:
|
||||
tstart = tstop = None
|
||||
self.tstart = tstart
|
||||
self.tstop = tstop
|
||||
|
||||
# if obspy_dmt:
|
||||
# wfdir = 'raw'
|
||||
# self.processed = False
|
||||
# for fname in fnames:
|
||||
# if fname.endswith('processed'):
|
||||
# wfdir = 'processed'
|
||||
# self.processed = True
|
||||
# break
|
||||
# for fpath in fnames:
|
||||
# if fpath.endswith(wfdir):
|
||||
# wffnames = [os.path.join(fpath, fname) for fname in os.listdir(fpath)]
|
||||
# if 'syngine' in fpath.split('/')[-1]:
|
||||
# wffnames_syn = [os.path.join(fpath, fname) for fname in os.listdir(fpath)]
|
||||
# else:
|
||||
# wffnames = fnames
|
||||
if fnames is not None:
|
||||
self.appendWFData(fnames)
|
||||
if fnames_syn is not None:
|
||||
self.appendWFData(fnames_syn, synthetic=True)
|
||||
else:
|
||||
return False
|
||||
|
||||
# various pre-processing steps:
|
||||
# remove possible underscores in station names
|
||||
# self.wfdata = remove_underscores(self.wfdata)
|
||||
# check for gaps and merge
|
||||
self.wfdata = check4gapsAndMerge(self.wfdata)
|
||||
# check for stations with rotated components
|
||||
if checkRotated and metadata is not None:
|
||||
self.wfdata = check4rotated(self.wfdata, metadata, verbosity=0)
|
||||
# trim station components to same start value
|
||||
trim_station_components(self.wfdata, trim_start=True, trim_end=False)
|
||||
|
||||
# make a copy of original data
|
||||
self.wforiginal = self.getWFData().copy()
|
||||
self.dirty = False
|
||||
return True
|
||||
|
||||
def appendWFData(self, fnames, synthetic=False):
|
||||
"""
|
||||
Read waveform data from fnames and append it to current wf data
|
||||
:param fnames: waveform data to append
|
||||
:type fnames: list
|
||||
"""
|
||||
assert isinstance(fnames, list), "input parameter 'fnames' is " \
|
||||
"supposed to be of type 'list' " \
|
||||
"but is actually" \
|
||||
" {0}".format(type(fnames))
|
||||
if self.dirty:
|
||||
self.resetWFData()
|
||||
|
||||
real_or_syn_data = {True: self.wfsyn,
|
||||
False: self.wfdata}
|
||||
|
||||
warnmsg = ''
|
||||
for fname in set(fnames):
|
||||
try:
|
||||
real_or_syn_data[synthetic] += read(fname, starttime=self.tstart, endtime=self.tstop)
|
||||
except TypeError:
|
||||
try:
|
||||
real_or_syn_data[synthetic] += read(fname, format='GSE2', starttime=self.tstart, endtime=self.tstop)
|
||||
except Exception as e:
|
||||
try:
|
||||
real_or_syn_data[synthetic] += read(fname, format='SEGY', starttime=self.tstart,
|
||||
endtime=self.tstop)
|
||||
except Exception as e:
|
||||
warnmsg += '{0}\n{1}\n'.format(fname, e)
|
||||
except SacIOError as se:
|
||||
warnmsg += '{0}\n{1}\n'.format(fname, se)
|
||||
if warnmsg:
|
||||
warnmsg = 'WARNING in appendWFData: unable to read waveform data\n' + warnmsg
|
||||
print(warnmsg)
|
||||
|
||||
def getWFData(self):
|
||||
return self.wfdata
|
||||
|
||||
def getOriginalWFData(self):
|
||||
return self.wforiginal
|
||||
|
||||
def getSynWFData(self):
|
||||
return self.wfsyn
|
||||
|
||||
def resetWFData(self):
|
||||
"""
|
||||
Set waveform data to original waveform data
|
||||
"""
|
||||
if self.getOriginalWFData():
|
||||
self.wfdata = self.getOriginalWFData().copy()
|
||||
else:
|
||||
self.wfdata = Stream()
|
||||
self.dirty = False
|
||||
|
||||
def resetPicks(self):
|
||||
"""
|
||||
Clear all picks from event
|
||||
"""
|
||||
self.get_evt_data().picks = []
|
||||
|
||||
def get_evt_data(self):
|
||||
return self.evtdata
|
||||
|
||||
def setEvtData(self, event):
|
||||
self.evtdata = event
|
||||
|
||||
def applyEVTData(self, data, typ='pick'):
|
||||
"""
|
||||
Either takes an `obspy.core.event.Event` object and applies all new
|
||||
information on the event to the actual data if typ is 'event or
|
||||
creates ObsPy pick objects and append it to the picks list from the
|
||||
PyLoT dictionary contain all picks if type is pick
|
||||
:param data: data to apply, either picks or complete event
|
||||
:type data:
|
||||
:param typ: which event data to apply, 'pick' or 'event'
|
||||
:type typ: str
|
||||
:param authority_id: (currently unused)
|
||||
:type: str
|
||||
:raise OverwriteError:
|
||||
"""
|
||||
|
||||
def applyPicks(picks):
|
||||
"""
|
||||
Creates ObsPy pick objects and append it to the picks list from the
|
||||
PyLoT dictionary contain all picks.
|
||||
:param picks:
|
||||
:raise OverwriteError: raises an OverwriteError if the picks list is
|
||||
not empty. The GUI will then ask for a decision.
|
||||
"""
|
||||
# firstonset = find_firstonset(picks)
|
||||
# check for automatic picks
|
||||
print("Writing phases to ObsPy-quakeml file")
|
||||
for key in picks:
|
||||
if not picks[key].get('P'):
|
||||
continue
|
||||
if picks[key]['P']['picker'] == 'auto':
|
||||
print("Existing auto-picks will be overwritten in pick-dictionary!")
|
||||
picks = picks_from_picksdict(picks)
|
||||
break
|
||||
else:
|
||||
if self.get_evt_data().picks:
|
||||
raise OverwriteError('Existing picks would be overwritten!')
|
||||
else:
|
||||
picks = picks_from_picksdict(picks)
|
||||
break
|
||||
self.get_evt_data().picks = picks
|
||||
# if 'smi:local' in self.getID() and firstonset:
|
||||
# fonset_str = firstonset.strftime('%Y_%m_%d_%H_%M_%S')
|
||||
# ID = ResourceIdentifier('event/' + fonset_str)
|
||||
# ID.convertIDToQuakeMLURI(authority_id=authority_id)
|
||||
# self.get_evt_data().resource_id = ID
|
||||
|
||||
def applyEvent(event):
|
||||
"""
|
||||
takes an `obspy.core.event.Event` object and applies all new
|
||||
information on the event to the actual data
|
||||
:param event:
|
||||
"""
|
||||
if event is None:
|
||||
print("applyEvent: Received None")
|
||||
return
|
||||
if self.isNew():
|
||||
self.setEvtData(event)
|
||||
else:
|
||||
# prevent overwriting original pick information
|
||||
event_old = self.get_evt_data()
|
||||
if not event_old.resource_id == event.resource_id:
|
||||
print("WARNING: Missmatch in event resource id's: {} and {}".format(
|
||||
event_old.resource_id,
|
||||
event.resource_id))
|
||||
else:
|
||||
picks = copy.deepcopy(event_old.picks)
|
||||
event = merge_picks(event, picks)
|
||||
# apply event information from location
|
||||
event_old.update(event)
|
||||
|
||||
applydata = {'pick': applyPicks,
|
||||
'event': applyEvent}
|
||||
|
||||
applydata[typ](data)
|
||||
self._new = False
|
||||
def rotate_wf_data(self):
|
||||
self.waveform_data.rotate_zne(self.metadata)
|
||||
|
||||
|
||||
class GenericDataStructure(object):
|
||||
@@ -803,22 +248,6 @@ class PilotDataStructure(GenericDataStructure):
|
||||
self.setExpandFields(['root', 'database'])
|
||||
|
||||
|
||||
class ObspyDMTdataStructure(GenericDataStructure):
|
||||
"""
|
||||
Object containing the data access information for the old PILOT data
|
||||
structure.
|
||||
"""
|
||||
|
||||
def __init__(self, **fields):
|
||||
if not fields:
|
||||
fields = {'database': '',
|
||||
'root': ''}
|
||||
|
||||
GenericDataStructure.__init__(self, **fields)
|
||||
|
||||
self.setExpandFields(['root', 'database'])
|
||||
|
||||
|
||||
class SeiscompDataStructure(GenericDataStructure):
|
||||
"""
|
||||
Dictionary containing the data access information for an SDS data archive:
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import copy
|
||||
from dataclasses import dataclass
|
||||
from typing import Union
|
||||
|
||||
from obspy import read_events
|
||||
from obspy.core.event import Event as ObsPyEvent
|
||||
|
||||
|
||||
@dataclass
|
||||
class EventData:
|
||||
evtdata: Union[ObsPyEvent, None] = None
|
||||
_new: bool = False
|
||||
|
||||
def __init__(self, evtdata=None):
|
||||
self.set_event_data(evtdata)
|
||||
|
||||
def set_event_data(self, evtdata):
|
||||
if isinstance(evtdata, ObsPyEvent):
|
||||
self.evtdata = evtdata
|
||||
elif isinstance(evtdata, dict):
|
||||
self.evtdata = self.read_pilot_event(**evtdata)
|
||||
elif isinstance(evtdata, str):
|
||||
self.evtdata = self.read_event_file(evtdata)
|
||||
else:
|
||||
self.set_new()
|
||||
self.evtdata = ObsPyEvent(picks=[])
|
||||
|
||||
def read_event_file(self, evtdata: str) -> ObsPyEvent:
|
||||
try:
|
||||
cat = read_events(evtdata)
|
||||
if len(cat) != 1:
|
||||
raise ValueError(f'ambiguous event information for file: {evtdata}')
|
||||
return cat[0]
|
||||
except TypeError as e:
|
||||
self.handle_event_file_error(e, evtdata)
|
||||
|
||||
def handle_event_file_error(self, e: TypeError, evtdata: str):
|
||||
if 'Unknown format for file' in str(e):
|
||||
if 'PHASES' in evtdata:
|
||||
picks = self.picksdict_from_pilot(evtdata)
|
||||
evtdata = ObsPyEvent(picks=self.picks_from_picksdict(picks))
|
||||
elif 'LOC' in evtdata:
|
||||
raise NotImplementedError('PILOT location information read support not yet implemented.')
|
||||
elif 'event.pkl' in evtdata:
|
||||
evtdata = self.qml_from_obspy_dmt(evtdata)
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
raise e
|
||||
|
||||
def set_new(self):
|
||||
self._new = True
|
||||
|
||||
def is_new(self) -> bool:
|
||||
return self._new
|
||||
|
||||
def get_picks_str(self) -> str:
|
||||
return '\n'.join(str(pick) for pick in self.evtdata.picks)
|
||||
|
||||
def replace_origin(self, event: ObsPyEvent, force_overwrite: bool = False):
|
||||
if self.evtdata.origins or force_overwrite:
|
||||
event.origins = self.evtdata.origins
|
||||
|
||||
def replace_magnitude(self, event: ObsPyEvent, force_overwrite: bool = False):
|
||||
if self.evtdata.magnitudes or force_overwrite:
|
||||
event.magnitudes = self.evtdata.magnitudes
|
||||
|
||||
def replace_picks(self, event: ObsPyEvent, picktype: str):
|
||||
checkflag = 1
|
||||
picks = event.picks
|
||||
for j, pick in reversed(list(enumerate(picks))):
|
||||
if picktype in str(pick.method_id.id):
|
||||
picks.pop(j)
|
||||
checkflag = 2
|
||||
|
||||
if checkflag > 0:
|
||||
for pick in self.evtdata.picks:
|
||||
if picktype in str(pick.method_id.id):
|
||||
picks.append(pick)
|
||||
|
||||
def get_id(self) -> Union[str, None]:
|
||||
try:
|
||||
return self.evtdata.resource_id.id
|
||||
except:
|
||||
return None
|
||||
|
||||
def apply_event_data(self, data, typ='pick'):
|
||||
if typ == 'pick':
|
||||
self.apply_picks(data)
|
||||
elif typ == 'event':
|
||||
self.apply_event(data)
|
||||
|
||||
def apply_picks(self, picks):
|
||||
self.evtdata.picks = picks
|
||||
|
||||
def apply_event(self, event: ObsPyEvent):
|
||||
if self.is_new():
|
||||
self.evtdata = event
|
||||
else:
|
||||
old_event = self.evtdata
|
||||
if old_event.resource_id == event.resource_id:
|
||||
picks = copy.deepcopy(old_event.picks)
|
||||
event = self.merge_picks(event, picks)
|
||||
old_event.update(event)
|
||||
else:
|
||||
print(f"WARNING: Mismatch in event resource id's: {old_event.resource_id} and {event.resource_id}")
|
||||
@@ -8,6 +8,11 @@
|
||||
Edited for use in PyLoT
|
||||
JG, igem, 01/2022
|
||||
"""
|
||||
|
||||
import os
|
||||
import argparse
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import glob
|
||||
|
||||
from obspy.core.event import read_events
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Script to get onset uncertainties from Quakeml.xml files created by PyLoT.
|
||||
Uncertainties are tranformed into quality classes and visualized via histogram if desired.
|
||||
Ludger Küperkoch, BESTEC GmbH, 07/2017
|
||||
rev.: Ludger Küperkoch, igem, 10/2020
|
||||
Edited for usage in PyLoT: Jeldrik Gaal, igem, 01/2022
|
||||
"""
|
||||
|
||||
import glob
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from obspy.core.event import read_events
|
||||
|
||||
|
||||
def getQualitiesfromxml(path):
|
||||
# uncertainties
|
||||
ErrorsP = [0.02, 0.04, 0.08, 0.16]
|
||||
ErrorsS = [0.04, 0.08, 0.16, 0.32]
|
||||
|
||||
Pw0 = []
|
||||
Pw1 = []
|
||||
Pw2 = []
|
||||
Pw3 = []
|
||||
Pw4 = []
|
||||
Sw0 = []
|
||||
Sw1 = []
|
||||
Sw2 = []
|
||||
Sw3 = []
|
||||
Sw4 = []
|
||||
|
||||
# data path
|
||||
dp = path + '/e*/*.xml'
|
||||
# list of all available xml-files
|
||||
xmlnames = glob.glob(dp)
|
||||
|
||||
# read all onset weights
|
||||
for names in xmlnames:
|
||||
print("Getting onset weights from {}".format(names))
|
||||
cat = read_events(names)
|
||||
arrivals = cat.events[0].picks
|
||||
for Pick in arrivals:
|
||||
if Pick.phase_hint[0] == 'P':
|
||||
if Pick.time_errors.uncertainty <= ErrorsP[0]:
|
||||
Pw0.append(Pick.time_errors.uncertainty)
|
||||
elif Pick.time_errors.uncertainty > ErrorsP[0] and \
|
||||
Pick.time_errors.uncertainty <= ErrorsP[1]:
|
||||
Pw1.append(Pick.time_errors.uncertainty)
|
||||
elif Pick.time_errors.uncertainty > ErrorsP[1] and \
|
||||
Pick.time_errors.uncertainty <= ErrorsP[2]:
|
||||
Pw2.append(Pick.time_errors.uncertainty)
|
||||
elif Pick.time_errors.uncertainty > ErrorsP[2] and \
|
||||
Pick.time_errors.uncertainty <= ErrorsP[3]:
|
||||
Pw3.append(Pick.time_errors.uncertainty)
|
||||
elif Pick.time_errors.uncertainty > ErrorsP[3]:
|
||||
Pw4.append(Pick.time_errors.uncertainty)
|
||||
else:
|
||||
pass
|
||||
elif Pick.phase_hint[0] == 'S':
|
||||
if Pick.time_errors.uncertainty <= ErrorsS[0]:
|
||||
Sw0.append(Pick.time_errors.uncertainty)
|
||||
elif Pick.time_errors.uncertainty > ErrorsS[0] and \
|
||||
Pick.time_errors.uncertainty <= ErrorsS[1]:
|
||||
Sw1.append(Pick.time_errors.uncertainty)
|
||||
elif Pick.time_errors.uncertainty > ErrorsS[1] and \
|
||||
Pick.time_errors.uncertainty <= ErrorsS[2]:
|
||||
Sw2.append(Pick.time_errors.uncertainty)
|
||||
elif Pick.time_errors.uncertainty > ErrorsS[2] and \
|
||||
Pick.time_errors.uncertainty <= ErrorsS[3]:
|
||||
Sw3.append(Pick.time_errors.uncertainty)
|
||||
elif Pick.time_errors.uncertainty > ErrorsS[3]:
|
||||
Sw4.append(Pick.time_errors.uncertainty)
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
print("Phase hint not defined for picking!")
|
||||
pass
|
||||
# get percentage of weights
|
||||
numPweights = np.sum([len(Pw0), len(Pw1), len(Pw2), len(Pw3), len(Pw4)])
|
||||
numSweights = np.sum([len(Sw0), len(Sw1), len(Sw2), len(Sw3), len(Sw4)])
|
||||
try:
|
||||
P0perc = 100.0 / numPweights * len(Pw0)
|
||||
except:
|
||||
P0perc = 0
|
||||
try:
|
||||
P1perc = 100.0 / numPweights * len(Pw1)
|
||||
except:
|
||||
P1perc = 0
|
||||
try:
|
||||
P2perc = 100.0 / numPweights * len(Pw2)
|
||||
except:
|
||||
P2perc = 0
|
||||
try:
|
||||
P3perc = 100.0 / numPweights * len(Pw3)
|
||||
except:
|
||||
P3perc = 0
|
||||
try:
|
||||
P4perc = 100.0 / numPweights * len(Pw4)
|
||||
except:
|
||||
P4perc = 0
|
||||
try:
|
||||
S0perc = 100.0 / numSweights * len(Sw0)
|
||||
except:
|
||||
Soperc = 0
|
||||
try:
|
||||
S1perc = 100.0 / numSweights * len(Sw1)
|
||||
except:
|
||||
S1perc = 0
|
||||
try:
|
||||
S2perc = 100.0 / numSweights * len(Sw2)
|
||||
except:
|
||||
S2perc = 0
|
||||
try:
|
||||
S3perc = 100.0 / numSweights * len(Sw3)
|
||||
except:
|
||||
S3perc = 0
|
||||
try:
|
||||
S4perc = 100.0 / numSweights * len(Sw4)
|
||||
except:
|
||||
S4perc = 0
|
||||
|
||||
weights = ('0', '1', '2', '3', '4')
|
||||
y_pos = np.arange(len(weights))
|
||||
width = 0.34
|
||||
p1, = plt.bar(0 - width, P0perc, width, color='black')
|
||||
p2, = plt.bar(0, S0perc, width, color='red')
|
||||
plt.bar(y_pos - width, [P0perc, P1perc, P2perc, P3perc, P4perc], width, color='black')
|
||||
plt.bar(y_pos, [S0perc, S1perc, S2perc, S3perc, S4perc], width, color='red')
|
||||
plt.ylabel('%')
|
||||
plt.xticks(y_pos, weights)
|
||||
plt.xlim([-0.5, 4.5])
|
||||
plt.xlabel('Qualities')
|
||||
plt.title('{0} P-Qualities, {1} S-Qualities'.format(numPweights, numSweights))
|
||||
plt.legend([p1, p2], ['P-Weights', 'S-Weights'])
|
||||
plt.show()
|
||||
@@ -1,7 +1,7 @@
|
||||
from obspy import UTCDateTime
|
||||
from obspy.core import event as ope
|
||||
|
||||
from pylot.core.util.utils import getLogin, getHash
|
||||
from pylot.core.util.utils import get_login, get_hash
|
||||
|
||||
|
||||
def create_amplitude(pickID, amp, unit, category, cinfo):
|
||||
@@ -61,7 +61,7 @@ def create_creation_info(agency_id=None, creation_time=None, author=None):
|
||||
:return:
|
||||
'''
|
||||
if author is None:
|
||||
author = getLogin()
|
||||
author = get_login()
|
||||
if creation_time is None:
|
||||
creation_time = UTCDateTime()
|
||||
return ope.CreationInfo(agency_id=agency_id, author=author,
|
||||
@@ -210,7 +210,7 @@ def create_resourceID(timetohash, restype, authority_id=None, hrstr=None):
|
||||
'''
|
||||
assert isinstance(timetohash, UTCDateTime), "'timetohash' is not an ObsPy" \
|
||||
"UTCDateTime object"
|
||||
hid = getHash(timetohash)
|
||||
hid = get_hash(timetohash)
|
||||
if hrstr is None:
|
||||
resID = ope.ResourceIdentifier(restype + '/' + hid[0:6])
|
||||
else:
|
||||
|
||||
+312
-800
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,177 @@
|
||||
import os
|
||||
|
||||
from pylot.core.util.event import Event
|
||||
|
||||
|
||||
class Project(object):
|
||||
'''
|
||||
Pickable class containing information of a PyLoT project, like event lists and file locations.
|
||||
'''
|
||||
|
||||
# TODO: remove rootpath
|
||||
def __init__(self):
|
||||
self.eventlist = []
|
||||
self.location = None
|
||||
self.rootpath = None
|
||||
self.datapath = None
|
||||
self.dirty = False
|
||||
self.parameter = None
|
||||
self._table = None
|
||||
|
||||
def add_eventlist(self, eventlist):
|
||||
'''
|
||||
Add events from an eventlist containing paths to event directories.
|
||||
Will skip existing paths.
|
||||
'''
|
||||
if len(eventlist) == 0:
|
||||
return
|
||||
for item in eventlist:
|
||||
event = Event(item)
|
||||
event.rootpath = self.parameter['rootpath']
|
||||
event.database = self.parameter['database']
|
||||
event.datapath = self.parameter['datapath']
|
||||
if not event.path in self.getPaths():
|
||||
self.eventlist.append(event)
|
||||
self.setDirty()
|
||||
else:
|
||||
print('Skipping event with path {}. Already part of project.'.format(event.path))
|
||||
self.eventlist.sort(key=lambda x: x.pylot_id)
|
||||
self.search_eventfile_info()
|
||||
|
||||
def remove_event(self, event):
|
||||
self.eventlist.remove(event)
|
||||
|
||||
def remove_event_by_id(self, eventID):
|
||||
for event in self.eventlist:
|
||||
if eventID in str(event.resource_id):
|
||||
self.remove_event(event)
|
||||
break
|
||||
|
||||
def read_eventfile_info(self, filename, separator=','):
|
||||
'''
|
||||
Try to read event information from file (:param:filename) comparing specific event datetimes.
|
||||
File structure (each row): event, date, time, magnitude, latitude, longitude, depth
|
||||
separated by :param:separator each.
|
||||
'''
|
||||
with open(filename, 'r') as infile:
|
||||
for line in infile.readlines():
|
||||
eventID, date, time, mag, lat, lon, depth = line.split(separator)[:7]
|
||||
# skip first line
|
||||
try:
|
||||
day, month, year = date.split('/')
|
||||
except:
|
||||
continue
|
||||
year = int(year)
|
||||
# hardcoded, if year only consists of 2 digits (e.g. 16 instead of 2016)
|
||||
if year < 100:
|
||||
year += 2000
|
||||
datetime = '{}-{}-{}T{}'.format(year, month, day, time)
|
||||
try:
|
||||
datetime = UTCDateTime(datetime)
|
||||
except Exception as e:
|
||||
print(e, datetime, filename)
|
||||
continue
|
||||
for event in self.eventlist:
|
||||
if eventID in str(event.resource_id) or eventID in event.origins:
|
||||
if event.origins:
|
||||
origin = event.origins[0] # should have only one origin
|
||||
if origin.time == datetime:
|
||||
origin.latitude = float(lat)
|
||||
origin.longitude = float(lon)
|
||||
origin.depth = float(depth)
|
||||
else:
|
||||
continue
|
||||
elif not event.origins:
|
||||
origin = Origin(resource_id=event.resource_id,
|
||||
time=datetime, latitude=float(lat),
|
||||
longitude=float(lon), depth=float(depth))
|
||||
event.origins.append(origin)
|
||||
event.magnitudes.append(Magnitude(resource_id=event.resource_id,
|
||||
mag=float(mag),
|
||||
mag_type='M'))
|
||||
break
|
||||
|
||||
def search_eventfile_info(self):
|
||||
'''
|
||||
Search all datapaths in rootpath for filenames with given file extension fext
|
||||
and try to read event info from it
|
||||
'''
|
||||
datapaths = []
|
||||
fext = '.csv'
|
||||
for event in self.eventlist:
|
||||
if not event.datapath in datapaths:
|
||||
datapaths.append(event.datapath)
|
||||
for datapath in datapaths:
|
||||
# datapath = os.path.join(self.rootpath, datapath)
|
||||
if os.path.isdir(datapath):
|
||||
for filename in os.listdir(datapath):
|
||||
filename = os.path.join(datapath, filename)
|
||||
if os.path.isfile(filename) and filename.endswith(fext):
|
||||
try:
|
||||
self.read_eventfile_info(filename)
|
||||
except Exception as e:
|
||||
print('Failed on reading eventfile info from file {}: {}'.format(filename, e))
|
||||
else:
|
||||
print("Directory %s does not exist!" % datapath)
|
||||
|
||||
def getPaths(self):
|
||||
'''
|
||||
Returns paths (eventlist) of all events saved in the project.
|
||||
'''
|
||||
paths = []
|
||||
for event in self.eventlist:
|
||||
paths.append(event.path)
|
||||
return paths
|
||||
|
||||
def setDirty(self, value=True):
|
||||
self.dirty = value
|
||||
|
||||
def getEventFromPath(self, path):
|
||||
'''
|
||||
Search for an event in the project by event path.
|
||||
'''
|
||||
for event in self.eventlist:
|
||||
if event.path == path:
|
||||
return event
|
||||
|
||||
def save(self, filename=None):
|
||||
'''
|
||||
Save PyLoT Project to a file.
|
||||
Can be loaded by using project.load(filename).
|
||||
'''
|
||||
try:
|
||||
import pickle
|
||||
except ImportError:
|
||||
import _pickle as pickle
|
||||
|
||||
if filename:
|
||||
self.location = filename
|
||||
else:
|
||||
filename = self.location
|
||||
|
||||
table = self._table # MP: see below
|
||||
try:
|
||||
outfile = open(filename, 'wb')
|
||||
self._table = [] # MP: Workaround as long as table cannot be saved as part of project
|
||||
pickle.dump(self, outfile, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
self.setDirty(False)
|
||||
self._table = table # MP: see above
|
||||
return True
|
||||
except Exception as e:
|
||||
print('Could not pickle PyLoT project. Reason: {}'.format(e))
|
||||
self.setDirty()
|
||||
self._table = table # MP: see above
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def load(filename):
|
||||
'''
|
||||
Load project from filename.
|
||||
'''
|
||||
import pickle
|
||||
infile = open(filename, 'rb')
|
||||
project = pickle.load(infile)
|
||||
infile.close()
|
||||
project.location = filename
|
||||
print('Loaded %s' % filename)
|
||||
return project
|
||||
@@ -0,0 +1,13 @@
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
|
||||
def validate_filenames(filenames: List[str]) -> List[str]:
|
||||
"""
|
||||
validate a list of filenames for file abundance
|
||||
:param filenames: list of possible filenames
|
||||
:type filenames: List[str]
|
||||
:return: list of valid filenames
|
||||
:rtype: List[str]
|
||||
"""
|
||||
return [fn for fn in filenames if os.path.isfile(fn)]
|
||||
@@ -0,0 +1,123 @@
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Union, List
|
||||
|
||||
from obspy import Stream, read
|
||||
from obspy.io.sac import SacIOError
|
||||
|
||||
from pylot.core.io.utils import validate_filenames
|
||||
from pylot.core.util.dataprocessing import Metadata
|
||||
from pylot.core.util.utils import get_stations, check_for_nan, check4rotated
|
||||
|
||||
|
||||
@dataclass
|
||||
class WaveformData:
|
||||
wfdata: Stream = field(default_factory=Stream)
|
||||
wforiginal: Union[Stream, None] = None
|
||||
wf_alt: Stream = field(default_factory=Stream)
|
||||
dirty: bool = False
|
||||
|
||||
def load_waveforms(self, fnames: List[str], fnames_alt: List[str] = None, check_rotated=False, metadata=None, tstart=0, tstop=0):
|
||||
fn_list = validate_filenames(fnames)
|
||||
if not fn_list:
|
||||
logging.warning('No valid filenames given for loading waveforms')
|
||||
else:
|
||||
self.clear()
|
||||
self.add_waveforms(fn_list)
|
||||
|
||||
if fnames_alt is None:
|
||||
pass
|
||||
else:
|
||||
alt_fn_list = validate_filenames(fnames_alt)
|
||||
if not alt_fn_list:
|
||||
logging.warning('No valid alternative filenames given for loading waveforms')
|
||||
else:
|
||||
self.add_waveforms(alt_fn_list, alternative=True)
|
||||
|
||||
if not fn_list and not alt_fn_list:
|
||||
logging.error('No filenames or alternative filenames given for loading waveforms')
|
||||
return False
|
||||
|
||||
self.merge()
|
||||
self.replace_nan()
|
||||
if not check_rotated or not metadata:
|
||||
pass
|
||||
else:
|
||||
self.rotate_zne()
|
||||
self.trim_station_traces()
|
||||
self.wforiginal = self.wfdata.copy()
|
||||
self.dirty = False
|
||||
return True
|
||||
|
||||
def add_waveforms(self, fnames: List[str], alternative: bool = False):
|
||||
data_stream = self.wf_alt if alternative else self.wfdata
|
||||
warnmsg = ''
|
||||
for fname in set(fnames):
|
||||
try:
|
||||
data_stream += read(fname)
|
||||
except TypeError:
|
||||
try:
|
||||
data_stream += read(fname, format='GSE2')
|
||||
except Exception as e:
|
||||
try:
|
||||
data_stream += read(fname, format='SEGY')
|
||||
except Exception as e:
|
||||
warnmsg += f'{fname}\n{e}\n'
|
||||
except SacIOError as se:
|
||||
warnmsg += f'{fname}\n{se}\n'
|
||||
|
||||
if warnmsg:
|
||||
print(f'WARNING in add_waveforms: unable to read waveform data\n{warnmsg}')
|
||||
|
||||
def clear(self):
|
||||
self.wfdata = Stream()
|
||||
self.wforiginal = None
|
||||
self.wf_alt = Stream()
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Resets the waveform data to its original state.
|
||||
"""
|
||||
if self.wforiginal:
|
||||
self.wfdata = self.wforiginal.copy()
|
||||
else:
|
||||
self.wfdata = Stream()
|
||||
self.dirty = False
|
||||
|
||||
def merge(self):
|
||||
"""
|
||||
check for gaps in Stream and merge if gaps are found
|
||||
"""
|
||||
gaps = self.wfdata.get_gaps()
|
||||
if gaps:
|
||||
merged = ['{}.{}.{}.{}'.format(*gap[:4]) for gap in gaps]
|
||||
self.wfdata.merge(method=1)
|
||||
logging.info('Merged the following stations because of gaps:')
|
||||
for station in merged:
|
||||
logging.info(station)
|
||||
|
||||
def replace_nan(self):
|
||||
"""
|
||||
Replace all NaNs in data with 0. (in place)
|
||||
"""
|
||||
self.wfdata = check_for_nan(self.wfdata)
|
||||
|
||||
def rotate_zne(self, metadata: Metadata = None):
|
||||
"""
|
||||
Check all traces in stream for rotation. If a trace is not in ZNE rotation (last symbol of channel code is numeric) and the trace
|
||||
is in the metadata with azimuth and dip, rotate it to classical ZNE orientation.
|
||||
Rotating the traces requires them to be of the same length, so, all traces will be trimmed to a common length as a
|
||||
side effect.
|
||||
"""
|
||||
|
||||
self.wfdata = check4rotated(self.wfdata, metadata)
|
||||
|
||||
def trim_station_traces(self):
|
||||
"""
|
||||
trim data stream to common time window
|
||||
"""
|
||||
|
||||
for station in get_stations(self.wfdata):
|
||||
station_traces = self.wfdata.select(station=station)
|
||||
station_traces.trim(starttime=max([trace.stats.starttime for trace in station_traces]),
|
||||
endtime=min([trace.stats.endtime for trace in station_traces]))
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pylot.core.io.phases import writephases
|
||||
from pylot.core.io.phases import write_phases
|
||||
from pylot.core.util.version import get_git_version as _getVersionString
|
||||
|
||||
__version__ = _getVersionString()
|
||||
@@ -25,4 +25,4 @@ def export(picks, fnout, parameter, eventinfo):
|
||||
:type eventinfo: list object
|
||||
'''
|
||||
# write phases to FOCMEC-phase file
|
||||
writephases(picks, 'FOCMEC', fnout, parameter, eventinfo)
|
||||
write_phases(picks, 'FOCMEC', fnout, parameter, eventinfo)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pylot.core.io.phases import writephases
|
||||
from pylot.core.io.phases import write_phases
|
||||
from pylot.core.util.version import get_git_version as _getVersionString
|
||||
|
||||
__version__ = _getVersionString()
|
||||
@@ -25,4 +25,4 @@ def export(picks, fnout, parameter, eventinfo):
|
||||
:type eventinfo: list object
|
||||
'''
|
||||
# write phases to HASH-phase file
|
||||
writephases(picks, 'HASH', fnout, parameter, eventinfo)
|
||||
write_phases(picks, 'HASH', fnout, parameter, eventinfo)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pylot.core.io.phases import writephases
|
||||
from pylot.core.io.phases import write_phases
|
||||
from pylot.core.util.version import get_git_version as _getVersionString
|
||||
|
||||
__version__ = _getVersionString()
|
||||
@@ -22,4 +22,4 @@ def export(picks, fnout, parameter):
|
||||
:type parameter: object
|
||||
'''
|
||||
# write phases to HYPO71-phase file
|
||||
writephases(picks, 'HYPO71', fnout, parameter)
|
||||
write_phases(picks, 'HYPO71', fnout, parameter)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pylot.core.io.phases import writephases
|
||||
from pylot.core.io.phases import write_phases
|
||||
from pylot.core.util.version import get_git_version as _getVersionString
|
||||
|
||||
__version__ = _getVersionString()
|
||||
@@ -25,4 +25,4 @@ def export(picks, fnout, parameter, eventinfo):
|
||||
:type eventinfo: list object
|
||||
'''
|
||||
# write phases to hypoDD-phase file
|
||||
writephases(picks, 'HYPODD', fnout, parameter, eventinfo)
|
||||
write_phases(picks, 'HYPODD', fnout, parameter, eventinfo)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pylot.core.io.phases import writephases
|
||||
from pylot.core.io.phases import write_phases
|
||||
from pylot.core.util.version import get_git_version as _getVersionString
|
||||
|
||||
__version__ = _getVersionString()
|
||||
@@ -22,4 +22,4 @@ def export(picks, fnout, parameter):
|
||||
:type parameter: object
|
||||
'''
|
||||
# write phases to HYPOSAT-phase file
|
||||
writephases(picks, 'HYPOSAT', fnout, parameter)
|
||||
write_phases(picks, 'HYPOSAT', fnout, parameter)
|
||||
|
||||
@@ -7,7 +7,7 @@ import subprocess
|
||||
|
||||
from obspy import read_events
|
||||
|
||||
from pylot.core.io.phases import writephases
|
||||
from pylot.core.io.phases import write_phases
|
||||
from pylot.core.util.gui import which
|
||||
from pylot.core.util.utils import getPatternLine, runProgram
|
||||
from pylot.core.util.version import get_git_version as _getVersionString
|
||||
@@ -34,7 +34,7 @@ def export(picks, fnout, parameter):
|
||||
:type parameter: object
|
||||
'''
|
||||
# write phases to NLLoc-phase file
|
||||
writephases(picks, 'NLLoc', fnout, parameter)
|
||||
write_phases(picks, 'NLLoc', fnout, parameter)
|
||||
|
||||
|
||||
def modify_inputs(ctrfn, root, nllocoutn, phasefn, tttn):
|
||||
@@ -82,8 +82,8 @@ def locate(fnin, parameter=None):
|
||||
:param fnin: external program name
|
||||
:return: None
|
||||
"""
|
||||
exe_path = which('NLLoc', parameter)
|
||||
if exe_path is None:
|
||||
exe_path = os.path.join(parameter['nllocbin'], 'NLLoc')
|
||||
if not os.path.isfile(exe_path):
|
||||
raise NLLocError('NonLinLoc executable not found; check your '
|
||||
'environment variables')
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pylot.core.io.phases import writephases
|
||||
from pylot.core.io.phases import write_phases
|
||||
from pylot.core.util.version import get_git_version as _getVersionString
|
||||
|
||||
__version__ = _getVersionString()
|
||||
@@ -25,4 +25,4 @@ def export(picks, fnout, eventinfo, parameter=None):
|
||||
:type parameter: object
|
||||
'''
|
||||
# write phases to VELEST-phase file
|
||||
writephases(picks, 'VELEST', fnout, parameter, eventinfo)
|
||||
write_phases(picks, 'VELEST', fnout, parameter, eventinfo)
|
||||
|
||||
+55
-46
@@ -20,9 +20,9 @@ from pylot.core.pick.charfuns import CharacteristicFunction
|
||||
from pylot.core.pick.charfuns import HOScf, AICcf, ARZcf, ARHcf, AR3Ccf
|
||||
from pylot.core.pick.picker import AICPicker, PragPicker
|
||||
from pylot.core.pick.utils import checksignallength, checkZ4S, earllatepicker, \
|
||||
getSNR, fmpicker, checkPonsets, wadaticheck, get_quality_class
|
||||
getSNR, fmpicker, checkPonsets, wadaticheck, get_quality_class, PickingFailedException, MissingTraceException
|
||||
from pylot.core.util.utils import getPatternLine, gen_Pool, \
|
||||
get_Bool, identifyPhaseID, get_None, correct_iplot
|
||||
get_bool, identifyPhaseID, get_none, correct_iplot
|
||||
|
||||
|
||||
def autopickevent(data, param, iplot=0, fig_dict=None, fig_dict_wadatijack=None, ncores=0, metadata=None, origin=None):
|
||||
@@ -232,20 +232,6 @@ class PickingContainer:
|
||||
self.Sflag = 0
|
||||
|
||||
|
||||
class MissingTraceException(ValueError):
|
||||
"""
|
||||
Used to indicate missing traces in a obspy.core.stream.Stream object
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PickingFailedException(Exception):
|
||||
"""
|
||||
Raised when picking fails due to missing values etc.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class AutopickStation(object):
|
||||
|
||||
def __init__(self, wfstream, pickparam, verbose, iplot=0, fig_dict=None, metadata=None, origin=None):
|
||||
@@ -272,7 +258,7 @@ class AutopickStation(object):
|
||||
self.pickparams = copy.deepcopy(pickparam)
|
||||
self.verbose = verbose
|
||||
self.iplot = correct_iplot(iplot)
|
||||
self.fig_dict = get_None(fig_dict)
|
||||
self.fig_dict = get_none(fig_dict)
|
||||
self.metadata = metadata
|
||||
self.origin = origin
|
||||
|
||||
@@ -429,7 +415,7 @@ class AutopickStation(object):
|
||||
if station_coords is None:
|
||||
exit_taupy()
|
||||
raise AttributeError('Warning: Could not find station in metadata')
|
||||
# TODO raise when metadata.get_coordinates returns None
|
||||
# TODO: raise when metadata.get_coordinates returns None
|
||||
source_origin = origin[0]
|
||||
model = TauPyModel(taup_model)
|
||||
taup_phases = self.pickparams['taup_phases']
|
||||
@@ -471,11 +457,13 @@ class AutopickStation(object):
|
||||
"""If taupy failed to calculate theoretical starttimes, picking continues.
|
||||
For this a clean exit is required, since the P starttime is no longer relative to the theoretic onset but
|
||||
to the vertical trace starttime, eg. it can't be < 0."""
|
||||
# TODO here the pickparams is modified, instead of a copy
|
||||
if self.pickparams["pstart"] < 0:
|
||||
# TODO here the pickparams is modified, instead of a copy
|
||||
self.pickparams["pstart"] = 0
|
||||
if self.pickparams["sstart"] < 0:
|
||||
self.pickparams["sstart"] = 0
|
||||
|
||||
if self.pickparams["use_taup"] is False:
|
||||
if get_bool(self.pickparams["use_taup"]) is False:
|
||||
# correct user mistake where a relative cuttime is selected (pstart < 0) but use of taupy is disabled/ has
|
||||
# not the required parameters
|
||||
exit_taupy()
|
||||
@@ -499,6 +487,20 @@ class AutopickStation(object):
|
||||
self.pickparams["pstart"] = max(self.pickparams["pstart"], 0)
|
||||
self.pickparams["pstop"] = min(self.pickparams["pstop"], len(self.ztrace) * self.ztrace.stats.delta)
|
||||
|
||||
if self.horizontal_traces_exist():
|
||||
# for the two horizontal components take earliest and latest time to make sure that the s onset is not clipped
|
||||
# if start and endtime of horizontal traces differ, the s windowsize will automatically increase
|
||||
trace_s_start = min([self.etrace.stats.starttime, self.ntrace.stats.starttime])
|
||||
# modifiy sstart and sstop relative to estimated first S arrival (relative to station time axis)
|
||||
self.pickparams["sstart"] += (self.origin[0].time + estFirstS) - trace_s_start
|
||||
self.pickparams["sstop"] += (self.origin[0].time + estFirstS) - trace_s_start
|
||||
print('autopick: CF calculation times respectively:'
|
||||
' sstart: {} s, sstop: {} s'.format(self.pickparams["sstart"], self.pickparams["sstop"]))
|
||||
# make sure pstart and pstop are inside the starttime/endtime of horizontal traces
|
||||
self.pickparams["sstart"] = max(self.pickparams["sstart"], 0)
|
||||
self.pickparams["sstop"] = min(self.pickparams["sstop"], len(self.ntrace) * self.ntrace.stats.delta,
|
||||
len(self.etrace) * self.etrace.stats.delta)
|
||||
|
||||
def autopickstation(self):
|
||||
"""
|
||||
Main function of autopickstation, which calculates P and S picks and returns them in a dictionary.
|
||||
@@ -508,6 +510,17 @@ class AutopickStation(object):
|
||||
station's value is the station name on which the picks were calculated.
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
if get_bool(self.pickparams['use_taup']) is True and self.origin is not None:
|
||||
try:
|
||||
# modify pstart, pstop, sstart, sstop to be around theoretical onset if taupy should be used,
|
||||
# else do nothing
|
||||
self.modify_starttimes_taupy()
|
||||
except AttributeError as ae:
|
||||
print(ae)
|
||||
except MissingTraceException as mte:
|
||||
print(mte)
|
||||
|
||||
try:
|
||||
self.pick_p_phase()
|
||||
except MissingTraceException as mte:
|
||||
@@ -515,13 +528,15 @@ class AutopickStation(object):
|
||||
except PickingFailedException as pfe:
|
||||
print(pfe)
|
||||
|
||||
if self.horizontal_traces_exist() and self.p_results.weight is not None and self.p_results.weight < 4:
|
||||
try:
|
||||
self.pick_s_phase()
|
||||
except MissingTraceException as mte:
|
||||
print(mte)
|
||||
except PickingFailedException as pfe:
|
||||
print(pfe)
|
||||
if self.horizontal_traces_exist():
|
||||
if (self.p_results.weight is not None and self.p_results.weight < 4) or \
|
||||
get_bool(self.pickparams.get('use_taup')):
|
||||
try:
|
||||
self.pick_s_phase()
|
||||
except MissingTraceException as mte:
|
||||
print(mte)
|
||||
except PickingFailedException as pfe:
|
||||
print(pfe)
|
||||
|
||||
self.plot_pick_results()
|
||||
self.finish_picking()
|
||||
@@ -631,7 +646,7 @@ class AutopickStation(object):
|
||||
ax1.set_ylim([-1.5, 1.5])
|
||||
ax1.set_ylabel('Normalized Counts')
|
||||
|
||||
if self.horizontal_traces_exist() and self.s_data.Sflag == 1:
|
||||
if self.horizontal_traces_exist():# and self.s_data.Sflag == 1:
|
||||
# plot E trace
|
||||
ax2 = fig.add_subplot(3, 1, 2, sharex=ax1)
|
||||
th1data = np.linspace(0, self.etrace.stats.endtime - self.etrace.stats.starttime,
|
||||
@@ -661,13 +676,14 @@ class AutopickStation(object):
|
||||
ax2.plot([self.s_results.lpp, self.s_results.lpp], [-1.1, 1.1], 'g--', label='lpp')
|
||||
ax2.plot([self.s_results.epp, self.s_results.epp], [-1.1, 1.1], 'g--', label='epp')
|
||||
title = '{channel}, S weight={sweight}, SNR={snr:7.2}, SNR[dB]={snrdb:7.2}'
|
||||
ax2.set_title(title.format(channel=self.etrace.stats.channel,
|
||||
sweight=self.s_results.weight,
|
||||
snr=self.s_results.snr,
|
||||
snrdb=self.s_results.snrdb))
|
||||
ax2.set_title(title.format(channel=str(self.etrace.stats.channel),
|
||||
sweight=str(self.s_results.weight),
|
||||
snr=str(self.s_results.snr),
|
||||
snrdb=str(self.s_results.snrdb)))
|
||||
else:
|
||||
title = '{channel}, S weight={sweight}, SNR=None, SNR[dB]=None'
|
||||
ax2.set_title(title.format(channel=self.etrace.stats.channel, sweight=self.s_results.weight))
|
||||
ax2.set_title(title.format(channel=str(self.etrace.stats.channel),
|
||||
sweight=str(self.s_results.weight)))
|
||||
ax2.legend(loc=1)
|
||||
ax2.set_yticks([])
|
||||
ax2.set_ylim([-1.5, 1.5])
|
||||
@@ -795,15 +811,6 @@ class AutopickStation(object):
|
||||
# save filtered trace in instance for later plotting
|
||||
self.tr_filt_z_bpz2 = tr_filt
|
||||
|
||||
if get_Bool(self.pickparams['use_taup']) is True and self.origin is not None:
|
||||
try:
|
||||
# modify pstart, pstop to be around theoretical onset if taupy should be used, else does nothing
|
||||
self.modify_starttimes_taupy()
|
||||
except AttributeError as ae:
|
||||
print(ae)
|
||||
except MissingTraceException as mte:
|
||||
print(mte)
|
||||
|
||||
Lc = self.pickparams['pstop'] - self.pickparams['pstart']
|
||||
|
||||
Lwf = self.ztrace.stats.endtime - self.ztrace.stats.starttime
|
||||
@@ -915,7 +922,7 @@ class AutopickStation(object):
|
||||
"minFMSNR"]:
|
||||
# if SNR is high enough, try to determine first motion of onset
|
||||
self.set_current_figure('fm_picker')
|
||||
self.p_results.fm = fmpicker(self.zstream, z_copy, self.pickparams["fmpickwin"], self.p_results.mpp,
|
||||
self.p_results.fm = fmpicker(self.zstream.copy(), z_copy, self.pickparams["fmpickwin"], self.p_results.mpp,
|
||||
self.iplot, self.current_figure, self.current_linecolor)
|
||||
msg = "autopickstation: P-weight: {}, SNR: {}, SNR[dB]: {}, Polarity: {}"
|
||||
msg = msg.format(self.p_results.weight, self.p_results.snr, self.p_results.snrdb, self.p_results.fm)
|
||||
@@ -1127,9 +1134,11 @@ class AutopickStation(object):
|
||||
''.format(self.s_results.weight, self.s_results.snr, self.s_results.snrdb))
|
||||
|
||||
def pick_s_phase(self):
|
||||
|
||||
# determine time window for calculating CF after P onset
|
||||
cuttimesh = self._calculate_cuttimes(type='S', iteration=1)
|
||||
if get_bool(self.pickparams.get('use_taup')) is True:
|
||||
cuttimesh = (self.pickparams.get('sstart'), self.pickparams.get('sstop'))
|
||||
else:
|
||||
# determine time window for calculating CF after P onset
|
||||
cuttimesh = self._calculate_cuttimes(type='S', iteration=1)
|
||||
|
||||
# calculate autoregressive CF
|
||||
self.arhcf1 = self._calculate_autoregressive_cf_s_pick(cuttimesh)
|
||||
|
||||
@@ -16,10 +16,11 @@ autoregressive prediction: application ot local and regional distances, Geophys.
|
||||
|
||||
:author: MAGS2 EP3 working group
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from obspy.core import Stream
|
||||
from scipy import signal
|
||||
from obspy.core import Stream
|
||||
|
||||
from pylot.core.pick.utils import PickingFailedException
|
||||
|
||||
|
||||
class CharacteristicFunction(object):
|
||||
@@ -259,7 +260,7 @@ class HOScf(CharacteristicFunction):
|
||||
"""
|
||||
Function to calculate skewness (statistics of order 3) or kurtosis
|
||||
(statistics of order 4), using one long moving window, as published
|
||||
in Kueperkoch et al. (2010).
|
||||
in Kueperkoch et al. (2010), or order 2, i.e. STA/LTA.
|
||||
:param data: data, time series (whether seismogram or CF)
|
||||
:type data: tuple
|
||||
:return: HOS cf
|
||||
@@ -276,28 +277,47 @@ class HOScf(CharacteristicFunction):
|
||||
elif self.getOrder() == 4: # this is kurtosis
|
||||
y = np.power(xnp, 4)
|
||||
y1 = np.power(xnp, 2)
|
||||
elif self.getOrder() == 2: # this is variance, used for STA/LTA processing
|
||||
y = np.power(xnp, 2)
|
||||
y1 = np.power(xnp, 2)
|
||||
|
||||
# Initialisation
|
||||
# t2: long term moving window
|
||||
ilta = int(round(self.getTime2() / self.getIncrement()))
|
||||
ista = int(round((self.getTime2() / 10) / self.getIncrement())) # TODO: still hard coded!!
|
||||
lta = y[0]
|
||||
lta1 = y1[0]
|
||||
sta = y[0]
|
||||
# moving windows
|
||||
LTA = np.zeros(len(xnp))
|
||||
STA = np.zeros(len(xnp))
|
||||
for j in range(0, len(xnp)):
|
||||
if j < 4:
|
||||
LTA[j] = 0
|
||||
STA[j] = 0
|
||||
elif j <= ista and self.getOrder() == 2:
|
||||
lta = (y[j] + lta * (j - 1)) / j
|
||||
if self.getOrder() == 2:
|
||||
sta = (y[j] + sta * (j - 1)) / j
|
||||
# elif j < 4:
|
||||
elif j <= ilta:
|
||||
lta = (y[j] + lta * (j - 1)) / j
|
||||
lta1 = (y1[j] + lta1 * (j - 1)) / j
|
||||
if self.getOrder() == 2:
|
||||
sta = (y[j] - y[j - ista]) / ista + sta
|
||||
else:
|
||||
lta = (y[j] - y[j - ilta]) / ilta + lta
|
||||
lta1 = (y1[j] - y1[j - ilta]) / ilta + lta1
|
||||
if self.getOrder() == 2:
|
||||
sta = (y[j] - y[j - ista]) / ista + sta
|
||||
# define LTA
|
||||
if self.getOrder() == 3:
|
||||
LTA[j] = lta / np.power(lta1, 1.5)
|
||||
elif self.getOrder() == 4:
|
||||
LTA[j] = lta / np.power(lta1, 2)
|
||||
else:
|
||||
LTA[j] = lta
|
||||
STA[j] = sta
|
||||
|
||||
# remove NaN's with first not-NaN-value,
|
||||
# so autopicker doesnt pick discontinuity at start of the trace
|
||||
@@ -306,7 +326,10 @@ class HOScf(CharacteristicFunction):
|
||||
first = ind[0]
|
||||
LTA[:first] = LTA[first]
|
||||
|
||||
self.cf = LTA
|
||||
if self.getOrder() > 2:
|
||||
self.cf = LTA
|
||||
else: # order 2 means STA/LTA!
|
||||
self.cf = STA / LTA
|
||||
self.xcf = x
|
||||
|
||||
|
||||
@@ -467,6 +490,9 @@ class ARHcf(CharacteristicFunction):
|
||||
print('Calculating AR-prediction error from both horizontal traces ...')
|
||||
|
||||
xnp = self.getDataArray(self.getCut())
|
||||
if len(xnp[0]) == 0:
|
||||
raise PickingFailedException('calcCF: Found empty data trace for cut times. Return')
|
||||
|
||||
n0 = np.isnan(xnp[0].data)
|
||||
if len(n0) > 1:
|
||||
xnp[0].data[n0] = 0
|
||||
|
||||
@@ -178,7 +178,9 @@ class AICPicker(AutoPicker):
|
||||
aic = tap * self.cf + max(abs(self.cf))
|
||||
# smooth AIC-CF
|
||||
ismooth = int(round(self.Tsmooth / self.dt))
|
||||
aicsmooth = np.zeros(len(aic))
|
||||
# MP MP better start with original data than zeros if array shall be smoothed, created artificial value before
|
||||
# when starting with i in range(1...) loop below and subtracting offset afterwards
|
||||
aicsmooth = np.copy(aic)
|
||||
if len(aic) < ismooth:
|
||||
print('AICPicker: Tsmooth larger than CF!')
|
||||
return
|
||||
@@ -188,7 +190,7 @@ class AICPicker(AutoPicker):
|
||||
ii1 = i - ismooth
|
||||
aicsmooth[i] = aicsmooth[i - 1] + (aic[i] - aic[ii1]) / ismooth
|
||||
else:
|
||||
aicsmooth[i] = np.mean(aic[1: i])
|
||||
aicsmooth[i] = np.mean(aic[0: i]) # MP MP created np.nan for i=1
|
||||
# remove offset in AIC function
|
||||
offset = abs(min(aic) - min(aicsmooth))
|
||||
aicsmooth = aicsmooth - offset
|
||||
@@ -197,7 +199,7 @@ class AICPicker(AutoPicker):
|
||||
# minimum in AIC function
|
||||
icfmax = np.argmax(cf)
|
||||
|
||||
# MP MP testing threshold
|
||||
# TODO: If this shall be kept, maybe add thresh_factor to pylot parameters
|
||||
thresh_hit = False
|
||||
thresh_factor = 0.7
|
||||
thresh = thresh_factor * cf[icfmax]
|
||||
@@ -209,7 +211,6 @@ class AICPicker(AutoPicker):
|
||||
if sample <= cf[index - 1]:
|
||||
icfmax = index - 1
|
||||
break
|
||||
# MP MP ---
|
||||
|
||||
# find minimum in AIC-CF front of maximum of HOS/AR-CF
|
||||
lpickwindow = int(round(self.PickWindow / self.dt))
|
||||
@@ -336,7 +337,7 @@ class AICPicker(AutoPicker):
|
||||
self.slope = 1 / (len(dataslope) * self.Data[0].stats.delta) * (datafit[-1] - datafit[0])
|
||||
# normalize slope to maximum of cf to make it unit independent
|
||||
self.slope /= aicsmooth[iaicmax]
|
||||
except ValueError as e:
|
||||
except Exception as e:
|
||||
print("AICPicker: Problems with data fitting! {}".format(e))
|
||||
|
||||
else:
|
||||
|
||||
+29
-29
@@ -15,7 +15,7 @@ import numpy as np
|
||||
from obspy.core import Stream, UTCDateTime
|
||||
from scipy.signal import argrelmax
|
||||
|
||||
from pylot.core.util.utils import get_Bool, get_None, SetChannelComponents
|
||||
from pylot.core.util.utils import get_bool, get_none, SetChannelComponents
|
||||
|
||||
|
||||
def earllatepicker(X, nfac, TSNR, Pick1, iplot=0, verbosity=1, fig=None, linecolor='k'):
|
||||
@@ -62,8 +62,8 @@ def earllatepicker(X, nfac, TSNR, Pick1, iplot=0, verbosity=1, fig=None, linecol
|
||||
plt_flag = 0
|
||||
try:
|
||||
iplot = int(iplot)
|
||||
except:
|
||||
if get_Bool(iplot):
|
||||
except ValueError:
|
||||
if get_bool(iplot):
|
||||
iplot = 2
|
||||
else:
|
||||
iplot = 0
|
||||
@@ -136,7 +136,7 @@ def earllatepicker(X, nfac, TSNR, Pick1, iplot=0, verbosity=1, fig=None, linecol
|
||||
PickError = symmetrize_error(diffti_te, diffti_tl)
|
||||
|
||||
if iplot > 1:
|
||||
if get_None(fig) is None:
|
||||
if get_none(fig) is None:
|
||||
fig = plt.figure() # iplot)
|
||||
plt_flag = 1
|
||||
fig._tight = True
|
||||
@@ -344,7 +344,7 @@ def fmpicker(Xraw, Xfilt, pickwin, Pick, iplot=0, fig=None, linecolor='k'):
|
||||
print("fmpicker: Found polarity %s" % FM)
|
||||
|
||||
if iplot > 1:
|
||||
if get_None(fig) is None:
|
||||
if get_none(fig) is None:
|
||||
fig = plt.figure() # iplot)
|
||||
plt_flag = 1
|
||||
fig._tight = True
|
||||
@@ -816,7 +816,7 @@ def checksignallength(X, pick, minsiglength, pickparams, iplot=0, fig=None, line
|
||||
try:
|
||||
iplot = int(iplot)
|
||||
except:
|
||||
if get_Bool(iplot):
|
||||
if get_bool(iplot):
|
||||
iplot = 2
|
||||
else:
|
||||
iplot = 0
|
||||
@@ -868,7 +868,7 @@ def checksignallength(X, pick, minsiglength, pickparams, iplot=0, fig=None, line
|
||||
returnflag = 0
|
||||
|
||||
if iplot > 1:
|
||||
if get_None(fig) is None:
|
||||
if get_none(fig) is None:
|
||||
fig = plt.figure() # iplot)
|
||||
plt_flag = 1
|
||||
fig._tight = True
|
||||
@@ -890,6 +890,8 @@ def checksignallength(X, pick, minsiglength, pickparams, iplot=0, fig=None, line
|
||||
input()
|
||||
except SyntaxError:
|
||||
pass
|
||||
except EOFError:
|
||||
pass
|
||||
plt.close(fig)
|
||||
|
||||
return returnflag
|
||||
@@ -1130,7 +1132,7 @@ def checkZ4S(X, pick, pickparams, iplot, fig=None, linecolor='k'):
|
||||
try:
|
||||
iplot = int(iplot)
|
||||
except:
|
||||
if get_Bool(iplot):
|
||||
if get_bool(iplot):
|
||||
iplot = 2
|
||||
else:
|
||||
iplot = 0
|
||||
@@ -1211,14 +1213,14 @@ def checkZ4S(X, pick, pickparams, iplot, fig=None, linecolor='k'):
|
||||
t = np.linspace(diff_dict[key], trace.stats.endtime - trace.stats.starttime + diff_dict[key],
|
||||
trace.stats.npts)
|
||||
if i == 0:
|
||||
if get_None(fig) is None:
|
||||
if get_none(fig) is None:
|
||||
fig = plt.figure() # self.iplot) ### WHY? MP MP
|
||||
plt_flag = 1
|
||||
ax1 = fig.add_subplot(3, 1, i + 1)
|
||||
ax = ax1
|
||||
ax.set_title('CheckZ4S, Station %s' % zdat[0].stats.station)
|
||||
else:
|
||||
if get_None(fig) is None:
|
||||
if get_none(fig) is None:
|
||||
fig = plt.figure() # self.iplot) ### WHY? MP MP
|
||||
plt_flag = 1
|
||||
ax = fig.add_subplot(3, 1, i + 1, sharex=ax1)
|
||||
@@ -1320,7 +1322,7 @@ def get_quality_class(uncertainty, weight_classes):
|
||||
:return: quality of pick (0-4)
|
||||
:rtype: int
|
||||
"""
|
||||
if not uncertainty: return max(weight_classes)
|
||||
if not uncertainty: return len(weight_classes)
|
||||
try:
|
||||
# create generator expression containing all indices of values in weight classes that are >= than uncertainty.
|
||||
# call next on it once to receive first value
|
||||
@@ -1332,22 +1334,6 @@ def get_quality_class(uncertainty, weight_classes):
|
||||
return quality
|
||||
|
||||
|
||||
def set_NaNs_to(data, nan_value):
|
||||
"""
|
||||
Replace all NaNs in data with nan_value
|
||||
:param data: array holding data
|
||||
:type data: `~numpy.ndarray`
|
||||
:param nan_value: value which all NaNs are set to
|
||||
:type nan_value: float, int
|
||||
:return: data array with all NaNs replaced with nan_value
|
||||
:rtype: `~numpy.ndarray`
|
||||
"""
|
||||
nn = np.isnan(data)
|
||||
if np.any(nn):
|
||||
data[nn] = nan_value
|
||||
return data
|
||||
|
||||
|
||||
def taper_cf(cf):
|
||||
"""
|
||||
Taper cf data to get rid off of side maximas
|
||||
@@ -1499,7 +1485,7 @@ def get_pickparams(pickparam):
|
||||
first_motion_params = dict(zip(first_motion_names, fm_parameter_values))
|
||||
signal_length_params = dict(zip(signal_length_names, sl_parameter_values))
|
||||
|
||||
p_params['use_taup'] = get_Bool(p_params['use_taup'])
|
||||
p_params['use_taup'] = get_bool(p_params['use_taup'])
|
||||
|
||||
return p_params, s_params, first_motion_params, signal_length_params
|
||||
|
||||
@@ -1508,7 +1494,7 @@ def getQualityFromUncertainty(uncertainty, Errors):
|
||||
# set initial quality to 4 (worst) and change only if one condition is hit
|
||||
quality = 4
|
||||
|
||||
if get_None(uncertainty) is None:
|
||||
if get_none(uncertainty) is None:
|
||||
return quality
|
||||
|
||||
if uncertainty <= Errors[0]:
|
||||
@@ -1532,3 +1518,17 @@ if __name__ == '__main__':
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
||||
|
||||
class PickingFailedException(Exception):
|
||||
"""
|
||||
Raised when picking fails due to missing values etc.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class MissingTraceException(ValueError):
|
||||
"""
|
||||
Used to indicate missing traces in a obspy.core.stream.Stream object
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -13,6 +13,7 @@ import obspy
|
||||
from PySide2 import QtWidgets
|
||||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
||||
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
|
||||
from pylot.core.util.utils import identifyPhaseID
|
||||
from scipy.interpolate import griddata
|
||||
|
||||
from pylot.core.pick.utils import get_quality_class
|
||||
@@ -75,7 +76,6 @@ class Array_map(QtWidgets.QWidget):
|
||||
|
||||
self._style = None if not hasattr(parent, '_style') else parent._style
|
||||
|
||||
self.show()
|
||||
|
||||
def init_map(self):
|
||||
self.init_colormap()
|
||||
@@ -124,8 +124,8 @@ class Array_map(QtWidgets.QWidget):
|
||||
self.cmaps_box = QtWidgets.QComboBox()
|
||||
self.cmaps_box.setMaxVisibleItems(20)
|
||||
[self.cmaps_box.addItem(map_name) for map_name in sorted(plt.colormaps())]
|
||||
# try to set to hsv as default
|
||||
self.cmaps_box.setCurrentIndex(self.cmaps_box.findText('hsv'))
|
||||
# try to set to viridis as default
|
||||
self.cmaps_box.setCurrentIndex(self.cmaps_box.findText('viridis'))
|
||||
|
||||
self.top_row.addWidget(QtWidgets.QLabel('Select a phase: '))
|
||||
self.top_row.addWidget(self.comboBox_phase)
|
||||
@@ -280,9 +280,12 @@ class Array_map(QtWidgets.QWidget):
|
||||
self.canvas.axes.figure.canvas.draw_idle()
|
||||
|
||||
def onpick(self, event):
|
||||
btn_msg = {1: ' in selection. Aborted', 2: ' to delete a pick on. Aborted', 3: ' to display info.'}
|
||||
ind = event.ind
|
||||
button = event.mouseevent.button
|
||||
if ind == []:
|
||||
msg_reason = None
|
||||
if len(ind) > 1:
|
||||
self._parent.update_status(f'Found more than one station {btn_msg.get(button)}')
|
||||
return
|
||||
if button == 1:
|
||||
self.openPickDlg(ind)
|
||||
@@ -385,7 +388,14 @@ class Array_map(QtWidgets.QWidget):
|
||||
try:
|
||||
station_name = st_id.split('.')[-1]
|
||||
# current_picks_dict: auto or manual
|
||||
pick = self.current_picks_dict()[station_name][phase]
|
||||
station_picks = self.current_picks_dict().get(station_name)
|
||||
if not station_picks:
|
||||
continue
|
||||
for phase_hint, pick in station_picks.items():
|
||||
if identifyPhaseID(phase_hint) == phase:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
if pick['picker'] == 'auto':
|
||||
if not pick['spe']:
|
||||
continue
|
||||
@@ -464,20 +474,23 @@ class Array_map(QtWidgets.QWidget):
|
||||
transform=ccrs.PlateCarree(), label='deleted'))
|
||||
|
||||
def openPickDlg(self, ind):
|
||||
data = self._parent.get_data().getWFData()
|
||||
wfdata = self._parent.get_data().get_wf_data()
|
||||
wfdata_comp = self._parent.get_data().get_wf_dataComp()
|
||||
for index in ind:
|
||||
network, station = self._station_onpick_ids[index].split('.')[:2]
|
||||
pyl_mw = self._parent
|
||||
try:
|
||||
data = data.select(station=station)
|
||||
if not data:
|
||||
wfdata = wfdata.select(station=station)
|
||||
wfdata_comp = wfdata_comp.select(station=station)
|
||||
if not wfdata:
|
||||
self._warn('No data for station {}'.format(station))
|
||||
return
|
||||
pickDlg = PickDlg(self._parent, parameter=self.parameter,
|
||||
data=data, network=network, station=station,
|
||||
data=wfdata.copy(), data_compare=wfdata_comp.copy(), network=network, station=station,
|
||||
picks=self._parent.get_current_event().getPick(station),
|
||||
autopicks=self._parent.get_current_event().getAutopick(station),
|
||||
filteroptions=self._parent.filteroptions, metadata=self.metadata,
|
||||
model=self.parameter.get('taup_model'),
|
||||
event=pyl_mw.get_current_event())
|
||||
except Exception as e:
|
||||
message = 'Could not generate Plot for station {st}.\n {er}'.format(st=station, er=e)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import glob
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
@@ -189,7 +190,11 @@ class Metadata(object):
|
||||
metadata = self.get_metadata(seed_id, time)
|
||||
if not metadata:
|
||||
return
|
||||
return metadata['data'].get_coordinates(seed_id, time)
|
||||
try:
|
||||
return metadata['data'].get_coordinates(seed_id, time)
|
||||
# no specific exception defined in obspy inventory
|
||||
except Exception as e:
|
||||
logging.warning(f'Could not get metadata for {seed_id}')
|
||||
|
||||
def get_all_coordinates(self):
|
||||
def stat_info_from_parser(parser):
|
||||
@@ -271,7 +276,7 @@ class Metadata(object):
|
||||
continue
|
||||
invtype, robj = self._read_metadata_file(os.path.join(path_to_inventory, fname))
|
||||
try:
|
||||
robj.get_coordinates(station_seed_id)
|
||||
# robj.get_coordinates(station_seed_id) # TODO: Commented out, failed with Parser, is this needed?
|
||||
self.inventory_files[fname] = {'invtype': invtype,
|
||||
'data': robj}
|
||||
if station_seed_id in self.seed_ids.keys():
|
||||
@@ -333,19 +338,6 @@ class Metadata(object):
|
||||
return inv, exc
|
||||
|
||||
|
||||
def time_from_header(header):
|
||||
"""
|
||||
Function takes in the second line from a .gse file and takes out the date and time from that line.
|
||||
:param header: second line from .gse file
|
||||
:type header: string
|
||||
:return: a list of integers of form [year, month, day, hour, minute, second, microsecond]
|
||||
"""
|
||||
timeline = header.split(' ')
|
||||
time = timeline[1].split('/') + timeline[2].split(':')
|
||||
time = time[:-1] + time[-1].split('.')
|
||||
return [int(t) for t in time]
|
||||
|
||||
|
||||
def check_time(datetime):
|
||||
"""
|
||||
Function takes in date and time as list and validates it's values by trying to make an UTCDateTime object from it
|
||||
@@ -383,167 +375,6 @@ def check_time(datetime):
|
||||
return False
|
||||
|
||||
|
||||
# TODO: change root to datapath
|
||||
def get_file_list(root_dir):
|
||||
"""
|
||||
Function uses a directorie to get all the *.gse files from it.
|
||||
:param root_dir: a directorie leading to the .gse files
|
||||
:type root_dir: string
|
||||
:return: returns a list of filenames (without path to them)
|
||||
"""
|
||||
file_list = glob.glob1(root_dir, '*.gse')
|
||||
return file_list
|
||||
|
||||
|
||||
def checks_station_second(datetime, file):
|
||||
"""
|
||||
Function uses the given list to check if the parameter 'second' is set to 60 by mistake
|
||||
and sets the time correctly if so. Can only correct time if no date change would be necessary.
|
||||
:param datetime: [year, month, day, hour, minute, second, microsecond]
|
||||
:return: returns the input with the correct value for second
|
||||
"""
|
||||
if datetime[5] == 60:
|
||||
if datetime[4] == 59:
|
||||
if datetime[3] == 23:
|
||||
err_msg = 'Date should be next day. ' \
|
||||
'File not changed: {0}'.format(file)
|
||||
raise ValueError(err_msg)
|
||||
else:
|
||||
datetime[3] += 1
|
||||
datetime[4] = 0
|
||||
datetime[5] = 0
|
||||
else:
|
||||
datetime[4] += 1
|
||||
datetime[5] = 0
|
||||
return datetime
|
||||
|
||||
|
||||
def make_time_line(line, datetime):
|
||||
"""
|
||||
Function takes in the original line from a .gse file and a list of date and
|
||||
time values to make a new line with corrected date and time.
|
||||
:param line: second line from .gse file.
|
||||
:type line: string
|
||||
:param datetime: list of integers [year, month, day, hour, minute, second, microsecond]
|
||||
:type datetime: list
|
||||
:return: returns a string to write it into a file.
|
||||
"""
|
||||
ins_form = '{0:02d}:{1:02d}:{2:02d}.{3:03d}'
|
||||
insertion = ins_form.format(int(datetime[3]),
|
||||
int(datetime[4]),
|
||||
int(datetime[5]),
|
||||
int(datetime[6] * 1e-3))
|
||||
newline = line[:16] + insertion + line[28:]
|
||||
return newline
|
||||
|
||||
|
||||
def evt_head_check(root_dir, out_dir=None):
|
||||
"""
|
||||
A function to make sure that an arbitrary number of .gse files have correct values in their header.
|
||||
:param root_dir: a directory leading to the .gse files.
|
||||
:type root_dir: string
|
||||
:param out_dir: a directory to store the new files somwhere els.
|
||||
:return: returns nothing
|
||||
"""
|
||||
if not out_dir:
|
||||
print('WARNING files are going to be overwritten!')
|
||||
inp = str(input('Continue? [y/N]'))
|
||||
if not inp == 'y':
|
||||
sys.exit()
|
||||
filelist = get_file_list(root_dir)
|
||||
nfiles = 0
|
||||
for file in filelist:
|
||||
infile = open(os.path.join(root_dir, file), 'r')
|
||||
lines = infile.readlines()
|
||||
infile.close()
|
||||
datetime = time_from_header(lines[1])
|
||||
if check_time(datetime):
|
||||
continue
|
||||
else:
|
||||
nfiles += 1
|
||||
datetime = checks_station_second(datetime, file)
|
||||
print('writing ' + file)
|
||||
# write File
|
||||
lines[1] = make_time_line(lines[1], datetime)
|
||||
if not out_dir:
|
||||
out = open(os.path.join(root_dir, file), 'w')
|
||||
out.writelines(lines)
|
||||
out.close()
|
||||
else:
|
||||
out = open(os.path.join(out_dir, file), 'w')
|
||||
out.writelines(lines)
|
||||
out.close()
|
||||
print(nfiles)
|
||||
|
||||
|
||||
def read_metadata(path_to_inventory):
|
||||
"""
|
||||
take path_to_inventory and return either the corresponding list of files
|
||||
found or the Parser object for a network dataless seed volume to prevent
|
||||
read overhead for large dataless seed volumes
|
||||
:param path_to_inventory:
|
||||
:return: tuple containing a either list of files or `obspy.io.xseed.Parser`
|
||||
object and the inventory type found
|
||||
:rtype: tuple
|
||||
"""
|
||||
dlfile = list()
|
||||
invfile = list()
|
||||
respfile = list()
|
||||
# possible file extensions specified here:
|
||||
inv = dict(dless=dlfile, xml=invfile, resp=respfile, dseed=dlfile[:])
|
||||
if os.path.isfile(path_to_inventory):
|
||||
ext = os.path.splitext(path_to_inventory)[1].split('.')[1]
|
||||
inv[ext] += [path_to_inventory]
|
||||
else:
|
||||
for ext in inv.keys():
|
||||
inv[ext] += glob.glob1(path_to_inventory, '*.{0}'.format(ext))
|
||||
|
||||
invtype = key_for_set_value(inv)
|
||||
|
||||
if invtype is None:
|
||||
print("Neither dataless-SEED file, inventory-xml file nor "
|
||||
"RESP-file found!")
|
||||
print("!!WRONG CALCULATION OF SOURCE PARAMETERS!!")
|
||||
robj = None,
|
||||
elif invtype == 'dless': # prevent multiple read of large dlsv
|
||||
print("Reading metadata information from dataless-SEED file ...")
|
||||
if len(inv[invtype]) == 1:
|
||||
fullpath_inv = os.path.join(path_to_inventory, inv[invtype][0])
|
||||
robj = Parser(fullpath_inv)
|
||||
else:
|
||||
robj = inv[invtype]
|
||||
else:
|
||||
print("Reading metadata information from inventory-xml file ...")
|
||||
robj = read_inventory(inv[invtype])
|
||||
return invtype, robj
|
||||
|
||||
|
||||
# idea to optimize read_metadata
|
||||
# def read_metadata_new(path_to_inventory):
|
||||
# metadata_objects = []
|
||||
# # read multiple files from directory
|
||||
# if os.path.isdir(path_to_inventory):
|
||||
# fnames = os.listdir(path_to_inventory)
|
||||
# # read single file
|
||||
# elif os.path.isfile(path_to_inventory):
|
||||
# fnames = [path_to_inventory]
|
||||
# else:
|
||||
# print("Neither dataless-SEED file, inventory-xml file nor "
|
||||
# "RESP-file found!")
|
||||
# print("!!WRONG CALCULATION OF SOURCE PARAMETERS!!")
|
||||
# fnames = []
|
||||
#
|
||||
# for fname in fnames:
|
||||
# path_to_inventory_filename = os.path.join(path_to_inventory, fname)
|
||||
# try:
|
||||
# ftype, robj = read_metadata_file(path_to_inventory_filename)
|
||||
# metadata_objects.append((ftype, robj))
|
||||
# except Exception as e:
|
||||
# print('Could not read metadata file {} '
|
||||
# 'because of the following Exception: {}'.format(path_to_inventory_filename, e))
|
||||
# return metadata_objects
|
||||
|
||||
|
||||
def restitute_trace(input_tuple):
|
||||
def no_metadata(tr, seed_id):
|
||||
print('no metadata file found '
|
||||
|
||||
@@ -26,9 +26,7 @@ elif system_name == "Windows":
|
||||
# suffix for phase name if not phase identified by last letter (P, p, etc.)
|
||||
ALTSUFFIX = ['diff', 'n', 'g', '1', '2', '3']
|
||||
|
||||
FILTERDEFAULTS = readDefaultFilterInformation(os.path.join(os.path.expanduser('~'),
|
||||
'.pylot',
|
||||
'pylot.in'))
|
||||
FILTERDEFAULTS = readDefaultFilterInformation()
|
||||
|
||||
TIMEERROR_DEFAULTS = os.path.join(os.path.expanduser('~'),
|
||||
'.pylot',
|
||||
|
||||
@@ -20,7 +20,7 @@ import matplotlib
|
||||
matplotlib.use('Qt5Agg')
|
||||
sys.path.append(os.path.join('/'.join(sys.argv[0].split('/')[:-1]), '../../..'))
|
||||
|
||||
from PyLoT import Project
|
||||
from pylot.core.io.project import Project
|
||||
from pylot.core.util.dataprocessing import Metadata
|
||||
from pylot.core.util.array_map import Array_map
|
||||
|
||||
@@ -42,7 +42,7 @@ def main(project_file_path, manual=False, auto=True, file_format='png', f_ext=''
|
||||
for item in input_list:
|
||||
array_map_worker(item)
|
||||
else:
|
||||
pool = multiprocessing.Pool(ncores)
|
||||
pool = multiprocessing.Pool(ncores, maxtasksperchild=1000)
|
||||
pool.map(array_map_worker, input_list)
|
||||
pool.close()
|
||||
pool.join()
|
||||
|
||||
@@ -56,7 +56,7 @@ def which(program, parameter):
|
||||
nllocpath = ":" + parameter.get('nllocbin')
|
||||
os.environ['PATH'] += nllocpath
|
||||
except Exception as e:
|
||||
print(e.message)
|
||||
print(e)
|
||||
|
||||
def is_exe(fpath):
|
||||
return os.path.exists(fpath) and os.access(fpath, os.X_OK)
|
||||
|
||||
@@ -6,7 +6,7 @@ Created on Wed Jan 26 17:47:25 2015
|
||||
@author: sebastianw
|
||||
"""
|
||||
|
||||
from pylot.core.io.data import SeiscompDataStructure, PilotDataStructure, ObspyDMTdataStructure
|
||||
from pylot.core.io.data import SeiscompDataStructure, PilotDataStructure
|
||||
|
||||
DATASTRUCTURE = {'PILOT': PilotDataStructure, 'SeisComP': SeiscompDataStructure,
|
||||
'obspyDMT': ObspyDMTdataStructure, None: PilotDataStructure}
|
||||
'obspyDMT': PilotDataStructure, None: PilotDataStructure}
|
||||
|
||||
@@ -41,7 +41,8 @@ class Thread(QThread):
|
||||
exctype, value = sys.exc_info()[:2]
|
||||
self._executedErrorInfo = '{} {} {}'. \
|
||||
format(exctype, value, traceback.format_exc())
|
||||
sys.stdout = self.old_stdout
|
||||
if self.redirect_stdout:
|
||||
sys.stdout = self.old_stdout
|
||||
|
||||
def showProgressbar(self):
|
||||
if self.progressText:
|
||||
@@ -159,7 +160,7 @@ class MultiThread(QThread):
|
||||
try:
|
||||
if not self.ncores:
|
||||
self.ncores = multiprocessing.cpu_count()
|
||||
pool = multiprocessing.Pool(self.ncores)
|
||||
pool = multiprocessing.Pool(self.ncores, maxtasksperchild=1000)
|
||||
self.data = pool.map_async(self.func, self.args, callback=self.emitDone)
|
||||
# self.data = pool.apply_async(self.func, self.shotlist, callback=self.emitDone) #emit each time returned
|
||||
pool.close()
|
||||
|
||||
+284
-172
@@ -1,12 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import glob
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import subprocess
|
||||
import warnings
|
||||
from functools import lru_cache
|
||||
|
||||
import numpy as np
|
||||
from obspy import UTCDateTime, read
|
||||
@@ -18,6 +20,10 @@ from pylot.core.io.inputs import PylotParameter, FilterOptions
|
||||
from pylot.core.util.obspyDMT_interface import check_obspydmt_eventfolder
|
||||
from pylot.styles import style_settings
|
||||
|
||||
Rgba: Type[tuple] = Tuple[int, int, int, int]
|
||||
Mplrgba: Type[tuple] = Tuple[float, float, float, float]
|
||||
Mplrgbastr: Type[tuple] = Tuple[str, str, str, str]
|
||||
|
||||
|
||||
def _pickle_method(m):
|
||||
if m.im_self is None:
|
||||
@@ -37,15 +43,14 @@ def getAutoFilteroptions(phase, parameter):
|
||||
return filteroptions
|
||||
|
||||
|
||||
def readDefaultFilterInformation(fname):
|
||||
def readDefaultFilterInformation():
|
||||
"""
|
||||
Read default filter information from pylot.in file
|
||||
:param fname: path to pylot.in file
|
||||
:type fname: str
|
||||
:return: dictionary containing the defailt filter information
|
||||
:rtype: dict
|
||||
"""
|
||||
pparam = PylotParameter(fname)
|
||||
pparam = PylotParameter()
|
||||
pparam.reset_defaults()
|
||||
return readFilterInformation(pparam)
|
||||
|
||||
|
||||
@@ -82,25 +87,6 @@ def fit_curve(x, y):
|
||||
return splev, splrep(x, y)
|
||||
|
||||
|
||||
def getindexbounds(f, eta):
|
||||
"""
|
||||
Get indices of values closest below and above maximum value in an array
|
||||
:param f: array
|
||||
:type f: `~numpy.ndarray`
|
||||
:param eta: look for value in array that is closes to max_value * eta
|
||||
:type eta: float
|
||||
:return: tuple containing index of max value, index of value closest below max value,
|
||||
index of value closest above max value
|
||||
:rtype: (int, int, int)
|
||||
"""
|
||||
mi = f.argmax() # get indices of max values
|
||||
m = max(f) # get maximum value
|
||||
b = m * eta #
|
||||
l = find_nearest(f[:mi], b) # find closest value below max value
|
||||
u = find_nearest(f[mi:], b) + mi # find closest value above max value
|
||||
return mi, l, u
|
||||
|
||||
|
||||
def gen_Pool(ncores=0):
|
||||
"""
|
||||
Generate mulitprocessing pool object utilizing ncores amount of cores
|
||||
@@ -166,11 +152,11 @@ def clims(lim1, lim2):
|
||||
"""
|
||||
takes two pairs of limits and returns one pair of common limts
|
||||
:param lim1: limit 1
|
||||
:type lim1: int
|
||||
:type lim1: List[int]
|
||||
:param lim2: limit 2
|
||||
:type lim2: int
|
||||
:type lim2: List[int]
|
||||
:return: new upper and lower limit common to both given limits
|
||||
:rtype: [int, int]
|
||||
:rtype: List[int]
|
||||
|
||||
>>> clims([0, 4], [1, 3])
|
||||
[0, 4]
|
||||
@@ -302,7 +288,7 @@ def fnConstructor(s):
|
||||
if type(s) is str:
|
||||
s = s.split(':')[-1]
|
||||
else:
|
||||
s = getHash(UTCDateTime())
|
||||
s = get_hash(UTCDateTime())
|
||||
|
||||
badchars = re.compile(r'[^A-Za-z0-9_. ]+|^\.|\.$|^ | $|^$')
|
||||
badsuffix = re.compile(r'(aux|com[1-9]|con|lpt[1-9]|prn)(\.|$)')
|
||||
@@ -314,32 +300,77 @@ def fnConstructor(s):
|
||||
return fn
|
||||
|
||||
|
||||
def get_None(value):
|
||||
def get_none(value):
|
||||
"""
|
||||
Convert "None" to None
|
||||
:param value:
|
||||
:type value: str, bool
|
||||
:type value: str, NoneType
|
||||
:return:
|
||||
:rtype: bool
|
||||
:rtype: type(value) or NoneType
|
||||
|
||||
>>> st = read()
|
||||
>>> print(get_none(st))
|
||||
3 Trace(s) in Stream:
|
||||
BW.RJOB..EHZ | 2009-08-24T00:20:03.000000Z - 2009-08-24T00:20:32.990000Z | 100.0 Hz, 3000 samples
|
||||
BW.RJOB..EHN | 2009-08-24T00:20:03.000000Z - 2009-08-24T00:20:32.990000Z | 100.0 Hz, 3000 samples
|
||||
BW.RJOB..EHE | 2009-08-24T00:20:03.000000Z - 2009-08-24T00:20:32.990000Z | 100.0 Hz, 3000 samples
|
||||
>>> get_none('Stream')
|
||||
'Stream'
|
||||
>>> get_none(0)
|
||||
0
|
||||
>>> get_none(0.)
|
||||
0.0
|
||||
>>> print(get_none('None'))
|
||||
None
|
||||
>>> print(get_none(None))
|
||||
None
|
||||
"""
|
||||
if value == 'None':
|
||||
if value is None or (type(value) is str and value == 'None'):
|
||||
return None
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def get_Bool(value):
|
||||
def get_bool(value):
|
||||
"""
|
||||
Convert string representations of bools to their true boolean value
|
||||
Convert string representations of bools to their true boolean value. Return value if it cannot be identified as bool.
|
||||
:param value:
|
||||
:type value: str, bool
|
||||
:type value: str, bool, int, float
|
||||
:return: true boolean value
|
||||
:rtype: bool
|
||||
|
||||
>>> get_bool(True)
|
||||
True
|
||||
>>> get_bool(False)
|
||||
False
|
||||
>>> get_bool(0)
|
||||
False
|
||||
>>> get_bool(0.)
|
||||
False
|
||||
>>> get_bool(0.1)
|
||||
True
|
||||
>>> get_bool(2)
|
||||
True
|
||||
>>> get_bool(-1)
|
||||
False
|
||||
>>> get_bool(-0.3)
|
||||
False
|
||||
>>> get_bool(None)
|
||||
None
|
||||
>>> get_bool('Stream')
|
||||
'Stream'
|
||||
"""
|
||||
if value in ['True', 'true']:
|
||||
if type(value) == bool:
|
||||
return value
|
||||
elif value in ['True', 'true']:
|
||||
return True
|
||||
elif value in ['False', 'false']:
|
||||
return False
|
||||
elif isinstance(value, float) or isinstance(value, int):
|
||||
if value > 0. or value > 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return value
|
||||
|
||||
@@ -353,8 +384,8 @@ def four_digits(year):
|
||||
:return: four digit year correspondent
|
||||
:rtype: int
|
||||
|
||||
>>> four_digits(20)
|
||||
1920
|
||||
>>> four_digits(75)
|
||||
1975
|
||||
>>> four_digits(16)
|
||||
2016
|
||||
>>> four_digits(00)
|
||||
@@ -436,36 +467,53 @@ def backtransformFilterString(st):
|
||||
return st
|
||||
|
||||
|
||||
def getHash(time):
|
||||
def get_hash(time):
|
||||
"""
|
||||
takes a time object and returns the corresponding SHA1 hash of the formatted date string
|
||||
:param time: time object for which a hash should be calculated
|
||||
:type time: `~obspy.core.utcdatetime.UTCDateTime`
|
||||
:return: SHA1 hash
|
||||
:rtype: str
|
||||
|
||||
>>> time = UTCDateTime(0)
|
||||
>>> get_hash(time)
|
||||
'7627cce3b1b58dd21b005dac008b34d18317dd15'
|
||||
>>> get_hash(0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AssertionError: 'time' is not an ObsPy UTCDateTime object
|
||||
"""
|
||||
assert isinstance(time, UTCDateTime), '\'time\' is not an ObsPy UTCDateTime object'
|
||||
hg = hashlib.sha1()
|
||||
hg.update(time.strftime('%Y-%m-%d %H:%M:%S.%f'))
|
||||
hg.update(time.strftime('%Y-%m-%d %H:%M:%S.%f').encode('utf-8'))
|
||||
return hg.hexdigest()
|
||||
|
||||
|
||||
def getLogin():
|
||||
def get_login():
|
||||
"""
|
||||
returns the actual user's login ID
|
||||
:return: login ID
|
||||
returns the actual user's name
|
||||
:return: login name
|
||||
:rtype: str
|
||||
"""
|
||||
import getpass
|
||||
return getpass.getuser()
|
||||
|
||||
|
||||
def getOwner(fn):
|
||||
def get_owner(fn):
|
||||
"""
|
||||
takes a filename and return the login ID of the actual owner of the file
|
||||
:param fn: filename of the file tested
|
||||
:type fn: str
|
||||
:return: login ID of the file's owner
|
||||
:rtype: str
|
||||
|
||||
>>> import tempfile
|
||||
>>> with tempfile.NamedTemporaryFile() as tmpfile:
|
||||
... tmpfile.write(b'') and True
|
||||
... tmpfile.flush()
|
||||
... get_owner(tmpfile.name) == os.path.expanduser('~').split('/')[-1]
|
||||
0
|
||||
True
|
||||
"""
|
||||
system_name = platform.system()
|
||||
if system_name in ["Linux", "Darwin"]:
|
||||
@@ -511,6 +559,11 @@ def is_executable(fn):
|
||||
:param fn: path to the file to be tested
|
||||
:return: True or False
|
||||
:rtype: bool
|
||||
|
||||
>>> is_executable('/bin/ls')
|
||||
True
|
||||
>>> is_executable('/var/log/system.log')
|
||||
False
|
||||
"""
|
||||
return os.path.isfile(fn) and os.access(fn, os.X_OK)
|
||||
|
||||
@@ -537,24 +590,36 @@ def isSorted(iterable):
|
||||
>>> isSorted([2,3,1,4])
|
||||
False
|
||||
"""
|
||||
assert isIterable(iterable), 'object is not iterable; object: {' \
|
||||
'}'.format(iterable)
|
||||
assert is_iterable(iterable), "object is not iterable; object: {}".format(iterable)
|
||||
if type(iterable) is str:
|
||||
iterable = [s for s in iterable]
|
||||
return sorted(iterable) == iterable
|
||||
|
||||
|
||||
def isIterable(obj):
|
||||
def is_iterable(obj):
|
||||
"""
|
||||
takes a python object and returns True is the object is iterable and
|
||||
False otherwise
|
||||
:param obj: a python object
|
||||
:type obj: object
|
||||
:type obj: obj
|
||||
:return: True of False
|
||||
:rtype: bool
|
||||
|
||||
>>> is_iterable(1)
|
||||
False
|
||||
>>> is_iterable(True)
|
||||
False
|
||||
>>> is_iterable(0.)
|
||||
False
|
||||
>>> is_iterable((0,1,3,4))
|
||||
True
|
||||
>>> is_iterable([1])
|
||||
True
|
||||
>>> is_iterable('a')
|
||||
True
|
||||
"""
|
||||
try:
|
||||
iterator = iter(obj)
|
||||
iter(obj)
|
||||
except TypeError as te:
|
||||
return False
|
||||
return True
|
||||
@@ -563,13 +628,19 @@ def isIterable(obj):
|
||||
def key_for_set_value(d):
|
||||
"""
|
||||
takes a dictionary and returns the first key for which's value the
|
||||
boolean is True
|
||||
boolean representation is True
|
||||
:param d: dictionary containing values
|
||||
:type d: dict
|
||||
:return: key to the first non-False value found; None if no value's
|
||||
boolean equals True
|
||||
:rtype:
|
||||
:rtype: bool or NoneType
|
||||
|
||||
>>> key_for_set_value({'one': 0, 'two': 1})
|
||||
'two'
|
||||
>>> print(key_for_set_value({1: 0, 2: False}))
|
||||
None
|
||||
"""
|
||||
assert type(d) is dict, "Function only defined for inputs of type 'dict'."
|
||||
r = None
|
||||
for k, v in d.items():
|
||||
if v:
|
||||
@@ -577,32 +648,53 @@ def key_for_set_value(d):
|
||||
return r
|
||||
|
||||
|
||||
def prepTimeAxis(stime, trace, verbosity=0):
|
||||
def prep_time_axis(offset, trace, verbosity=0):
|
||||
"""
|
||||
takes a starttime and a trace object and returns a valid time axis for
|
||||
takes an offset and a trace object and returns a valid time axis for
|
||||
plotting
|
||||
:param stime: start time of the actual seismogram as UTCDateTime
|
||||
:type stime: `~obspy.core.utcdatetime.UTCDateTime`
|
||||
:param offset: offset of the actual seismogram on plotting axis
|
||||
:type offset: float or int
|
||||
:param trace: seismic trace object
|
||||
:type trace: `~obspy.core.trace.Trace`
|
||||
:param verbosity: if != 0, debug output will be written to console
|
||||
:type verbosity: int
|
||||
:return: valid numpy array with time stamps for plotting
|
||||
:rtype: `~numpy.ndarray`
|
||||
|
||||
>>> tr = read()[0]
|
||||
>>> prep_time_axis(0., tr)
|
||||
array([0.00000000e+00, 1.00033344e-02, 2.00066689e-02, ...,
|
||||
2.99799933e+01, 2.99899967e+01, 3.00000000e+01])
|
||||
>>> prep_time_axis(22.5, tr)
|
||||
array([22.5 , 22.51000333, 22.52000667, ..., 52.47999333,
|
||||
52.48999667, 52.5 ])
|
||||
>>> prep_time_axis(tr.stats.starttime, tr)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AssertionError: 'offset' is not of type 'float' or 'int'; type: <class 'obspy.core.utcdatetime.UTCDateTime'>
|
||||
>>> tr.stats.npts -= 1
|
||||
>>> prep_time_axis(0, tr)
|
||||
array([0.00000000e+00, 1.00033356e-02, 2.00066711e-02, ...,
|
||||
2.99699933e+01, 2.99799967e+01, 2.99900000e+01])
|
||||
>>> tr.stats.npts += 2
|
||||
>>> prep_time_axis(0, tr)
|
||||
array([0.00000000e+00, 1.00033333e-02, 2.00066667e-02, ...,
|
||||
2.99899933e+01, 2.99999967e+01, 3.00100000e+01])
|
||||
"""
|
||||
assert isinstance(offset, (float, int)), "'offset' is not of type 'float' or 'int'; type: {}".format(type(offset))
|
||||
nsamp = trace.stats.npts
|
||||
srate = trace.stats.sampling_rate
|
||||
tincr = trace.stats.delta
|
||||
etime = stime + nsamp / srate
|
||||
time_ax = np.linspace(stime, etime, nsamp)
|
||||
etime = offset + nsamp / srate
|
||||
time_ax = np.linspace(offset, etime, nsamp)
|
||||
if len(time_ax) < nsamp:
|
||||
if verbosity:
|
||||
print('elongate time axes by one datum')
|
||||
time_ax = np.arange(stime, etime + tincr, tincr)
|
||||
time_ax = np.arange(offset, etime + tincr, tincr)
|
||||
elif len(time_ax) > nsamp:
|
||||
if verbosity:
|
||||
print('shorten time axes by one datum')
|
||||
time_ax = np.arange(stime, etime - tincr, tincr)
|
||||
time_ax = np.arange(offset, etime - tincr, tincr)
|
||||
if len(time_ax) != nsamp:
|
||||
print('Station {0}, {1} samples of data \n '
|
||||
'{2} length of time vector \n'
|
||||
@@ -618,13 +710,13 @@ def find_horizontals(data):
|
||||
:param data: waveform data
|
||||
:type data: `obspy.core.stream.Stream`
|
||||
:return: components list
|
||||
:rtype: list
|
||||
:rtype: List(str)
|
||||
|
||||
..example::
|
||||
|
||||
>>> st = read()
|
||||
>>> find_horizontals(st)
|
||||
[u'N', u'E']
|
||||
['N', 'E']
|
||||
"""
|
||||
rval = []
|
||||
for tr in data:
|
||||
@@ -635,7 +727,7 @@ def find_horizontals(data):
|
||||
return rval
|
||||
|
||||
|
||||
def pick_color(picktype, phase, quality=0):
|
||||
def pick_color(picktype: Literal['manual', 'automatic'], phase: Literal['P', 'S'], quality: int = 0) -> Rgba:
|
||||
"""
|
||||
Create pick color by modifying the base color by the quality.
|
||||
|
||||
@@ -648,7 +740,7 @@ def pick_color(picktype, phase, quality=0):
|
||||
:param quality: quality of pick. Decides the new intensity of the modifier color
|
||||
:type quality: int
|
||||
:return: tuple containing modified rgba color values
|
||||
:rtype: (int, int, int, int)
|
||||
:rtype: Rgba
|
||||
"""
|
||||
min_quality = 3
|
||||
bpc = base_phase_colors(picktype, phase) # returns dict like {'modifier': 'g', 'rgba': (0, 0, 255, 255)}
|
||||
@@ -704,17 +796,17 @@ def pick_linestyle_plt(picktype, key):
|
||||
return linestyles[picktype][key]
|
||||
|
||||
|
||||
def modify_rgba(rgba, modifier, intensity):
|
||||
def modify_rgba(rgba: Rgba, modifier: Literal['r', 'g', 'b'], intensity: float) -> Rgba:
|
||||
"""
|
||||
Modify rgba color by adding the given intensity to the modifier color
|
||||
:param rgba: tuple containing rgba values
|
||||
:type rgba: (int, int, int, int)
|
||||
:param modifier: which color should be modified, eg. 'r', 'g', 'b'
|
||||
:type modifier: str
|
||||
:type rgba: Rgba
|
||||
:param modifier: which color should be modified; options: 'r', 'g', 'b'
|
||||
:type modifier: Literal['r', 'g', 'b']
|
||||
:param intensity: intensity to be added to selected color
|
||||
:type intensity: float
|
||||
:return: tuple containing rgba values
|
||||
:rtype: (int, int, int, int)
|
||||
:rtype: Rgba
|
||||
"""
|
||||
rgba = list(rgba)
|
||||
index = {'r': 0,
|
||||
@@ -748,18 +840,20 @@ def transform_colors_mpl_str(colors, no_alpha=False):
|
||||
Transforms rgba color values to a matplotlib string of color values with a range of [0, 1]
|
||||
:param colors: tuple of rgba color values ranging from [0, 255]
|
||||
:type colors: (float, float, float, float)
|
||||
:param no_alpha: Wether to return a alpha value in the matplotlib color string
|
||||
:param no_alpha: Whether to return an alpha value in the matplotlib color string
|
||||
:type no_alpha: bool
|
||||
:return: String containing r, g, b values and alpha value if no_alpha is False (default)
|
||||
:rtype: str
|
||||
|
||||
>>> transform_colors_mpl_str((255., 255., 255., 255.), True)
|
||||
'(1.0, 1.0, 1.0)'
|
||||
>>> transform_colors_mpl_str((255., 255., 255., 255.))
|
||||
'(1.0, 1.0, 1.0, 1.0)'
|
||||
"""
|
||||
colors = list(colors)
|
||||
colors_mpl = tuple([color / 255. for color in colors])
|
||||
if no_alpha:
|
||||
colors_mpl = '({}, {}, {})'.format(*colors_mpl)
|
||||
return '({}, {}, {})'.format(*transform_colors_mpl(colors))
|
||||
else:
|
||||
colors_mpl = '({}, {}, {}, {})'.format(*colors_mpl)
|
||||
return colors_mpl
|
||||
return '({}, {}, {}, {})'.format(*transform_colors_mpl(colors))
|
||||
|
||||
|
||||
def transform_colors_mpl(colors):
|
||||
@@ -769,27 +863,16 @@ def transform_colors_mpl(colors):
|
||||
:type colors: (float, float, float, float)
|
||||
:return: tuple of rgba color values ranging from [0, 1]
|
||||
:rtype: (float, float, float, float)
|
||||
|
||||
>>> transform_colors_mpl((127.5, 0., 63.75, 255.))
|
||||
(0.5, 0.0, 0.25, 1.0)
|
||||
>>> transform_colors_mpl(())
|
||||
"""
|
||||
colors = list(colors)
|
||||
colors_mpl = tuple([color / 255. for color in colors])
|
||||
return colors_mpl
|
||||
|
||||
|
||||
def remove_underscores(data):
|
||||
"""
|
||||
takes a `obspy.core.stream.Stream` object and removes all underscores
|
||||
from station names
|
||||
:param data: stream of seismic data
|
||||
:type data: `~obspy.core.stream.Stream`
|
||||
:return: data stream
|
||||
:rtype: `~obspy.core.stream.Stream`
|
||||
"""
|
||||
# for tr in data:
|
||||
# # remove underscores
|
||||
# tr.stats.station = tr.stats.station.strip('_')
|
||||
return data
|
||||
|
||||
|
||||
def trim_station_components(data, trim_start=True, trim_end=True):
|
||||
"""
|
||||
cut a stream so only the part common to all three traces is kept to avoid dealing with offsets
|
||||
@@ -897,13 +980,53 @@ def check4doubled(data):
|
||||
return data
|
||||
|
||||
|
||||
def check_for_nan(data, nan_value=0.):
|
||||
"""
|
||||
Replace all NaNs in data with nan_value (in place)
|
||||
:param data: stream of seismic data
|
||||
:type data: `~obspy.core.stream.Stream`
|
||||
:param nan_value: value which all NaNs are set to
|
||||
:type nan_value: float, int
|
||||
:return: None
|
||||
"""
|
||||
if not data:
|
||||
return
|
||||
for trace in data:
|
||||
np.nan_to_num(trace.data, copy=False, nan=nan_value)
|
||||
|
||||
|
||||
def get_pylot_eventfile_with_extension(event, fext):
|
||||
if hasattr(event, 'path'):
|
||||
eventpath = event.path
|
||||
else:
|
||||
logging.warning('No attribute path found for event.')
|
||||
return
|
||||
eventname = event.pylot_id #path.split('/')[-1] # or event.pylot_id
|
||||
filename = os.path.join(eventpath, 'PyLoT_' + eventname + fext)
|
||||
if os.path.isfile(filename):
|
||||
return filename
|
||||
|
||||
|
||||
def get_possible_pylot_eventfile_extensions(event, fext):
|
||||
if hasattr(event, 'path'):
|
||||
eventpath = event.path
|
||||
else:
|
||||
logging.warning('No attribute path found for event.')
|
||||
return []
|
||||
eventname = event.pylot_id
|
||||
filename = os.path.join(eventpath, 'PyLoT_' + eventname + fext)
|
||||
filenames = glob.glob(filename)
|
||||
extensions = [os.path.split(path)[-1].split('PyLoT_' + eventname)[-1] for path in filenames]
|
||||
return extensions
|
||||
|
||||
|
||||
def get_stations(data):
|
||||
"""
|
||||
Get list of all station names in data stream
|
||||
Get list of all station names in data-stream
|
||||
:param data: stream containing seismic traces
|
||||
:type data: `~obspy.core.stream.Stream`
|
||||
:return: list of all station names in data, no duplicates
|
||||
:rtype: list of str
|
||||
:rtype: List(str)
|
||||
"""
|
||||
stations = []
|
||||
for tr in data:
|
||||
@@ -930,66 +1053,87 @@ def check4rotated(data, metadata=None, verbosity=1):
|
||||
:rtype: `~obspy.core.stream.Stream`
|
||||
"""
|
||||
|
||||
def rotate_components(wfstream, metadata=None):
|
||||
def rotation_required(trace_ids):
|
||||
"""
|
||||
Derive if any rotation is required from the orientation code of the input.
|
||||
|
||||
:param trace_ids: string identifier of waveform data trace
|
||||
:type trace_ids: List(str)
|
||||
:return: boolean representing if rotation is necessary for any of the traces
|
||||
:rtype: bool
|
||||
"""
|
||||
orientations = [trace_id[-1] for trace_id in trace_ids]
|
||||
return any([orientation.isnumeric() for orientation in orientations])
|
||||
|
||||
def rotate_components(wfs_in, metadata=None):
|
||||
"""
|
||||
Rotate components if orientation code is numeric (= non traditional orientation).
|
||||
|
||||
Azimut and dip are fetched from metadata. To be rotated, traces of a station have to be cut to the same length.
|
||||
Returns unrotated traces of no metadata is provided
|
||||
:param wfstream: stream containing seismic traces of a station
|
||||
:type wfstream: `~obspy.core.stream.Stream`
|
||||
:param wfs_in: stream containing seismic traces of a station
|
||||
:type wfs_in: `~obspy.core.stream.Stream`
|
||||
:param metadata: tuple containing metadata type string and metadata parser object
|
||||
:type metadata: (str, `~obspy.io.xseed.parser.Parser`)
|
||||
:return: stream object with traditionally oriented traces (ZNE)
|
||||
:rtype: `~obspy.core.stream.Stream`
|
||||
"""
|
||||
|
||||
# check if any traces in this station need to be rotated
|
||||
trace_ids = [trace.id for trace in wfstream]
|
||||
orientations = [trace_id[-1] for trace_id in trace_ids]
|
||||
rotation_required = [orientation.isnumeric() for orientation in orientations]
|
||||
if any(rotation_required):
|
||||
t_start = full_range(wfstream)
|
||||
try:
|
||||
azimuts = []
|
||||
dips = []
|
||||
for tr_id in trace_ids:
|
||||
azimuts.append(metadata.get_coordinates(tr_id, t_start)['azimuth'])
|
||||
dips.append(metadata.get_coordinates(tr_id, t_start)['dip'])
|
||||
except (KeyError, TypeError) as e:
|
||||
print('Failed to rotate trace {}, no azimuth or dip available in metadata'.format(tr_id))
|
||||
return wfstream
|
||||
if len(wfstream) < 3:
|
||||
print('Failed to rotate Stream {}, not enough components available.'.format(wfstream))
|
||||
return wfstream
|
||||
# to rotate all traces must have same length, so trim them
|
||||
wfstream = trim_station_components(wfstream, trim_start=True, trim_end=True)
|
||||
try:
|
||||
z, n, e = rotate2zne(wfstream[0], azimuts[0], dips[0],
|
||||
wfstream[1], azimuts[1], dips[1],
|
||||
wfstream[2], azimuts[2], dips[2])
|
||||
print('check4rotated: rotated trace {} to ZNE'.format(trace_ids))
|
||||
# replace old data with rotated data, change the channel code to ZNE
|
||||
z_index = dips.index(min(
|
||||
dips)) # get z-trace index, z has minimum dip of -90 (dip is measured from 0 to -90, with -90 being vertical)
|
||||
wfstream[z_index].data = z
|
||||
wfstream[z_index].stats.channel = wfstream[z_index].stats.channel[0:-1] + 'Z'
|
||||
del trace_ids[z_index]
|
||||
for trace_id in trace_ids:
|
||||
coordinates = metadata.get_coordinates(trace_id, t_start)
|
||||
dip, az = coordinates['dip'], coordinates['azimuth']
|
||||
trace = wfstream.select(id=trace_id)[0]
|
||||
if az > 315 or az <= 45 or az > 135 and az <= 225:
|
||||
trace.data = n
|
||||
trace.stats.channel = trace.stats.channel[0:-1] + 'N'
|
||||
elif az > 45 and az <= 135 or az > 225 and az <= 315:
|
||||
trace.data = e
|
||||
trace.stats.channel = trace.stats.channel[0:-1] + 'E'
|
||||
except (ValueError) as e:
|
||||
print(e)
|
||||
return wfstream
|
||||
if len(wfs_in) < 3:
|
||||
print(f"Stream {wfs_in=}, has not enough components to rotate.")
|
||||
return wfs_in
|
||||
|
||||
return wfstream
|
||||
# check if any traces in this station need to be rotated
|
||||
trace_ids = [trace.id for trace in wfs_in]
|
||||
if not rotation_required(trace_ids):
|
||||
logging.debug(f"Stream does not need any rotation: Traces are {trace_ids=}")
|
||||
return wfs_in
|
||||
|
||||
# check metadata quality
|
||||
t_start = full_range(wfs_in)
|
||||
try:
|
||||
azimuths = []
|
||||
dips = []
|
||||
for tr_id in trace_ids:
|
||||
azimuths.append(metadata.get_coordinates(tr_id, t_start)['azimuth'])
|
||||
dips.append(metadata.get_coordinates(tr_id, t_start)['dip'])
|
||||
except (KeyError, TypeError) as err:
|
||||
logging.error(f"{type(err)=} occurred: {err=} Rotating not possible, not all azimuth and dip information "
|
||||
f"available in metadata. Stream remains unchanged.")
|
||||
return wfs_in
|
||||
except Exception as err:
|
||||
print(f"Unexpected {err=}, {type(err)=}")
|
||||
raise
|
||||
|
||||
# to rotate all traces must have same length, so trim them
|
||||
wfs_out = trim_station_components(wfs_in, trim_start=True, trim_end=True)
|
||||
try:
|
||||
z, n, e = rotate2zne(wfs_out[0], azimuths[0], dips[0],
|
||||
wfs_out[1], azimuths[1], dips[1],
|
||||
wfs_out[2], azimuths[2], dips[2])
|
||||
print('check4rotated: rotated trace {} to ZNE'.format(trace_ids))
|
||||
# replace old data with rotated data, change the channel code to ZNE
|
||||
z_index = dips.index(min(
|
||||
dips)) # get z-trace index, z has minimum dip of -90 (dip is measured from 0 to -90, with -90
|
||||
# being vertical)
|
||||
wfs_out[z_index].data = z
|
||||
wfs_out[z_index].stats.channel = wfs_out[z_index].stats.channel[0:-1] + 'Z'
|
||||
del trace_ids[z_index]
|
||||
for trace_id in trace_ids:
|
||||
coordinates = metadata.get_coordinates(trace_id, t_start)
|
||||
dip, az = coordinates['dip'], coordinates['azimuth']
|
||||
trace = wfs_out.select(id=trace_id)[0]
|
||||
if az > 315 or az <= 45 or 135 < az <= 225:
|
||||
trace.data = n
|
||||
trace.stats.channel = trace.stats.channel[0:-1] + 'N'
|
||||
elif 45 < az <= 135 or 225 < az <= 315:
|
||||
trace.data = e
|
||||
trace.stats.channel = trace.stats.channel[0:-1] + 'E'
|
||||
except ValueError as err:
|
||||
print(f"{err=} Rotation failed. Stream remains unchanged.")
|
||||
return wfs_in
|
||||
|
||||
return wfs_out
|
||||
|
||||
if metadata is None:
|
||||
if verbosity:
|
||||
@@ -1003,38 +1147,6 @@ def check4rotated(data, metadata=None, verbosity=1):
|
||||
return data
|
||||
|
||||
|
||||
def scaleWFData(data, factor=None, components='all'):
|
||||
"""
|
||||
produce scaled waveforms from given waveform data and a scaling factor,
|
||||
waveform may be selected by their components name
|
||||
:param data: waveform data to be scaled
|
||||
:type data: `~obspy.core.stream.Stream` object
|
||||
:param factor: scaling factor
|
||||
:type factor: float
|
||||
:param components: components labels for the traces in data to be scaled by
|
||||
the scaling factor (optional, default: 'all')
|
||||
:type components: tuple
|
||||
:return: scaled waveform data
|
||||
:rtype: `~obspy.core.stream.Stream` object
|
||||
"""
|
||||
if components != 'all':
|
||||
for comp in components:
|
||||
if factor is None:
|
||||
max_val = np.max(np.abs(data.select(component=comp)[0].data))
|
||||
data.select(component=comp)[0].data /= 2 * max_val
|
||||
else:
|
||||
data.select(component=comp)[0].data /= 2 * factor
|
||||
else:
|
||||
for tr in data:
|
||||
if factor is None:
|
||||
max_val = float(np.max(np.abs(tr.data)))
|
||||
tr.data /= 2 * max_val
|
||||
else:
|
||||
tr.data /= 2 * factor
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def runProgram(cmd, parameter=None):
|
||||
"""
|
||||
run an external program specified by cmd with parameters input returning the
|
||||
@@ -1169,7 +1281,7 @@ def correct_iplot(iplot):
|
||||
try:
|
||||
iplot = int(iplot)
|
||||
except ValueError:
|
||||
if get_Bool(iplot):
|
||||
if get_bool(iplot):
|
||||
iplot = 2
|
||||
else:
|
||||
iplot = 0
|
||||
|
||||
+728
-239
File diff suppressed because it is too large
Load Diff
+6
-9
@@ -1,12 +1,9 @@
|
||||
# This file may be used to create an environment using:
|
||||
# $ conda create --name <env> --file <this file>
|
||||
# platform: win-64
|
||||
cartopy=0.20.2
|
||||
matplotlib-base=3.3.4
|
||||
numpy=1.22.3
|
||||
obspy=1.3.0
|
||||
pyqtgraph=0.12.4
|
||||
pyside2=5.13.2
|
||||
python=3.8.12
|
||||
qt=5.12.9
|
||||
scipy=1.8.0
|
||||
cartopy>=0.20.2
|
||||
numpy<2
|
||||
obspy>=1.3.0
|
||||
pyqtgraph>=0.12.4
|
||||
pyside2>=5.13.2
|
||||
scipy>=1.8.0
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ from pylot.core.io.phases import getQualitiesfromxml
|
||||
|
||||
class TestQualityFromXML(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.xmlpaths = ['PyLoT_e0019.048.13.xml']
|
||||
self.path = '.'
|
||||
self.ErrorsP = [0.02, 0.04, 0.08, 0.16]
|
||||
self.ErrorsS = [0.04, 0.08, 0.16, 0.32]
|
||||
self.test0_result = [[0.0136956521739, 0.0126, 0.0101612903226, 0.00734848484849, 0.0135069444444,
|
||||
@@ -23,10 +23,10 @@ class TestQualityFromXML(unittest.TestCase):
|
||||
[92.0, 4.0, 4.0, 0, 0]
|
||||
|
||||
def test_result_plotflag0(self):
|
||||
self.assertEqual(getQualitiesfromxml(self.xmlpaths, self.ErrorsP, self.ErrorsS, 0), self.test0_result)
|
||||
self.assertEqual(getQualitiesfromxml(self.path, self.ErrorsP, self.ErrorsS, 0), self.test0_result)
|
||||
|
||||
def test_result_plotflag1(self):
|
||||
self.assertEqual(getQualitiesfromxml(self.xmlpaths, self.ErrorsP, self.ErrorsS, 1), self.test1_result)
|
||||
self.assertEqual(getQualitiesfromxml(self.path, self.ErrorsP, self.ErrorsS, 1), self.test1_result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user