Merge remote-tracking branch 'origin/develop' into feature/port-to-python-3.9.0

This commit is contained in:
Marcel Paffrath 2021-02-01 17:24:51 +01:00
commit 341db86861
10 changed files with 261 additions and 68 deletions

165
PyLoT.py
View File

@ -190,6 +190,58 @@ class MainWindow(QMainWindow):
settings.setValue("output/Format", outformat)
settings.sync()
# track deleted picks for logging
self.deleted_picks = {}
# headers for event table
self.table_headers = ['', 'Event', 'Time', 'Lat', 'Lon', 'Depth', 'Ml', 'Mw', '[N] MP', '[N] AP', 'Tuning Set',
'Test Set', 'Notes']
while True:
try:
if settings.value("user/FullName", None) is None:
fulluser = QInputDialog.getText(self, "Enter Name:", "Full name")
settings.setValue("user/FullName", fulluser)
settings.setValue("user/Login", getLogin())
if settings.value("agency_id", None) is None:
agency = QInputDialog.getText(self,
"Enter authority/institution name:",
"Authority")
settings.setValue("agency_id", agency)
structure_setting = settings.value("data/Structure", "PILOT")
if not structure_setting:
structure_setting = 'PILOT'
self.dataStructure = DATASTRUCTURE[structure_setting]()
self.seismicPhase = str(settings.value("phase", "P"))
if settings.value("data/dataRoot", None) is None:
dirname = QFileDialog().getExistingDirectory(
caption='Choose data root ...')
settings.setValue("data/dataRoot", dirname)
if settings.value('useGuiFilter') is None:
settings.setValue('useGuiFilter', False)
if settings.value('output/Format', None) is None:
outformat = QInputDialog.getText(self,
"Enter output format (*.xml, *.cnv, *.obs, *_focmec.in, *.pha):",
"Format")
settings.setValue("output/Format", outformat)
if settings.value('autoFilter', None) is None:
settings.setValue('autoFilter', True)
settings.sync()
break
except Exception as e:
qmb = QMessageBox(self, icon=QMessageBox.Question,
text='Could not init application settings: {}.'
'Do you want to reset application settings?'.format(e),
windowTitle='PyLoT - Init QSettings warning')
qmb.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
qmb.setDefaultButton(QMessageBox.No)
ret = qmb.exec_()
if ret == qmb.Yes:
settings.clear()
else:
sys.exit()
print('Settings cleared!')
# setup UI
self.setupUi()
@ -1114,6 +1166,34 @@ class MainWindow(QMainWindow):
self.refreshEvents()
tabindex = self.tabs.currentIndex()
def user_modify_path(self, reason=''):
dialog = QtGui.QInputDialog(parent=self)
new_path, executed = dialog.getText(self, 'Change Project rootpath',
'{}Rename project path {}:'.format(reason, self.project.rootpath))
return new_path, executed
def data_check(self):
paths_exist = [os.path.exists(event.path) for event in self.project.eventlist]
if all(paths_exist):
return True
elif not any(paths_exist):
return False
else:
info_str = ''
for event, path_exists in zip(self.project.eventlist, paths_exist):
if not path_exists:
info_str += '\n{} exists: {}'.format(event.path, path_exists)
print('Unable to find certain event paths:{}'.format(info_str))
return True
def modify_project_path(self, new_rootpath):
self.project.rootpath = new_rootpath
for event in self.project.eventlist:
event.rootpath = new_rootpath
event.path = os.path.join(event.rootpath, event.datapath, event.database, event.pylot_id)
event.path = event.path.replace('\\', '/')
event.path = event.path.replace('//', '/')
def fill_eventbox(self, event=None, eventBox=None, select_events='all'):
'''
(Re)fill the selected eventBox (type = QtGui.QComboBox).
@ -1171,14 +1251,33 @@ class MainWindow(QMainWindow):
event_ref = event.isRefEvent()
event_test = event.isTestEvent()
time = lat = lon = depth = mag = None
if len(event.origins) == 1:
origin = event.origins[0]
time = origin.time + 0 # add 0 because there was an exception for time = 0s
lat = origin.latitude
lon = origin.longitude
depth = origin.depth
if len(event.magnitudes) == 1:
magnitude = event.magnitudes[0]
mag = magnitude.mag
# text = '{path:{plen}} | manual: [{p:3d}] | auto: [{a:3d}]'
# text = text.format(path=event_path,
# plen=plmax,
# p=event_npicks,
# a=event_nautopicks)
item_path = QtGui.QStandardItem('{path:{plen}}'.format(path=event_path, plen=plmax))
item_nmp = QtGui.QStandardItem(str(ma_count['manual']))
event_str = '{path:{plen}}'.format(path=event_path, plen=plmax)
if event.dirty:
event_str += '*'
item_path = QtGui.QStandardItem(event_str)
item_time = QtGui.QStandardItem('{}'.format(time))
item_lat = QtGui.QStandardItem('{}'.format(lat))
item_lon = QtGui.QStandardItem('{}'.format(lon))
item_depth = QtGui.QStandardItem('{}'.format(depth))
item_mag = QtGui.QStandardItem('{}'.format(mag))
item_nmp = QtGui.QStandardItem('{}({})'.format(ma_count['manual'], ma_count_total['manual']))
item_nmp.setIcon(self.manupicksicon_small)
item_nap = QtGui.QStandardItem(str(ma_count['auto']))
item_nap.setIcon(self.autopicksicon_small)
@ -1203,8 +1302,11 @@ class MainWindow(QMainWindow):
# item.setFont(font)
# item2.setForeground(QtGui.QColor('black'))
# item2.setFont(font)
itemlist = [item_path, item_nmp, item_nap, item_ref, item_test, item_notes]
if event_test and select_events == 'ref':
itemlist = [item_path, item_time, item_lat, item_lon, item_depth,
item_mag, item_nmp, item_nap, item_ref, item_test, item_notes]
for item in itemlist:
item.setTextAlignment(Qt.AlignCenter)
if event_test and select_events == 'ref' or self.isEmpty(event_path):
for item in itemlist:
item.setEnabled(False)
model.appendRow(itemlist)
@ -1236,7 +1338,7 @@ class MainWindow(QMainWindow):
self.set_fname(self.get_data().getEventFileName(), type)
return self.get_fnames(type)
def saveData(self, event=None, directory=None, outformats=['.xml', '.cnv', '.obs']):
def saveData(self, event=None, directory=None, outformats=['.xml', '.cnv', '.obs', '.focmec', '.pha']):
'''
Save event data to directory with specified output formats.
:param event: PyLoT Event, if not set current event will be used
@ -1966,8 +2068,40 @@ class MainWindow(QMainWindow):
def pickDialog(self, wfID, nextStation=False):
station = self.getStationName(wfID)
network = self.getNetworkName(wfID)
if not station:
location = self.getLocationName(wfID)
seed_id = self.getTraceID(wfID)
if button == 1:
self.pickDialog(wfID, seed_id)
elif button == 4:
self.toggle_station_color(wfID, network, station, location)
def toggle_station_color(self, wfID, network, station, location):
black_pen = pg.mkPen((0, 0, 0))
red_pen = pg.mkPen((200, 50, 50))
line_item = self.dataPlot.plotWidget.getPlotItem().listDataItems()[wfID - 1]
current_pen = line_item.opts['pen']
nsl = '{}.{}.{}'.format(network, station, location)
if current_pen == black_pen:
line_item.setPen(red_pen)
if not nsl in self.stations_highlighted:
self.stations_highlighted.append(nsl)
else:
line_item.setPen(black_pen)
if nsl in self.stations_highlighted:
self.stations_highlighted.pop(self.stations_highlighted.index(nsl))
def highlight_stations(self):
for wfID, value in self.getPlotWidget().getPlotDict().items():
station, channel, location, network = value.split('.')
nsl = '{}.{}.{}'.format(network, station, location)
if nsl in self.stations_highlighted:
self.toggle_station_color(wfID, network, station, location)
def pickDialog(self, wfID, seed_id=None):
if not seed_id:
seed_id = self.getTraceID(wfID)
network, station, location = seed_id.split('.')[:3]
if not station or not network:
return
self.update_status('picking on station {0}'.format(station))
data = self.get_data().getWFData()
@ -2682,7 +2816,7 @@ class MainWindow(QMainWindow):
item_lon = QtGui.QTableWidgetItem()
item_depth = QtGui.QTableWidgetItem()
item_mag = QtGui.QTableWidgetItem()
item_nmp = QtGui.QTableWidgetItem(str(ma_count['manual']))
item_nmp = QtGui.QTableWidgetItem('{}({})'.format(ma_count['manual'], ma_count_total['manual']))
item_nmp.setIcon(self.manupicksicon_small)
item_nap = QtGui.QTableWidgetItem(str(ma_count['auto']))
item_nap.setIcon(self.autopicksicon_small)
@ -2703,8 +2837,12 @@ class MainWindow(QMainWindow):
item_depth.setText(str(origin.depth))
if hasattr(event, 'magnitudes'):
if event.magnitudes:
magnitude = event.magnitudes[0]
item_mag.setText(str(magnitude.mag))
moment_magnitude = event.magnitudes[0]
moment_magnitude.mag = '%4.1f' % moment_magnitude.mag
local_magnitude = event.magnitudes[1]
local_magnitude.mag = '%4.1f' % local_magnitude.mag
item_momentmag.setText(str(moment_magnitude.mag))
item_localmag.setText(str(local_magnitude.mag))
item_notes.setText(event.notes)
set_enabled(item_path, True, False)
@ -2727,9 +2865,9 @@ class MainWindow(QMainWindow):
else:
item_test.setCheckState(QtCore.Qt.Unchecked)
column = [item_delete, item_path, item_time, item_lat, item_lon, item_depth, item_mag,
row = [item_delete, item_path, item_time, item_lat, item_lon, item_depth, item_mag,
item_nmp, item_nap, item_ref, item_test, item_notes]
self.project._table.append(column)
self.project._table.append(row)
for r_index, row in enumerate(self.project._table):
for c_index, item in enumerate(row):
@ -3131,6 +3269,7 @@ class Project(object):
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):
@ -3138,6 +3277,8 @@ class Project(object):
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):
'''

View File

@ -166,7 +166,7 @@ def installPyLoT(verbosity=None):
if verbosity > 1:
print('copying input files into destination folder ...')
ans = input('please specify scope of interest '
'([0]=local, 1=regional, 2=global) :') or 0
'([0]=local, 1=regional, 2=global, 3=active) :') or 0
if not isinstance(ans, int):
ans = int(ans)
if ans == 0:
@ -175,6 +175,8 @@ def installPyLoT(verbosity=None):
ans = 'regional'
elif ans == 2:
ans = 'global'
elif ans == 3:
ans = 'active'
link_dest = []
for file, destination in files_to_copy.items():
link_file = ans in file

View File

@ -522,7 +522,6 @@ def calcsourcespec(wfstream, onset, vp, delta, azimuth, incidence,
Fc = None
w0 = None
zdat = select_for_phase(wfstream, "P")
if len(zdat) == 0:
@ -537,7 +536,6 @@ def calcsourcespec(wfstream, onset, vp, delta, azimuth, incidence,
# trim traces to common range (for rotation)
trstart, trend = common_range(wfstream)
wfstream.trim(trstart, trend)
# rotate into LQT (ray-coordindate-) system using Obspy's rotate
# L: P-wave direction
# Q: SV-wave direction
@ -591,7 +589,8 @@ def calcsourcespec(wfstream, onset, vp, delta, azimuth, incidence,
#n = freq * l
# find next power of 2 of data length
m = pow(2, np.ceil(np.log(len(xdat)) / np.log(2)))
N = int(np.power(m, 2))
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])
L = (N - 1) / freq

View File

@ -366,7 +366,7 @@ class Data(object):
except KeyError as e:
raise KeyError('''{0} export format
not implemented: {1}'''.format(evtformat, e))
if fnext == '.focmec':
if fnext == '_focmec.in':
try:
infile = os.path.join(os.path.expanduser('~'), '.pylot', 'pylot.in')
print('Using default input file {}'.format(infile))

View File

@ -27,7 +27,7 @@ defaults = {'rootpath': {'type': str,
'namestring': 'Event ID'},
'extent': {'type': str,
'tooltip': 'extent of array ("local", "regional" or "global")',
'tooltip': 'extent of array ("active", "local", "regional" or "global")',
'value': 'local',
'namestring': 'Array extent'},

View File

@ -290,6 +290,15 @@ def picksdict_from_picks(evt):
phase['channel'] = channel
phase['network'] = network
phase['picker'] = picker
try:
if pick.polarity == 'positive':
phase['fm'] = 'U'
elif pick.polarity == 'negative':
phase['fm'] = 'D'
else:
phase['fm'] = 'N'
except:
print("No FM info available!")
phase['fm'] = 'N'
phase['filter_id'] = filter_id if filter_id is not None else ''
@ -350,19 +359,18 @@ def picks_from_picksdict(picks, creation_info=None):
warnings.warn(str(e), RuntimeWarning)
filter_id = ''
pick.filter_id = filter_id
try:
polarity = phase['fm']
if polarity == 'U' or '+':
polarity = picks[station][label]['fm']
if polarity == 'U' or polarity == '+':
pick.polarity = 'positive'
elif polarity == 'D' or '-':
elif polarity == 'D' or polarity == '-':
pick.polarity = 'negative'
else:
pick.polarity = 'undecidable'
except KeyError as e:
if 'fm' in str(e): # no polarity information found for this phase
pass
else:
raise e
except:
pick.polarity = 'undecidable'
print("No polarity information available!")
picks_list.append(pick)
return picks_list
@ -564,7 +572,9 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
sweight = 0 # do not use pick
except KeyError as e:
print(str(e) + '; no weight set during processing')
fid.write('%s ? ? ? S %s %d%02d%02d %02d%02d %7.4f GAU 0 0 0 0 %d \n' % (key,
Ao = arrivals[key]['S']['Ao'] # peak-to-peak amplitude
#fid.write('%s ? ? ? S %s %d%02d%02d %02d%02d %7.4f GAU 0 0 0 0 %d \n' % (key,
fid.write('%s ? ? ? S %s %d%02d%02d %02d%02d %7.4f GAU 0 %9.2f 0 0 %d \n' % (key,
fm,
year,
month,
@ -572,6 +582,7 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
hh,
mm,
ss_ms,
Ao,
sweight))
fid.close()
@ -832,8 +843,15 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
print("No source origin calculated yet, thus no FOCMEC-infile creation possible!")
return
stime = eventsource['time']
# avoid printing '*' in focmec-input file
if parameter.get('eventid') == '*' or parameter.get('eventid') is None:
evID = 'e0000'
else:
evID = parameter.get('eventid')
# write header line including event information
fid.write('%s %d%02d%02d%02d%02d%02.0f %7.4f %6.4f %3.1f %3.1f\n' % (parameter.get('eventID'),
fid.write('%s %d%02d%02d%02d%02d%02.0f %7.4f %6.4f %3.1f %3.1f\n' % (evID,
stime.year, stime.month, stime.day,
stime.hour, stime.minute, stime.second,
eventsource['latitude'],

View File

@ -892,7 +892,7 @@ class AutopickStation(object):
# weight P-onset using symmetric error
self.p_results.weight = get_quality_class(self.p_results.spe, self.pickparams["timeerrorsP"])
if self.p_results.weight <= self.pickparams["minfmweight"] and self.p_results.snr >= self.pickparams["minFMSNR"]:
# if SNR is low enough, try to determine first motion of onset
# 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.iplot, self.current_figure, self.current_linecolor)

View File

@ -498,22 +498,16 @@ def getResolutionWindow(snr, extent='local'):
2 > SNR >= 1.5 -> 10 sec LRW
1.5 > SNR -> 15 sec VLRW
see also Diehl et al. 2009
:parameter: extent, can be 'local', 'regional', 'global'
>>> getResolutionWindow(0.5)
7.5
>>> getResolutionWindow(1.8)
5.0
>>> getResolutionWindow(2.3)
2.5
>>> getResolutionWindow(4)
1.0
>>> getResolutionWindow(2)
2.5
:param snr: Signal to noise ration which decides the witdth of the resolution window
:type snr: float
:param extent: can be 'active', 'local', 'regional', 'global'
:type extent: str
:return: half width of the resolution window
:rtype: float
"""
res_wins = {
'active': {'HRW': .02, 'MRW': .05, 'LRW': .1, 'VLRW': .15},
'regional': {'HRW': 2., 'MRW': 5., 'LRW': 10., 'VLRW': 15.},
'local': {'HRW': 2., 'MRW': 5., 'LRW': 10., 'VLRW': 15.},
'global': {'HRW': 40., 'MRW': 100., 'LRW': 200., 'VLRW': 300.}

View File

@ -37,7 +37,7 @@ TIMEERROR_DEFAULTS = os.path.join(os.path.expanduser('~'),
OUTPUTFORMATS = {'.xml': 'QUAKEML',
'.cnv': 'CNV',
'.obs': 'NLLOC_OBS',
'.focmec': 'FOCMEC',
'_focmec.in': 'FOCMEC',
'.pha': 'HYPODD'}
LOCTOOLS = dict(nll=nll, hyposat=hyposat, velest=velest, hypo71=hypo71, hypodd=hypodd)

View File

@ -46,11 +46,14 @@ from pylot.core.io.inputs import FilterOptions, PylotParameter
from pylot.core.pick.utils import getSNR, earllatepicker, getnoisewin, \
getResolutionWindow, getQualityFromUncertainty
from pylot.core.pick.compare import Comparison
from pylot.core.util.defaults import OUTPUTFORMATS, FILTERDEFAULTS, \
SetChannelComponents
from pylot.core.util.utils import prepTimeAxis, full_range, scaleWFData, \
demeanTrace, isSorted, findComboBoxIndex, clims, pick_linestyle_plt, pick_color_plt, \
check4rotated, check4doubled, check4gaps, remove_underscores
from pylot.core.pick.autopick import fmpicker
from pylot.core.util.defaults import OUTPUTFORMATS, FILTERDEFAULTS
from pylot.core.util.utils import prepTimeAxis, full_range, demeanTrace, isSorted, findComboBoxIndex, clims, \
pick_linestyle_plt, pick_color_plt, \
check4rotated, check4doubled, merge_stream, identifyPhase, \
loopIdentifyPhase, trim_station_components, transformFilteroptions2String, \
identifyPhaseID, get_Bool, get_None, pick_color, getAutoFilteroptions, SetChannelComponents,\
station_id_remove_channel
from autoPyLoT import autoPyLoT
from pylot.core.util.thread import Thread
@ -1903,13 +1906,32 @@ class PickDlg(QDialog):
pick - stime_diff, verbosity=1)
mpp = stime + pick
if epp:
epp = stime + epp + stime_diff
if lpp:
lpp = stime + lpp + stime_diff
noise_win, gap_win, signal_win = self.getNoiseWin(phase)
snr, snrDB, noiselevel = getSNR(wfdata, (noise_win, gap_win, signal_win), pick - stime_diff)
print('SNR of final pick: {}'.format(snr))
if snr < 1.5:
QMessageBox.warning(self, 'SNR too low', 'WARNING! SNR of final pick below 1.5! SNR = {}'.format(snr))
# get first motion and quality classes
FM = ''
if self.getPhaseID(phase) == 'P':
# get first motion quality of P onset is sufficeint
minFMweight = parameter.get('minfmweight')
minFMSNR = parameter.get('minFMSNR')
quality = get_quality_class(spe, parameter.get('timeerrorsP'))
if quality <= minFMweight and snr >= minFMSNR:
FM = fmpicker(self.getWFData().select(channel=channel), wfdata, parameter.get('fmpickwin'),
pick -stime_diff)
# save pick times for actual phase
phasepicks = dict(epp=epp, lpp=lpp, mpp=mpp, spe=spe,
phasepicks = dict(epp=epp, lpp=lpp, mpp=mpp, spe=spe, fm=FM,
picker='manual', channel=channel,
network=wfdata[0].stats.network)
@ -1943,7 +1965,13 @@ class PickDlg(QDialog):
self.disconnectPressEvent()
self.enable_ar_buttons()
self.zoomAction.setEnabled(True)
#self.pick_block = self.togglePickBlocker()
# self.pick_block = self.togglPickBlocker()
# self.resetZoom()
noise_win, gap_win, signal_win = self.getNoiseWin(phase)
snr, snrDB, noiselevel = getSNR(wfdata, (noise_win, gap_win, signal_win), pick - stime_diff)
print('SNR of final pick: {}'.format(snr))
if snr < 1.5:
QMessageBox.warning(self, 'SNR too low', 'WARNING! SNR of final pick below 1.5! SNR = {}'.format(snr))
self.leave_picking_mode()
self.setDirty(True)
@ -2017,16 +2045,27 @@ class PickDlg(QDialog):
elif picktype == 'auto':
color = pick_color_plt(picktype, phaseID, quality)
linestyle_mpp, width_mpp = pick_linestyle_plt(picktype, 'mpp')
if not textOnly:
ax.plot(mpp, ylims[1], color=color, marker='v')
ax.plot(mpp, ylims[0], color=color, marker='^')
ax.vlines(mpp, ylims[0], ylims[1], color=color, linestyle=linestyle_mpp, linewidth=width_mpp,
picker=5, label='{}-Autopick (quality: {})'.format(phase, quality))
vl = ax.axvline(mpp, ylims[0], ylims[1], color=color, linestyle=linestyle_mpp, linewidth=width_mpp,
label='{}-{}-Pick (quality: {})'.format(phase, picktype, quality), picker=5,
zorder=baseorder + 9)
phaseLineKey = '{}-{}'.format(phase, picktype)
self.phaseLines[phaseLineKey] = vl
if spe:
ax.fill_between([mpp - spe, mpp + spe], ylims[0], ylims[1],
alpha=.25, color=color, label='{}-{}-SPE'.format(phase, picktype), zorder=baseorder + 1)
if picks['epp']:
linestyle_epp, width_epp = pick_linestyle_plt(picktype, 'epp')
ax.axvline(epp, ylims[0], ylims[1], color=color, linestyle=linestyle_epp,
linewidth=width_epp, label='{}-{}-EPP'.format(phase, picktype), zorder=baseorder + 2)
if picks['lpp']:
linestyle_lpp, width_lpp = pick_linestyle_plt(picktype, 'lpp')
ax.axvline(lpp, ylims[0], ylims[1], color=color, linestyle=linestyle_lpp,
linewidth=width_lpp, label='{}-{}-LPP'.format(phase, picktype), zorder=baseorder + 2)
if picktype == 'auto':
ax.plot(mpp, ylims[1], color=color, marker='v', zorder=baseorder + 3)
ax.plot(mpp, ylims[0], color=color, marker='^', zorder=baseorder + 3)
# append phase text (if textOnly: draw with current ylims)
self.phaseText.append(ax.text(mpp, ylims[1], phase, color=color))
else:
raise TypeError('Unknown picktype {0}'.format(picktype))
self.phaseText.append(ax.text(mpp, ylims[1], phase, color=color, zorder=baseorder + 10))
ax.legend(loc=1)
def connect_pick_delete(self):