63 Commits

Author SHA1 Message Date
6cce05b035 Merge branch 'feature/dae' into develop
# Conflicts:
#	pylot/core/io/data.py
#	pylot/core/util/widgets.py
2024-06-12 16:19:21 +02:00
7326f061e5 [minor] inform if station coordinates were not found in metadata 2024-06-12 16:11:53 +02:00
1a18401fe3 [bugfix] added missing Parameter object in call for picksdict_from_picks 2024-06-12 13:44:02 +02:00
ec930dbc12 [minor] removed unneeded imports 2024-06-07 15:56:05 +02:00
b991f771af [bugfix] removing redundancy and wrong bullsh.. try-except code 2024-06-07 15:04:16 +02:00
2c3b1876ab [minor] switch default cmap for array_map to 'viridis' 2024-06-07 14:34:27 +02:00
0acd23d4d0 [update] further improved Pickfile selection dialog, now providing methods "overwrite" or "merge" 2024-06-07 14:32:57 +02:00
f349c8bc7e [update] improve pickfile selection, give the ability to select only specific files 2024-06-07 13:09:34 +02:00
6688ef845d [bugfix] re-implement ability of get_bool to return unidentifiable input 2024-06-07 13:08:51 +02:00
5b18e9ab71 [merge] merge branch 'improve-util-utils' of pull request #35 into develop 2024-06-07 10:29:39 +02:00
c79e886d77 [minor] update default shell script for SGE 2024-06-06 15:55:47 +02:00
76f2d5d972 [update] improve SearchForFileExtensionDialog now proposing available file endings 2024-06-06 15:54:36 +02:00
2d08fd029d [hotfix] datetime formatting caused error when time not set 2024-06-06 13:55:11 +02:00
8f22d438d3 [update] changed eventbox overview to show P and S onsets separately 2024-06-05 16:18:25 +02:00
93b7de3baa [update] raising PickingFailedException when CF cannot be calculated due to missing signal (too short waveform)
[update] raising PickingFailedException when CF cannot be calculated due to missing signal (too short waveform)
2024-06-05 14:31:09 +02:00
05642e775b [minor] some tweaks (convenience)
[update] raising PickingFailedException when CF cannot be calculated due to missing signal (too short waveform)
2024-06-05 14:31:07 +02:00
47205ca493 [update] improved calculation of smoothed AIC. Old code always created an artificial value and a np.nan at the array start 2024-06-05 14:31:07 +02:00
5c7f0b56eb [update] improved SearchFileByExtensionDialog widget 2024-06-05 14:19:17 +02:00
c574031931 [bugfix] the implementation approach of STA/LTA inside characteristic function calculation (skewness/kurtosis) corrupted the old, working code due to a mistake in the logic 2024-06-05 14:17:57 +02:00
e1a0fde619 [update] improved array map to identify and display other phase names than P / S 2024-05-29 11:43:31 +02:00
48d196df11 [update] improved SearchFileByExtensionDialog widget (table, auto refresh) 2024-05-29 11:42:10 +02:00
6cc9cb4a96 [minor] reduced maxtasksperchild for multiprocessing 2024-05-29 11:40:34 +02:00
5eab686445 [bugfix] accidentally removed return value 2024-05-24 16:10:32 +02:00
b12e7937ac [update] added a widget for loading pick files that lists available files depending on chosen filemask 2024-05-22 10:58:07 +02:00
78f2dbcab2 [update] added check for nan values in waveforms which crashed obspy filter routines
[minor] some tweaks and optimisations
2024-04-30 15:48:54 +02:00
8b95c7a0fe [update] changed sorting of traces overview if all station names are numeric (e.g. active experiments) 2024-04-09 16:02:31 +02:00
65dbaad446 [update] adding possibility to display other waveform data (e.g. denoised/synthetic) together with genuine data for comparison 2024-03-22 17:12:04 +01:00
5b97d51517 [minor] mpl.figure.canvas.draw -> draw_idle 2024-03-22 17:12:04 +01:00
f03ace75e7 [bugfix] QWidget.show() killed figure axis dimensions creating unexpected error of fig.aspect=0 when creating colorbar inset_axes in Python 3.11 2024-03-22 17:10:04 +01:00
9c78471d20 [bugfix] header resize method renamed in QT5 2024-03-22 15:34:05 +01:00
09d2fb1022 [bugfix] pt2 of fmpicker fix, make sure to also copy stream in autoPyLoT
closes #24
2023-08-24 12:55:30 +02:00
3cae6d3a78 [bugfix] use copies of wfdata when calling fmpicker to prevent modification of actual data used inside GUI 2023-08-24 11:28:30 +02:00
2e85d083a3 [bugfix] do not call calcsourcespec if incidence angle is outside bounds (for whatever reason) 2023-08-24 11:27:30 +02:00
ba4e6cfe50 [bugfix] bin directory + /bin creates "/bin/bin". Also it is not taken care of os compatibility and also compatibility with existing code (line 86ff was useless after recent change in line 85) 2023-08-23 14:48:21 +02:00
1f16d01648 [minor] give more precise user warning if no pick channel was selected 2023-08-23 09:38:16 +02:00
3069e7d526 [minor] commented - possibly unnecessary - line of code that created an error when using old metadata Parser 2023-08-22 15:53:49 +02:00
a9aeb7aaa3 [bugfix] set simple phase hint (P or S) 2023-08-22 15:53:49 +02:00
b9adb182ad [bugfix] could not handle asterisk-marked events when opening tune-autopicker 2023-08-22 15:53:49 +02:00
a823eb2440 [workaround] using explicit Exception definition without a special handling does not make sense. Function broke on other errors in polyfit. Still might need fixes in the two lines above the "except" block(s). 2023-08-22 15:53:49 +02:00
486e3dc9c3 Merge pull request 'Disabled button in case flag is false' (#31) from disable-show-log-widget into develop
Reviewed-on: #31
2023-08-22 12:05:33 +02:00
8d356050d7 [update] corrected original authors of PILOT 2023-08-22 12:01:51 +02:00
43cab3767f [Bugfix] fixxed wrong check for taupymodel 2023-06-27 08:04:00 +02:00
b3fdbc811e Merge branch 'develop' into improve-util-utils 2023-06-23 09:37:54 +02:00
a1f6c5ffca Bugfixxes, spectogram tab wip 2023-06-14 13:11:54 +02:00
e4e7afa996 Minor changes to adjust to python 3. Temporary Fix for file exporting not working properly. WIP spectrogram view. 2023-04-27 10:24:55 +02:00
9fce4998d3 bugfix: remove unused functions; correct for wrong formatting (PEP) 2023-04-23 22:05:11 +02:00
c468bfbe84 feat: add type hints and tests for plot utils 2023-04-23 21:37:20 +02:00
4861d33e9a bugfix: add tests to key_for_set_value 2023-04-16 09:58:51 +02:00
f5f4635c3d bugfix: rename is_iterable and add doc tests 2023-04-16 09:50:42 +02:00
b12d92eebb bugfix: refactor get_owner and get_hash; add tests 2023-04-12 21:22:58 +02:00
e9da81376e bugfix: add new tests and refactor get_none 2023-04-12 20:32:44 +02:00
e68fc849f0 bugfix: correct erroneous and add new doctests 2023-04-10 19:14:23 +02:00
efb117177c bugfix: update check4rotate 2023-04-10 18:35:58 +02:00
0634d24814 fix: disabled button in case flag is false
The button was not disabled in case the flag variable was false. The get_Bool function was renamed and improved to also work in case in the input variable is of type int or float.

Additionally, the environment file was corrected to also work for macOS installations with ARM architecture.
2023-04-06 16:40:20 +02:00
43c2b97b3d Small changes 2023-01-24 11:45:12 +01:00
ann-christin
8d94440e77 [bugfix] logwidget always initiated 2022-11-14 14:14:59 +01:00
ann-christin
66b7dea706 [update] pylot.in no longer mandatory 2022-11-14 14:14:12 +01:00
ann-christin
ebf6d4806a [minor] reformating 2022-11-14 11:52:25 +01:00
ann-christin
207d0b3a6f [update] directly pass args from arg parser 2022-11-14 11:18:15 +01:00
ann-christin
3b3bbc29d1 Merge remote-tracking branch 'origin/develop' into develop 2022-11-14 10:30:38 +01:00
ann-christin
a8c6f4c972 [reformat] spell checking 2022-08-25 15:31:08 +02:00
ann-christin
0d91f9e3fe update github link 2022-08-25 14:03:05 +02:00
ann-christin
494d281d61 update github link 2022-08-25 14:00:37 +02:00
21 changed files with 1159 additions and 613 deletions

283
PyLoT.py
View File

@@ -25,6 +25,7 @@ https://www.iconfinder.com/iconsets/flavour
import argparse
import json
import logging
import os
import platform
import shutil
@@ -60,7 +61,7 @@ except ImportError:
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from pylot.core.analysis.magnitude import LocalMagnitude, MomentMagnitude, calcsourcespec
from pylot.core.analysis.magnitude import LocalMagnitude, MomentMagnitude
from pylot.core.io.data import Data
from pylot.core.io.inputs import FilterOptions, PylotParameter
from autoPyLoT import autoPyLoT
@@ -72,11 +73,11 @@ from pylot.core.util.errors import DatastructureError, \
OverwriteError
from pylot.core.util.connection import checkurl
from pylot.core.util.dataprocessing import Metadata, restitute_data
from pylot.core.util.utils import fnConstructor, getLogin, \
from pylot.core.util.utils import fnConstructor, get_login, \
full_range, readFilterInformation, pick_color_plt, \
pick_linestyle_plt, identifyPhaseID, excludeQualityClasses, \
transform_colors_mpl, transform_colors_mpl_str, getAutoFilteroptions, check_all_obspy, \
check_all_pylot, get_Bool, get_None
check_all_pylot, get_bool, get_none
from pylot.core.util.gui import make_pen
from pylot.core.util.event import Event
from pylot.core.io.location import create_creation_info, create_event
@@ -84,7 +85,7 @@ from pylot.core.util.widgets import FilterOptionsDialog, NewEventDlg, \
PylotCanvas, WaveformWidgetPG, PropertiesDlg, HelpForm, createAction, PickDlg, \
ComparisonWidget, TuneAutopicker, PylotParaBox, AutoPickDlg, CanvasWidget, AutoPickWidget, \
CompareEventsWidget, ProgressBarWidget, AddMetadataWidget, SingleTextLineDialog, LogWidget, PickQualitiesFromXml, \
SourceSpecWindow, ChooseWaveFormWindow
SpectrogramTab, SearchFileByExtensionDialog
from pylot.core.util.array_map import Array_map
from pylot.core.util.structure import DATASTRUCTURE
from pylot.core.util.thread import Thread, Worker
@@ -113,21 +114,19 @@ class MainWindow(QMainWindow):
def __init__(self, parent=None, infile=None, reset_qsettings=False):
super(MainWindow, self).__init__(parent)
# check for default pylot.in-file
if not infile:
infile = os.path.join(os.path.expanduser('~'), '.pylot', 'pylot.in')
print('Using default input file {}'.format(infile))
if os.path.isfile(infile) == False:
infile = QFileDialog().getOpenFileName(caption='Choose PyLoT-input file')
if infile and os.path.isfile(infile) is False:
infile = QFileDialog().getOpenFileName(caption='Choose PyLoT-input file')[0]
if not os.path.exists(infile[0]):
if not os.path.exists(infile):
QMessageBox.warning(self, "PyLoT Warning",
"No PyLoT-input file declared!")
sys.exit(0)
self.infile = infile[0]
else:
self.infile = infile
"No PyLoT-input file declared! Using default parameters!")
infile = None
self._inputs = PylotParameter(infile)
if not infile:
self._inputs.reset_defaults()
self.infile = infile
self._props = None
self.gain = 1.
@@ -179,6 +178,7 @@ class MainWindow(QMainWindow):
self.autodata = Data(self)
self.fnames = None
self.fnames_comp = None
self._stime = None
# track deleted picks for logging
@@ -194,7 +194,7 @@ class MainWindow(QMainWindow):
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())
settings.setValue("user/Login", get_login())
if settings.value("agency_id", None) is None:
agency = QInputDialog.getText(self,
"Enter authority/institution name:",
@@ -251,7 +251,7 @@ class MainWindow(QMainWindow):
self._inputs.reset_defaults()
# check for default pylot.in-file
infile = os.path.join(pylot_config_dir, '.pylot.in')
print('Using default input file {}'.format(infile))
logging.warning('Using default input file {}'.format(infile))
self._inputs.export2File(infile)
self.infile = infile
@@ -481,7 +481,7 @@ class MainWindow(QMainWindow):
"automatic pick "
"data.", False)
self.compare_action.setEnabled(False)
self.qualities_action = self.createAction(parent=self, text='Show pick qualitites...',
self.qualities_action = self.createAction(parent=self, text='Show pick qualities...',
slot=self.pickQualities, shortcut='Alt+Q',
icon=qualities_icon, tip='Histogram of pick qualities')
self.qualities_action.setEnabled(False)
@@ -494,7 +494,6 @@ class MainWindow(QMainWindow):
icon=eventlist_xml_icon,
tip='Create an Eventlist from a XML File')
self.eventlist_xml_action.setEnabled(False)
printAction = self.createAction(self, "&Print event ...",
self.show_event_information, QKeySequence.Print,
print_icon,
@@ -507,6 +506,8 @@ class MainWindow(QMainWindow):
logAction = self.createAction(self, "&Show Log", self.showLogWidget,
tip="""Display Log""")
logAction.setEnabled(use_logwidget)
# create button group for component selection
componentGroup = QActionGroup(self)
@@ -696,14 +697,17 @@ class MainWindow(QMainWindow):
wf_tab = QtWidgets.QWidget(self)
array_tab = QtWidgets.QWidget(self)
events_tab = QtWidgets.QWidget(self)
spectro_tab = QtWidgets.QWidget(self)
# init main widgets layouts
self.wf_layout = QtWidgets.QVBoxLayout()
self.array_layout = QtWidgets.QVBoxLayout()
self.events_layout = QtWidgets.QVBoxLayout()
self.spectro_layout = QtWidgets.QVBoxLayout()
wf_tab.setLayout(self.wf_layout)
array_tab.setLayout(self.array_layout)
events_tab.setLayout(self.events_layout)
spectro_tab.setLayout(self.spectro_layout)
# tighten up layouts inside tabs
for layout in [self.wf_layout, self.array_layout, self.events_layout]:
@@ -714,12 +718,14 @@ class MainWindow(QMainWindow):
self.tabs.addTab(wf_tab, 'Waveform Plot')
self.tabs.addTab(array_tab, 'Array Map')
self.tabs.addTab(events_tab, 'Eventlist')
self.tabs.addTab(spectro_tab, 'Spectro')
self.wf_layout.addWidget(self.no_data_label)
self.wf_layout.addWidget(self.wf_scroll_area)
self.wf_scroll_area.setWidgetResizable(True)
self.init_array_tab()
self.init_event_table()
self.init_spectro_tab()
self.tabs.setCurrentIndex(0)
self.eventLabel = QLabel()
@@ -732,19 +738,13 @@ class MainWindow(QMainWindow):
_widget.setLayout(self._main_layout)
_widget.showFullScreen()
if use_logwidget:
self.logwidget = LogWidget(parent=None)
self.logwidget.show()
self.stdout = self.logwidget.stdout
self.stderr = self.logwidget.stderr
sys.stdout = self.stdout
sys.stderr = self.stderr
# Not sure why but the lines above kept messing with the Ouput even with use_logwidget disabled
sys.stdout = self.stdout
sys.stderr = self.stderr
self.setCentralWidget(_widget)
# Need to store PickQualities Window somewhere so it doesnt disappear
@@ -1004,18 +1004,17 @@ class MainWindow(QMainWindow):
return
refresh = False
events = self.project.eventlist
sld = SingleTextLineDialog(label='Specify file extension: ', default_text='.xml')
sld = SearchFileByExtensionDialog(label='Specify file extension: ', default_text='.xml',
events=events)
if not sld.exec_():
return
fext = sld.lineEdit.text()
# fext = '.xml'
filenames = sld.getChecked()
for event in events:
path = event.path
eventname = path.split('/')[-1] # or event.pylot_id
filename = os.path.join(path, 'PyLoT_' + eventname + fext)
if os.path.isfile(filename):
self.load_data(filename, draw=False, event=event, overwrite=True)
refresh = True
for filename in filenames:
if os.path.isfile(filename) and event.pylot_id in filename:
self.load_data(filename, draw=False, event=event, ask_user=True, merge_strategy=sld.merge_strategy)
refresh = True
if not refresh:
return
if self.get_current_event().pylot_picks:
@@ -1023,8 +1022,8 @@ class MainWindow(QMainWindow):
self.fill_eventbox()
self.setDirty(True)
def load_data(self, fname=None, loc=False, draw=True, event=None, overwrite=False):
if not overwrite:
def load_data(self, fname=None, loc=False, draw=True, event=None, ask_user=False, merge_strategy='Overwrite'):
if not ask_user:
if not self.okToContinue():
return
if fname is None:
@@ -1038,9 +1037,12 @@ class MainWindow(QMainWindow):
data = Data(self, event)
try:
data_new = Data(self, evtdata=str(fname))
# MP MP commented because adding several picks might cause inconsistencies
data = data_new
# data += data_new
if merge_strategy == 'Overwrite':
data = data_new
elif merge_strategy == 'Merge':
data += data_new
else:
raise NotImplementedError(f'Unknown merge strategy: {merge_strategy}')
except ValueError:
qmb = QMessageBox(self, icon=QMessageBox.Question,
text='Warning: Missmatch in event identifiers {} and {}. Continue?'.format(
@@ -1128,16 +1130,19 @@ class MainWindow(QMainWindow):
else:
return
def getWFFnames_from_eventbox(self, eventbox=None):
def getWFFnames_from_eventbox(self, eventbox: str = None, subpath: str = None) -> list:
'''
Return waveform filenames from event in eventbox.
'''
# TODO: add dataStructure class for obspyDMT here, this is just a workaround!
eventpath = self.get_current_event_path(eventbox)
basepath = eventpath.split(os.path.basename(eventpath))[0]
if subpath:
eventpath = os.path.join(eventpath, subpath)
if not os.path.isdir(eventpath):
return []
if self.dataStructure:
if not eventpath:
return
return []
fnames = [os.path.join(eventpath, f) for f in os.listdir(eventpath)]
else:
raise DatastructureError('not specified')
@@ -1181,7 +1186,7 @@ class MainWindow(QMainWindow):
'''
if not self.project:
self.createNewProject()
ed = getExistingDirectories(self, 'Select event directories...')
ed = GetExistingDirectories(self, 'Select event directories...')
if ed.exec_():
eventlist = [event for event in ed.selectedFiles() if not event.endswith('EVENTS-INFO')]
basepath = eventlist[0].split(os.path.basename(eventlist[0]))[0]
@@ -1382,7 +1387,7 @@ class MainWindow(QMainWindow):
index = eventBox.currentIndex()
tv = QtWidgets.QTableView()
header = tv.horizontalHeader()
header.setResizeMode(QtWidgets.QHeaderView.ResizeToContents)
header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
header.setStretchLastSection(True)
header.hide()
tv.verticalHeader().hide()
@@ -1402,25 +1407,28 @@ class MainWindow(QMainWindow):
for id, event in enumerate(self.project.eventlist):
event_path = event.path
phaseErrors = {'P': self._inputs['timeerrorsP'],
'S': self._inputs['timeerrorsS']}
#phaseErrors = {'P': self._inputs['timeerrorsP'],
# 'S': self._inputs['timeerrorsS']}
ma_props = {'manual': event.pylot_picks,
'auto': event.pylot_autopicks}
ma_count = {'manual': 0,
'auto': 0}
ma_count_total = {'manual': 0,
'auto': 0}
man_au_picks = {'manual': event.pylot_picks,
'auto': event.pylot_autopicks}
npicks = {'manual': {'P': 0, 'S': 0},
'auto': {'P': 0, 'S': 0}}
npicks_total = {'manual': {'P': 0, 'S': 0},
'auto': {'P': 0, 'S': 0}}
for ma in ma_props.keys():
if ma_props[ma]:
for picks in ma_props[ma].values():
for ma in man_au_picks.keys():
if man_au_picks[ma]:
for picks in man_au_picks[ma].values():
for phasename, pick in picks.items():
if not type(pick) in [dict, AttribDict]:
continue
phase_ID = identifyPhaseID(phasename)
if not phase_ID in npicks[ma].keys():
continue
if pick.get('spe'):
ma_count[ma] += 1
ma_count_total[ma] += 1
npicks[ma][phase_ID] += 1
npicks_total[ma][phase_ID] += 1
event_ref = event.isRefEvent()
event_test = event.isTestEvent()
@@ -1455,16 +1463,23 @@ class MainWindow(QMainWindow):
if event.dirty:
event_str += '*'
item_path = QStandardItem(event_str)
item_time = QStandardItem('{}'.format(time))
item_time = QStandardItem('{}'.format(time.strftime("%Y-%m-%d %H:%M:%S") if time else ''))
item_lat = QStandardItem('{}'.format(lat))
item_lon = QStandardItem('{}'.format(lon))
item_depth = QStandardItem('{}'.format(depth))
item_localmag = QStandardItem('{}'.format(localmag))
item_momentmag = QStandardItem('{}'.format(momentmag))
item_nmp = QStandardItem('{}({})'.format(ma_count['manual'], ma_count_total['manual']))
item_nmp = QStandardItem()
item_nap = QStandardItem()
item_nmp.setIcon(self.manupicksicon_small)
item_nap = QStandardItem('{}({})'.format(ma_count['auto'], ma_count_total['auto']))
item_nap.setIcon(self.autopicksicon_small)
for picktype, item_np in [('manual', item_nmp), ('auto', item_nap)]:
npicks_str = f"{npicks[picktype]['P']}|{npicks[picktype]['S']}"
#npicks_str += f"({npicks_total[picktype]['P']}/{npicks_total[picktype]['S']})"
item_np.setText(npicks_str)
item_ref = QStandardItem() # str(event_ref))
item_test = QStandardItem() # str(event_test))
if event_ref:
@@ -1679,20 +1694,20 @@ class MainWindow(QMainWindow):
def pickQualities(self):
path = self.get_current_event_path()
(_, _, plot) = getQualitiesfromxml(path, self._inputs.get('timeerrorsP'), self._inputs.get('timeerrorsS'),plotflag=1)
(_, plot) = getQualitiesfromxml(path, self._inputs.get('timeerrorsP'), self._inputs.get('timeerrorsS'),plotflag=1)
self.pickQualitiesWindow = PickQualitiesFromXml(figure=plot, path=self.get_current_event_path(),inputVar=self._inputs)
self.pickQualitiesWindow.showUI()
return
# WIP JG
def eventlistXml2(self):
def eventlistXml(self):
path = self._inputs['rootpath'] + '/' + self._inputs['datapath'] + '/' + self._inputs['database']
outpath = self.project.location[:self.project.location.rfind('/')]
geteventlistfromxml(path, outpath)
return
# WIP JG
def eventlistXml(self):
# WIP JG
def spectogramView(self):
global test
stations = []
names = []
@@ -1707,12 +1722,25 @@ class MainWindow(QMainWindow):
for tr in self.get_data().wfdata.select(component=ch).traces:
traces[tr.stats.station][ch] = tr
names.sort()
a = self.get_current_event()
test = ChooseWaveFormWindow(WaveForms=names, traces=traces, stream=self.get_data())
#self.get_data().wfdata.spectrogram()
test.show()
print (self.get_data().wfdata.traces[0])
test = SpectrogramTab(traces, self.get_data().wfdata)
height = self.tabs.widget(0).height()
width = self.tabs.widget(0).width()
self.tabs.setCurrentIndex(3)
figCanvas = test.makeSpecFig(direction=self.dispComponent, height = height, width = width, parent = self.tabs.widget)
return figCanvas
#self.spectro_layout.addWidget()
# self.get_data().wfdata.spectrogram()
# self.tabs.addTab(figCanvas, 'Spectrogram')
# self.tabs[3] = figCanvas
# self.refreshTabs()
# test.show()
def compareMulti(self):
if not self.compareoptions:
@@ -1872,6 +1900,7 @@ class MainWindow(QMainWindow):
# which will read in data input twice. Therefore current tab is changed to 0
# in loadProject before calling this function.
self.fill_eventbox()
#print(f'{self.get_current_event()=}')
plotted = False
if self.tabs.currentIndex() == 2:
self.init_event_table()
@@ -1899,6 +1928,12 @@ class MainWindow(QMainWindow):
self.newWF(plot=False)
self.update_obspy_dmt()
self.refresh_array_map()
if self.tabs.currentIndex() == 3:
if self.spectroWidget != None:
self.spectro_layout.removeWidget(self.spectroWidget)
newSpectroWidget = self.spectogramView()
self.spectro_layout.addWidget(newSpectroWidget)
self.spectroWidget = newSpectroWidget
def newWF(self, event=None, plot=True):
'''
@@ -1929,13 +1964,20 @@ class MainWindow(QMainWindow):
def prepareLoadWaveformData(self):
self.fnames = self.getWFFnames_from_eventbox()
self.fnames_syn = []
self.fnames_comp = []
fnames_comp = self.getWFFnames_from_eventbox(subpath='compare')
self.dataPlot.activateCompareOptions(bool(fnames_comp))
if fnames_comp:
if self.dataPlot.comp_checkbox.isChecked():
self.fnames_comp = fnames_comp
eventpath = self.get_current_event_path()
basepath = eventpath.split(os.path.basename(eventpath))[0]
self.obspy_dmt = check_obspydmt_structure(basepath)
self.dataPlot.activateObspyDMToptions(self.obspy_dmt)
if self.obspy_dmt:
self.prepareObspyDMT_data(eventpath)
self.dataPlot.activateCompareOptions(True)
def loadWaveformData(self):
'''
@@ -1950,6 +1992,8 @@ class MainWindow(QMainWindow):
# ans = False
settings = QSettings()
# process application events to wait for event items to appear in event box
QApplication.processEvents()
curr_event = self.get_current_event()
if not curr_event:
print('Could not find current event. Try reload?')
@@ -1957,8 +2001,8 @@ class MainWindow(QMainWindow):
if len(curr_event.origins) > 0:
origin_time = curr_event.origins[0].time
tstart = settings.value('tstart') if get_None(settings.value('tstart')) else 0
tstop = settings.value('tstop') if get_None(settings.value('tstop')) else 0
tstart = settings.value('tstart') if get_none(settings.value('tstart')) else 0
tstop = settings.value('tstop') if get_none(settings.value('tstop')) else 0
tstart = origin_time + float(tstart)
tstop = origin_time + float(tstop)
else:
@@ -1966,7 +2010,7 @@ class MainWindow(QMainWindow):
tstop = None
self.data.setWFData(self.fnames,
self.fnames_syn,
self.fnames_comp,
checkRotated=True,
metadata=self.metadata,
tstart=tstart,
@@ -1974,7 +2018,7 @@ class MainWindow(QMainWindow):
def prepareObspyDMT_data(self, eventpath):
qcbox_processed = self.dataPlot.qcombo_processed
qcheckb_syn = self.dataPlot.syn_checkbox
qcheckb_syn = self.dataPlot.comp_checkbox
qcbox_processed.setEnabled(False)
qcheckb_syn.setEnabled(False)
for fpath in os.listdir(eventpath):
@@ -1982,8 +2026,8 @@ class MainWindow(QMainWindow):
if 'syngine' in fpath:
eventpath_syn = os.path.join(eventpath, fpath)
qcheckb_syn.setEnabled(True)
if self.dataPlot.syn_checkbox.isChecked():
self.fnames_syn = [os.path.join(eventpath_syn, filename) for filename in os.listdir(eventpath_syn)]
if self.dataPlot.comp_checkbox.isChecked():
self.fnames_comp = [os.path.join(eventpath_syn, filename) for filename in os.listdir(eventpath_syn)]
if 'processed' in fpath:
qcbox_processed.setEnabled(True)
if qcbox_processed.isEnabled():
@@ -2105,7 +2149,7 @@ class MainWindow(QMainWindow):
def finish_pg_plot(self):
self.getPlotWidget().updateWidget()
plots, gaps = self.wfp_thread.data
plots = self.wfp_thread.data
# do not show plot if no data are given
self.wf_scroll_area.setVisible(len(plots) > 0)
self.no_data_label.setVisible(not len(plots) > 0)
@@ -2156,6 +2200,7 @@ class MainWindow(QMainWindow):
self.locateEventAction.setEnabled(True)
self.qualities_action.setEnabled(True)
self.eventlist_xml_action.setEnabled(True)
if True in self.comparable.values():
self.compare_action.setEnabled(True)
self.draw()
@@ -2263,7 +2308,7 @@ class MainWindow(QMainWindow):
comp = self.getComponent()
title = 'section: {0} components'.format(zne_text[comp])
wfst = self.get_data().getWFData()
wfsyn = self.get_data().getSynWFData()
wfsyn = self.get_data().getAltWFdata()
if self.filterActionP.isChecked() and filter:
self.filterWaveformData(plot=False, phase='P')
elif self.filterActionS.isChecked() and filter:
@@ -2272,14 +2317,14 @@ class MainWindow(QMainWindow):
# wfst += self.get_data().getWFData().select(component=alter_comp)
plotWidget = self.getPlotWidget()
self.adjustPlotHeight()
if get_Bool(settings.value('large_dataset')) == True:
if get_bool(settings.value('large_dataset')) == True:
self.plot_method = 'fast'
else:
self.plot_method = 'normal'
rval = plotWidget.plotWFData(wfdata=wfst, wfsyn=wfsyn, title=title, mapping=False, component=comp,
nth_sample=int(nth_sample), method=self.plot_method, gain=self.gain)
plots, gaps = rval if rval else ([], [])
return plots, gaps
plots = rval if rval else []
return plots
def adjustPlotHeight(self):
if self.pg:
@@ -2575,18 +2620,21 @@ class MainWindow(QMainWindow):
print("Warning! No network, station, and location info available!")
return
self.update_status('picking on station {0}'.format(station))
data = self.get_data().getOriginalWFData().copy()
wfdata = self.get_data().getOriginalWFData().copy()
wfdata_comp = self.get_data().getAltWFdata().copy()
event = self.get_current_event()
wftype = self.dataPlot.qcombo_processed.currentText() if self.obspy_dmt else None
pickDlg = PickDlg(self, parameter=self._inputs,
data=data.select(station=station),
data=wfdata.select(station=station),
data_compare=wfdata_comp.select(station=station),
station=station, network=network,
location=location,
picks=self.getPicksOnStation(station, 'manual'),
autopicks=self.getPicksOnStation(station, 'auto'),
metadata=self.metadata, event=event,
model=self.inputs.get('taup_model'),
filteroptions=self.filteroptions, wftype=wftype)
filteroptions=self.filteroptions, wftype=wftype,
show_comp_data=self.dataPlot.comp_checkbox.isChecked())
if self.filterActionP.isChecked():
pickDlg.currentPhase = "P"
pickDlg.filterWFData()
@@ -2898,7 +2946,9 @@ class MainWindow(QMainWindow):
self.log_deleted_picks([deleted_pick])
def log_deleted_picks(self, deleted_picks, event_path=None):
''' Log deleted picks to list self.deleted_picks '''
'''
Log deleted picks to list self.deleted_picks
'''
if not event_path:
event_path = self.get_current_event_path()
for deleted_pick in deleted_picks:
@@ -2912,7 +2962,9 @@ class MainWindow(QMainWindow):
self.deleted_picks[event_path].append(deleted_pick)
def dump_deleted_picks(self, event_path):
''' Save deleted picks to json file for event in event_path. Load old file before and merge'''
'''
Save deleted picks to json file for event in event_path. Load old file before and merge
'''
try:
deleted_picks_from_file = self.load_deleted_picks(event_path)
except Exception as e:
@@ -2961,10 +3013,16 @@ class MainWindow(QMainWindow):
event = self.get_current_event()
event.pylot_picks = {}
event.pylot_autopicks = {}
picksdict = picksdict_from_picks(evt=self.get_data().get_evt_data())
picksdict = picksdict_from_picks(evt=self.get_data().get_evt_data(), parameter=self.getParameter())
event.addPicks(picksdict['manual'])
event.addAutopicks(picksdict['auto'])
def getParameter(self):
if hasattr(self.project, 'parameter') and isinstance(self.project.parameter, PylotParameter):
return self.project.parameter
else:
return self._inputs
def drawPicks(self, station=None, picktype=None, stime=None):
# if picktype not specified, draw both
if not stime:
@@ -3125,7 +3183,7 @@ class MainWindow(QMainWindow):
phasefile = os.path.join(obsdir, filename + '.obs')
lt.modify_inputs(ctrfile, locroot, filename, phasefile, ttt)
try:
lt.locate(ctrfile, self.obspy_dmt)
lt.locate(ctrfile, self._inputs)
except RuntimeError as e:
print(e.message)
# finally:
@@ -3179,7 +3237,7 @@ class MainWindow(QMainWindow):
'''
if checked: pass # dummy argument for QAction trigger signal
self.tabs.setCurrentIndex(1)
# if there is no metadata (invetories is an empty list), just initialize the default empty tab
# if there is no metadata (inventories is an empty list), just initialize the default empty tab
if not self.metadata.inventories:
self.init_array_tab()
return
@@ -3201,6 +3259,15 @@ class MainWindow(QMainWindow):
self.tabs.setCurrentIndex(index)
self.refresh_array_map()
def init_spectro_tab(self):
'''
Init spectrogram tab with currently selected event.
'''
self.spectroWidget = None
#self.spectro_layout.addWidget( self.spectogramView() )
pass
def array_map_thread(self):
'''
Start modal thread to init the array_map object.
@@ -3423,7 +3490,7 @@ class MainWindow(QMainWindow):
self.event_table.setCellWidget(r_index, c_index, item)
header = self.event_table.horizontalHeader()
header.setResizeMode(QtWidgets.QHeaderView.ResizeToContents)
header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
header.setStretchLastSection(True)
self.event_table.cellChanged[int, int].connect(cell_changed)
self.event_table.cellClicked[int, int].connect(cell_clicked)
@@ -3597,7 +3664,7 @@ class MainWindow(QMainWindow):
return True
return False
def update_status(self, message, duration=5000):
def update_status(self, message, duration=10000):
self.statusBar().showMessage(message, duration)
if self.get_data() is not None:
if not self.get_current_event() or not self.project.location:
@@ -3650,10 +3717,13 @@ class MainWindow(QMainWindow):
if not self.okToContinue():
return
if not fnm:
dlg = QFileDialog(parent=self)
settings = QSettings()
dir = settings.value('current_project_path')
dlg = QFileDialog(parent=self, directory=dir)
fnm = dlg.getOpenFileName(self, 'Open project file...', filter='Pylot project (*.plp)')[0]
if not fnm:
return
settings.setValue('current_project_path', os.path.split(fnm)[0])
if not os.path.exists(fnm):
QMessageBox.warning(self, 'Could not open file',
'Could not open project file {}. File does not exist.'.format(fnm))
@@ -3709,7 +3779,7 @@ class MainWindow(QMainWindow):
filename = fnm[0] + '.plp'
self.project.parameter = self._inputs
settings = QSettings()
autosaveXML = get_Bool(settings.value('autosaveXML', True))
autosaveXML = get_bool(settings.value('autosaveXML', True))
if autosaveXML:
self.exportEvents()
if not self.project.save(filename): return False
@@ -3733,7 +3803,7 @@ class MainWindow(QMainWindow):
self.metadata.clear_inventory()
self.project.parameter = self._inputs
settings = QSettings()
autosaveXML = get_Bool(settings.value('autosaveXML', True))
autosaveXML = get_bool(settings.value('autosaveXML', True))
if autosaveXML:
self.exportEvents()
if not self.project.save(): return False
@@ -3771,7 +3841,8 @@ class MainWindow(QMainWindow):
def closeEvent(self, event):
if self.okToContinue():
self.logwidget.close()
if hasattr(self, 'logwidget'):
self.logwidget.close()
event.accept()
else:
event.ignore()
@@ -3822,7 +3893,7 @@ class MainWindow(QMainWindow):
def helpHelp(self):
if checkurl():
form = HelpForm(self,
'https://ariadne.geophysik.ruhr-uni-bochum.de/trac/PyLoT/wiki')
'https://github.com/seismology-RUB/PyLoT')
else:
form = HelpForm(self, ':/help.html')
form.show()
@@ -4002,13 +4073,13 @@ class Project(object):
return project
class getExistingDirectories(QFileDialog):
class GetExistingDirectories(QFileDialog):
'''
File dialog with possibility to select multiple folders.
'''
def __init__(self, *args):
super(getExistingDirectories, self).__init__(*args)
super(GetExistingDirectories, self).__init__(*args)
self.setOption(self.DontUseNativeDialog, True)
self.setOption(self.ReadOnly, True)
self.setFileMode(self.Directory)
@@ -4034,16 +4105,7 @@ def create_window():
return app, app_created
def main(args=None):
project_filename = None
# args.project_filename = 'C:/Shared/AlpArray/alparray_data/project_alparray_test.plp'
pylot_infile = None
if args:
if args.project_filename:
project_filename = args.project_filename
if args.input_filename:
pylot_infile = args.input_filename
reset_qsettings = args.reset_qsettings
def main(project_filename=None, pylot_infile=None, reset_qsettings=False):
# create the Qt application
pylot_app, app_created = create_window()
@@ -4092,4 +4154,5 @@ if __name__ == "__main__":
parser.add_argument('--reset_qsettings', default=False, action='store_true',
help='reset qsettings (debug option)')
args = parser.parse_args()
sys.exit(main(args))
sys.exit(main(project_filename=args.project_filename, pylot_infile=args.input_filename,
reset_qsettings=args.reset_qsettings))

View File

@@ -99,7 +99,7 @@ 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, M. Paffrath, L. Kueperkoch, K. Olbert, M. Bischoff, C. Wollin, M. Rische, D. Arnold, K. Cökerim, S. Zimmermann

View File

@@ -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')

View File

@@ -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

View File

@@ -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

View File

@@ -418,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)

View File

@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
import copy
import logging
import os
from PySide2.QtWidgets import QMessageBox
@@ -19,7 +20,7 @@ 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
check_for_gaps_and_merge, trim_station_components, check_for_nan
class Data(object):
@@ -64,7 +65,7 @@ class Data(object):
elif 'LOC' in evtdata:
raise NotImplementedError('PILOT location information '
'read support not yet '
'implemeted.')
'implemented.')
elif 'event.pkl' in evtdata:
evtdata = qml_from_obspyDMT(evtdata)
else:
@@ -260,7 +261,6 @@ class Data(object):
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]
@@ -321,35 +321,61 @@ class Data(object):
if lendiff != 0:
print("Manual as well as automatic picks available. Prefered the {} manual ones!".format(lendiff))
no_uncertainties_p = []
no_uncertainties_s = []
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):
# Skipping pick if no upper_uncertainty is found and warning user
if picks_copy[i].time_errors['upper_uncertainty'] is None:
#print("{1} P-Pick of station {0} does not have upper_uncertainty and cant be checked".format(
# picks_copy[i].waveform_id.station_code,
# picks_copy[i].method_id))
if not picks_copy[i].waveform_id.station_code in no_uncertainties_p:
no_uncertainties_p.append(picks_copy[i].waveform_id.station_code)
continue
#print ("checking for upper_uncertainty")
if (picks_copy[i].time_errors['uncertainty'] is None) or \
(picks_copy[i].time_errors['upper_uncertainty'] >= upperErrors[0]):
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):
# Skipping pick if no upper_uncertainty is found and warning user
if picks_copy[i].time_errors['upper_uncertainty'] is None:
#print("{1} S-Pick of station {0} does not have upper_uncertainty and cant be checked".format(
#picks_copy[i].waveform_id.station_code,
#picks_copy[i].method_id))
if not picks_copy[i].waveform_id.station_code in no_uncertainties_s:
no_uncertainties_s.append(picks_copy[i].waveform_id.station_code)
continue
if (picks_copy[i].time_errors['uncertainty'] is None) or \
(picks_copy[i].time_errors['upper_uncertainty'] >= upperErrors[1]):
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
for s in no_uncertainties_p:
print("P-Pick of station {0} does not have upper_uncertainty and cant be checked".format(s))
for s in no_uncertainties_s:
print("S-Pick of station {0} does not have upper_uncertainty and cant be checked".format(s))
if fnext == '.obs':
try:
@@ -383,18 +409,16 @@ class Data(object):
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)
parameter = PylotParameter()
logging.warning('Using default input parameter')
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)
parameter = PylotParameter()
logging.warning('Using default input parameter')
hypodd.export(picks_copy, fnout + fnext, parameter, eventinfo=self.get_evt_data())
except KeyError as e:
raise KeyError('''{0} export format
@@ -426,20 +450,30 @@ class Data(object):
data.filter(**kwargs)
self.dirty = True
def setWFData(self, fnames, fnames_syn=None, checkRotated=False, metadata=None, tstart=0, tstop=0):
def setWFData(self, fnames, fnames_alt=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
:param fnames_alt: alternative data to show (e.g. synthetic/processed)
:type fnames: list
"""
def check_fname_exists(filenames: list) -> list:
if filenames:
filenames = [fn for fn in filenames if os.path.isfile(fn)]
return filenames
self.wfdata = Stream()
self.wforiginal = None
self.wfsyn = Stream()
self.wf_alt = Stream()
if tstart == tstop:
tstart = tstop = None
self.tstart = tstart
self.tstop = tstop
# remove directories
fnames = check_fname_exists(fnames)
fnames_alt = check_fname_exists(fnames_alt)
# if obspy_dmt:
# wfdir = 'raw'
# self.processed = False
@@ -457,8 +491,8 @@ class Data(object):
# wffnames = fnames
if fnames is not None:
self.appendWFData(fnames)
if fnames_syn is not None:
self.appendWFData(fnames_syn, synthetic=True)
if fnames_alt is not None:
self.appendWFData(fnames_alt, alternative=True)
else:
return False
@@ -466,7 +500,9 @@ class Data(object):
# remove possible underscores in station names
# self.wfdata = remove_underscores(self.wfdata)
# check for gaps and merge
self.wfdata = check4gapsAndMerge(self.wfdata)
self.wfdata, _ = check_for_gaps_and_merge(self.wfdata)
# check for nans
check_for_nan(self.wfdata)
# check for stations with rotated components
if checkRotated and metadata is not None:
self.wfdata = check4rotated(self.wfdata, metadata, verbosity=0)
@@ -478,7 +514,7 @@ class Data(object):
self.dirty = False
return True
def appendWFData(self, fnames, synthetic=False):
def appendWFData(self, fnames, alternative=False):
"""
Read waveform data from fnames and append it to current wf data
:param fnames: waveform data to append
@@ -491,20 +527,20 @@ class Data(object):
if self.dirty:
self.resetWFData()
real_or_syn_data = {True: self.wfsyn,
False: self.wfdata}
orig_or_alternative_data = {True: self.wf_alt,
False: self.wfdata}
warnmsg = ''
for fname in set(fnames):
try:
real_or_syn_data[synthetic] += read(fname, starttime=self.tstart, endtime=self.tstop)
orig_or_alternative_data[alternative] += 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)
orig_or_alternative_data[alternative] += 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)
orig_or_alternative_data[alternative] += 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:
@@ -519,8 +555,8 @@ class Data(object):
def getOriginalWFData(self):
return self.wforiginal
def getSynWFData(self):
return self.wfsyn
def getAltWFdata(self):
return self.wf_alt
def resetWFData(self):
"""

View File

@@ -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:

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import glob
import logging
import os
import warnings
@@ -16,7 +17,7 @@ from pylot.core.io.inputs import PylotParameter
from pylot.core.io.location import create_event, \
create_magnitude
from pylot.core.pick.utils import select_for_phase, get_quality_class
from pylot.core.util.utils import getOwner, full_range, four_digits, transformFilterString4Export, \
from pylot.core.util.utils import get_owner, full_range, four_digits, transformFilterString4Export, \
backtransformFilterString, loopIdentifyPhase, identifyPhase
@@ -58,7 +59,7 @@ def readPILOTEvent(phasfn=None, locfn=None, authority_id='RUB', **kwargs):
if phasfn is not None and os.path.isfile(phasfn):
phases = sio.loadmat(phasfn)
phasctime = UTCDateTime(os.path.getmtime(phasfn))
phasauthor = getOwner(phasfn)
phasauthor = get_owner(phasfn)
else:
phases = None
phasctime = None
@@ -66,7 +67,7 @@ def readPILOTEvent(phasfn=None, locfn=None, authority_id='RUB', **kwargs):
if locfn is not None and os.path.isfile(locfn):
loc = sio.loadmat(locfn)
locctime = UTCDateTime(os.path.getmtime(locfn))
locauthor = getOwner(locfn)
locauthor = get_owner(locfn)
else:
loc = None
locctime = None
@@ -217,7 +218,7 @@ def picksdict_from_obs(fn):
return picks
def picksdict_from_picks(evt):
def picksdict_from_picks(evt, parameter=None):
"""
Takes an Event object and return the pick dictionary commonly used within
PyLoT
@@ -230,6 +231,7 @@ def picksdict_from_picks(evt):
'auto': {}
}
for pick in evt.picks:
errors = None
phase = {}
station = pick.waveform_id.station_code
if pick.waveform_id.channel_code is None:
@@ -273,31 +275,28 @@ def picksdict_from_picks(evt):
phase['epp'] = epp
phase['lpp'] = lpp
phase['spe'] = spe
try:
phase['weight'] = weight
except:
# get onset weight from uncertainty
infile = os.path.join(os.path.expanduser('~'), '.pylot', 'pylot.in')
print('Using default input file {}'.format(infile))
parameter = PylotParameter(infile)
weight = phase.get('weight')
if not weight:
if not parameter:
logging.warning('Using ')
logging.warning('Using default input parameter')
parameter = PylotParameter()
pick.phase_hint = identifyPhase(pick.phase_hint)
if pick.phase_hint == 'P':
errors = parameter['timeerrorsP']
elif pick.phase_hint == 'S':
errors = parameter['timeerrorsS']
weight = get_quality_class(spe, errors)
phase['weight'] = weight
if errors:
weight = get_quality_class(spe, errors)
phase['weight'] = weight
phase['channel'] = channel
phase['network'] = network
phase['picker'] = pick_method
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!")
if pick.polarity == 'positive':
phase['fm'] = 'U'
elif pick.polarity == 'negative':
phase['fm'] = 'D'
else:
phase['fm'] = 'N'
phase['filter_id'] = filter_id if filter_id is not None else ''
@@ -517,7 +516,7 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
arrivals = chooseArrivals(arrivals) # MP MP what is chooseArrivals? It is not defined anywhere
for key in arrivals:
# P onsets
if arrivals[key].has_key('P'):
if 'P' in arrivals[key]:
try:
fm = arrivals[key]['P']['fm']
except KeyError as e:
@@ -551,7 +550,7 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
ss_ms,
pweight))
# S onsets
if arrivals[key].has_key('S') and arrivals[key]['S']['mpp'] is not None:
if 'S' in arrivals[key] and arrivals[key]['S']['mpp'] is not None:
fm = '?'
onset = arrivals[key]['S']['mpp']
year = onset.year
@@ -670,7 +669,7 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
arrivals = chooseArrivals(arrivals) # MP MP what is chooseArrivals? It is not defined anywhere
for key in arrivals:
# P onsets
if arrivals[key].has_key('P') and arrivals[key]['P']['mpp'] is not None:
if 'P' in arrivals[key] and arrivals[key]['P']['mpp'] is not None:
if arrivals[key]['P']['weight'] < 4:
Ponset = arrivals[key]['P']['mpp']
pyear = Ponset.year
@@ -699,7 +698,7 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
fid.write('%-5s P1 %4.0f %02d %02d %02d %02d %05.02f %5.3f -999. 0.00 -999. 0.00\n'
% (key, pyear, pmonth, pday, phh, pmm, Pss, pstd))
# S onsets
if arrivals[key].has_key('S') and arrivals[key]['S']['mpp'] is not None:
if 'S' in arrivals[key] and arrivals[key]['S']['mpp'] is not None:
if arrivals[key]['S']['weight'] < 4:
Sonset = arrivals[key]['S']['mpp']
syear = Sonset.year
@@ -768,7 +767,7 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
usedarrivals = chooseArrivals(arrivals)
for key in usedarrivals:
# P onsets
if usedarrivals[key].has_key('P'):
if 'P' in usedarrivals[key]:
if usedarrivals[key]['P']['weight'] < 4:
n += 1
stat = key
@@ -782,7 +781,7 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
else:
fid.write('%-4sP%d%6.2f\n' % (stat, Pweight, Prt))
# S onsets
if usedarrivals[key].has_key('S'):
if 'S' in usedarrivals[key]:
if usedarrivals[key]['S']['weight'] < 4:
n += 1
stat = key
@@ -828,13 +827,13 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
# prefer manual picks
usedarrivals = chooseArrivals(arrivals)
for key in usedarrivals:
if usedarrivals[key].has_key('P'):
if 'P' in usedarrivals[key]:
# P onsets
if usedarrivals[key]['P']['weight'] < 4:
Ponset = usedarrivals[key]['P']['mpp']
Prt = Ponset - stime # onset time relative to source time
fid.write('%s %6.3f 1 P\n' % (key, Prt))
if usedarrivals[key].has_key('S'):
if 'S' in usedarrivals[key]:
# S onsets
if usedarrivals[key]['S']['weight'] < 4:
Sonset = usedarrivals[key]['S']['mpp']
@@ -879,7 +878,7 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
# prefer manual picks
usedarrivals = chooseArrivals(arrivals)
for key in usedarrivals:
if usedarrivals[key].has_key('P'):
if 'P' in usedarrivals[key]:
if usedarrivals[key]['P']['weight'] < 4 and usedarrivals[key]['P']['fm'] is not None:
stat = key
for i in range(len(picks)):
@@ -961,7 +960,7 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
arrivals = chooseArrivals(arrivals) # MP MP what is chooseArrivals? It is not defined anywhere
# write phase lines
for key in arrivals:
if arrivals[key].has_key('P'):
if 'P' in arrivals[key]:
if arrivals[key]['P']['weight'] < 4 and arrivals[key]['P']['fm'] is not None:
stat = key
ccode = arrivals[key]['P']['channel']
@@ -1014,7 +1013,6 @@ def chooseArrivals(arrivals):
:return: arrivals but with the manual picks prefered if possible
"""
# If len of arrivals is greater than 2 it comes from autopicking so only autopicks are available
print("=== CHOOSE ===")
if len(arrivals) > 2:
return arrivals
if arrivals['auto'] and arrivals['manual']:

View File

@@ -82,8 +82,8 @@ def locate(fnin, parameter=None):
:param fnin: external program name
:return: None
"""
exe_path = parameter['nllocbin'] + '/bin/NLLoc'
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')

View File

@@ -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
@@ -477,7 +463,7 @@ class AutopickStation(object):
if self.pickparams["sstart"] < 0:
self.pickparams["sstart"] = 0
if get_Bool(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()
@@ -525,7 +511,7 @@ class AutopickStation(object):
:rtype: dict
"""
if get_Bool(self.pickparams['use_taup']) is True and self.origin is not None:
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
@@ -544,7 +530,7 @@ class AutopickStation(object):
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')):
get_bool(self.pickparams.get('use_taup')):
try:
self.pick_s_phase()
except MissingTraceException as mte:
@@ -660,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,
@@ -936,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)
@@ -1148,7 +1134,7 @@ class AutopickStation(object):
''.format(self.s_results.weight, self.s_results.snr, self.s_results.snrdb))
def pick_s_phase(self):
if get_Bool(self.pickparams.get('use_taup')) is True:
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

View File

@@ -20,6 +20,8 @@ import numpy as np
from scipy import signal
from obspy.core import Stream
from pylot.core.pick.utils import PickingFailedException
class CharacteristicFunction(object):
"""
@@ -293,7 +295,7 @@ class HOScf(CharacteristicFunction):
if j < 4:
LTA[j] = 0
STA[j] = 0
elif j <= ista:
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
@@ -488,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

View File

@@ -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:

View File

@@ -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)
@@ -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

View File

@@ -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,17 +474,19 @@ 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().getWFData()
wfdata_comp = self._parent.get_data().getWFDataComp()
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,

View File

@@ -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():

View File

@@ -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',

View File

@@ -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()

View File

@@ -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()

View File

@@ -1,12 +1,15 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import glob
import hashlib
import logging
import os
import platform
import re
import subprocess
import warnings
from typing import Literal, Tuple, Type
from functools import lru_cache
import numpy as np
from obspy import UTCDateTime, read
@@ -18,6 +21,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 +44,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 +88,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
@@ -120,7 +107,7 @@ def gen_Pool(ncores=0):
print('gen_Pool: Generated multiprocessing Pool with {} cores\n'.format(ncores))
pool = multiprocessing.Pool(ncores)
pool = multiprocessing.Pool(ncores, maxtasksperchild=100)
return pool
@@ -166,11 +153,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 +289,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 +301,75 @@ 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
"""
if value in ['True', 'true']:
if type(value) is 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 +383,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 +466,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 +558,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 +589,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 +627,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 +647,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 +709,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 +726,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 +739,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 +795,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 +839,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 +862,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
@@ -818,19 +900,6 @@ def trim_station_components(data, trim_start=True, trim_end=True):
return data
def merge_stream(stream):
gaps = stream.get_gaps()
if gaps:
# list of merged stations (seed_ids)
merged = ['{}.{}.{}.{}'.format(*gap[:4]) for gap in gaps]
stream.merge(method=1)
print('Merged the following stations because of gaps:')
for merged_station in merged:
print(merged_station)
return stream, gaps
def check4gapsAndRemove(data):
"""
check for gaps in Stream and remove them
@@ -851,12 +920,12 @@ def check4gapsAndRemove(data):
return data
def check4gapsAndMerge(data):
def check_for_gaps_and_merge(data):
"""
check for gaps in Stream and merge if gaps are found
:param data: stream of seismic data
:type data: `~obspy.core.stream.Stream`
:return: data stream
:return: data stream, gaps returned from obspy get_gaps
:rtype: `~obspy.core.stream.Stream`
"""
gaps = data.get_gaps()
@@ -867,7 +936,7 @@ def check4gapsAndMerge(data):
for merged_station in merged:
print(merged_station)
return data
return data, gaps
def check4doubled(data):
@@ -897,13 +966,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 +1039,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 +1133,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
@@ -1106,6 +1204,7 @@ def identifyPhase(phase):
return False
@lru_cache
def identifyPhaseID(phase):
"""
Returns phase id (capital P or S)
@@ -1169,7 +1268,7 @@ def correct_iplot(iplot):
try:
iplot = int(iplot)
except ValueError:
if get_Bool(iplot):
if get_bool(iplot):
iplot = 2
else:
iplot = 0

File diff suppressed because it is too large Load Diff