Merge branch 'develop' into feature/parameter_limits

This commit is contained in:
Darius Arnold 2018-03-07 11:16:37 +01:00
commit ef8c19b747
33 changed files with 123003 additions and 105727 deletions

210
PyLoT.py Normal file → Executable file
View File

@ -75,7 +75,7 @@ from pylot.core.util.dataprocessing import read_metadata, restitute_data
from pylot.core.util.utils import fnConstructor, getLogin, \ from pylot.core.util.utils import fnConstructor, getLogin, \
full_range, readFilterInformation, trim_station_components, check4gaps, make_pen, pick_color_plt, \ full_range, readFilterInformation, trim_station_components, check4gaps, make_pen, pick_color_plt, \
pick_linestyle_plt, remove_underscores, check4doubled, identifyPhaseID, excludeQualityClasses, has_spe, \ pick_linestyle_plt, remove_underscores, check4doubled, identifyPhaseID, excludeQualityClasses, has_spe, \
check4rotated, transform_colors_mpl, transform_colors_mpl_str check4rotated, transform_colors_mpl, transform_colors_mpl_str, getAutoFilteroptions
from pylot.core.util.event import Event from pylot.core.util.event import Event
from pylot.core.io.location import create_creation_info, create_event from pylot.core.io.location import create_creation_info, create_event
from pylot.core.util.widgets import FilterOptionsDialog, NewEventDlg, \ from pylot.core.util.widgets import FilterOptionsDialog, NewEventDlg, \
@ -182,25 +182,22 @@ class MainWindow(QMainWindow):
settings.setValue("data/dataRoot", dirname) settings.setValue("data/dataRoot", dirname)
if settings.value('compclass', None) is None: if settings.value('compclass', None) is None:
settings.setValue('compclass', SetChannelComponents()) settings.setValue('compclass', SetChannelComponents())
if settings.value('useGuiFilter') is None:
settings.setValue('useGuiFilter', False)
if settings.value('output/Format', None) is None: if settings.value('output/Format', None) is None:
outformat = QInputDialog.getText(self, outformat = QInputDialog.getText(self,
"Enter output format (*.xml, *.cnv, *.obs):", "Enter output format (*.xml, *.cnv, *.obs):",
"Format") "Format")
settings.setValue("output/Format", outformat) settings.setValue("output/Format", outformat)
if settings.value('autoFilter', None) is None:
settings.setValue('autoFilter', True)
settings.sync() settings.sync()
# setup UI # setup UI
self.setupUi() self.setupUi()
filter_info = readFilterInformation(self._inputs) self.updateFilteroptions()
p_filter = filter_info['P']
s_filter = filter_info['S']
self.filteroptions = {'P': FilterOptions(p_filter['filtertype'],
p_filter['freq'],
p_filter['order']),
'S': FilterOptions(s_filter['filtertype'],
s_filter['freq'],
s_filter['order'])}
self.loc = False self.loc = False
def setupUi(self): def setupUi(self):
@ -270,8 +267,12 @@ class MainWindow(QMainWindow):
s_icon.addPixmap(QPixmap(':/icons/key_S.png')) s_icon.addPixmap(QPixmap(':/icons/key_S.png'))
print_icon = QIcon() print_icon = QIcon()
print_icon.addPixmap(QPixmap(':/icons/printer.png')) print_icon.addPixmap(QPixmap(':/icons/printer.png'))
filter_icon = QIcon() self.filter_icon = QIcon()
filter_icon.addPixmap(QPixmap(':/icons/filter.png')) self.filter_icon.addPixmap(QPixmap(':/icons/filter.png'))
self.filter_icon_p = QIcon()
self.filter_icon_p.addPixmap(QPixmap(':/icons/filter_p.png'))
self.filter_icon_s = QIcon()
self.filter_icon_s.addPixmap(QPixmap(':/icons/filter_s.png'))
z_icon = QIcon() z_icon = QIcon()
z_icon.addPixmap(QPixmap(':/icons/key_Z.png')) z_icon.addPixmap(QPixmap(':/icons/key_Z.png'))
n_icon = QIcon() n_icon = QIcon()
@ -286,6 +287,8 @@ class MainWindow(QMainWindow):
locate_icon.addPixmap(QPixmap(':/icons/locate_button.png')) locate_icon.addPixmap(QPixmap(':/icons/locate_button.png'))
compare_icon = QIcon() compare_icon = QIcon()
compare_icon.addPixmap(QPixmap(':/icons/compare_button.png')) compare_icon.addPixmap(QPixmap(':/icons/compare_button.png'))
qualities_icon = QIcon()
qualities_icon.addPixmap(QPixmap(':/icons/pick_qualities_button.png'))
self.newProjectAction = self.createAction(self, "&New project ...", self.newProjectAction = self.createAction(self, "&New project ...",
self.createNewProject, self.createNewProject,
QKeySequence.New, newIcon, QKeySequence.New, newIcon,
@ -366,15 +369,23 @@ class MainWindow(QMainWindow):
self.setParameter, self.setParameter,
None, paraIcon, None, paraIcon,
"Modify Parameter") "Modify Parameter")
self.filterAction = self.createAction(self, "&Filter ...", self.filterActionP = createAction(parent=self, text='Apply P Filter',
self.filterWaveformData, slot=self.filterP,
"Ctrl+F", filter_icon, icon=self.filter_icon_p,
"""Toggle un-/filtered waveforms tip='Toggle filtered/original'
to be displayed, according to the ' waveforms',
desired seismic phase.""", True) checkable=True,
shortcut='P')
self.filterActionS = createAction(parent=self, text='Apply S Filter',
slot=self.filterS,
icon=self.filter_icon_s,
tip='Toggle filtered/original'
' waveforms',
checkable=True,
shortcut='S')
filterEditAction = self.createAction(self, "&Filter parameter ...", filterEditAction = self.createAction(self, "&Filter parameter ...",
self.adjustFilterOptions, self.adjustFilterOptions,
"Alt+F", filter_icon, "Ctrl+F", self.filter_icon,
"""Adjust filter parameters.""") """Adjust filter parameters.""")
self.inventoryAction = self.createAction(self, "Select &Inventory ...", self.inventoryAction = self.createAction(self, "Select &Inventory ...",
self.get_new_metadata, self.get_new_metadata,
@ -402,6 +413,10 @@ class MainWindow(QMainWindow):
"automatic pick " "automatic pick "
"data.", False) "data.", False)
self.compare_action.setEnabled(False) self.compare_action.setEnabled(False)
self.qualities_action = self.createAction(parent=self, text='Show pick qualitites...',
slot=self.pickQualities, shortcut='Alt+Q',
icon=qualities_icon, tip='Histogram of pick qualities')
self.qualities_action.setEnabled(False)
printAction = self.createAction(self, "&Print event ...", printAction = self.createAction(self, "&Print event ...",
self.show_event_information, QKeySequence.Print, self.show_event_information, QKeySequence.Print,
@ -472,7 +487,7 @@ class MainWindow(QMainWindow):
' the complete project on grid engine.') ' the complete project on grid engine.')
self.auto_pick_sge.setEnabled(False) self.auto_pick_sge.setEnabled(False)
pickActions = (self.auto_tune, self.auto_pick, self.compare_action) pickActions = (self.auto_tune, self.auto_pick, self.compare_action, self.qualities_action)
# pickToolBar = self.addToolBar("PickTools") # pickToolBar = self.addToolBar("PickTools")
# pickToolActions = (selectStation, ) # pickToolActions = (selectStation, )
@ -500,8 +515,8 @@ class MainWindow(QMainWindow):
self.updateFileMenu() self.updateFileMenu()
self.editMenu = self.menuBar().addMenu('&Edit') self.editMenu = self.menuBar().addMenu('&Edit')
editActions = (self.filterAction, filterEditAction, None, editActions = (self.filterActionP, self.filterActionS, filterEditAction, None,
self.selectPAction, self.selectSAction, None, #self.selectPAction, self.selectSAction, None,
self.inventoryAction, self.initMapAction, None, self.inventoryAction, self.initMapAction, None,
prefsEventAction) prefsEventAction)
#printAction) #TODO: print event? #printAction) #TODO: print event?
@ -561,20 +576,29 @@ class MainWindow(QMainWindow):
style = settings.value('style') style = settings.value('style')
self.set_style(style) self.set_style(style)
# add event combo box and ref/test buttons # add event combo box, forward, backward and ref/test buttons
self.eventBox = self.createEventBox() self.eventBox = self.createEventBox()
self.eventBox.setMaxVisibleItems(30) self.eventBox.setMaxVisibleItems(30)
self.eventBox.setEnabled(False) self.eventBox.setEnabled(False)
self.previous_button = QPushButton('<')
self.next_button = QPushButton('>')
self.init_ref_test_buttons() self.init_ref_test_buttons()
self._event_layout = QHBoxLayout() self._event_layout = QHBoxLayout()
self._event_layout.addWidget(QLabel('Event: ')) self._event_layout.addWidget(QLabel('Event: '))
self._event_layout.addWidget(self.eventBox) self._event_layout.addWidget(self.eventBox)
self._event_layout.addWidget(self.previous_button)
self._event_layout.addWidget(self.next_button)
self._event_layout.addWidget(self.ref_event_button) self._event_layout.addWidget(self.ref_event_button)
self._event_layout.addWidget(self.test_event_button) self._event_layout.addWidget(self.test_event_button)
self._event_layout.setStretch(1, 1) # set stretch of item 1 to 1 self._event_layout.setStretch(1, 1) # set stretch of item 1 to 1
self._main_layout.addLayout(self._event_layout) self._main_layout.addLayout(self._event_layout)
self.eventBox.activated.connect(self.refreshEvents) self.eventBox.activated.connect(self.refreshEvents)
self.previous_button.clicked.connect(self.previous_event)
self.next_button.clicked.connect(self.next_event)
self.previous_button.setEnabled(False)
self.next_button.setEnabled(False)
# add main tab widget # add main tab widget
self.tabs = QTabWidget(self) self.tabs = QTabWidget(self)
self._main_layout.addWidget(self.tabs) self._main_layout.addWidget(self.tabs)
@ -650,6 +674,8 @@ class MainWindow(QMainWindow):
''' '''
self.ref_event_button = QtGui.QPushButton('Tune') self.ref_event_button = QtGui.QPushButton('Tune')
self.test_event_button = QtGui.QPushButton('Test') self.test_event_button = QtGui.QPushButton('Test')
self.ref_event_button.setMinimumWidth(100)
self.test_event_button.setMinimumWidth(100)
self.ref_event_button.setToolTip('Set manual picks of current ' + self.ref_event_button.setToolTip('Set manual picks of current ' +
'event as reference picks for autopicker tuning.') 'event as reference picks for autopicker tuning.')
self.test_event_button.setToolTip('Set manual picks of current ' + self.test_event_button.setToolTip('Set manual picks of current ' +
@ -763,6 +789,17 @@ class MainWindow(QMainWindow):
def metadata(self, value): def metadata(self, value):
self._metadata = value self._metadata = value
def updateFilteroptions(self):
filter_info = readFilterInformation(self._inputs)
p_filter = filter_info['P']
s_filter = filter_info['S']
self.filteroptions = {'P': FilterOptions(p_filter['filtertype'],
p_filter['freq'],
p_filter['order']),
'S': FilterOptions(s_filter['filtertype'],
s_filter['freq'],
s_filter['order'])}
def updateFileMenu(self): def updateFileMenu(self):
self.fileMenu.clear() self.fileMenu.clear()
@ -1004,7 +1041,8 @@ class MainWindow(QMainWindow):
and len(item.split('/')[-1].split('.')) == 3 and len(item.split('/')[-1].split('.')) == 3
and len(item.split('/')[-1]) == 12] and len(item.split('/')[-1]) == 12]
if not eventlist: if not eventlist:
print('No events found! Expected structure for event folders: [evID.DOY.YR]') print('No events found! Expected structure for event folders: [eEVID.DOY.YR],\n'
' e.g. eventID=1, doy=2, yr=2016: e0001.002.16')
return return
else: else:
return return
@ -1353,6 +1391,9 @@ class MainWindow(QMainWindow):
self.cmpw.refresh_tooltips() self.cmpw.refresh_tooltips()
self.cmpw.show() self.cmpw.show()
def pickQualities(self):
return
def compareMulti(self): def compareMulti(self):
if not self.compareoptions: if not self.compareoptions:
return return
@ -1401,7 +1442,8 @@ class MainWindow(QMainWindow):
return None return None
def getStime(self): def getStime(self):
return self._stime if self.get_data():
return full_range(self.get_data().getWFData())[0]
def addActions(self, target, actions): def addActions(self, target, actions):
for action in actions: for action in actions:
@ -1470,6 +1512,26 @@ class MainWindow(QMainWindow):
if self.tap: if self.tap:
self.tap.fill_eventbox() self.tap.fill_eventbox()
def checkEventButtons(self):
if self.eventBox.currentIndex() == 0:
prev_state = False
else:
prev_state = True
if self.eventBox.currentIndex() == len(self.project.eventlist) - 1:
next_state = False
else:
next_state = True
self.previous_button.setEnabled(prev_state)
self.next_button.setEnabled(next_state)
def previous_event(self):
self.eventBox.setCurrentIndex(self.eventBox.currentIndex() - 1)
self.eventBox.activated.emit(-1)
def next_event(self):
self.eventBox.setCurrentIndex(self.eventBox.currentIndex() + 1)
self.eventBox.activated.emit(+1)
def refreshEvents(self): def refreshEvents(self):
''' '''
Refresh GUI when events get changed. Refresh GUI when events get changed.
@ -1480,6 +1542,7 @@ class MainWindow(QMainWindow):
# array_map refresh is not necessary when changing event in waveform plot tab, # array_map refresh is not necessary when changing event in waveform plot tab,
# but gets necessary when switching from one to another after changing an event. # but gets necessary when switching from one to another after changing an event.
self._eventChanged = [True, True] self._eventChanged = [True, True]
self.checkEventButtons()
self.refreshTabs() self.refreshTabs()
def refreshTabs(self): def refreshTabs(self):
@ -1554,18 +1617,9 @@ class MainWindow(QMainWindow):
# else: # else:
# ans = False # ans = False
self.fnames = self.getWFFnames_from_eventbox() self.fnames = self.getWFFnames_from_eventbox()
self.data.setWFData(self.fnames) self.data.setWFData(self.fnames,
wfdat = self.data.getWFData() # all available streams checkRotated=True,
# remove possible underscores in station names metadata=self.metadata)
wfdat = remove_underscores(wfdat)
# check for gaps and doubled channels
check4gaps(wfdat)
check4doubled(wfdat)
# check for stations with rotated components
wfdat = check4rotated(wfdat, self.metadata, verbosity=0)
# trim station components to same start value
trim_station_components(wfdat, trim_start=True, trim_end=False)
self._stime = full_range(self.get_data().getWFData())[0]
def connectWFplotEvents(self): def connectWFplotEvents(self):
''' '''
@ -1633,7 +1687,7 @@ class MainWindow(QMainWindow):
if self.pg: if self.pg:
self.finish_pg_plot() self.finish_pg_plot()
else: else:
self._max_xlims = self.dataPlot.getXLims() self._max_xlims = self.dataPlot.getXLims(self.dataPlot.axes[0])
plotWidget = self.getPlotWidget() plotWidget = self.getPlotWidget()
plotDict = plotWidget.getPlotDict() plotDict = plotWidget.getPlotDict()
pos = plotDict.keys() pos = plotDict.keys()
@ -1661,8 +1715,10 @@ class MainWindow(QMainWindow):
if event.pylot_picks: if event.pylot_picks:
self.drawPicks(picktype='manual') self.drawPicks(picktype='manual')
self.locateEvent.setEnabled(True) self.locateEvent.setEnabled(True)
self.qualities_action.setEnabled(True)
if event.pylot_autopicks: if event.pylot_autopicks:
self.drawPicks(picktype='auto') self.drawPicks(picktype='auto')
self.qualities_action.setEnabled(True)
if True in self.comparable.values(): if True in self.comparable.values():
self.compare_action.setEnabled(True) self.compare_action.setEnabled(True)
self.draw() self.draw()
@ -1707,7 +1763,8 @@ class MainWindow(QMainWindow):
self.dataPlot.plotWidget.hideAxis('bottom') self.dataPlot.plotWidget.hideAxis('bottom')
self.dataPlot.plotWidget.hideAxis('left') self.dataPlot.plotWidget.hideAxis('left')
else: else:
self.dataPlot.getAxes().cla() for ax in self.dataPlot.axes:
ax.cla()
self.loadlocationaction.setEnabled(False) self.loadlocationaction.setEnabled(False)
self.auto_tune.setEnabled(False) self.auto_tune.setEnabled(False)
self.auto_pick.setEnabled(False) self.auto_pick.setEnabled(False)
@ -1723,18 +1780,19 @@ class MainWindow(QMainWindow):
self.disableSaveEventAction() self.disableSaveEventAction()
self.draw() self.draw()
def plotWaveformDataThread(self): def plotWaveformDataThread(self, filter=True):
''' '''
Open a modal thread to plot current waveform data. Open a modal thread to plot current waveform data.
''' '''
self.clearWaveformDataPlot() self.clearWaveformDataPlot()
self.wfp_thread = Thread(self, self.plotWaveformData, self.wfp_thread = Thread(self, self.plotWaveformData,
arg=filter,
progressText='Plotting waveform data...', progressText='Plotting waveform data...',
pb_widget=self.mainProgressBarWidget) pb_widget=self.mainProgressBarWidget)
self.wfp_thread.finished.connect(self.finishWaveformDataPlot) self.wfp_thread.finished.connect(self.finishWaveformDataPlot)
self.wfp_thread.start() self.wfp_thread.start()
def plotWaveformData(self): def plotWaveformData(self, filter=True):
''' '''
Plot waveform data to current plotWidget. Plot waveform data to current plotWidget.
''' '''
@ -1746,6 +1804,10 @@ class MainWindow(QMainWindow):
comp = self.getComponent() comp = self.getComponent()
title = 'section: {0} components'.format(zne_text[comp]) title = 'section: {0} components'.format(zne_text[comp])
wfst = self.get_data().getWFData() wfst = self.get_data().getWFData()
if self.filterActionP.isChecked() and filter:
self.filterWaveformData(plot=False, phase='P')
elif self.filterActionS.isChecked() and filter:
self.filterWaveformData(plot=False, phase='S')
# wfst = self.get_data().getWFData().select(component=comp) # wfst = self.get_data().getWFData().select(component=comp)
# wfst += self.get_data().getWFData().select(component=alter_comp) # wfst += self.get_data().getWFData().select(component=alter_comp)
plotWidget = self.getPlotWidget() plotWidget = self.getPlotWidget()
@ -1785,18 +1847,51 @@ class MainWindow(QMainWindow):
def pushFilterWF(self, param_args): def pushFilterWF(self, param_args):
self.get_data().filterWFData(param_args) self.get_data().filterWFData(param_args)
def filterWaveformData(self): def filterP(self):
self.filterActionS.setChecked(False)
if self.filterActionP.isChecked():
self.filterWaveformData(phase='P')
else:
self.resetWFData()
def filterS(self):
self.filterActionP.setChecked(False)
if self.filterActionS.isChecked():
self.filterWaveformData(phase='S')
else:
self.resetWFData()
def resetWFData(self):
self.get_data().resetWFData()
self.plotWaveformDataThread()
def filterWaveformData(self, plot=True, phase=None):
if not self.get_current_event():
return
if self.get_data(): if self.get_data():
if self.getFilterOptions() and self.filterAction.isChecked(): if not phase:
kwargs = self.getFilterOptions()[self.getSeismicPhase()].parseFilterOptions() if self.filterActionP.isChecked():
phase = 'P'
elif self.filterActionS.isChecked():
phase = 'S'
if self.getFilterOptions():
if (phase == 'P' and self.filterActionP.isChecked()) or (phase == 'S' and self.filterActionS.isChecked()):
kwargs = self.getFilterOptions()[phase].parseFilterOptions()
self.pushFilterWF(kwargs) self.pushFilterWF(kwargs)
elif self.filterAction.isChecked(): else:
self.get_data().resetWFData()
elif self.filterActionP.isChecked() or self.filterActionS.isChecked():
self.adjustFilterOptions() self.adjustFilterOptions()
else: else:
self.get_data().resetWFData() self.get_data().resetWFData()
self.plotWaveformDataThread() if plot:
self.drawPicks() self.plotWaveformDataThread(filter=False)
self.draw() #self.drawPicks()
#self.draw()
def getAutoFilteroptions(self, phase):
return getAutoFilteroptions(phase, self._inputs)
def adjustFilterOptions(self): def adjustFilterOptions(self):
fstring = "Filter Options" fstring = "Filter Options"
@ -1805,10 +1900,11 @@ class MainWindow(QMainWindow):
if self.filterDlg.exec_(): if self.filterDlg.exec_():
filteroptions = self.filterDlg.getFilterOptions() filteroptions = self.filterDlg.getFilterOptions()
self.setFilterOptions(filteroptions) self.setFilterOptions(filteroptions)
if self.filterAction.isChecked(): if self.filterActionP.isChecked() or self.filterActionS.isChecked():
kwargs = self.getFilterOptions()[self.getSeismicPhase()].parseFilterOptions() kwargs = self.getFilterOptions()[self.getSeismicPhase()].parseFilterOptions()
self.pushFilterWF(kwargs) self.pushFilterWF(kwargs)
self.plotWaveformDataThread() self.plotWaveformDataThread()
return True
def checkFilterOptions(self): def checkFilterOptions(self):
fstring = "Filter Options" fstring = "Filter Options"
@ -1885,7 +1981,7 @@ class MainWindow(QMainWindow):
# '[{0}: {1} Hz]'.format( # '[{0}: {1} Hz]'.format(
# self.getFilterOptions().getFilterType(), # self.getFilterOptions().getFilterType(),
# self.getFilterOptions().getFreq())) # self.getFilterOptions().getFreq()))
# if self.filterAction.isChecked(): # if self.filterActionP.isChecked() or self.filterActionS.isChecked():
# self.filterWaveformData() # self.filterWaveformData()
def getSeismicPhase(self): def getSeismicPhase(self):
@ -1933,7 +2029,7 @@ class MainWindow(QMainWindow):
if self._shift: if self._shift:
factor = {'up': 1. / 2., factor = {'up': 1. / 2.,
'down': 2.} 'down': 2.}
xlims = self.dataPlot.getXLims() xlims = self.dataPlot.getXLims(self.dataPlot.axes[0])
xdiff = xlims[1] - xlims[0] xdiff = xlims[1] - xlims[0]
xdiff *= factor[button] xdiff *= factor[button]
xl = x - 0.5 * xdiff xl = x - 0.5 * xdiff
@ -1942,7 +2038,7 @@ class MainWindow(QMainWindow):
xl = self._max_xlims[0] xl = self._max_xlims[0]
if xr > self._max_xlims[1]: if xr > self._max_xlims[1]:
xr = self._max_xlims[1] xr = self._max_xlims[1]
self.dataPlot.setXLims((xl, xr)) self.dataPlot.setXLims(self.dataPlot.axes[0], (xl, xr))
self.dataPlot.draw() self.dataPlot.draw()
def pickOnStation(self, gui_event): def pickOnStation(self, gui_event):
@ -1969,7 +2065,7 @@ class MainWindow(QMainWindow):
if not station: if not station:
return return
self.update_status('picking on station {0}'.format(station)) self.update_status('picking on station {0}'.format(station))
data = self.get_data().getWFData() data = self.get_data().getOriginalWFData().copy()
event = self.get_current_event() event = self.get_current_event()
pickDlg = PickDlg(self, parameter=self._inputs, pickDlg = PickDlg(self, parameter=self._inputs,
data=data.select(station=station), data=data.select(station=station),
@ -1978,6 +2074,9 @@ class MainWindow(QMainWindow):
autopicks=self.getPicksOnStation(station, 'auto'), autopicks=self.getPicksOnStation(station, 'auto'),
metadata=self.metadata, event=event, metadata=self.metadata, event=event,
filteroptions=self.filteroptions) filteroptions=self.filteroptions)
if self.filterActionP.isChecked() or self.filterActionS.isChecked():
pickDlg.currentPhase = self.getSeismicPhase()
pickDlg.filterWFData()
pickDlg.nextStation.setChecked(nextStation) pickDlg.nextStation.setChecked(nextStation)
if pickDlg.exec_(): if pickDlg.exec_():
if pickDlg._dirty: if pickDlg._dirty:
@ -2314,7 +2413,7 @@ class MainWindow(QMainWindow):
if self.pg: if self.pg:
pw = self.getPlotWidget().plotWidget pw = self.getPlotWidget().plotWidget
else: else:
ax = self.getPlotWidget().axes ax = self.getPlotWidget().axes[0]
ylims = np.array([-.5, +.5]) + plotID ylims = np.array([-.5, +.5]) + plotID
stat_picks = self.getPicks(type=picktype)[station] stat_picks = self.getPicks(type=picktype)[station]
@ -2384,7 +2483,7 @@ class MainWindow(QMainWindow):
else: else:
if picktype == 'manual': if picktype == 'manual':
linestyle_mpp, width_mpp = pick_linestyle_plt(picktype, 'mpp') linestyle_mpp, width_mpp = pick_linestyle_plt(picktype, 'mpp')
color = pick_color_plt(picktype, phase, quality) color = pick_color_plt(picktype, self.getPhaseID(phase), quality)
if picks['epp'] and picks['lpp']: if picks['epp'] and picks['lpp']:
ax.fill_between([epp, lpp], ylims[0], ylims[1], ax.fill_between([epp, lpp], ylims[0], ylims[1],
alpha=.25, color=color, label='EPP, LPP') alpha=.25, color=color, label='EPP, LPP')
@ -2926,6 +3025,7 @@ class MainWindow(QMainWindow):
if hasattr(self.project, 'parameter'): if hasattr(self.project, 'parameter'):
if self.project.parameter: if self.project.parameter:
self._inputs = self.project.parameter self._inputs = self.project.parameter
self.updateFilteroptions()
self.tabs.setCurrentIndex(0) # implemented to prevent double-loading of waveform data self.tabs.setCurrentIndex(0) # implemented to prevent double-loading of waveform data
self.init_events(new=True) self.init_events(new=True)
self.setDirty(False) self.setDirty(False)

View File

@ -338,6 +338,7 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
local_mag = LocalMagnitude(corr_dat, evt, local_mag = LocalMagnitude(corr_dat, evt,
parameter.get('sstop'), parameter.get('sstop'),
WAscaling, True, iplot) WAscaling, True, iplot)
# update pick with local magnitude property values
for stats, amplitude in local_mag.amplitudes.items(): for stats, amplitude in local_mag.amplitudes.items():
picks[stats]['S']['Ao'] = amplitude.generic_amplitude picks[stats]['S']['Ao'] = amplitude.generic_amplitude
print("Local station magnitudes scaled with:") print("Local station magnitudes scaled with:")
@ -412,6 +413,7 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even
local_mag = LocalMagnitude(corr_dat, evt, local_mag = LocalMagnitude(corr_dat, evt,
parameter.get('sstop'), parameter.get('sstop'),
WAscaling, True, iplot) WAscaling, True, iplot)
# update pick with local magnitude property values
for stats, amplitude in local_mag.amplitudes.items(): for stats, amplitude in local_mag.amplitudes.items():
if stats in picks: if stats in picks:
picks[stats]['S']['Ao'] = amplitude.generic_amplitude picks[stats]['S']['Ao'] = amplitude.generic_amplitude
@ -496,7 +498,7 @@ if __name__ == "__main__":
help='''full path to the file containing the input help='''full path to the file containing the input
parameters for autoPyLoT''') parameters for autoPyLoT''')
parser.add_argument('-p', '-P', '--iplot', type=int, parser.add_argument('-p', '-P', '--iplot', type=int,
action='store', action='store', default=0,
help='''optional, logical variable for plotting: 0=none, 1=partial, 2=all''') help='''optional, logical variable for plotting: 0=none, 1=partial, 2=all''')
parser.add_argument('-f', '-F', '--fnames', type=str, parser.add_argument('-f', '-F', '--fnames', type=str,
action='store', action='store',

View File

@ -29,10 +29,12 @@
<file>icons/map.png</file> <file>icons/map.png</file>
<file>icons/openloc.png</file> <file>icons/openloc.png</file>
<file>icons/compare_button.png</file> <file>icons/compare_button.png</file>
<file>icons/pick_qualities_button.png</file>
<file>icons/locate_button.png</file> <file>icons/locate_button.png</file>
<file>icons/Matlab_PILOT_icon.png</file> <file>icons/Matlab_PILOT_icon.png</file>
<file>icons/printer.png</file> <file>icons/printer.png</file>
<file>icons/delete.png</file> <file>icons/delete.png</file>
<file>icons/key_A.png</file>
<file>icons/key_E.png</file> <file>icons/key_E.png</file>
<file>icons/key_N.png</file> <file>icons/key_N.png</file>
<file>icons/key_P.png</file> <file>icons/key_P.png</file>
@ -45,6 +47,8 @@
<file>icons/key_W.png</file> <file>icons/key_W.png</file>
<file>icons/key_Z.png</file> <file>icons/key_Z.png</file>
<file>icons/filter.png</file> <file>icons/filter.png</file>
<file>icons/filter_p.png</file>
<file>icons/filter_s.png</file>
<file>icons/sync.png</file> <file>icons/sync.png</file>
<file>icons/zoom_0.png</file> <file>icons/zoom_0.png</file>
<file>icons/zoom_in.png</file> <file>icons/zoom_in.png</file>

BIN
icons/filter_p.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
icons/filter_s.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
icons/key_A.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1
pylot/RELEASE-VERSION Normal file
View File

@ -0,0 +1 @@
9e74-dirty

View File

@ -16,7 +16,6 @@ from pylot.core.util.utils import common_range, fit_curve
from scipy import integrate, signal from scipy import integrate, signal
from scipy.optimize import curve_fit from scipy.optimize import curve_fit
def richter_magnitude_scaling(delta): def richter_magnitude_scaling(delta):
distance = np.array([0, 10, 20, 25, 30, 35, 40, 45, 50, 60, 70, 75, 85, 90, 100, 110, distance = np.array([0, 10, 20, 25, 30, 35, 40, 45, 50, 60, 70, 75, 85, 90, 100, 110,
120, 130, 140, 150, 160, 170, 180, 190, 200, 210, 230, 240, 250, 120, 130, 140, 150, 160, 170, 180, 190, 200, 210, 230, 240, 250,
@ -133,7 +132,7 @@ class Magnitude(object):
station_count=len(self.magnitudes), station_count=len(self.magnitudes),
azimuthal_gap=self.origin_id.get_referred_object().quality.azimuthal_gap) azimuthal_gap=self.origin_id.get_referred_object().quality.azimuthal_gap)
else: else:
# no saling necessary # no scaling necessary
mag = ope.Magnitude( mag = ope.Magnitude(
mag=np.median([M.mag for M in self.magnitudes.values()]), mag=np.median([M.mag for M in self.magnitudes.values()]),
magnitude_type=self.type, magnitude_type=self.type,
@ -233,17 +232,33 @@ class LocalMagnitude(Magnitude):
# check for plot flag (for debugging only) # check for plot flag (for debugging only)
fig = None fig = None
if iplot > 1: if iplot > 1:
st.plot()
fig = plt.figure() fig = plt.figure()
ax = fig.add_subplot(111) ax = fig.add_subplot(211)
ax.plot(th, st[0].data, 'k')
ax.plot(th, sqH) ax.plot(th, sqH)
ax.plot(th[iwin], sqH[iwin], 'g') ax.plot(th[iwin], sqH[iwin], 'g')
ax.plot([t0, t0], [0, max(sqH)], 'r', linewidth=2) ax.plot([t0 - stime, t0 - stime], [0, max(sqH)], 'r', linewidth=2)
ax.title( ax.set_title('Station %s, Channel %s, RMS Horizontal Trace, '
'Station %s, RMS Horizontal Traces, WA-peak-to-peak=%4.1f mm' \ 'WA-peak-to-peak=%6.3f mm' % (st[0].stats.station,
% (st[0].stats.station, wapp)) st[0].stats.channel,
wapp))
ax.set_xlabel('Time [s]') ax.set_xlabel('Time [s]')
ax.set_ylabel('Displacement [mm]') ax.set_ylabel('Displacement [mm]')
ax = fig.add_subplot(212)
ax.plot(th, st[1].data, 'k')
ax.plot(th, sqH)
ax.plot(th[iwin], sqH[iwin], 'g')
ax.plot([t0 - stime, t0 - stime], [0, max(sqH)], 'r', linewidth=2)
ax.set_title('Channel %s, RMS Horizontal Trace, '
'WA-peak-to-peak=%6.3f mm' % (st[1].stats.channel,
wapp))
ax.set_xlabel('Time [s]')
ax.set_ylabel('Displacement [mm]')
fig.show()
try: input()
except SyntaxError: pass
plt.close(fig)
return wapp, fig return wapp, fig
@ -251,6 +266,10 @@ class LocalMagnitude(Magnitude):
for a in self.arrivals: for a in self.arrivals:
if a.phase not in 'sS': if a.phase not in 'sS':
continue continue
# make sure calculating Ml only from reliable onsets
# NLLoc: time_weight = 0 => do not use onset!
if a.time_weight == 0:
continue
pick = a.pick_id.get_referred_object() pick = a.pick_id.get_referred_object()
station = pick.waveform_id.station_code station = pick.waveform_id.station_code
wf = select_for_phase(self.stream.select( wf = select_for_phase(self.stream.select(
@ -349,6 +368,10 @@ class MomentMagnitude(Magnitude):
for a in self.arrivals: for a in self.arrivals:
if a.phase not in 'pP': if a.phase not in 'pP':
continue continue
# make sure calculating Mo only from reliable onsets
# NLLoc: time_weight = 0 => do not use onset!
if a.time_weight == 0:
continue
pick = a.pick_id.get_referred_object() pick = a.pick_id.get_referred_object()
station = pick.waveform_id.station_code station = pick.waveform_id.station_code
scopy = self.stream.copy() scopy = self.stream.copy()

View File

@ -12,7 +12,8 @@ from pylot.core.io.phases import readPILOTEvent, picks_from_picksdict, \
picksdict_from_pilot, merge_picks picksdict_from_pilot, merge_picks
from pylot.core.util.errors import FormatError, OverwriteError from pylot.core.util.errors import FormatError, OverwriteError
from pylot.core.util.event import Event from pylot.core.util.event import Event
from pylot.core.util.utils import fnConstructor, full_range from pylot.core.util.utils import fnConstructor, full_range, remove_underscores, check4gaps, check4doubled, \
check4rotated, trim_station_components
import pylot.core.loc.velest as velest import pylot.core.loc.velest as velest
@ -99,6 +100,11 @@ class Data(object):
return self return self
def getPicksStr(self): def getPicksStr(self):
"""
Return picks in event data
:return: picks seperated by newlines
:rtype: str
"""
picks_str = '' picks_str = ''
for pick in self.get_evt_data().picks: for pick in self.get_evt_data().picks:
picks_str += str(pick) + '\n' picks_str += str(pick) + '\n'
@ -106,18 +112,11 @@ class Data(object):
def getParent(self): def getParent(self):
""" """
Get PySide.QtGui.QWidget parent object
:return:
""" """
return self._parent return self._parent
def isNew(self): def isNew(self):
"""
:return:
"""
return self._new return self._new
def setNew(self): def setNew(self):
@ -125,9 +124,9 @@ class Data(object):
def getCutTimes(self): def getCutTimes(self):
""" """
Returns earliest start and latest end of all waveform data
:return: minimum start time and maximum end time as a tuple
:return: :rtype: (UTCDateTime, UTCDateTime)
""" """
if self.cuttimes is None: if self.cuttimes is None:
self.updateCutTimes() self.updateCutTimes()
@ -135,22 +134,34 @@ class Data(object):
def updateCutTimes(self): def updateCutTimes(self):
""" """
Update cuttimes to contain earliest start and latest end time
of all waveform data
:rtype: None
""" """
self.cuttimes = full_range(self.getWFData()) self.cuttimes = full_range(self.getWFData())
def getEventFileName(self): def getEventFileName(self):
"""
:return:
"""
ID = self.getID() ID = self.getID()
# handle forbidden filenames especially on windows systems # handle forbidden filenames especially on windows systems
return fnConstructor(str(ID)) return fnConstructor(str(ID))
def checkEvent(self, event, fcheck, forceOverwrite=False): def checkEvent(self, event, fcheck, forceOverwrite=False):
"""
Check information in supplied event and own event and replace own
information with supplied information if own information not exiisting
or forced by forceOverwrite
:param event: Event that supplies information for comparison
:type event: pylot.core.util.event.Event
:param fcheck: check and delete existing information
can be a str or a list of strings of ['manual', 'auto', 'origin', 'magnitude']
:type fcheck: str, [str]
:param forceOverwrite: Set to true to force overwrite own information. If false,
supplied information from event is only used if there is no own information in that
category (given in fcheck: manual, auto, origin, magnitude)
:type forceOverwrite: bool
:return:
:rtype: None
"""
if 'origin' in fcheck: if 'origin' in fcheck:
self.replaceOrigin(event, forceOverwrite) self.replaceOrigin(event, forceOverwrite)
if 'magnitude' in fcheck: if 'magnitude' in fcheck:
@ -161,18 +172,47 @@ class Data(object):
self.replacePicks(event, 'manual') self.replacePicks(event, 'manual')
def replaceOrigin(self, event, forceOverwrite=False): def replaceOrigin(self, event, forceOverwrite=False):
"""
Replace own origin with the one supplied in event if own origin is not
existing or forced by forceOverwrite = True
:param event: Event that supplies information for comparison
:type event: pylot.core.util.event.Event
:param forceOverwrite: always replace own information with supplied one if true
:type forceOverwrite: bool
:return:
:rtype: None
"""
if self.get_evt_data().origins or forceOverwrite: if self.get_evt_data().origins or forceOverwrite:
if event.origins: if event.origins:
print("Found origin, replace it by new origin.") print("Found origin, replace it by new origin.")
event.origins = self.get_evt_data().origins event.origins = self.get_evt_data().origins
def replaceMagnitude(self, event, forceOverwrite=False): def replaceMagnitude(self, event, forceOverwrite=False):
"""
Replace own magnitude with the one supplied in event if own magnitude is not
existing or forced by forceOverwrite = True
:param event: Event that supplies information for comparison
:type event: pylot.core.util.event.Event
:param forceOverwrite: always replace own information with supplied one if true
:type forceOverwrite: bool
:return:
:rtype: None
"""
if self.get_evt_data().magnitudes or forceOverwrite: if self.get_evt_data().magnitudes or forceOverwrite:
if event.magnitudes: if event.magnitudes:
print("Found magnitude, replace it by new magnitude") print("Found magnitude, replace it by new magnitude")
event.magnitudes = self.get_evt_data().magnitudes event.magnitudes = self.get_evt_data().magnitudes
def replacePicks(self, event, picktype): def replacePicks(self, event, picktype):
"""
Replace own picks with the one in event
:param event: Event that supplies information for comparison
:type event: pylot.core.util.event.Event
:param picktype: 'auto' or 'manual' picks
:type picktype: str
:return:
:rtype: None
"""
checkflag = 0 checkflag = 0
picks = event.picks picks = event.picks
# remove existing picks # remove existing picks
@ -189,10 +229,10 @@ class Data(object):
picks.append(pick) picks.append(pick)
def exportEvent(self, fnout, fnext='.xml', fcheck='auto', upperErrors=None): def exportEvent(self, fnout, fnext='.xml', fcheck='auto', upperErrors=None):
""" """
Export event to file
:param fnout: basename of file :param fnout: basename of file
:param fnext: file extension :param fnext: file extension, xml, cnv, obs
:param fcheck: check and delete existing information :param fcheck: check and delete existing information
can be a str or a list of strings of ['manual', 'auto', 'origin', 'magnitude'] can be a str or a list of strings of ['manual', 'auto', 'origin', 'magnitude']
""" """
@ -304,17 +344,13 @@ class Data(object):
def getComp(self): def getComp(self):
""" """
Get component (ZNE)
:return:
""" """
return self.comp return self.comp
def getID(self): def getID(self):
""" """
Get unique resource id
:return:
""" """
try: try:
return self.evtdata.get('resource_id').id return self.evtdata.get('resource_id').id
@ -323,16 +359,20 @@ class Data(object):
def filterWFData(self, kwargs): def filterWFData(self, kwargs):
""" """
Filter waveform data
:param kwargs: :param kwargs: arguments to pass through to filter function
""" """
self.getWFData().filter(**kwargs) data = self.getWFData()
data.detrend('linear')
data.taper(0.02, type='cosine')
data.filter(**kwargs)
self.dirty = True self.dirty = True
def setWFData(self, fnames): def setWFData(self, fnames, checkRotated=False, metadata=None):
""" """
Clear current waveform data and set given waveform data
:param fnames: :param fnames: waveform data names to append
:type fnames: list
""" """
self.wfdata = Stream() self.wfdata = Stream()
self.wforiginal = None self.wforiginal = None
@ -340,14 +380,31 @@ class Data(object):
self.appendWFData(fnames) self.appendWFData(fnames)
else: else:
return False return False
# various pre-processing steps:
# remove possible underscores in station names
self.wfdata = remove_underscores(self.wfdata)
# check for gaps and doubled channels
check4gaps(self.wfdata)
check4doubled(self.wfdata)
# check for stations with rotated components
if checkRotated and metadata is not None:
self.wfdata = check4rotated(self.wfdata, metadata, verbosity=0)
# trim station components to same start value
trim_station_components(self.wfdata, trim_start=True, trim_end=False)
# make a copy of original data
self.wforiginal = self.getWFData().copy() self.wforiginal = self.getWFData().copy()
self.dirty = False self.dirty = False
return True return True
def appendWFData(self, fnames): def appendWFData(self, fnames):
""" """
Read waveform data from fnames and append it to current wf data
:param fnames: :param fnames: waveform data to append
:type fnames: list
""" """
assert isinstance(fnames, list), "input parameter 'fnames' is " \ assert isinstance(fnames, list), "input parameter 'fnames' is " \
"supposed to be of type 'list' " \ "supposed to be of type 'list' " \
@ -372,54 +429,45 @@ class Data(object):
print(warnmsg) print(warnmsg)
def getWFData(self): def getWFData(self):
"""
:return:
"""
return self.wfdata return self.wfdata
def getOriginalWFData(self): def getOriginalWFData(self):
"""
:return:
"""
return self.wforiginal return self.wforiginal
def resetWFData(self): def resetWFData(self):
""" """
Set waveform data to original waveform data
""" """
if self.getOriginalWFData():
self.wfdata = self.getOriginalWFData().copy() self.wfdata = self.getOriginalWFData().copy()
else:
self.wfdata = Stream()
self.dirty = False self.dirty = False
def resetPicks(self): def resetPicks(self):
""" """
Clear all picks from event
""" """
self.get_evt_data().picks = [] self.get_evt_data().picks = []
def get_evt_data(self): def get_evt_data(self):
"""
:return:
"""
return self.evtdata return self.evtdata
def setEvtData(self, event): def setEvtData(self, event):
self.evtdata = event self.evtdata = event
def applyEVTData(self, data, typ='pick', authority_id='rub'): def applyEVTData(self, data, typ='pick', authority_id='rub'):
""" """
Either takes an `obspy.core.event.Event` object and applies all new
:param data: information on the event to the actual data if typ is 'event or
:param typ: creates ObsPy pick objects and append it to the picks list from the
:param authority_id: PyLoT dictionary contain all picks if type is pick
:param data: data to apply, either picks or complete event
:type data:
:param typ: which event data to apply, 'pick' or 'event'
:type typ: str
:param authority_id: (currently unused)
:type: str
:raise OverwriteError: :raise OverwriteError:
""" """

View File

@ -2,6 +2,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import numpy as np import numpy as np
"""
Default parameters used for picking
"""
defaults = {'rootpath': {'type': str, defaults = {'rootpath': {'type': str,
'tooltip': 'project path', 'tooltip': 'project path',
'value': '', 'value': '',
@ -239,7 +243,7 @@ defaults = {'rootpath': {'type': str,
'namestring': 'AIC smooth P'}, 'namestring': 'AIC smooth P'},
'tsmoothP': {'type': float, 'tsmoothP': {'type': float,
'tooltip': 'for HOS/AR, take average of samples for smoothing CF [s]', 'tooltip': 'for HOS/AR, take average of samples in this time window for smoothing CF [s]',
'value': 0.1, 'value': 0.1,
'min:': 0., 'min:': 0.,
'max': np.inf, 'max': np.inf,
@ -321,7 +325,7 @@ defaults = {'rootpath': {'type': str,
'namestring': ('SNR windows S', 'Noise', 'Safety', 'Signal', 'Slope')}, 'namestring': ('SNR windows S', 'Noise', 'Safety', 'Signal', 'Slope')},
'aictsmoothS': {'type': float, 'aictsmoothS': {'type': float,
'tooltip': 'for AIC-picker, take average of samples for smoothing of AIC-function [s]', 'tooltip': 'for AIC-picker, take average of samples in this time window for smoothing of AIC-function [s]',
'value': 0.5, 'value': 0.5,
'min:': 0., 'min:': 0.,
'max': np.inf, 'max': np.inf,
@ -361,7 +365,7 @@ defaults = {'rootpath': {'type': str,
'namestring': 'Min SNR'}, 'namestring': 'Min SNR'},
'fmpickwin': {'type': float, 'fmpickwin': {'type': float,
'tooltip': 'pick window around P onset for calculating zero crossings', 'tooltip': 'pick window [s] around P onset for calculating zero crossings',
'value': 0.2, 'value': 0.2,
'min:': 0., 'min:': 0.,
'max': np.inf, 'max': np.inf,

View File

@ -70,6 +70,7 @@ class PylotParameter(object):
# Set default values of parameter names # Set default values of parameter names
def __init_default_paras(self): def __init_default_paras(self):
"""set default values of parameter names"""
parameters = default_parameters.defaults parameters = default_parameters.defaults
self.__defaults = parameters self.__defaults = parameters
@ -104,15 +105,34 @@ class PylotParameter(object):
return len(self.__parameter.keys()) return len(self.__parameter.keys())
def iteritems(self): def iteritems(self):
"""
Iterate over parameters
:return: key, value tupel
:rtype:
"""
for key, value in self.__parameter.items(): for key, value in self.__parameter.items():
yield key, value yield key, value
def hasParam(self, parameter): def hasParam(self, parameter):
"""
Check if parameter is in keys
:param parameter: parameter to look for in keys
:type parameter:
:return:
:rtype: bool
"""
if parameter in self.__parameter.keys(): if parameter in self.__parameter.keys():
return True return True
return False return False
def get(self, *args): def get(self, *args):
"""
Get first available parameter in args
:param args:
:type args:
:return:
:rtype:
"""
try: try:
for param in args: for param in args:
try: try:
@ -128,15 +148,35 @@ class PylotParameter(object):
raise ParameterError(e) raise ParameterError(e)
def get_defaults(self): def get_defaults(self):
"""
get default parameters
:return:
:rtype: dict
"""
return self.__defaults return self.__defaults
def get_main_para_names(self): def get_main_para_names(self):
"""
Get main parameter names
:return: list of keys available in parameters
:rtype:
"""
return self._settings_main return self._settings_main
def get_special_para_names(self): def get_special_para_names(self):
"""
Get pick parameter names
:return: list of keys available in parameters
:rtype:
"""
return self._settings_special_pick return self._settings_special_pick
def get_all_para_names(self): def get_all_para_names(self):
"""
Get all parameter names
:return:
:rtype: list
"""
all_names = [] all_names = []
all_names += self.get_main_para_names()['dirs'] all_names += self.get_main_para_names()['dirs']
all_names += self.get_main_para_names()['nlloc'] all_names += self.get_main_para_names()['nlloc']
@ -151,6 +191,16 @@ class PylotParameter(object):
return all_names return all_names
def checkValue(self, param, value): def checkValue(self, param, value):
"""
Check type of value against expected type of param.
Print warning message if type check fails
:param param:
:type param:
:param value:
:type value:
:return:
:rtype:
"""
is_type = type(value) is_type = type(value)
expect_type = self.get_defaults()[param]['type'] expect_type = self.get_defaults()[param]['type']
if not is_type == expect_type and not is_type == tuple: if not is_type == expect_type and not is_type == tuple:
@ -159,9 +209,25 @@ class PylotParameter(object):
print(Warning(message)) print(Warning(message))
def setParamKV(self, param, value): def setParamKV(self, param, value):
"""
set parameter param to value
:param param:
:type param:
:param value:
:type value:
:return:
:rtype: None
"""
self.__setitem__(param, value) self.__setitem__(param, value)
def setParam(self, **kwargs): def setParam(self, **kwargs):
"""
Set multiple parameters
:param kwargs:
:type kwargs:
:return:
:rtype: None
"""
for key in kwargs: for key in kwargs:
self.__setitem__(key, kwargs[key]) self.__setitem__(key, kwargs[key])
@ -170,11 +236,23 @@ class PylotParameter(object):
print('ParameterError:\n non-existent parameter %s' % errmsg) print('ParameterError:\n non-existent parameter %s' % errmsg)
def reset_defaults(self): def reset_defaults(self):
"""
Reset current parameters to default parameters
:return:
:rtype: None
"""
defaults = self.get_defaults() defaults = self.get_defaults()
for param in defaults: for param in defaults:
self.setParamKV(param, defaults[param]['value']) self.setParamKV(param, defaults[param]['value'])
def from_file(self, fnin=None): def from_file(self, fnin=None):
"""
read parameters from file and set values to read values
:param fnin: filename
:type fnin:
:return:
:rtype: None
"""
if not fnin: if not fnin:
if self.__filename is not None: if self.__filename is not None:
fnin = self.__filename fnin = self.__filename
@ -221,6 +299,13 @@ class PylotParameter(object):
self.__parameter = self._parFileCont self.__parameter = self._parFileCont
def export2File(self, fnout): def export2File(self, fnout):
"""
Export parameters to file
:param fnout: Filename of export file
:type fnout: str
:return:
:rtype:
"""
fid_out = open(fnout, 'w') fid_out = open(fnout, 'w')
lines = [] lines = []
# for key, value in self.iteritems(): # for key, value in self.iteritems():
@ -257,6 +342,19 @@ class PylotParameter(object):
'quality assessment', None) 'quality assessment', None)
def write_section(self, fid, names, title, separator): def write_section(self, fid, names, title, separator):
"""
write a section of parameters to file
:param fid: File object to write to
:type fid:
:param names: which parameter names to write to file
:type names:
:param title: title of section
:type title: str
:param separator: section separator, written at start of section
:type separator: str
:return:
:rtype:
"""
if separator: if separator:
fid.write(separator) fid.write(separator)
fid.write('#{}#\n'.format(title)) fid.write('#{}#\n'.format(title))
@ -341,7 +439,9 @@ class FilterOptions(object):
def parseFilterOptions(self): def parseFilterOptions(self):
if self: if self:
robject = {'type': self.getFilterType(), 'corners': self.getOrder()} robject = {'type': self.getFilterType(),
'corners': self.getOrder(),
'zerophase': False}
if not self.getFilterType() in ['highpass', 'lowpass']: if not self.getFilterType() in ['highpass', 'lowpass']:
robject['freqmin'] = self.getFreq()[0] robject['freqmin'] = self.getFreq()[0]
robject['freqmax'] = self.getFreq()[1] robject['freqmax'] = self.getFreq()[1]

View File

@ -54,7 +54,7 @@ def create_arrival(pickresID, cinfo, phase, azimuth=None, dist=None):
def create_creation_info(agency_id=None, creation_time=None, author=None): def create_creation_info(agency_id=None, creation_time=None, author=None):
''' '''
get creation info of obspy event
:param agency_id: :param agency_id:
:param creation_time: :param creation_time:
:param author: :param author:
@ -197,9 +197,9 @@ def create_pick(origintime, picknum, picktime, eventnum, cinfo, phase, station,
def create_resourceID(timetohash, restype, authority_id=None, hrstr=None): def create_resourceID(timetohash, restype, authority_id=None, hrstr=None):
''' '''
create unique resource id
:param timetohash: :param timetohash: event origin time to hash
:type timetohash :type timetohash: class: `~obspy.core.utcdatetime.UTCDateTime` object
:param restype: type of the resource, e.g. 'orig', 'earthquake' ... :param restype: type of the resource, e.g. 'orig', 'earthquake' ...
:type restype: str :type restype: str
:param authority_id: name of the institution carrying out the processing :param authority_id: name of the institution carrying out the processing

View File

@ -16,7 +16,8 @@ from pylot.core.io.inputs import PylotParameter
from pylot.core.io.location import create_event, \ from pylot.core.io.location import create_event, \
create_magnitude create_magnitude
from pylot.core.pick.utils import select_for_phase from pylot.core.pick.utils import select_for_phase
from pylot.core.util.utils import getOwner, full_range, four_digits from pylot.core.util.utils import getOwner, full_range, four_digits, transformFilteroptions2String, \
transformFilterString4Export, backtransformFilterString
def add_amplitudes(event, amplitudes): def add_amplitudes(event, amplitudes):
@ -118,6 +119,13 @@ def readPILOTEvent(phasfn=None, locfn=None, authority_id='RUB', **kwargs):
def picksdict_from_pilot(fn): def picksdict_from_pilot(fn):
"""
Create pick dictionary from matlab file
:param fn: matlab file
:type fn:
:return: pick dictionary
:rtype: dict
"""
from pylot.core.util.defaults import TIMEERROR_DEFAULTS from pylot.core.util.defaults import TIMEERROR_DEFAULTS
picks = dict() picks = dict()
phases_pilot = sio.loadmat(fn) phases_pilot = sio.loadmat(fn)
@ -147,6 +155,13 @@ def picksdict_from_pilot(fn):
def stations_from_pilot(stat_array): def stations_from_pilot(stat_array):
"""
Create stations list from pilot station array
:param stat_array:
:type stat_array:
:return:
:rtype: list
"""
stations = list() stations = list()
cur_stat = None cur_stat = None
for stat in stat_array: for stat in stat_array:
@ -164,6 +179,13 @@ def stations_from_pilot(stat_array):
def convert_pilot_times(time_array): def convert_pilot_times(time_array):
"""
Convert pilot times to UTCDateTimes
:param time_array: pilot times
:type time_array:
:return:
:rtype:
"""
times = [int(time) for time in time_array] times = [int(time) for time in time_array]
microseconds = int((time_array[-1] - times[-1]) * 1e6) microseconds = int((time_array[-1] - times[-1]) * 1e6)
times.append(microseconds) times.append(microseconds)
@ -171,6 +193,13 @@ def convert_pilot_times(time_array):
def picksdict_from_obs(fn): def picksdict_from_obs(fn):
"""
create pick dictionary from obs file
:param fn: filename
:type fn:
:return:
:rtype:
"""
picks = dict() picks = dict()
station_name = str() station_name = str()
for line in open(fn, 'r'): for line in open(fn, 'r'):
@ -207,6 +236,10 @@ def picksdict_from_picks(evt):
network = pick.waveform_id.network_code network = pick.waveform_id.network_code
mpp = pick.time mpp = pick.time
spe = pick.time_errors.uncertainty spe = pick.time_errors.uncertainty
if pick.filter_id:
filter_id = backtransformFilterString(str(pick.filter_id.id))
else:
filter_id = None
try: try:
picker = str(pick.method_id) picker = str(pick.method_id)
if picker.startswith('smi:local/'): if picker.startswith('smi:local/'):
@ -222,10 +255,15 @@ def picksdict_from_picks(evt):
lpp = mpp + pick.time_errors.upper_uncertainty lpp = mpp + pick.time_errors.upper_uncertainty
epp = mpp - pick.time_errors.lower_uncertainty epp = mpp - pick.time_errors.lower_uncertainty
except TypeError as e: except TypeError as e:
msg = e + ',\n falling back to symmetric uncertainties' if not spe:
warnings.warn(msg) msg = 'No uncertainties found for pick: {}. Uncertainty set to 0'.format(pick)
lpp = mpp
epp = mpp
else:
msg = str(e) + ',\n falling back to symmetric uncertainties'
lpp = mpp + spe lpp = mpp + spe
epp = mpp - spe epp = mpp - spe
warnings.warn(msg)
phase['mpp'] = mpp phase['mpp'] = mpp
phase['epp'] = epp phase['epp'] = epp
phase['lpp'] = lpp phase['lpp'] = lpp
@ -233,6 +271,7 @@ def picksdict_from_picks(evt):
phase['channel'] = channel phase['channel'] = channel
phase['network'] = network phase['network'] = network
phase['picker'] = picker phase['picker'] = picker
phase['filter_id'] = filter_id if filter_id is not None else ''
onsets[pick.phase_hint] = phase.copy() onsets[pick.phase_hint] = phase.copy()
picksdict[picker][station] = onsets.copy() picksdict[picker][station] = onsets.copy()
@ -240,6 +279,16 @@ def picksdict_from_picks(evt):
def picks_from_picksdict(picks, creation_info=None): def picks_from_picksdict(picks, creation_info=None):
"""
Create a list of picks out of a pick dictionary
:param picks: pick dictionary
:type picks: dict
:param creation_info: obspy creation information to apply to picks
:type creation_info:
:param creation_info: obspy creation information to apply to picks
:return: list of picks
:rtype: list
"""
picks_list = list() picks_list = list()
for station, onsets in picks.items(): for station, onsets in picks.items():
for label, phase in onsets.items(): for label, phase in onsets.items():
@ -274,6 +323,13 @@ def picks_from_picksdict(picks, creation_info=None):
pick.waveform_id = ope.WaveformStreamID(station_code=station, pick.waveform_id = ope.WaveformStreamID(station_code=station,
channel_code=ccode, channel_code=ccode,
network_code=ncode) network_code=ncode)
try:
filter_id = phase['filteroptions']
filter_id = transformFilterString4Export(filter_id)
except KeyError as e:
warnings.warn(e.message, RuntimeWarning)
filter_id = ''
pick.filter_id = filter_id
try: try:
polarity = phase['fm'] polarity = phase['fm']
if polarity == 'U' or '+': if polarity == 'U' or '+':
@ -290,7 +346,6 @@ def picks_from_picksdict(picks, creation_info=None):
picks_list.append(pick) picks_list.append(pick)
return picks_list return picks_list
def reassess_pilot_db(root_dir, db_dir, out_dir=None, fn_param=None, verbosity=0): def reassess_pilot_db(root_dir, db_dir, out_dir=None, fn_param=None, verbosity=0):
import glob import glob
@ -410,25 +465,24 @@ def writephases(arrivals, fformat, filename, parameter=None, eventinfo=None):
HYPO71, NLLoc, VELEST, HYPOSAT, and hypoDD HYPO71, NLLoc, VELEST, HYPOSAT, and hypoDD
:param: arrivals :param arrivals:dictionary containing all phase information including
:type: dictionary containing all phase information including station ID, phase, first motion, weight (uncertainty), ...
station ID, phase, first motion, weight (uncertainty), :type arrivals: dict
....
:param: fformat :param fformat: chosen file format (location routine),
:type: string, chosen file format (location routine),
choose between NLLoc, HYPO71, HYPOSAT, VELEST, choose between NLLoc, HYPO71, HYPOSAT, VELEST,
HYPOINVERSE, and hypoDD HYPOINVERSE, and hypoDD
:type fformat: str
:param: filename, full path and name of phase file :param filename: full path and name of phase file
:type: string :type filename: string
:param: parameter, all input information :param parameter: all input information
:type: object :type parameter: object
:param: eventinfo, optional, needed for VELEST-cnv file :param eventinfo: optional, needed for VELEST-cnv file
and FOCMEC- and HASH-input files and FOCMEC- and HASH-input files
:type: `obspy.core.event.Event` object :type eventinfo: `obspy.core.event.Event` object
""" """
if fformat == 'NLLoc': if fformat == 'NLLoc':
@ -877,6 +931,16 @@ def getQualitiesfromxml(xmlnames, ErrorsP, ErrorsS, plotflag=1):
Script to get onset uncertainties from Quakeml.xml files created by PyLoT. Script to get onset uncertainties from Quakeml.xml files created by PyLoT.
Uncertainties are tranformed into quality classes and visualized via histogram if desired. Uncertainties are tranformed into quality classes and visualized via histogram if desired.
Ludger Küperkoch, BESTEC GmbH, 07/2017 Ludger Küperkoch, BESTEC GmbH, 07/2017
:param xmlnames: list of xml obspy event files containing picks
:type xmlnames: list
:param ErrorsP: time errors of P waves for the four discrete quality classes
:type ErrorsP:
:param ErrorsS: time errors of S waves for the four discrete quality classes
:type ErrorsS:
:param plotflag:
:type plotflag:
:return:
:rtype:
""" """
from pylot.core.pick.utils import getQualityFromUncertainty from pylot.core.pick.utils import getQualityFromUncertainty

View File

@ -18,11 +18,11 @@ def export(picks, fnout, parameter, eventinfo):
:param fnout: complete path to the exporting obs file :param fnout: complete path to the exporting obs file
:type fnout: str :type fnout: str
:param: parameter, all input information :param parameter: all input information
:type: object :type parameter: object
:param: eventinfo, source information needed for focmec format :param eventinfo: source information needed for focmec format
:type: list object :type eventinfo: list object
''' '''
# write phases to FOCMEC-phase file # write phases to FOCMEC-phase file
writephases(picks, 'FOCMEC', fnout, parameter, eventinfo) writephases(picks, 'FOCMEC', fnout, parameter, eventinfo)

View File

@ -18,11 +18,11 @@ def export(picks, fnout, parameter, eventinfo):
:param fnout: complete path to the exporting obs file :param fnout: complete path to the exporting obs file
:type fnout: str :type fnout: str
:param: parameter, all input information :param parameter: all input information
:type: object :type parameter: object
:param: eventinfo, source information needed for HASH format :param eventinfo: source information needed for HASH format
:type: list object :type eventinfo: list object
''' '''
# write phases to HASH-phase file # write phases to HASH-phase file
writephases(picks, 'HASH', fnout, parameter, eventinfo) writephases(picks, 'HASH', fnout, parameter, eventinfo)

View File

@ -18,8 +18,8 @@ def export(picks, fnout, parameter):
:param fnout: complete path to the exporting obs file :param fnout: complete path to the exporting obs file
:type fnout: str :type fnout: str
:param: parameter, all input information :param parameter: all input information
:type: object :type parameter: object
''' '''
# write phases to HYPO71-phase file # write phases to HYPO71-phase file
writephases(picks, 'HYPO71', fnout, parameter) writephases(picks, 'HYPO71', fnout, parameter)

View File

@ -18,11 +18,11 @@ def export(picks, fnout, parameter, eventinfo):
:param fnout: complete path to the exporting obs file :param fnout: complete path to the exporting obs file
:type fnout: str :type fnout: str
:param: parameter, all input information :param parameter: all input information
:type: object :type parameter: object
:param: eventinfo, source information needed for hypoDD format :param eventinfo: source information needed for hypoDD format
:type: list object :type eventinfo: list object
''' '''
# write phases to hypoDD-phase file # write phases to hypoDD-phase file
writephases(picks, 'hypoDD', fnout, parameter, eventinfo) writephases(picks, 'hypoDD', fnout, parameter, eventinfo)

View File

@ -18,8 +18,8 @@ def export(picks, fnout, parameter):
:param fnout: complete path to the exporting obs file :param fnout: complete path to the exporting obs file
:type fnout: str :type fnout: str
:param: parameter, all input information :param parameter: all input information
:type: object :type parameter: object
''' '''
# write phases to HYPOSAT-phase file # write phases to HYPOSAT-phase file
writephases(picks, 'HYPOSAT', fnout, parameter) writephases(picks, 'HYPOSAT', fnout, parameter)

View File

@ -28,8 +28,8 @@ def export(picks, fnout, parameter):
:param fnout: complete path to the exporting obs file :param fnout: complete path to the exporting obs file
:type fnout: str :type fnout: str
:param: parameter, all input information :param parameter: all input information
:type: object :type parameter: object
''' '''
# write phases to NLLoc-phase file # write phases to NLLoc-phase file
writephases(picks, 'NLLoc', fnout, parameter) writephases(picks, 'NLLoc', fnout, parameter)
@ -38,19 +38,19 @@ def export(picks, fnout, parameter):
def modify_inputs(ctrfn, root, nllocoutn, phasefn, tttn): def modify_inputs(ctrfn, root, nllocoutn, phasefn, tttn):
''' '''
:param ctrfn: name of NLLoc-control file :param ctrfn: name of NLLoc-control file
:type: str :type ctrfn: str
:param root: root path to NLLoc working directory :param root: root path to NLLoc working directory
:type: str :type root: str
:param nllocoutn: name of NLLoc-location output file :param nllocoutn: name of NLLoc-location output file
:type: str :type nllocoutn: str
:param phasefn: name of NLLoc-input phase file :param phasefn: name of NLLoc-input phase file
:type: str :type phasefn: str
:param tttn: pattern of precalculated NLLoc traveltime tables :param tttn: pattern of precalculated NLLoc traveltime tables
:type: str :type tttn: str
''' '''
# For locating the event the NLLoc-control file has to be modified! # For locating the event the NLLoc-control file has to be modified!
# create comment line for NLLoc-control file NLLoc-output file # create comment line for NLLoc-control file NLLoc-output file
@ -75,9 +75,9 @@ def modify_inputs(ctrfn, root, nllocoutn, phasefn, tttn):
def locate(fnin, infile=None): def locate(fnin, infile=None):
""" """
takes an external program name takes an external program name and tries to run it
:param fnin: :param fnin: external program name
:return: :return: None
""" """
if infile is None: if infile is None:

View File

@ -18,11 +18,11 @@ def export(picks, fnout, eventinfo, parameter=None):
:param fnout: complete path to the exporting obs file :param fnout: complete path to the exporting obs file
:type fnout: str :type fnout: str
:param: eventinfo, source time needed for VELEST-cnv format :param eventinfo: source time needed for VELEST-cnv format
:type: list object :type eventinfo: list object
:param: parameter, all input information :param parameter: all input information
:type: object :type parameter: object
''' '''
# write phases to VELEST-phase file # write phases to VELEST-phase file
writephases(picks, 'VELEST', fnout, parameter, eventinfo) writephases(picks, 'VELEST', fnout, parameter, eventinfo)

View File

@ -155,7 +155,7 @@ def autopickstation(wfstream, pickparam, verbose=False,
:type metadata: tuple (str, ~obspy.io.xseed.parser.Parser) :type metadata: tuple (str, ~obspy.io.xseed.parser.Parser)
:param origin: list containing origin objects representing origins for all events :param origin: list containing origin objects representing origins for all events
:type origin: list(~obspy.core.event.origin) :type origin: list(~obspy.core.event.origin)
:return: :dictionary containing P pick, S pick and station name :return: dictionary containing P pick, S pick and station name
:rtype: dict :rtype: dict
""" """

View File

@ -109,10 +109,9 @@ class Comparison(object):
Comparison is carried out with the help of pdf representation of the picks Comparison is carried out with the help of pdf representation of the picks
and a probabilistic approach to the time difference of two onset and a probabilistic approach to the time difference of two onset
measurements. measurements.
:param a: filename for pickset A :param type: type of the returned `~pylot.core.util.pdf.ProbabilityDensityFunction` object.
:type a: str Possible values: 'exp' and 'gauss', representing the type of branches of the PDF
:param b: filename for pickset B :type type: str
:type b: str
:return: dictionary containing the resulting comparison pdfs for all picks :return: dictionary containing the resulting comparison pdfs for all picks
:rtype: dict :rtype: dict
""" """
@ -142,8 +141,7 @@ class Comparison(object):
istations = range(nstations) istations = range(nstations)
fig, axarr = plt.subplots(nstations, 2, sharex='col', sharey='row') fig, axarr = plt.subplots(nstations, 2, sharex='col', sharey='row')
for n in istations: for n, station in enumerate(stations):
station = stations[n]
if station not in self.comparison.keys(): if station not in self.comparison.keys():
continue continue
compare_pdf = self.comparison[station] compare_pdf = self.comparison[station]
@ -190,6 +188,20 @@ class Comparison(object):
return self.get_array(phase, 'standard_deviation') return self.get_array(phase, 'standard_deviation')
def hist_expectation(self, phases='all', bins=20, normed=False): def hist_expectation(self, phases='all', bins=20, normed=False):
"""
Plot a histogram of the expectation values of the PDFs.
Expectation represents the time difference between two most likely arrival times
:param phases: type of phases to compare
:type phases: str
:param bins: number of bins in histogram
:type bins: int
:param normed: Normalize histogram
:type normed: bool
:return: None
:rtype: None
"""
phases.strip() phases.strip()
if phases.find('all') is 0: if phases.find('all') is 0:
phases = 'ps' phases = 'ps'
@ -210,6 +222,20 @@ class Comparison(object):
plt.show() plt.show()
def hist_standard_deviation(self, phases='all', bins=20, normed=False): def hist_standard_deviation(self, phases='all', bins=20, normed=False):
"""
Plot a histogram of the compared standard deviation values of two arrivals.
Standard deviation of two compared picks represents the combined uncertainties/pick errors
(earliest possible pick, latest possible pick)
:param phases: type of phases to compare
:type phases: str
:param bins: number of bins in histogram
:type bins: int
:param normed: Normalize histogram
:type normed: bool
:return: None
:rtype: None
"""
phases.strip() phases.strip()
if phases.find('all') == 0: if phases.find('all') == 0:
phases = 'ps' phases = 'ps'
@ -370,7 +396,7 @@ class PDFDictionary(object):
class PDFstatistics(object): class PDFstatistics(object):
""" """
This object can be used to get various statistic values from probabillity density functions. This object can be used to get various statistic values from probability density functions.
Takes a path as argument. Takes a path as argument.
""" """

View File

@ -29,38 +29,36 @@ from pylot.core.pick.utils import getnoisewin, getsignalwin
class AutoPicker(object): class AutoPicker(object):
''' """
Superclass of different, automated picking algorithms applied on a CF determined Superclass of different, automated picking algorithms applied on a CF determined
using AIC, HOS, or AR prediction. using AIC, HOS, or AR prediction.
''' """
warnings.simplefilter('ignore') warnings.simplefilter('ignore')
def __init__(self, cf, TSNR, PickWindow, iplot=0, aus=None, Tsmooth=None, Pick1=None, fig=None, linecolor='k'): def __init__(self, cf, TSNR, PickWindow, iplot=0, aus=None, Tsmooth=None, Pick1=None, fig=None, linecolor='k'):
''' """
:param: cf, characteristic function, on which the picking algorithm is applied Create AutoPicker object
:type: `~pylot.core.pick.CharFuns.CharacteristicFunction` object :param cf: characteristic function, on which the picking algorithm is applied
:type cf: `~pylot.core.pick.CharFuns.CharacteristicFunction`
:param: TSNR, length of time windows around pick used to determine SNR [s] :param TSNR: length of time windows around pick used to determine SNR [s], tuple (T_noise, T_gap, T_signal)
:type: tuple (T_noise, T_gap, T_signal) :type TSNR: (float, float, float)
:param PickWindow: length of pick window [s]
:param: PickWindow, length of pick window [s] :type PickWindow: float
:type: float :param iplot: flag used for plotting, if > 1, results will be plotted. Use iplot = 0 to disable plotting
:type iplot: int
:param: iplot, no. of figure window for plotting interims results :param aus: ("artificial uplift of samples"), find local minimum at i if aic(i-1)*(1+aus) >= aic(i)
:type: integer :type aus: float
:param Tsmooth: length of moving smoothing window to calculate smoothed CF [s]
:param: aus ("artificial uplift of samples"), find local minimum at i if aic(i-1)*(1+aus) >= aic(i) :type Tsmooth: float
:type: float :param Pick1: initial (preliminary) onset time, starting point for PragPicker and EarlLatePicker
:type Pick1: float
:param: Tsmooth, length of moving smoothing window to calculate smoothed CF [s] :param fig: matplotlib figure used for plotting. If not given and plotting is enabled, a new figure will
:type: float be created
:type fig: `~matplotlib.figure.Figure`
:param: Pick1, initial (prelimenary) onset time, starting point for PragPicker and :param linecolor: matplotlib line color string
EarlLatePicker :type linecolor: str
:type: float """
'''
assert isinstance(cf, CharacteristicFunction), "%s is not a CharacteristicFunction object" % str(cf) assert isinstance(cf, CharacteristicFunction), "%s is not a CharacteristicFunction object" % str(cf)
self._linecolor = linecolor self._linecolor = linecolor
@ -79,6 +77,11 @@ class AutoPicker(object):
self.calcPick() self.calcPick()
def __str__(self): def __str__(self):
"""
String representation of AutoPicker object
:return:
:rtype: str
"""
return '''\n\t{name} object:\n return '''\n\t{name} object:\n
TSNR:\t\t\t{TSNR}\n TSNR:\t\t\t{TSNR}\n
PickWindow:\t{PickWindow}\n PickWindow:\t{PickWindow}\n
@ -142,12 +145,12 @@ class AutoPicker(object):
class AICPicker(AutoPicker): class AICPicker(AutoPicker):
''' """
Method to derive the onset time of an arriving phase based on CF Method to derive the onset time of an arriving phase based on CF
derived from AIC. In order to get an impression of the quality of this inital pick, derived from AIC. In order to get an impression of the quality of this initial pick,
a quality assessment is applied based on SNR and slope determination derived from the CF, a quality assessment is applied based on SNR and slope determination derived from the CF,
from which the AIC has been calculated. from which the AIC has been calculated.
''' """
def calcPick(self): def calcPick(self):
@ -214,7 +217,6 @@ class AICPicker(AutoPicker):
self.Pick = self.Tcf[i] self.Pick = self.Tcf[i]
break break
# quality assessment using SNR and slope from CF
if self.Pick is not None: if self.Pick is not None:
# get noise window # get noise window
inoise = getnoisewin(self.Tcf, self.Pick, self.TSNR[0], self.TSNR[1]) inoise = getnoisewin(self.Tcf, self.Pick, self.TSNR[0], self.TSNR[1])
@ -251,8 +253,8 @@ class AICPicker(AutoPicker):
except IndexError: except IndexError:
print("Slope Calculation: empty array islope, check signal window") print("Slope Calculation: empty array islope, check signal window")
return return
if len(dataslope) < 1: if len(dataslope) <= 1:
print('No data in slope window found!') print('No or not enough data in slope window found!')
return return
imaxs, = argrelmax(dataslope) imaxs, = argrelmax(dataslope)
if imaxs.size: if imaxs.size:
@ -364,9 +366,9 @@ class AICPicker(AutoPicker):
class PragPicker(AutoPicker): class PragPicker(AutoPicker):
''' """
Method of pragmatic picking exploiting information given by CF. Method of pragmatic picking exploiting information given by CF.
''' """
def calcPick(self): def calcPick(self):

View File

@ -13,31 +13,39 @@ import warnings
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
from obspy.core import Stream, UTCDateTime from obspy.core import Stream, UTCDateTime
from pylot.core.util.utils import real_Bool, real_None
def earllatepicker(X, nfac, TSNR, Pick1, iplot=0, verbosity=1, fig=None, linecolor='k'): def earllatepicker(X, nfac, TSNR, Pick1, iplot=0, verbosity=1, fig=None, linecolor='k'):
''' """
Function to derive earliest and latest possible pick after Diehl & Kissling (2009) Function to derive earliest and latest possible pick after Diehl & Kissling (2009)
as reasonable uncertainties. Latest possible pick is based on noise level, as reasonable uncertainties. Latest possible pick is based on noise level,
earliest possible pick is half a signal wavelength in front of most likely earliest possible pick is half a signal wavelength in front of most likely
pick given by PragPicker or manually set by analyst. Most likely pick pick given by PragPicker or manually set by analyst. Most likely pick
(initial pick Pick1) must be given. (initial pick Pick1) must be given.
:param X: time series (seismogram)
:param: X, time series (seismogram) :type X: `~obspy.core.stream.Stream`
:type: `~obspy.core.stream.Stream` :param nfac: (noise factor), nfac times noise level to calculate latest possible pick
:type nfac: int
:param: nfac (noise factor), nfac times noise level to calculate latest possible pick :param TSNR: length of time windows around pick used to determine SNR [s]
:type: int :type TSNR: tuple (T_noise, T_gap, T_signal)
:param Pick1: initial (most likely) onset time, starting point for earllatepicker
:param: TSNR, length of time windows around pick used to determine SNR [s] :type Pick1: float
:type: tuple (T_noise, T_gap, T_signal) :param iplot: if given, results are plotted in figure(iplot)
:type iplot: int
:param: Pick1, initial (most likely) onset time, starting point for earllatepicker :param verbosity: amount of displayed information about the process:
:type: float 2 = all
1 = default
:param: iplot, if given, results are plotted in figure(iplot) 0 = none
:type: int :type verbosity: int
''' :param fig: Matplotlib figure ised for plotting
:type fig: `~matplotlib.figure.Figure`
:param linecolor: color for plotting results
:type linecolor: str
:return: tuple containing earliest possible pick, latest possible pick and pick error
:rtype: (float, float, float)
"""
assert isinstance(X, Stream), "%s is not a stream object" % str(X) assert isinstance(X, Stream), "%s is not a stream object" % str(X)
@ -54,7 +62,7 @@ def earllatepicker(X, nfac, TSNR, Pick1, iplot=0, verbosity=1, fig=None, linecol
try: try:
iplot = int(iplot) iplot = int(iplot)
except: except:
if iplot == True or iplot == 'True': if real_Bool(iplot):
iplot = 2 iplot = 2
else: else:
iplot = 0 iplot = 0
@ -127,7 +135,7 @@ def earllatepicker(X, nfac, TSNR, Pick1, iplot=0, verbosity=1, fig=None, linecol
PickError = symmetrize_error(diffti_te, diffti_tl) PickError = symmetrize_error(diffti_te, diffti_tl)
if iplot > 1: if iplot > 1:
if fig == None or fig == 'None': if real_None(fig) is None:
fig = plt.figure() # iplot) fig = plt.figure() # iplot)
plt_flag = 1 plt_flag = 1
fig._tight = True fig._tight = True
@ -162,26 +170,27 @@ def earllatepicker(X, nfac, TSNR, Pick1, iplot=0, verbosity=1, fig=None, linecol
def fmpicker(Xraw, Xfilt, pickwin, Pick, iplot=0, fig=None, linecolor='k'): def fmpicker(Xraw, Xfilt, pickwin, Pick, iplot=0, fig=None, linecolor='k'):
''' """
Function to derive first motion (polarity) of given phase onset Pick. Function to derive first motion (polarity) of given phase onset Pick.
Calculation is based on zero crossings determined within time window pickwin Calculation is based on zero crossings determined within time window pickwin
after given onset time. after given onset time.
:param Xraw: unfiltered time series (seismogram)
:param: Xraw, unfiltered time series (seismogram) :type Xraw: `~obspy.core.stream.Stream`
:type: `~obspy.core.stream.Stream` :param Xfilt: filtered time series (seismogram)
:type Xfilt: `~obspy.core.stream.Stream`
:param: Xfilt, filtered time series (seismogram) :param pickwin: time window after onset Pick within zero crossings are calculated
:type: `~obspy.core.stream.Stream` :type pickwin: float
:param Pick: initial (most likely) onset time, starting point for fmpicker
:param: pickwin, time window after onset Pick within zero crossings are calculated :type Pick: float
:type: float :param iplot: if given, results are plotted in figure(iplot)
:type iplot: int
:param: Pick, initial (most likely) onset time, starting point for fmpicker :param fig: Matplotlib figure ised for plotting
:type: float :type fig: `~matplotlib.figure.Figure`
:param linecolor: color for plotting results
:param: iplot, if given, results are plotted in figure(iplot) :type linecolor: str
:type: int :return: None if first motion detection was skipped, otherwise string indicating the polarity
''' :rtype: None, str
"""
plt_flag = 0 plt_flag = 0
try: try:
@ -268,7 +277,7 @@ def fmpicker(Xraw, Xfilt, pickwin, Pick, iplot=0, fig=None, linecolor='k'):
index2 = [] index2 = []
i = 0 i = 0
for j in range(ipick[0][1], ipick[0][len(t[ipick]) - 1]): for j in range(ipick[0][1], ipick[0][len(t[ipick]) - 1]):
i = i + 1 i += 1
if xfilt[j - 1] <= 0 <= xfilt[j]: if xfilt[j - 1] <= 0 <= xfilt[j]:
zc2.append(t[ipick][i]) zc2.append(t[ipick][i])
index2.append(i) index2.append(i)
@ -321,7 +330,7 @@ def fmpicker(Xraw, Xfilt, pickwin, Pick, iplot=0, fig=None, linecolor='k'):
print("fmpicker: Found polarity %s" % FM) print("fmpicker: Found polarity %s" % FM)
if iplot > 1: if iplot > 1:
if fig == None or fig == 'None': if real_None(fig) is None:
fig = plt.figure() # iplot) fig = plt.figure() # iplot)
plt_flag = 1 plt_flag = 1
fig._tight = True fig._tight = True
@ -360,8 +369,15 @@ def fmpicker(Xraw, Xfilt, pickwin, Pick, iplot=0, fig=None, linecolor='k'):
def crossings_nonzero_all(data): def crossings_nonzero_all(data):
"""
Returns the indices of zero crossings the data array
:param data: data array (seismic trace)
:type data: `~numpy.ndarray`
:return: array containing indices of zero crossings in the data array.
:rtype: `~numpy.ndarray`
"""
pos = data > 0 pos = data > 0
npos = ~pos npos = ~pos # get positions of negative values
return ((pos[:-1] & npos[1:]) | (npos[:-1] & pos[1:])).nonzero()[0] return ((pos[:-1] & npos[1:]) | (npos[:-1] & pos[1:])).nonzero()[0]
@ -372,26 +388,28 @@ def symmetrize_error(dte, dtl):
:param dte: relative lower uncertainty :param dte: relative lower uncertainty
:param dtl: relative upper uncertainty :param dtl: relative upper uncertainty
:return: symmetrized error :return: symmetrized error
:rtype: float
""" """
return (dte + 2 * dtl) / 3 return (dte + 2 * dtl) / 3
def getSNR(X, TSNR, t1, tracenum=0): def getSNR(X, TSNR, t1, tracenum=0):
''' """
Function to calculate SNR of certain part of seismogram relative to Function to calculate SNR of certain part of seismogram relative to
given time (onset) out of given noise and signal windows. A safety gap given time (onset) out of given noise and signal windows. A safety gap
between noise and signal part can be set. Returns SNR and SNR [dB] and between noise and signal part can be set. Returns SNR and SNR [dB] and
noiselevel. noiselevel.
:param X: time series (seismogram)
:param: X, time series (seismogram) :type X: `~obspy.core.stream.Stream`
:type: `~obspy.core.stream.Stream` :param TSNR: length of time windows [s] around t1 (onset) used to determine SNR
:type TSNR: (T_noise, T_gap, T_signal)
:param: TSNR, length of time windows [s] around t1 (onset) used to determine SNR :param t1: initial time (onset) from which noise and signal windows are calculated
:type: tuple (T_noise, T_gap, T_signal) :type t1: float
:param tracenum: used to select the trace in stream X
:param: t1, initial time (onset) from which noise and signal windows are calculated :type tracenum: int
:type: float :return: tuple containing SNR, SNRdB and noise level
''' :rtype: (float, float, float)
"""
assert isinstance(X, Stream), "%s is not a stream object" % str(X) assert isinstance(X, Stream), "%s is not a stream object" % str(X)
@ -435,26 +453,24 @@ def getSNR(X, TSNR, t1, tracenum=0):
def getnoisewin(t, t1, tnoise, tgap): def getnoisewin(t, t1, tnoise, tgap):
''' """
Function to extract indeces of data out of time series for noise calculation. Function to extract indices of data out of time series for noise calculation.
Returns an array of indeces. Returns an array of indices.
:param t: array of time stamps
:param: t, array of time stamps :type t: `numpy.ndarray`
:type: numpy array :param t1: time from which relative to it noise window is extracted
:type t1: float
:param: t1, time from which relativ to it noise window is extracted :param tnoise: length of time window [s] for noise part extraction
:type: float :type tnoise: float
:param tgap: safety gap between t1 (onset) and noise window to ensure, that
:param: tnoise, length of time window [s] for noise part extraction the noise window contains no signal
:type: float :type tgap: float
:return: indices of noise window in t
:param: tgap, safety gap between t1 (onset) and noise window to :rtype: `~numpy.ndarray`
ensure, that noise window contains no signal """
:type: float
'''
# get noise window # get noise window
inoise, = np.where((t <= max([t1 - tgap, 0])) \ inoise, = np.where((t <= max([t1 - tgap, 0]))
& (t >= max([t1 - tnoise - tgap, 0]))) & (t >= max([t1 - tnoise - tgap, 0])))
if np.size(inoise) < 1: if np.size(inoise) < 1:
inoise, = np.where((t >= t[0]) & (t <= t1)) inoise, = np.where((t >= t[0]) & (t <= t1))
@ -465,22 +481,21 @@ def getnoisewin(t, t1, tnoise, tgap):
def getsignalwin(t, t1, tsignal): def getsignalwin(t, t1, tsignal):
''' """
Function to extract data out of time series for signal level calculation. Function to extract data out of time series for signal level calculation.
Returns an array of indeces. Returns an array of indices.
:param t: array of time stamps
:param: t, array of time stamps :type t: `numpy.ndarray`
:type: numpy array :param t1: time from which relative to it signal window is extracted
:type t1: float
:param: t1, time from which relativ to it signal window is extracted :param tsignal: length of time window [s] for signal level calculation
:type: float :type tsignal: float
:return: indices of signal window i t
:param: tsignal, length of time window [s] for signal level calculation :rtype: `numpy.ndarray`
:type: float """
'''
# get signal window # get signal window
isignal, = np.where((t <= min([t1 + tsignal, t[-1]])) \ isignal, = np.where((t <= min([t1 + tsignal, t[-1]]))
& (t >= t1)) & (t >= t1))
if np.size(isignal) < 1: if np.size(isignal) < 1:
print("getsignalwin: Empty array isignal, check signal window!") print("getsignalwin: Empty array isignal, check signal window!")
@ -490,27 +505,18 @@ def getsignalwin(t, t1, tsignal):
def getResolutionWindow(snr, extent): def getResolutionWindow(snr, extent):
""" """
Number -> Float Produce the half of the time resolution window width from given SNR value
produce the half of the time resolution window width from given SNR
value
SNR >= 3 -> 2 sec HRW SNR >= 3 -> 2 sec HRW
3 > SNR >= 2 -> 5 sec MRW 3 > SNR >= 2 -> 5 sec MRW
2 > SNR >= 1.5 -> 10 sec LRW 2 > SNR >= 1.5 -> 10 sec LRW
1.5 > SNR -> 15 sec VLRW 1.5 > SNR -> 15 sec VLRW
see also Diehl et al. 2009 see also Diehl et al. 2009
:param snr: Signal to noise ration which decides the witdth of the resolution window
:parameter: extent, can be 'local', 'regional', 'global' :type snr: float
:param extent: can be 'local', 'regional', 'global'
>>> getResolutionWindow(0.5) :type extent: str
7.5 :return: half width of the resolution window
>>> getResolutionWindow(1.8) :rtype: float
5.0
>>> getResolutionWindow(2.3)
2.5
>>> getResolutionWindow(4)
1.0
>>> getResolutionWindow(2)
2.5
""" """
res_wins = { res_wins = {
@ -535,17 +541,17 @@ def getResolutionWindow(snr, extent):
def select_for_phase(st, phase): def select_for_phase(st, phase):
''' """
takes a STream object and a phase name and returns that particular component Takes a Stream object and a phase name and returns that particular component
which presumably shows the chosen PHASE best which presumably shows the chosen PHASE best
:param st: stream object containing one or more component[s] :param st: stream object containing one or more component[s]
:type st: `~obspy.core.stream.Stream` :type st: `~obspy.core.stream.Stream`
:param phase: label of the phase for which the stream selection is carried :param phase: label of the phase for which the stream selection is carried out; 'P' or 'S'
out; 'P' or 'S'
:type phase: str :type phase: str
:return: :return: stream object containing the selected phase
''' :rtype: `~obspy.core.stream.Stream`
"""
from pylot.core.util.defaults import SetChannelComponents from pylot.core.util.defaults import SetChannelComponents
sel_st = Stream() sel_st = Stream()
@ -569,21 +575,22 @@ def select_for_phase(st, phase):
def wadaticheck(pickdic, dttolerance, iplot=0, fig_dict=None): def wadaticheck(pickdic, dttolerance, iplot=0, fig_dict=None):
''' """
Function to calculate Wadati-diagram from given P and S onsets in order Function to calculate Wadati-diagram from given P and S onsets in order
to detect S pick outliers. If a certain S-P time deviates by dttolerance to detect S pick outliers. If a certain S-P time deviates by dttolerance
from regression of S-P time the S pick is marked and down graded. from regression of S-P time the S pick is marked and down graded.
:param pickdic: dictionary containing picks and quality parameters
: param: pickdic, dictionary containing picks and quality parameters :type pickdic: dict
: type: dictionary :param dttolerance: dttolerance, maximum adjusted deviation of S-P time from
: param: dttolerance, maximum adjusted deviation of S-P time from
S-P time regression S-P time regression
: type: float :type dttolerance: float
:param iplot: iplot, if iplot > 1, Wadati diagram is shown
: param: iplot, if iplot > 1, Wadati diagram is shown :type iplot: int
: type: int :param fig_dict: Matplotlib figure used for plotting
''' :type fig_dict: `~matplotlib.figure.Figure`
:return: dictionary containing all onsets that passed the wadati check
:rtype: dict
"""
checkedonsets = pickdic checkedonsets = pickdic
@ -699,7 +706,7 @@ def wadaticheck(pickdic, dttolerance, iplot=0, fig_dict=None):
for Ppick, SPtime, station in zip(Ppicks, SPtimes, stations): for Ppick, SPtime, station in zip(Ppicks, SPtimes, stations):
ax.text(Ppick, SPtime + 0.01, '{0}'.format(station), color='0.25') ax.text(Ppick, SPtime + 0.01, '{0}'.format(station), color='0.25')
ax.set_title('Wadati-Diagram, %d S-P Times, Vp/Vs(raw)=%5.2f,' \ ax.set_title('Wadati-Diagram, %d S-P Times, Vp/Vs(raw)=%5.2f,'
'Vp/Vs(checked)=%5.2f' % (len(SPtimes), vpvsr, cvpvsr)) 'Vp/Vs(checked)=%5.2f' % (len(SPtimes), vpvsr, cvpvsr))
ax.legend(loc=1, numpoints=1) ax.legend(loc=1, numpoints=1)
else: else:
@ -714,48 +721,51 @@ def wadaticheck(pickdic, dttolerance, iplot=0, fig_dict=None):
def RMS(X): def RMS(X):
''' """
Function returns root mean square of a given array X Returns root mean square of a given array X
''' :param X: Array
:type X: `~numpy.ndarray`
:return: root mean square value of given array
:rtype: float
"""
return np.sqrt(np.sum(np.power(X, 2)) / len(X)) return np.sqrt(np.sum(np.power(X, 2)) / len(X))
def checksignallength(X, pick, TSNR, minsiglength, nfac, minpercent, iplot=0, fig=None, linecolor='k'): def checksignallength(X, pick, TSNR, minsiglength, nfac, minpercent, iplot=0, fig=None, linecolor='k'):
''' """
Function to detect spuriously picked noise peaks. Function to detect spuriously picked noise peaks.
Uses RMS trace of all 3 components (if available) to determine, Uses RMS trace of all 3 components (if available) to determine,
how many samples [per cent] after P onset are below certain how many samples [per cent] after P onset are below certain
threshold, calculated from noise level times noise factor. threshold, calculated from noise level times noise factor.
:param X: time series (seismogram)
: param: X, time series (seismogram) :type X: `~obspy.core.stream.Stream`
: type: `~obspy.core.stream.Stream` :param pick: initial (AIC) P onset time
:type pick: float
: param: pick, initial (AIC) P onset time :param TSNR: length of time windows around initial pick [s]
: type: float :type TSNR: (T_noise, T_gap, T_signal)
:param minsiglength: minium required signal length [s] to declare pick as P onset
: param: TSNR, length of time windows around initial pick [s] :type minsiglength: float
: type: tuple (T_noise, T_gap, T_signal) :param nfac: noise factor (nfac * noise level = threshold)
:type nfac: float
: param: minsiglength, minium required signal length [s] to :param minpercent: minimum required percentage of samples above calculated threshold
declare pick as P onset :type minpercent: float
: type: float :param iplot: iplot, if iplot > 1, results are shown in figure
:type iplot: int
: param: nfac, noise factor (nfac * noise level = threshold) :param fig: Matplotlib figure to plot results in
: type: float :type fig: `~matplotlib.figure.Figure`
:param linecolor: color of seismic traces
: param: minpercent, minimum required percentage of samples :type linecolor: str
above calculated threshold :return: flag, value of 1 if signal reached required length, 0 if signal is shorter than
: type: float required length
:rtype: int
: param: iplot, if iplot > 1, results are shown in figure """
: type: int
'''
plt_flag = 0 plt_flag = 0
try: try:
iplot = int(iplot) iplot = int(iplot)
except: except:
if iplot == True or iplot == 'True': if real_Bool(iplot):
iplot = 2 iplot = 2
else: else:
iplot = 0 iplot = 0
@ -802,7 +812,7 @@ def checksignallength(X, pick, TSNR, minsiglength, nfac, minpercent, iplot=0, fi
returnflag = 0 returnflag = 0
if iplot > 1: if iplot > 1:
if fig == None or fig == 'None': if real_None(fig) is None:
fig = plt.figure() # iplot) fig = plt.figure() # iplot)
plt_flag = 1 plt_flag = 1
fig._tight = True fig._tight = True
@ -828,21 +838,26 @@ def checksignallength(X, pick, TSNR, minsiglength, nfac, minpercent, iplot=0, fi
def checkPonsets(pickdic, dttolerance, jackfactor=5, iplot=0, fig_dict=None): def checkPonsets(pickdic, dttolerance, jackfactor=5, iplot=0, fig_dict=None):
''' """
Function to check statistics of P-onset times: Control deviation from Function to check statistics of P-onset times: Control deviation from
median (maximum adjusted deviation = dttolerance) and apply pseudo- median (maximum adjusted deviation = dttolerance) and apply pseudo-
bootstrapping jackknife. bootstrapping jackknife.
:param pickdic: dictionary containing picks and quality parameters
: param: pickdic, dictionary containing picks and quality parameters :type pickdic: dict
: type: dictionary :param dttolerance: maximum adjusted deviation of P onset time from the
: param: dttolerance, maximum adjusted deviation of P-onset time from
median of all P onsets median of all P onsets
: type: float :type dttolerance: float
:param jackfactor: if pseudo value is larger than jackfactor * Jackknife estimator,
: param: iplot, if iplot > 1, Wadati diagram is shown the distorts the estimator too much and will be removed
: type: int :type jackfactor: int
''' :param iplot: if iplot > 1, Wadati diagram is shown
:type iplot: int
:param fig_dict: Matplotlib figure used for plotting
:type fig_dict: `~matplotlib.figure.Figure`
:return: dictionary containing all onsets that passed the jackknife and the
median test
:rtype: dict
"""
checkedonsets = pickdic checkedonsets = pickdic
@ -883,7 +898,7 @@ def checkPonsets(pickdic, dttolerance, jackfactor=5, iplot=0, fig_dict=None):
print("checkPonsets: %d pick(s) deviate too much from median!" % len(ibad)) print("checkPonsets: %d pick(s) deviate too much from median!" % len(ibad))
print(badstations) print(badstations)
print("checkPonsets: Skipped %d P pick(s) out of %d" % (len(badstations) \ print("checkPonsets: Skipped %d P pick(s) out of %d" % (len(badstations)
+ len(badjkstations), len(stations))) + len(badjkstations), len(stations)))
goodmarker = 'goodPonsetcheck' goodmarker = 'goodPonsetcheck'
@ -921,7 +936,7 @@ def checkPonsets(pickdic, dttolerance, jackfactor=5, iplot=0, fig_dict=None):
ax = fig.add_subplot(111) ax = fig.add_subplot(111)
if len(badstations) > 0: if len(badstations) > 0:
ax.plot(ibad, np.array(Ppicks)[ibad], marker ='o', markerfacecolor='orange', markersize=14, ax.plot(ibad, np.array(Ppicks)[ibad], marker='o', markerfacecolor='orange', markersize=14,
linestyle='None', label='Median Skipped P Picks') linestyle='None', label='Median Skipped P Picks')
if len(badjkstations) > 0: if len(badjkstations) > 0:
ax.plot(badjk[0], np.array(Ppicks)[badjk], 'ro', markersize=14, label='Jackknife Skipped P Picks') ax.plot(badjk[0], np.array(Ppicks)[badjk], 'ro', markersize=14, label='Jackknife Skipped P Picks')
@ -943,24 +958,27 @@ def checkPonsets(pickdic, dttolerance, jackfactor=5, iplot=0, fig_dict=None):
return checkedonsets return checkedonsets
def jackknife(X, phi, h): def jackknife(X, phi, h=1):
''' """
Function to calculate the Jackknife Estimator for a given quantity, Function to calculate the Jackknife Estimator for a given quantity,
special type of boot strapping. Returns the jackknife estimator PHI_jack special type of boot strapping.
the pseudo values PHI_pseudo and the subgroup parameters PHI_sub.
: param: X, given quantity Returns the jackknife estimator PHI_jack the pseudo values PHI_pseudo
: type: list and the subgroup parameters PHI_sub.
:param X: list containing UTCDateTime objcects representing P onsets
: param: phi, chosen estimator, choose between: :type X: list (`~obspy.core.utcdatetime.UTCDateTime`)
:param phi:phi, chosen estimator, choose between:
"MED" for median "MED" for median
"MEA" for arithmetic mean "MEA" for arithmetic mean
"VAR" for variance "VAR" for variance
: type: string :type phi: str
:param h: size of subgroups, optional (default = 1)
: param: h, size of subgroups, optinal, default = 1 :type h: int
: type: integer :return: Tuple containing the Jackknife estimator PHI_jack, a list of jackknife pseudo
''' values (PHI_pseudo) and the Jackknife estimators (PHI_sub) of the subgroups.
Will return (None, None, None) if X cannot be divided in h subgroups of equals size
:rtype: (float, list, list)
"""
PHI_jack = None PHI_jack = None
PHI_pseudo = None PHI_pseudo = None
@ -1008,40 +1026,42 @@ def jackknife(X, phi, h):
def checkZ4S(X, pick, zfac, checkwin, iplot, fig=None, linecolor='k'): def checkZ4S(X, pick, zfac, checkwin, iplot, fig=None, linecolor='k'):
''' """
Function to compare energy content of vertical trace with Function to compare energy content of vertical trace with
energy content of horizontal traces to detect spuriously energy content of horizontal traces to detect spuriously
picked S onsets instead of P onsets. Usually, P coda shows picked S onsets instead of P onsets.
larger longitudal energy on vertical trace than on horizontal
traces, where the transversal energy is larger within S coda.
Be careful: there are special circumstances, where this is not
the case!
: param: X, fitered(!) time series, three traces Usually, P coda shows larger longitudal energy on vertical trace
: type: `~obspy.core.stream.Stream` than on horizontal traces, where the transversal energy is larger
within S coda. Be careful: there are special circumstances, where
this is not the case!
To pass the test, vertical P-coda level must exceed horizontal P-coda level
zfac times EN-coda level
: param: pick, initial (AIC) P onset time :param X: fitered(!) time series, three traces
: type: float :type X: `~obspy.core.stream.Stream`
:param pick: initial (AIC) P onset time
: param: zfac, factor for threshold determination, :type pick: float
vertical energy must exceed coda level times zfac :param zfac: factor for threshold determination, vertical energy must
to declare a pick as P onset exceed coda level times zfac to declare a pick as P onset
: type: float :type zfac: float
:param checkwin: window length [s] for calculating P-coda engergy content
: param: checkwin, window length [s] for calculating P-coda :type checkwin: float
energy content :param iplot: if iplot > 1, energy content and threshold are shown
: type: float :type iplot: int
:param fig: Matplotlib figure to plot results in
: param: iplot, if iplot > 1, energy content and threshold :type fig: `~matplotlib.figure.Figure`
are shown :param linecolor: color of seismic traces
: type: int :type linecolor: str
''' :return: returnflag; 0 if onset failed test, 1 if onset passed test
:rtype: int
"""
plt_flag = 0 plt_flag = 0
try: try:
iplot = int(iplot) iplot = int(iplot)
except: except:
if iplot == True or iplot == 'True': if real_Bool(iplot):
iplot = 2 iplot = 2
else: else:
iplot = 0 iplot = 0
@ -1123,14 +1143,14 @@ def checkZ4S(X, pick, zfac, checkwin, iplot, fig=None, linecolor='k'):
t = np.arange(diff_dict[key], trace.stats.npts / trace.stats.sampling_rate + diff_dict[key], t = np.arange(diff_dict[key], trace.stats.npts / trace.stats.sampling_rate + diff_dict[key],
trace.stats.delta) trace.stats.delta)
if i == 0: if i == 0:
if fig == None or fig == 'None': if real_None(fig) is None:
fig = plt.figure() # self.iplot) ### WHY? MP MP fig = plt.figure() # self.iplot) ### WHY? MP MP
plt_flag = 1 plt_flag = 1
ax1 = fig.add_subplot(3, 1, i + 1) ax1 = fig.add_subplot(3, 1, i + 1)
ax = ax1 ax = ax1
ax.set_title('CheckZ4S, Station %s' % zdat[0].stats.station) ax.set_title('CheckZ4S, Station %s' % zdat[0].stats.station)
else: else:
if fig == None or fig == 'None': if real_None(fig) is None:
fig = plt.figure() # self.iplot) ### WHY? MP MP fig = plt.figure() # self.iplot) ### WHY? MP MP
plt_flag = 1 plt_flag = 1
ax = fig.add_subplot(3, 1, i + 1, sharex=ax1) ax = fig.add_subplot(3, 1, i + 1, sharex=ax1)
@ -1154,14 +1174,20 @@ def checkZ4S(X, pick, zfac, checkwin, iplot, fig=None, linecolor='k'):
def getQualityFromUncertainty(uncertainty, Errors): def getQualityFromUncertainty(uncertainty, Errors):
'''Script to transform uncertainty into quality classes 0-4 """
regarding adjusted time errors Errors. Script to transform uncertainty into quality classes 0-4 regarding adjusted time errors
''' :param uncertainty: symmetric picking error of picks
:type uncertainty: float
:param Errors: Width of uncertainty classes 0-4 in seconds
:type Errors: list
:return: quality of pick (0-4)
:rtype: int
"""
# set initial quality to 4 (worst) and change only if one condition is hit # set initial quality to 4 (worst) and change only if one condition is hit
quality = 4 quality = 4
if uncertainty == None or uncertainty == 'None': if real_None(uncertainty) is None:
return quality return quality
if uncertainty <= Errors[0]: if uncertainty <= Errors[0]:

View File

@ -8,6 +8,13 @@ except:
def checkurl(url='https://ariadne.geophysik.ruhr-uni-bochum.de/trac/PyLoT/'): def checkurl(url='https://ariadne.geophysik.ruhr-uni-bochum.de/trac/PyLoT/'):
"""
check if URL is available
:param url: url
:type url: str
:return: available: True/False
:rtype: bool
"""
try: try:
urlopen(url, timeout=1) urlopen(url, timeout=1)
return True return True

View File

@ -15,6 +15,11 @@ class Event(ObsPyEvent):
''' '''
def __init__(self, path): def __init__(self, path):
"""
Initialize event by event directory
:param path: path to event directory
:type path: str
"""
self.pylot_id = path.split('/')[-1] self.pylot_id = path.split('/')[-1]
# initialize super class # initialize super class
super(Event, self).__init__(resource_id=ResourceIdentifier('smi:local/' + self.pylot_id)) super(Event, self).__init__(resource_id=ResourceIdentifier('smi:local/' + self.pylot_id))
@ -30,10 +35,20 @@ class Event(ObsPyEvent):
self.get_notes() self.get_notes()
def get_notes_path(self): def get_notes_path(self):
"""
Notes files is freely editable by the user and can contain notes regarding the event
:return: path to notes file
:rtype: str
"""
notesfile = os.path.join(self.path, 'notes.txt') notesfile = os.path.join(self.path, 'notes.txt')
return notesfile return notesfile
def get_notes(self): def get_notes(self):
"""
set self.note attribute to content of notes file
:return:
:rtype: None
"""
notesfile = self.get_notes_path() notesfile = self.get_notes_path()
if os.path.isfile(notesfile): if os.path.isfile(notesfile):
with open(notesfile) as infile: with open(notesfile) as infile:
@ -48,34 +63,81 @@ class Event(ObsPyEvent):
pass pass
def addNotes(self, notes): def addNotes(self, notes):
"""
Set new notes string
:param notes: notes to save in Event object
:type notes: str
:return:
:rtype: None
"""
self.notes = str(notes) self.notes = str(notes)
def clearNotes(self): def clearNotes(self):
"""
Clear event notes
:return:
:rtype: None
"""
self.notes = None self.notes = None
def isRefEvent(self): def isRefEvent(self):
"""
Return reference event flag
:return: True if event is refence event
:rtype: bool
"""
return self._refEvent return self._refEvent
def isTestEvent(self): def isTestEvent(self):
"""
Return test event flag
:return: True if event is test event
:rtype: bool
"""
return self._testEvent return self._testEvent
def setRefEvent(self, bool): def setRefEvent(self, bool):
"""
Set reference event flag
:param bool: new reference event flag
:type bool: bool
:return:
:rtype: None
"""
self._refEvent = bool self._refEvent = bool
if bool: self._testEvent = False if bool: self._testEvent = False
def setTestEvent(self, bool): def setTestEvent(self, bool):
"""
Set test event flag
:param bool: new test event flag
:type bool: bool
:return:
:rtype: None
"""
self._testEvent = bool self._testEvent = bool
if bool: self._refEvent = False if bool: self._refEvent = False
def clearObsPyPicks(self, picktype): def clearObsPyPicks(self, picktype):
"""
Remove picks of a certain type from event
:param picktype: type of picks to remove, 'auto' or 'manual'
:type picktype: str
:return:
:rtype: None
"""
for index, pick in reversed(list(enumerate(self.picks))): for index, pick in reversed(list(enumerate(self.picks))):
if picktype in str(pick.method_id): if picktype in str(pick.method_id):
self.picks.pop(index) self.picks.pop(index)
def addPicks(self, picks): def addPicks(self, picks):
''' """
add pylot picks and overwrite existing ones add pylot picks and overwrite existing ones
''' :param picks: picks to add to event in pick dictionary
:type picks: dict
:return:
:rtype: None
"""
for station in picks: for station in picks:
self.pylot_picks[station] = picks[station] self.pylot_picks[station] = picks[station]
# add ObsPy picks (clear old manual and copy all new manual from pylot) # add ObsPy picks (clear old manual and copy all new manual from pylot)
@ -83,6 +145,13 @@ class Event(ObsPyEvent):
self.picks += picks_from_picksdict(self.pylot_picks) self.picks += picks_from_picksdict(self.pylot_picks)
def addAutopicks(self, autopicks): def addAutopicks(self, autopicks):
"""
Add automatic picks to event
:param autopicks: automatic picks to add to event
:type autopicks dict:
:return:
:rtype: None
"""
for station in autopicks: for station in autopicks:
self.pylot_autopicks[station] = autopicks[station] self.pylot_autopicks[station] = autopicks[station]
# add ObsPy picks (clear old auto and copy all new auto from pylot) # add ObsPy picks (clear old auto and copy all new auto from pylot)
@ -90,6 +159,15 @@ class Event(ObsPyEvent):
self.picks += picks_from_picksdict(self.pylot_autopicks) self.picks += picks_from_picksdict(self.pylot_autopicks)
def setPick(self, station, pick): def setPick(self, station, pick):
"""
Set pick for a station
:param station: station name
:type station: str
:param pick:
:type pick: dict
:return:
:rtype:
"""
if pick: if pick:
self.pylot_picks[station] = pick self.pylot_picks[station] = pick
else: else:
@ -101,21 +179,46 @@ class Event(ObsPyEvent):
self.picks += picks_from_picksdict(self.pylot_picks) self.picks += picks_from_picksdict(self.pylot_picks)
def setPicks(self, picks): def setPicks(self, picks):
''' """
set pylot picks and delete and overwrite all existing Set pylot picks and delete and overwrite all existing
''' :param picks: new picks
:type picks: dict
:return:
:rtype: None
"""
self.pylot_picks = picks self.pylot_picks = picks
self.clearObsPyPicks('manual') self.clearObsPyPicks('manual')
self.picks += picks_from_picksdict(self.pylot_picks) self.picks += picks_from_picksdict(self.pylot_picks)
def getPick(self, station): def getPick(self, station):
"""
Get pick at station
:param station: station name
:type station: str
:return: pick dictionary of station
:rtype: dict
"""
if station in self.pylot_picks.keys(): if station in self.pylot_picks.keys():
return self.pylot_picks[station] return self.pylot_picks[station]
def getPicks(self): def getPicks(self):
"""
Return pylot picks
:return:
:rtype: dict
"""
return self.pylot_picks return self.pylot_picks
def setAutopick(self, station, pick): def setAutopick(self, station, pick):
"""
Set pick at station
:param station: station name
:type station: str
:param pick:
:type pick: dict
:return:
:rtype: None
"""
if pick: if pick:
self.pylot_autopicks[station] = pick self.pylot_autopicks[station] = pick
else: else:
@ -127,25 +230,46 @@ class Event(ObsPyEvent):
self.picks += picks_from_picksdict(self.pylot_autopicks) self.picks += picks_from_picksdict(self.pylot_autopicks)
def setAutopicks(self, picks): def setAutopicks(self, picks):
''' """
set pylot picks and delete and overwrite all existing Set pylot picks and delete and overwrite all existing
''' :param picks: new picks
:type picks: dict
:return:
:rtype: None
"""
self.pylot_autopicks = picks self.pylot_autopicks = picks
self.clearObsPyPicks('auto') self.clearObsPyPicks('auto')
self.picks += picks_from_picksdict(self.pylot_autopicks) self.picks += picks_from_picksdict(self.pylot_autopicks)
def getAutopick(self, station): def getAutopick(self, station):
"""
Return autopick at station
:param station: station name
:type station: str
:return: pick dictionary
:rtype: dict
"""
if station in self.pylot_autopicks.keys(): if station in self.pylot_autopicks.keys():
return self.pylot_autopicks[station] return self.pylot_autopicks[station]
def getAutopicks(self): def getAutopicks(self):
"""
Get autopicks of event
:return: dict containing automatic picks
:rtype: dict
"""
return self.pylot_autopicks return self.pylot_autopicks
def save(self, filename): def save(self, filename):
''' """
Save PyLoT Event to a file. Save PyLoT Event to a file.
Can be loaded by using event.load(filename). Can be loaded by using event.load(filename).
''' Uses pickling to save event object to file
:param filename: filename to save project under
:type filename: str
:return:
:rtype: None
"""
try: try:
import cPickle import cPickle
except ImportError: except ImportError:
@ -159,9 +283,13 @@ class Event(ObsPyEvent):
@staticmethod @staticmethod
def load(filename): def load(filename):
''' """
Load project from filename. Load project from filename
''' :param filename: to load event file
:type filename: str
:return: event loaded from file
:rtype: Event
"""
try: try:
import cPickle import cPickle
except ImportError: except ImportError:

View File

@ -24,7 +24,7 @@ class Thread(QThread):
if self.redirect_stdout: if self.redirect_stdout:
sys.stdout = self sys.stdout = self
try: try:
if self.arg: if self.arg is not None:
self.data = self.func(self.arg) self.data = self.func(self.arg)
else: else:
self.data = self.func() self.data = self.func()

View File

@ -13,7 +13,7 @@ from obspy.core import AttribDict
from obspy.signal.rotate import rotate2zne from obspy.signal.rotate import rotate2zne
from obspy.io.xseed.utils import SEEDParserException from obspy.io.xseed.utils import SEEDParserException
from pylot.core.io.inputs import PylotParameter from pylot.core.io.inputs import PylotParameter, FilterOptions
from pylot.styles import style_settings from pylot.styles import style_settings
from scipy.interpolate import splrep, splev from scipy.interpolate import splrep, splev
@ -31,13 +31,36 @@ def _pickle_method(m):
else: else:
return getattr, (m.im_self, m.im_func.func_name) return getattr, (m.im_self, m.im_func.func_name)
def getAutoFilteroptions(phase, parameter):
filtername = {'P': 'bpz2',
'S': 'bph2'}
if not phase in filtername.keys():
print('autoPickParameter: No filter options for phase {}.'.format(phase))
return
freqmin, freqmax = parameter.get(filtername[phase])
filteroptions = FilterOptions(type='bandpass', freq=[freqmin, freqmax], order=4) # order=4 default from obspy
return filteroptions
def readDefaultFilterInformation(fname): def readDefaultFilterInformation(fname):
"""
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(fname)
return readFilterInformation(pparam) return readFilterInformation(pparam)
def readFilterInformation(pylot_parameter): def readFilterInformation(pylot_parameter):
"""
Read filter information from PylotParameter object into a dictionary
:param pylot_parameter: PylotParameter object
:type pylot_parameter: `~pylot.pylot.core.io.inputs.PylotParameter`
:return: dictionary containing the filter information
:rtype: dict
"""
p_filter = {'filtertype': pylot_parameter['filter_type'][0], p_filter = {'filtertype': pylot_parameter['filter_type'][0],
'freq': [pylot_parameter['minfreq'][0], pylot_parameter['maxfreq'][0]], 'freq': [pylot_parameter['minfreq'][0], pylot_parameter['maxfreq'][0]],
'order': int(pylot_parameter['filter_order'][0])} 'order': int(pylot_parameter['filter_order'][0])}
@ -50,23 +73,46 @@ def readFilterInformation(pylot_parameter):
def fit_curve(x, y): def fit_curve(x, y):
"""
:param x: data points defining a curve y = f(x)
:type x: array_like
:param y: data points defining a curve y = f(x)
:type y: array_like
:return: tuple containing a function to evaluate a B-spline and
(the vector of knots, B-spline coefficients, degree of the spline)
:rtype: (func, (t, c, k))
"""
return splev, splrep(x, y) return splev, splrep(x, y)
def getindexbounds(f, eta): def getindexbounds(f, eta):
mi = f.argmax() """
m = max(f) Get indices of values closest below and above maximum value in an array
b = m * eta :param f: array
l = find_nearest(f[:mi], b) :type f: `~numpy.ndarray`
u = find_nearest(f[mi:], b) + mi :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 return mi, l, u
def gen_Pool(ncores=0): def gen_Pool(ncores=0):
''' """
Generate mulitprocessing pool object utilizing ncores amount of cores
:param ncores: number of CPU cores for multiprocessing.Pool, if ncores == 0 use all available :param ncores: number of CPU cores for multiprocessing.Pool, if ncores == 0 use all available
:type ncores: int
:return: multiprocessing.Pool object :return: multiprocessing.Pool object
''' :rtype: `~multiprocessing.Pool`
"""
import multiprocessing import multiprocessing
if ncores == 0: if ncores == 0:
@ -79,14 +125,19 @@ def gen_Pool(ncores=0):
def excludeQualityClasses(picks, qClasses, timeerrorsP, timeerrorsS): def excludeQualityClasses(picks, qClasses, timeerrorsP, timeerrorsS):
''' """
takes PyLoT picks dictionary and returns a new dictionary with certain classes excluded. takes PyLoT picks dictionary and returns a new dictionary with certain classes excluded.
:param picks: PyLoT picks dictionary :param picks: PyLoT picks dictionary
:type picks: dict
:param qClasses: list (or int) of quality classes (0-4) to exclude :param qClasses: list (or int) of quality classes (0-4) to exclude
:param timeerrorsP: time errors for classes (0-4) for P :type qClasses: [int]
:param timeerrorsS: time errors for classes (0-4) for S :param timeerrorsP: width of quality classes for P onsets in seconds
:return: new picks dictionary :type timeerrorsP: (float, float, float, float)
''' :param timeerrorsS: width of quality classes for S onsets in seconds
:type timeerrorsS: (float, float, float, float])
:return: dictionary containing only picks above the excluded quality class(es)
:rtype: dict
"""
from pylot.core.pick.utils import getQualityFromUncertainty from pylot.core.pick.utils import getQualityFromUncertainty
if type(qClasses) in [int, float]: if type(qClasses) in [int, float]:
@ -114,9 +165,12 @@ def excludeQualityClasses(picks, qClasses, timeerrorsP, timeerrorsS):
def clims(lim1, lim2): def clims(lim1, lim2):
""" """
takes two pairs of limits and returns one pair of common limts takes two pairs of limits and returns one pair of common limts
:param lim1: :param lim1: limit 1
:param lim2: :type lim1: int
:return: :param lim2: limit 2
:type lim2: int
:return: new upper and lower limit common to both given limits
:rtype: [int, int]
>>> clims([0, 4], [1, 3]) >>> clims([0, 4], [1, 3])
[0, 4] [0, 4]
@ -155,7 +209,7 @@ def demeanTrace(trace, window):
demeaned within a certain time window demeaned within a certain time window
:param trace: waveform trace object :param trace: waveform trace object
:type trace: `~obspy.core.stream.Trace` :type trace: `~obspy.core.stream.Trace`
:param window: :param window: time window whitin which data is demeaned
:type window: tuple :type window: tuple
:return: trace :return: trace
:rtype: `~obspy.core.stream.Trace` :rtype: `~obspy.core.stream.Trace`
@ -182,8 +236,11 @@ def find_in_list(list, str):
takes a list of strings and a string and returns the first list item takes a list of strings and a string and returns the first list item
matching the string pattern matching the string pattern
:param list: list to search in :param list: list to search in
:type list: list
:param str: pattern to search for :param str: pattern to search for
:type str: str
:return: first list item containing pattern :return: first list item containing pattern
:rtype: str
.. example:: .. example::
@ -208,13 +265,15 @@ def find_in_list(list, str):
def find_nearest(array, value): def find_nearest(array, value):
''' """
function find_nearest takes an array and a value and returns the function find_nearest takes an array and a value and returns the
index of the nearest value found in the array index of the nearest value found in the array
:param array: array containing values :param array: array containing values
:type array: `~numpy.ndarray` :type array: `~numpy.ndarray`
:param value: number searched for :param value: number searched for
:type value: float
:return: index of the array item being nearest to the value :return: index of the array item being nearest to the value
:rtype: int
>>> a = np.array([ 1.80339578, -0.72546654, 0.95769195, -0.98320759, 0.85922623]) >>> a = np.array([ 1.80339578, -0.72546654, 0.95769195, -0.98320759, 0.85922623])
>>> find_nearest(a, 1.3) >>> find_nearest(a, 1.3)
@ -228,17 +287,18 @@ def find_nearest(array, value):
>>> a = np.array([ 1.1, -0.7, 0.9, -0.9, 0.8]) >>> a = np.array([ 1.1, -0.7, 0.9, -0.9, 0.8])
>>> find_nearest(a, 0.849) >>> find_nearest(a, 0.849)
4 4
''' """
return (np.abs(array - value)).argmin() return (np.abs(array - value)).argmin()
def fnConstructor(s): def fnConstructor(s):
''' """
takes a string and returns a valid filename (especially on windows machines) takes a string and returns a valid filename (especially on windows machines)
:param s: desired filename :param s: desired filename
:type s: str :type s: str
:return: valid filename :return: valid filename
''' :rtype: str
"""
if type(s) is str: if type(s) is str:
s = s.split(':')[-1] s = s.split(':')[-1]
else: else:
@ -255,6 +315,13 @@ def fnConstructor(s):
def real_None(value): def real_None(value):
"""
Convert "None" to None
:param value:
:type value: str, bool
:return:
:rtype: bool
"""
if value == 'None': if value == 'None':
return None return None
else: else:
@ -262,9 +329,16 @@ def real_None(value):
def real_Bool(value): def real_Bool(value):
if value == 'True': """
Convert string representations of bools to their true boolean value
:param value:
:type value: str, bool
:return: true boolean value
:rtype: bool
"""
if value in ['True', 'true']:
return True return True
elif value == 'False': elif value in ['False', 'false']:
return False return False
else: else:
return value return value
@ -276,7 +350,8 @@ def four_digits(year):
from the last 100 years from the last 100 years
:param year: two digit year :param year: two digit year
:type year: int :type year: int
:return: four digit year correspondant :return: four digit year correspondent
:rtype: int
>>> four_digits(20) >>> four_digits(20)
1920 1920
@ -293,13 +368,13 @@ def four_digits(year):
def common_range(stream): def common_range(stream):
''' """
takes a stream object and returns the earliest end and the latest start takes a stream object and returns the earliest end and the latest start time of all contained trace objects
time of all contained trace objects
:param stream: seismological data stream :param stream: seismological data stream
:type stream: `~obspy.core.stream.Stream` :type stream: `~obspy.core.stream.Stream`
:return: maximum start time and minimum end time :return: maximum start time and minimum end time
''' :rtype: (`~maximum start time and minimum end time`, maximum start time and minimum end time)
"""
max_start = None max_start = None
min_end = None min_end = None
for trace in stream: for trace in stream:
@ -311,51 +386,82 @@ def common_range(stream):
def full_range(stream): def full_range(stream):
''' """
takes a stream object and returns the latest end and the earliest start takes a stream object and returns the latest end and the earliest start
time of all contained trace objects time of all contained trace objects
:param stream: seismological data stream :param stream: seismological data stream
:type stream: `~obspy.core.stream.Stream` :type stream: `~obspy.core.stream.Stream`
:return: minimum start time and maximum end time :return: minimum start time and maximum end time
''' :rtype: (`~maximum start time and minimum end time`, maximum start time and minimum end time)
min_start = UTCDateTime() """
max_end = None min_start = min([trace.stats.starttime for trace in stream])
for trace in stream: max_end = max([trace.stats.endtime for trace in stream])
if trace.stats.starttime < min_start:
min_start = trace.stats.starttime
if max_end is None or trace.stats.endtime > max_end:
max_end = trace.stats.endtime
return min_start, max_end return min_start, max_end
def transformFilteroptions2String(filtopts):
st = ''
if not filtopts:
return st
if 'type' in filtopts.keys():
st += '{}'.format(filtopts['type'])
if 'freq' in filtopts.keys():
st += ' | freq: {}'.format(filtopts['freq'])
elif 'freqmin' in filtopts.keys() and 'freqmax' in filtopts.keys():
st += ' | freqmin: {} | freqmax: {}'.format(filtopts['freqmin'], filtopts['freqmax'])
for key, value in filtopts.items():
if key in ['type', 'freq', 'freqmin', 'freqmax']:
continue
st += ' | {}: {}'.format(key, value)
return st
def transformFilterString4Export(st):
st = st.replace('|', '//')
st = st.replace(':', '/')
st = st.replace(' ', '')
return st
def backtransformFilterString(st):
st = st.split('smi:local/')
st = st[1] if len(st) > 1 else st[0]
st = st.replace('//', ' | ')
st = st.replace('/', ': ')
return st
def getHash(time): def getHash(time):
''' """
takes a time object and returns the corresponding SHA1 hash of the takes a time object and returns the corresponding SHA1 hash of the formatted date string
formatted date string
:param time: time object for which a hash should be calculated :param time: time object for which a hash should be calculated
:type time: :class: `~obspy.core.utcdatetime.UTCDateTime` object :type time: `~obspy.core.utcdatetime.UTCDateTime`
:return: str :return: SHA1 hash
''' :rtype: str
"""
hg = hashlib.sha1() 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'))
return hg.hexdigest() return hg.hexdigest()
def getLogin(): def getLogin():
''' """
returns the actual user's login ID returns the actual user's login ID
:return: login ID :return: login ID
''' :rtype: str
"""
return os.getlogin() return os.getlogin()
def getOwner(fn): def getOwner(fn):
''' """
takes a filename and return the login ID of the actual owner of the file takes a filename and return the login ID of the actual owner of the file
:param fn: filename of the file tested :param fn: filename of the file tested
:type fn: str :type fn: str
:return: login ID of the file's owner :return: login ID of the file's owner
''' :rtype: str
"""
system_name = platform.system() system_name = platform.system()
if system_name in ["Linux", "Darwin"]: if system_name in ["Linux", "Darwin"]:
import pwd import pwd
@ -376,6 +482,8 @@ def getPatternLine(fn, pattern):
:param pattern: pattern string to search for :param pattern: pattern string to search for
:type pattern: str :type pattern: str
:return: the complete line containing the pattern string or None :return: the complete line containing the pattern string or None
:rtype: int, None
>>> getPatternLine('utils.py', 'python') >>> getPatternLine('utils.py', 'python')
'#!/usr/bin/env python\\n' '#!/usr/bin/env python\\n'
@ -397,18 +505,20 @@ def is_executable(fn):
and False otherwise and False otherwise
:param fn: path to the file to be tested :param fn: path to the file to be tested
:return: True or False :return: True or False
:rtype: bool
""" """
return os.path.isfile(fn) and os.access(fn, os.X_OK) return os.path.isfile(fn) and os.access(fn, os.X_OK)
def isSorted(iterable): def isSorted(iterable):
''' """
takes an iterable and returns 'True' if the items are in order otherwise takes an iterable and returns True if the items are in order otherwise False
'False'
:param iterable: an iterable object :param iterable: an iterable object
:type iterable: :type iterable:
:return: Boolean :return: Boolean
:rtype: bool
..example::
>>> isSorted(1) >>> isSorted(1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -421,7 +531,7 @@ def isSorted(iterable):
False False
>>> isSorted([2,3,1,4]) >>> isSorted([2,3,1,4])
False False
''' """
assert isIterable(iterable), 'object is not iterable; object: {' \ assert isIterable(iterable), 'object is not iterable; object: {' \
'0}'.format(iterable) '0}'.format(iterable)
if type(iterable) is str: if type(iterable) is str:
@ -431,10 +541,12 @@ def isSorted(iterable):
def isIterable(obj): def isIterable(obj):
""" """
takes a python object and returns 'True' is the object is iterable and takes a python object and returns True is the object is iterable and
'False' otherwise False otherwise
:param obj: a python object :param obj: a python object
:type obj: object
:return: True of False :return: True of False
:rtype: bool
""" """
try: try:
iterator = iter(obj) iterator = iter(obj)
@ -451,6 +563,7 @@ def key_for_set_value(d):
:type d: dict :type d: dict
:return: key to the first non-False value found; None if no value's :return: key to the first non-False value found; None if no value's
boolean equals True boolean equals True
:rtype:
""" """
r = None r = None
for k, v in d.items(): for k, v in d.items():
@ -460,13 +573,18 @@ def key_for_set_value(d):
def prepTimeAxis(stime, trace, verbosity=0): def prepTimeAxis(stime, trace, verbosity=0):
''' """
takes a starttime and a trace object and returns a valid time axis for takes a starttime and a trace object and returns a valid time axis for
plotting plotting
:param stime: start time of the actual seismogram as UTCDateTime :param stime: start time of the actual seismogram as UTCDateTime
:type stime: `~obspy.core.utcdatetime.UTCDateTime`
:param trace: seismic trace object :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 :return: valid numpy array with time stamps for plotting
''' :rtype: `~numpy.ndarray`
"""
nsamp = trace.stats.npts nsamp = trace.stats.npts
srate = trace.stats.sampling_rate srate = trace.stats.sampling_rate
tincr = trace.stats.delta tincr = trace.stats.delta
@ -513,6 +631,20 @@ def find_horizontals(data):
def make_pen(picktype, phase, key, quality): def make_pen(picktype, phase, key, quality):
"""
Make PyQtGraph.QPen
:param picktype: 'manual' or 'automatic'
:type picktype: str
:param phase: 'P' or 'S'
:type phase: str
:param key: 'mpp', 'epp', 'lpp' or 'spe', (earliest/latest possible pick, symmetric picking error or
most probable pick)
:type key: str
:param quality: quality class of pick, decides color modifier
:type quality: int
:return: PyQtGraph QPen
:rtype: `~QPen`
"""
if pg: if pg:
rgba = pick_color(picktype, phase, quality) rgba = pick_color(picktype, phase, quality)
linestyle, width = pick_linestyle_pg(picktype, key) linestyle, width = pick_linestyle_pg(picktype, key)
@ -521,8 +653,22 @@ def make_pen(picktype, phase, key, quality):
def pick_color(picktype, phase, quality=0): def pick_color(picktype, phase, quality=0):
"""
Create pick color by modifying the base color by the quality.
Returns rgba values in a range of [0, 255]. picktype, phase decide the base color,
quality decides the applied modifier
:param picktype: 'manual' or 'automatic'
:type picktype: str
:param phase: 'P' or 'S'
:type phase: str
: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)
"""
min_quality = 3 min_quality = 3
bpc = base_phase_colors(picktype, phase) bpc = base_phase_colors(picktype, phase) # returns dict like {'modifier': 'g', 'rgba': (0, 0, 255, 255)}
rgba = bpc['rgba'] rgba = bpc['rgba']
modifier = bpc['modifier'] modifier = bpc['modifier']
intensity = 255.*quality/min_quality intensity = 255.*quality/min_quality
@ -531,6 +677,20 @@ def pick_color(picktype, phase, quality=0):
def pick_color_plt(picktype, phase, quality=0): def pick_color_plt(picktype, phase, quality=0):
"""
Create pick color by modifying the base color by the quality.
Returns rgba values in a range of [0, 1]. picktype, phase decide the base color,
quality decides the applied modifier
:param picktype: 'manual' or 'automatic'
:type picktype: str
:param phase: 'P' or 'S'
:type phase: str
:param quality: quality of pick. Decides the new intensity of the modifier color
:type quality: int
:return: tuple containing rgba values matplotlib style, ranging from [0, 1]
:rtype: (float, float, float, float)
"""
rgba = list(pick_color(picktype, phase, quality)) rgba = list(pick_color(picktype, phase, quality))
for index, val in enumerate(rgba): for index, val in enumerate(rgba):
rgba[index] /= 255. rgba[index] /= 255.
@ -538,6 +698,16 @@ def pick_color_plt(picktype, phase, quality=0):
def pick_linestyle_plt(picktype, key): def pick_linestyle_plt(picktype, key):
"""
Get matplotlib line style for plotting by pick type and pick parameter (earliest/latest possible pick,
symmetric picking error or most probable pick).
:param picktype: 'manual' or 'automatic'
:type picktype: str
:param key: which pick parameter should be plotted, 'mpp', 'epp', 'lpp' or 'spe'
:type key: str
:return: tuple containing matplotlib line style string and line thicknes
:rtype: (str, float)
"""
linestyles_manu = {'mpp': ('solid', 2.), linestyles_manu = {'mpp': ('solid', 2.),
'epp': ('dashed', 1.), 'epp': ('dashed', 1.),
'lpp': ('dashed', 1.), 'lpp': ('dashed', 1.),
@ -552,6 +722,16 @@ def pick_linestyle_plt(picktype, key):
def pick_linestyle_pg(picktype, key): def pick_linestyle_pg(picktype, key):
"""
Get Qt line style by picktype and pick parameter (earliest/latest possible pick, symmetric picking error or
most probable pick)
:param picktype: 'manual' or 'automatic'
:type picktype: str
:param key: which pick parameter should be plotted, 'mpp', 'epp', 'lpp' or 'spe'
:type key: str
:return: Qt line style parameters
:rtype:
"""
linestyles_manu = {'mpp': (QtCore.Qt.SolidLine, 2.), linestyles_manu = {'mpp': (QtCore.Qt.SolidLine, 2.),
'epp': (QtCore.Qt.DashLine, 1.), 'epp': (QtCore.Qt.DashLine, 1.),
'lpp': (QtCore.Qt.DashLine, 1.), 'lpp': (QtCore.Qt.DashLine, 1.),
@ -566,6 +746,17 @@ def pick_linestyle_pg(picktype, key):
def modify_rgba(rgba, modifier, intensity): def modify_rgba(rgba, modifier, intensity):
"""
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
:param intensity: intensity to be added to selected color
:type intensity: float
:return: tuple containing rgba values
:rtype: (int, int, int, int)
"""
rgba = list(rgba) rgba = list(rgba)
index = {'r': 0, index = {'r': 0,
'g': 1, 'g': 1,
@ -580,10 +771,28 @@ def modify_rgba(rgba, modifier, intensity):
def base_phase_colors(picktype, phase): def base_phase_colors(picktype, phase):
"""
Get base color for a phase from style settings
:param picktype: 'manual' or 'automatic' picks
:type picktype: str
:param phase: Phase to select color for, 'P' or 'S'
:type phase: str
:return: dictionary {'modifier': 'g', 'rgba': (0, 0, 255, 255)}
:rtype: dict
"""
phasecolors = style_settings.phasecolors phasecolors = style_settings.phasecolors
return phasecolors[picktype][phase] return phasecolors[picktype][phase]
def transform_colors_mpl_str(colors, no_alpha=False): 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
:type no_alpha: bool
:return: String containing r, g, b values and alpha value if no_alpha is False (default)
:rtype: str
"""
colors = list(colors) colors = list(colors)
colors_mpl = tuple([color / 255. for color in colors]) colors_mpl = tuple([color / 255. for color in colors])
if no_alpha: if no_alpha:
@ -593,6 +802,13 @@ def transform_colors_mpl_str(colors, no_alpha=False):
return colors_mpl return colors_mpl
def transform_colors_mpl(colors): def transform_colors_mpl(colors):
"""
Transform rgba colors from [0, 255] to [0, 1]
:param colors: tuple of rgba color values ranging from [0, 255]
:type colors: (float, float, float, float)
:return: tuple of rgba color values ranging from [0, 1]
:rtype: (float, float, float, float)
"""
colors = list(colors) colors = list(colors)
colors_mpl = tuple([color / 255. for color in colors]) colors_mpl = tuple([color / 255. for color in colors])
return colors_mpl return colors_mpl
@ -600,10 +816,11 @@ def transform_colors_mpl(colors):
def remove_underscores(data): def remove_underscores(data):
""" """
takes a `obspy.core.stream.Stream` object and removes all underscores takes a `obspy.core.stream.Stream` object and removes all underscores
from stationnames from station names
:param data: stream of seismic data :param data: stream of seismic data
:type data: `obspy.core.stream.Stream` :type data: `~obspy.core.stream.Stream`
:return: data stream :return: data stream
:rtype: `~obspy.core.stream.Stream`
""" """
for tr in data: for tr in data:
# remove underscores # remove underscores
@ -612,16 +829,17 @@ def remove_underscores(data):
def trim_station_components(data, trim_start=True, trim_end=True): 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 cut a stream so only the part common to all three traces is kept to avoid dealing with offsets
:param data: stream of seismic data :param data: stream of seismic data
:type data: `obspy.core.stream.Stream` :type data: `~obspy.core.stream.Stream`
:param trim_start: trim start of stream :param trim_start: trim start of stream
:type trim_start: bool :type trim_start: bool
:param trim_end: trim end of stream :param trim_end: trim end of stream
:type trim_end: bool :type trim_end: bool
:return: data stream :return: data stream
''' :rtype: `~obspy.core.stream.Stream`
"""
starttime = {False: None} starttime = {False: None}
endtime = {False: None} endtime = {False: None}
@ -639,11 +857,13 @@ def trim_station_components(data, trim_start=True, trim_end=True):
def check4gaps(data): def check4gaps(data):
''' """
check for gaps in Stream and remove them check for gaps in Stream and remove them
:param data: stream of seismic data :param data: stream of seismic data
:type data: `~obspy.core.stream.Stream`
:return: data stream :return: data stream
''' :rtype: `~obspy.core.stream.Stream`
"""
stations = get_stations(data) stations = get_stations(data)
for station in stations: for station in stations:
@ -657,11 +877,13 @@ def check4gaps(data):
def check4doubled(data): def check4doubled(data):
''' """
check for doubled stations for same channel in Stream and take only the first one check for doubled stations for same channel in Stream and take only the first one
:param data: stream of seismic data :param data: stream of seismic data
:type data: `~obspy.core.stream.Stream`
:return: data stream :return: data stream
''' :rtype: `~obspy.core.stream.Stream`
"""
stations = get_stations(data) stations = get_stations(data)
for station in stations: for station in stations:
@ -682,6 +904,13 @@ def check4doubled(data):
def get_stations(data): def get_stations(data):
"""
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
"""
stations = [] stations = []
for tr in data: for tr in data:
station = tr.stats.station station = tr.stats.station
@ -692,14 +921,35 @@ def get_stations(data):
def check4rotated(data, metadata=None, verbosity=1): def check4rotated(data, metadata=None, verbosity=1):
"""
:param data: stream object containing seismic traces
:type data: `~obspy.core.stream.Stream`
:param metadata: tuple containing metadata type string and metadata parser object
:type metadata: (str, `~obspy.io.xseed.parser.Parser`)
:param verbosity: if 0 print no information at runtime
:type verbosity: int
:return: stream object with traditionally oriented traces (ZNE) for stations that had misaligned traces (123) before
:rtype: `~obspy.core.stream.Stream`
"""
def rotate_components(wfstream, metadata=None): def rotate_components(wfstream, metadata=None):
"""rotates components if orientation code is numeric. """
azimut and dip are fetched from metadata""" 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
:type wfstream: `~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`
"""
try: try:
# indexing fails if metadata is None # indexing fails if metadata is None
metadata[0] metadata[0]
except: except TypeError:
if verbosity: if verbosity:
msg = 'Warning: could not rotate traces since no metadata was given\nset Inventory file!' msg = 'Warning: could not rotate traces since no metadata was given\nset Inventory file!'
print(msg) print(msg)
@ -714,7 +964,15 @@ def check4rotated(data, metadata=None, verbosity=1):
parser = metadata[1] parser = metadata[1]
def get_dip_azimut(parser, trace_id): def get_dip_azimut(parser, trace_id):
"""gets azimut and dip for a trace out of the metadata parser""" """
Gets azimuth and dip by trace id out of the metadata parser
:param parser: metadata parser object
:type parser: `~obspy.io.xseed.parser.Parser`
:param trace_id: eg. 'BW.RJOB..EHZ',
:type trace_id: str
:return: tuple containing dip and azimuth of the trace corresponding to trace_id
:rtype: (float, float)
"""
dip = None dip = None
azimut = None azimut = None
try: try:
@ -728,14 +986,14 @@ def check4rotated(data, metadata=None, verbosity=1):
dip = blockette_.dip dip = blockette_.dip
azimut = blockette_.azimuth azimut = blockette_.azimuth
break break
if dip is None or azimut is None: if (dip is None or azimut is None) or (dip == 0 and azimut == 0):
error_msg = 'Dip and azimuth not available for trace_id {}'.format(trace_id) error_msg = 'Dip and azimuth not available for trace_id {}'.format(trace_id)
raise ValueError(error_msg) raise ValueError(error_msg)
return dip, azimut return dip, azimut
trace_ids = [trace.id for trace in wfstream] trace_ids = [trace.id for trace in wfstream]
for trace_id in trace_ids: for trace_id in trace_ids:
orientation = trace_id[-1] orientation = trace_id[-1] # last letter if trace id is orientation code, ZNE or 123
if orientation.isnumeric(): if orientation.isnumeric():
# misaligned channels have a number as orientation # misaligned channels have a number as orientation
azimuts = [] azimuts = []
@ -755,7 +1013,7 @@ def check4rotated(data, metadata=None, verbosity=1):
wfstream[1], azimuts[1], dips[1], wfstream[1], azimuts[1], dips[1],
wfstream[2], azimuts[2], dips[2]) wfstream[2], azimuts[2], dips[2])
print('check4rotated: rotated station {} to ZNE'.format(trace_id)) print('check4rotated: rotated station {} to ZNE'.format(trace_id))
z_index = dips.index(min(dips)) # get z-trace index (dip is measured from 0 to -90 z_index = dips.index(min(dips)) # get z-trace index (dip is measured from 0 to -90)
wfstream[z_index].data = z wfstream[z_index].data = z
wfstream[z_index].stats.channel = wfstream[z_index].stats.channel[0:-1] + 'Z' wfstream[z_index].stats.channel = wfstream[z_index].stats.channel[0:-1] + 'Z'
del trace_ids[z_index] del trace_ids[z_index]
@ -775,9 +1033,9 @@ def check4rotated(data, metadata=None, verbosity=1):
stations = get_stations(data) stations = get_stations(data)
for station in stations: for station in stations: # loop through all stations and rotate data if neccessary
wf_station = data.select(station=station) wf_station = data.select(station=station)
wf_station = rotate_components(wf_station, metadata) rotate_components(wf_station, metadata)
return data return data
@ -837,7 +1095,9 @@ def which(program, infile=None):
takes a program name and returns the full path to the executable or None takes a program name and returns the full path to the executable or None
modified after: http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python modified after: http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
:param program: name of the desired external program :param program: name of the desired external program
:type program: str
:return: full path of the executable file :return: full path of the executable file
:rtype: str
""" """
try: try:
from PySide.QtCore import QSettings from PySide.QtCore import QSettings
@ -880,14 +1140,16 @@ def which(program, infile=None):
def loopIdentifyPhase(phase): def loopIdentifyPhase(phase):
''' """
Loop through phase string and try to recognize its type (P or S wave). Loop through phase string and try to recognize its type (P or S wave).
Global variable ALTSUFFIX gives alternative suffix for phases if they do not end with P, p or S, s. Global variable ALTSUFFIX gives alternative suffix for phases if they do not end with P, p or S, s.
If ALTSUFFIX is not given, the function will cut the last letter of the phase string until string ends If ALTSUFFIX is not given, the function will cut the last letter of the phase string until string ends
with P or S. with P or S.
:param phase: phase name (str) :param phase: phase name
:return: :type phase: str
''' :return: str of phase ending with identified type, None if phase could not be identified
:rtype: str or None
"""
from pylot.core.util.defaults import ALTSUFFIX from pylot.core.util.defaults import ALTSUFFIX
phase_copy = phase phase_copy = phase
@ -906,14 +1168,18 @@ def loopIdentifyPhase(phase):
def identifyPhase(phase): def identifyPhase(phase):
''' """
Returns capital P or S if phase string is identified by last letter. Else returns False. Returns capital P or S if phase string is identified by last letter. Else returns False.
:param phase: phase name (str) :param phase: phase name
:type phase: str
:return: 'P', 'S' or False :return: 'P', 'S' or False
''' :rtype: str or bool
"""
# common phase suffix for P and S # common phase suffix for P and S
common_P = ['P', 'p'] common_P = ['P', 'p', 'R']
common_S = ['S', 's'] common_S = ['S', 's']
if phase is None:
return False
if phase[-1] in common_P: if phase[-1] in common_P:
return 'P' return 'P'
if phase[-1] in common_S: if phase[-1] in common_S:
@ -923,10 +1189,24 @@ def identifyPhase(phase):
def identifyPhaseID(phase): def identifyPhaseID(phase):
"""
Returns phase id (capital P or S)
:param phase: phase name
:type phase: str
:return: phase type string
:rtype: str
"""
return identifyPhase(loopIdentifyPhase(phase)) return identifyPhase(loopIdentifyPhase(phase))
def has_spe(pick): def has_spe(pick):
"""
Check for 'spe' key (symmetric picking error) in dict and return its value if found, else return None
:param pick: pick dictionary
:type pick: dict
:return: value of 'spe' key
:rtype: float or None
"""
if not 'spe' in pick.keys(): if not 'spe' in pick.keys():
return None return None
else: else:

File diff suppressed because it is too large Load Diff

View File

@ -124,7 +124,6 @@ color:rgba(0, 0, 0, 255);
border-style: outset; border-style: outset;
border-width: 1px; border-width: 1px;
border-color: rgba(100, 100, 120, 255); border-color: rgba(100, 100, 120, 255);
min-width: 6em;
padding: 4px; padding: 4px;
padding-left:5px; padding-left:5px;
padding-right:5px; padding-right:5px;

View File

@ -123,7 +123,6 @@ color:rgba(255, 255, 255, 255);
border-style: outset; border-style: outset;
border-width: 2px; border-width: 2px;
border-color: rgba(50, 50, 60, 255); border-color: rgba(50, 50, 60, 255);
min-width: 6em;
padding: 4px; padding: 4px;
padding-left:5px; padding-left:5px;
padding-right:5px; padding-right:5px;