diff --git a/PyLoT.py b/PyLoT.py index 063a89ca..ef88c010 100755 --- a/PyLoT.py +++ b/PyLoT.py @@ -274,6 +274,10 @@ class MainWindow(QMainWindow): print_icon.addPixmap(QPixmap(':/icons/printer.png')) self.filter_icon = QIcon() 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.addPixmap(QPixmap(':/icons/key_Z.png')) n_icon = QIcon() @@ -368,15 +372,23 @@ class MainWindow(QMainWindow): self.setParameter, None, paraIcon, "Modify Parameter") - self.filterAction = self.createAction(self, "&Filter ...", - self.filterWaveformData, - "Ctrl+F", self.filter_icon, - """Toggle un-/filtered waveforms - to be displayed, according to the - desired seismic phase.""", True) + self.filterActionP = createAction(parent=self, text='Apply P Filter', + slot=self.filterP, + icon=self.filter_icon_p, + tip='Toggle filtered/original' + ' waveforms', + 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 ...", self.adjustFilterOptions, - "Alt+F", self.filter_icon, + "Ctrl+F", self.filter_icon, """Adjust filter parameters.""") self.inventoryAction = self.createAction(self, "Select &Inventory ...", self.get_new_metadata, @@ -502,8 +514,8 @@ class MainWindow(QMainWindow): self.updateFileMenu() self.editMenu = self.menuBar().addMenu('&Edit') - editActions = (self.filterAction, filterEditAction, None, - self.selectPAction, self.selectSAction, None, + editActions = (self.filterActionP, self.filterActionS, filterEditAction, None, + #self.selectPAction, self.selectSAction, None, self.inventoryAction, self.initMapAction, None, prefsEventAction) #printAction) #TODO: print event? @@ -1415,7 +1427,8 @@ class MainWindow(QMainWindow): return None def getStime(self): - return self._stime + if self.get_data(): + return full_range(self.get_data().getWFData())[0] def addActions(self, target, actions): for action in actions: @@ -1589,18 +1602,9 @@ class MainWindow(QMainWindow): # else: # ans = False self.fnames = self.getWFFnames_from_eventbox() - self.data.setWFData(self.fnames) - wfdat = self.data.getWFData() # all available streams - # remove possible underscores in station names - 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] + self.data.setWFData(self.fnames, + checkRotated=True, + metadata=self.metadata) def connectWFplotEvents(self): ''' @@ -1759,18 +1763,19 @@ class MainWindow(QMainWindow): self.disableSaveEventAction() self.draw() - def plotWaveformDataThread(self): + def plotWaveformDataThread(self, filter=True): ''' Open a modal thread to plot current waveform data. ''' self.clearWaveformDataPlot() self.wfp_thread = Thread(self, self.plotWaveformData, + arg=filter, progressText='Plotting waveform data...', pb_widget=self.mainProgressBarWidget) self.wfp_thread.finished.connect(self.finishWaveformDataPlot) self.wfp_thread.start() - def plotWaveformData(self): + def plotWaveformData(self, filter=True): ''' Plot waveform data to current plotWidget. ''' @@ -1782,8 +1787,10 @@ class MainWindow(QMainWindow): comp = self.getComponent() title = 'section: {0} components'.format(zne_text[comp]) wfst = self.get_data().getWFData() - if self.filterAction.isChecked(): - self.filterWaveformData(plot=False) + 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=alter_comp) plotWidget = self.getPlotWidget() @@ -1823,19 +1830,48 @@ class MainWindow(QMainWindow): def pushFilterWF(self, param_args): self.get_data().filterWFData(param_args) - def filterWaveformData(self, plot=True): + 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.getFilterOptions() and self.filterAction.isChecked(): - kwargs = self.getFilterOptions()[self.getSeismicPhase()].parseFilterOptions() - self.pushFilterWF(kwargs) - elif self.filterAction.isChecked(): + if not phase: + 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) + else: + self.get_data().resetWFData() + elif self.filterActionP.isChecked() or self.filterActionS.isChecked(): self.adjustFilterOptions() else: self.get_data().resetWFData() - if plot: - self.plotWaveformDataThread() - self.drawPicks() - self.draw() + if plot: + self.plotWaveformDataThread(filter=False) + #self.drawPicks() + #self.draw() def adjustFilterOptions(self): fstring = "Filter Options" @@ -1844,7 +1880,7 @@ class MainWindow(QMainWindow): if self.filterDlg.exec_(): filteroptions = self.filterDlg.getFilterOptions() self.setFilterOptions(filteroptions) - if self.filterAction.isChecked(): + if self.filterActionP.isChecked() or self.filterActionS.isChecked(): kwargs = self.getFilterOptions()[self.getSeismicPhase()].parseFilterOptions() self.pushFilterWF(kwargs) self.plotWaveformDataThread() @@ -1925,7 +1961,7 @@ class MainWindow(QMainWindow): # '[{0}: {1} Hz]'.format( # self.getFilterOptions().getFilterType(), # self.getFilterOptions().getFreq())) - # if self.filterAction.isChecked(): + # if self.filterActionP.isChecked() or self.filterActionS.isChecked(): # self.filterWaveformData() def getSeismicPhase(self): @@ -2018,7 +2054,7 @@ class MainWindow(QMainWindow): autopicks=self.getPicksOnStation(station, 'auto'), metadata=self.metadata, event=event, filteroptions=self.filteroptions) - if self.filterAction.isChecked(): + if self.filterActionP.isChecked() or self.filterActionS.isChecked(): pickDlg.currentPhase = self.getSeismicPhase() pickDlg.filterWFData() pickDlg.nextStation.setChecked(nextStation) diff --git a/pylot/RELEASE-VERSION b/pylot/RELEASE-VERSION index b775f1a3..878d172c 100644 --- a/pylot/RELEASE-VERSION +++ b/pylot/RELEASE-VERSION @@ -1 +1 @@ -0ebc-dirty +f483-dirty diff --git a/pylot/core/io/data.py b/pylot/core/io/data.py index eef5ef88..60d017d1 100644 --- a/pylot/core/io/data.py +++ b/pylot/core/io/data.py @@ -12,7 +12,8 @@ from pylot.core.io.phases import readPILOTEvent, picks_from_picksdict, \ picksdict_from_pilot, merge_picks from pylot.core.util.errors import FormatError, OverwriteError 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 @@ -367,7 +368,7 @@ class Data(object): data.filter(**kwargs) 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: waveform data names to append @@ -379,10 +380,26 @@ class Data(object): self.appendWFData(fnames) else: 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.dirty = False return True + + def appendWFData(self, fnames): """ Read waveform data from fnames and append it to current wf data diff --git a/pylot/core/io/phases.py b/pylot/core/io/phases.py index b5650fe6..55a496e2 100644 --- a/pylot/core/io/phases.py +++ b/pylot/core/io/phases.py @@ -16,7 +16,8 @@ from pylot.core.io.inputs import PylotParameter from pylot.core.io.location import create_event, \ create_magnitude from pylot.core.pick.utils import select_for_phase -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): @@ -235,6 +236,10 @@ def picksdict_from_picks(evt): network = pick.waveform_id.network_code mpp = pick.time spe = pick.time_errors.uncertainty + if pick.filter_id: + filter_id = backtransformFilterString(str(pick.filter_id.id)) + else: + filter_id = None try: picker = str(pick.method_id) if picker.startswith('smi:local/'): @@ -261,6 +266,7 @@ def picksdict_from_picks(evt): phase['channel'] = channel phase['network'] = network phase['picker'] = picker + phase['filter_id'] = filter_id if filter_id is not None else '' onsets[pick.phase_hint] = phase.copy() picksdict[picker][station] = onsets.copy() @@ -312,6 +318,13 @@ def picks_from_picksdict(picks, creation_info=None): pick.waveform_id = ope.WaveformStreamID(station_code=station, channel_code=ccode, 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: polarity = phase['fm'] if polarity == 'U' or '+': @@ -328,7 +341,6 @@ def picks_from_picksdict(picks, creation_info=None): picks_list.append(pick) return picks_list - def reassess_pilot_db(root_dir, db_dir, out_dir=None, fn_param=None, verbosity=0): import glob diff --git a/pylot/core/util/thread.py b/pylot/core/util/thread.py index ae56c697..0a8151d0 100644 --- a/pylot/core/util/thread.py +++ b/pylot/core/util/thread.py @@ -24,7 +24,7 @@ class Thread(QThread): if self.redirect_stdout: sys.stdout = self try: - if self.arg: + if self.arg is not None: self.data = self.func(self.arg) else: self.data = self.func() diff --git a/pylot/core/util/utils.py b/pylot/core/util/utils.py index 6a91c844..6de1e8e4 100644 --- a/pylot/core/util/utils.py +++ b/pylot/core/util/utils.py @@ -391,6 +391,38 @@ def full_range(stream): 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): """ takes a time object and returns the corresponding SHA1 hash of the formatted date string @@ -1137,6 +1169,8 @@ def identifyPhase(phase): # common phase suffix for P and S common_P = ['P', 'p', 'R'] common_S = ['S', 's'] + if phase is None: + return False if phase[-1] in common_P: return 'P' if phase[-1] in common_S: diff --git a/pylot/core/util/widgets.py b/pylot/core/util/widgets.py index aeaa2eec..23c2380b 100644 --- a/pylot/core/util/widgets.py +++ b/pylot/core/util/widgets.py @@ -17,8 +17,6 @@ import time import numpy as np from matplotlib.figure import Figure -from pylot.core.util.utils import find_horizontals, identifyPhase, loopIdentifyPhase, trim_station_components, \ - identifyPhaseID, check4rotated, real_Bool, pick_color try: from matplotlib.backends.backend_qt4agg import FigureCanvas @@ -49,7 +47,9 @@ from pylot.core.util.defaults import OUTPUTFORMATS, FILTERDEFAULTS, \ SetChannelComponents from pylot.core.util.utils import prepTimeAxis, full_range, scaleWFData, \ demeanTrace, isSorted, findComboBoxIndex, clims, pick_linestyle_plt, pick_color_plt, \ - check4rotated, check4doubled, check4gaps, remove_underscores + check4rotated, check4doubled, check4gaps, remove_underscores, find_horizontals, identifyPhase, \ + loopIdentifyPhase, trim_station_components, transformFilteroptions2String, \ + identifyPhaseID, real_Bool, pick_color from autoPyLoT import autoPyLoT from pylot.core.util.thread import Thread @@ -375,8 +375,8 @@ class ComparisonWidget(QWidget): ax = axes_dict[phase]['exp'] xlims = ax.get_xlim() ylims = ax.get_ylim() - ax.fill_between([xlims[0], 0], ylims[0], ylims[1], color=(0.9, 1.0, 0.9, 0.5), label='earlier than manual') - ax.fill_between([0, xlims[1]], ylims[0], ylims[1], color=(1.0, 0.9, 0.9, 0.5), label='later than manual') + #ax.fill_between([xlims[0], 0], ylims[0], ylims[1], color=(0.9, 1.0, 0.9, 0.5), label='earlier than manual') + #ax.fill_between([0, xlims[1]], ylims[0], ylims[1], color=(1.0, 0.9, 0.9, 0.5), label='later than manual') legend = ax.legend() legend.draggable() @@ -1152,9 +1152,11 @@ class PickDlg(QDialog): self.components = 'ZNE' self.currentPhase = None self.phaseText = [] + self.phaseLines = [] self.arrivals = [] self.arrivalsText = [] self.cidpick = [] + self.cidpress = None settings = QSettings() pylot_user = getpass.getuser() self._user = settings.value('user/Login', pylot_user) @@ -1242,6 +1244,7 @@ class PickDlg(QDialog): # init pick delete (with right click) self.connect_pick_delete() + self.connect_mouse_motion() self.setWindowTitle('Pickwindow on station: {}'.format(self.getStation())) self.setWindowState(QtCore.Qt.WindowMaximized) @@ -1270,6 +1273,8 @@ class PickDlg(QDialog): home_icon.addPixmap(QPixmap(':/icons/zoom_0.png')) del_icon = QIcon() del_icon.addPixmap(QPixmap(':/icons/delete.png')) + sync_icon = QIcon() + sync_icon.addPixmap(QPixmap(':/icons/sync.png')) # create actions self.filterActionP = createAction(parent=self, text='Apply P Filter', @@ -1278,14 +1283,14 @@ class PickDlg(QDialog): tip='Toggle filtered/original' ' waveforms', checkable=True, - shortcut='Ctrl+F') + shortcut='P') self.filterActionS = createAction(parent=self, text='Apply S Filter', slot=self.filterS, icon=filter_icon_s, tip='Toggle filtered/original' ' waveforms', checkable=True, - shortcut='Shift+F') + shortcut='S') self.autoFilterAction = createAction(parent=self, text='Automatic Filtering', slot=self.toggleAutoFilter, icon=key_a_icon, @@ -1302,6 +1307,10 @@ class PickDlg(QDialog): self.resetPicksAction = createAction(parent=self, text='Delete Picks', slot=self.delPicks, icon=del_icon, tip='Delete current picks.') + self.renamePhaseAction = createAction(parent=self, text='Rename Phase', + slot=self.initRenamePhase, icon=sync_icon, + tip='Rename a Phase.', checkable=True, + shortcut='R') self.addPickPhases(menuBar) @@ -1330,6 +1339,8 @@ class PickDlg(QDialog): self.reject_button = QPushButton('&Reject') self.disable_ar_buttons() + self.statusbar = QtGui.QStatusBar(self) + # add hotkeys self._shortcut_space = QtGui.QShortcut(QtGui.QKeySequence(' '), self) self._shortcut_space.activated.connect(self.accept_button.clicked) @@ -1352,6 +1363,7 @@ class PickDlg(QDialog): _dialtoolbar.addAction(self.resetZoomAction) _dialtoolbar.addSeparator() _dialtoolbar.addAction(self.resetPicksAction) + _dialtoolbar.addAction(self.renamePhaseAction) _dialtoolbar.addSeparator() if self._embedded: manu_label = QLabel('Manual Onsets:') @@ -1375,9 +1387,12 @@ class PickDlg(QDialog): # layout the innermost widget _innerlayout = QVBoxLayout() _innerinnerlayout = QtGui.QHBoxLayout() + _lowerlayout = QHBoxLayout() _innerinnerlayout.addWidget(self.multicompfig) _innerinnerlayout.addWidget(self.phaseplot) _innerlayout.addLayout(_innerinnerlayout) + _innerlayout.addLayout(_lowerlayout) + _lowerlayout.addWidget(self.statusbar) # add button box to the dialog _buttonbox = QDialogButtonBox(QDialogButtonBox.Ok | @@ -1385,13 +1400,17 @@ class PickDlg(QDialog): # merge widgets and layouts to establish the dialog if not self._embedded: - _innerlayout.addWidget(_buttonbox) + _lowerlayout.addWidget(_buttonbox) _outerlayout.addWidget(menuBar) _outerlayout.addWidget(_dialtoolbar) _outerlayout.addLayout(_innerlayout) _outerlayout.setStretch(0, 0) _outerlayout.setStretch(1, 0) _outerlayout.setStretch(2, 1) + _lowerlayout.setStretch(0, 5) + _lowerlayout.setStretch(1, 1) + _innerlayout.setStretch(0, 1) + _innerlayout.setStretch(1, 0) # connect widget element signals with slots (methods to the dialog # object @@ -1555,7 +1574,7 @@ class PickDlg(QDialog): filterOptionsAction = createAction(parent=self, text="&Filter parameter ...", slot=self.filterOptions, - shortcut='Alt+F', + shortcut='Ctrl+F', icon=self.orig_parent.filter_icon) filterMenu = menuBar.addMenu('Filter') filterMenu.addAction(self.filterActionP) @@ -1675,8 +1694,15 @@ class PickDlg(QDialog): self.deactivatePicking() def activatePicking(self): + self.leave_rename_phase() + self.renamePhaseAction.setEnabled(False) phase = self.currentPhase - color = pick_color_plt('manual', self.getPhaseID(phase)) + phaseID = self.getPhaseID(phase) + if not phaseID: + self.warn_unknown_phase(phase) + self.leave_picking_mode() + return + color = pick_color_plt('manual', phaseID) self.multicompfig.set_frame_color(color) self.multicompfig.set_frame_linewidth(1.5) if self.zoomAction.isChecked(): @@ -1700,6 +1726,7 @@ class PickDlg(QDialog): self.disconnectPressEvent() self.multicompfig.connectEvents() + self.renamePhaseAction.setEnabled(True) self.connect_pick_delete() self.draw() @@ -1799,6 +1826,17 @@ class PickDlg(QDialog): self.resetPicks() self.refreshPlot() + def initRenamePhase(self): + if self.renamePhaseAction.isChecked(): + self.multicompfig.disconnectEvents() + self.multicompfig.set_frame_color('orange') + self.draw() + self.statusbar.showMessage('Click on a phase you want to rename.') + else: + self.multicompfig.set_frame_color() + self.multicompfig.connectEvents() + self.draw() + def setIniPick(self, gui_event): self.multicompfig.set_frame_color('green') trace_number = round(gui_event.ydata) @@ -2006,41 +2044,30 @@ class PickDlg(QDialog): # save pick times for actual phase phasepicks = dict(epp=epp, lpp=lpp, mpp=mpp, spe=spe, picker='manual', channel=channel, - network=wfdata[0].stats.network) + network=wfdata[0].stats.network, + filteroptions=transformFilteroptions2String(filteroptions)) - try: - oldphasepick = self.picks[phase] - except KeyError: - self.picks[phase] = phasepicks - else: - self.picks[phase] = phasepicks - oepp = oldphasepick['epp'] - ompp = oldphasepick['mpp'] - olpp = oldphasepick['lpp'] - msg = """Warning old phase information for phase {phase} has been - altered.\n - New phase times:\n - earliest possible pick: {epp}\n - most probable pick: {mpp}\n - latest possible pick: {lpp}\n - \n - Old phase times (overwritten):\n - earliest possible pick: {oepp}\n - most probable pick: {ompp}\n - latest possible pick: {olpp}\n""".format(phase=phase, - epp=epp, - mpp=pick, - lpp=lpp, - oepp=oepp, - ompp=ompp, - olpp=olpp) + saved = self.savePick(phase, phasepicks) + if saved: + self.setDirty(True) self.disconnectPressEvent() self.enable_ar_buttons() self.zoomAction.setEnabled(True) - #self.pick_block = self.togglePickBlocker() + #self.pick_block = self.togglPickBlocker() self.leave_picking_mode() - self.setDirty(True) + + def savePick(self, phase, phasepicks): + if not self.getPhaseID(phase): + self.warn_unknown_phase(phase) + return + + self.picks[phase] = phasepicks + return True + + def warn_unknown_phase(self, phase=None): + QtGui.QMessageBox.warning(self, 'Unknown phase ID', + 'Could not identify phase ID: {}.'.format(phase)) def disconnectPressEvent(self): self.multicompfig.mpl_disconnect(self.cidpress) @@ -2050,6 +2077,7 @@ class PickDlg(QDialog): self.removePhaseText() self.drawPicks(picktype='manual') self.drawPicks(picktype='auto') + self.draw() def drawPicks(self, phase=None, picktype='manual', textOnly=False, picks=None): # plotting picks @@ -2091,19 +2119,20 @@ class PickDlg(QDialog): color = pick_color_plt(picktype, phaseID, quality) if not textOnly: linestyle_mpp, width_mpp = pick_linestyle_plt(picktype, 'mpp') - ax.plot([mpp, mpp], ylims, color=color, linestyle=linestyle_mpp, linewidth=width_mpp, - label='{}-Pick (quality: {})'.format(phase, quality), picker=5) + vl = ax.axvline(mpp, ylims[0], ylims[1], color=color, linestyle=linestyle_mpp, linewidth=width_mpp, + label='{}-Pick (quality: {})'.format(phase, quality), picker=5) + self.phaseLines.append(vl) if spe: ax.fill_between([mpp-spe, mpp+spe], ylims[0], ylims[1], alpha=.25, color=color, label='{}-SPE'.format(phase)) if picks['epp']: linestyle_epp, width_epp = pick_linestyle_plt(picktype, 'epp') - ax.plot([epp, epp], ylims, color=color, linestyle=linestyle_epp, - linewidth=width_epp, label='{}-EPP'.format(phase)) + ax.axvline(epp, ylims[0], ylims[1], color=color, linestyle=linestyle_epp, + linewidth=width_epp, label='{}-EPP'.format(phase)) if picks['lpp']: linestyle_lpp, width_lpp = pick_linestyle_plt(picktype, 'lpp') - ax.plot([lpp, lpp], ylims, color=color, linestyle=linestyle_lpp, - linewidth=width_lpp, label='{}-LPP'.format(phase)) + ax.axvline(lpp, ylims[0], ylims[1], color=color, linestyle=linestyle_lpp, + linewidth=width_lpp, label='{}-LPP'.format(phase)) # else: # ax.plot([mpp, mpp], ylims, color=color, linestyle=linestyle_mpp, linewidth=width_mpp, # label='{}-Pick (NO PICKERROR)'.format(phase), picker=5) @@ -2115,8 +2144,9 @@ class PickDlg(QDialog): if not textOnly: ax.plot(mpp, ylims[1], color=color, marker='v') ax.plot(mpp, ylims[0], color=color, marker='^') - ax.vlines(mpp, ylims[0], ylims[1], color=color, linestyle=linestyle_mpp, linewidth=width_mpp, - picker=5, label='{}-Autopick (quality: {})'.format(phase, quality)) + vl = ax.axvline(mpp, ylims[0], ylims[1], color=color, linestyle=linestyle_mpp, linewidth=width_mpp, + picker=5, label='{}-Autopick (quality: {})'.format(phase, quality)) + self.phaseLines.append(vl) # append phase text (if textOnly: draw with current ylims) self.phaseText.append(ax.text(mpp, ylims[1], phase, color=color)) else: @@ -2124,24 +2154,121 @@ class PickDlg(QDialog): ax.legend(loc=1) + def connect_mouse_motion(self): + self.cidmotion = self.multicompfig.mpl_connect( + 'motion_notify_event', self.on_motion) + def connect_pick_delete(self): - self.cidpick = self.multicompfig.mpl_connect('pick_event', self.onpick_delete) + self.cidpick = self.multicompfig.mpl_connect('pick_event', self.onpick) + self.cidpick = self.multicompfig.mpl_connect('motion_notify_event', self.on_hover_info) def disconnect_pick_delete(self): if hasattr(self, 'cidpick'): self.multicompfig.mpl_disconnect(self.cidpick) + def on_motion(self, event): + x = event.xdata + if x is not None: + time_code = 'T = {}, t = {} [s]'.format(self.stime+x, x) + user_help = ' - Left-Click to Drag | Right-Click to Pan-Zoom |' \ + ' Mousewheel to Zoom | Middle-Click to Delete Pick' + self.statusbar.showMessage(time_code + user_help) + + def onpick(self, event): + if event.mouseevent.button == 1: + self.onpick_info(event) + elif event.mouseevent.button == 2: + self.onpick_delete(event) + + def on_hover_info(self, event): + if not any([phase.contains(event)[0] for phase in self.phaseLines]): + return + x = event.xdata + if not x: + return + allpicks, pick_rel, phase, picktype = self.identify_selected_picks(x) + pick = allpicks[picktype][phase] + message = '{} {}-pick'.format(picktype, phase) + if 'mpp' in pick: + message += ', MPP: {}'.format(pick['mpp']) + if 'spe' in pick: + message += ', SPE: {} [s]'.format(pick['spe']) + if 'filteroptions' in pick: + message += ', FILTER: {}'.format(pick['filteroptions']) + x = event.x + y = event.y + y = self.size().height() - y + pt = self.mapToGlobal(QtCore.QPoint(x, y)) + QtGui.QToolTip.showText(pt, message) + + def onpick_info(self, event): + if not event.mouseevent.button == 1: + return + x = event.mouseevent.xdata + allpicks, pick_rel, phase, picktype = self.identify_selected_picks(x) + pick = allpicks[picktype][phase] + message = '{} {}-pick'.format(picktype, phase) + if 'mpp' in pick: + message += ', MPP: {}'.format(pick['mpp']) + if 'spe' in pick: + message += ', SPE: {}'.format(pick['spe']) + if 'filteroptions' in pick: + message += ', FILTER: {}'.format(pick['filteroptions']) + + if self.renamePhaseAction.isChecked(): + self.renamePhase(picktype, phase) + + self.statusbar.showMessage(message, 10e3) + def onpick_delete(self, event): - if not event.mouseevent.button == 3: + if not event.mouseevent.button == 2: return x = event.mouseevent.xdata self.remove_pick_by_x(x) self.refreshPlot() + def renamePhase(self, picktype, phase): + allpicks = {'manual': self.picks, + 'auto': self.autopicks} + picks = allpicks[picktype] + dialog = QtGui.QInputDialog(parent=self) + new_phase, executed = dialog.getText(self, 'Rename phase', 'Rename phase {} to:'.format(phase)) + if executed: + try: + self.renamePhaseInDict(picks, phase, new_phase) + except KeyError as e: + QtGui.QMessageBox.warning(self, 'Could not rename phase', + 'Could not rename phase {} to {}: {}'.format(phase, new_phase, e)) + self.leave_rename_phase() + self.refreshPlot() + + def renamePhaseInDict(self, picks, phase_old, phase_new): + if phase_new in picks: + raise KeyError('New phase ID already assigned.') + picks_new = picks[phase_old].copy() + saved = self.savePick(phase_new, picks_new) + if saved: + picks.pop(phase_old) + self.setDirty(True) + + def leave_rename_phase(self): + self.renamePhaseAction.setChecked(False) + self.multicompfig.set_frame_color() + self.multicompfig.connectEvents() + def remove_pick_by_x(self, x): if not self.picks and not self.autopicks: return - # init empty list and get station starttime + allpicks, pick_rel, phase, picktype = self.identify_selected_picks(x) + # delete the value from corresponding dictionary + allpicks[picktype].pop(phase) + # information output + msg = 'Deleted {} pick for phase {}, at timestamp {} (relative time: {} s)' + print(msg.format(picktype, phase, self.getStartTime()+pick_rel, pick_rel)) + self.setDirty(True) + + def identify_selected_picks(self, x): + # init empty list and get stat5ion starttime X = [] starttime = self.getStartTime() # init dictionaries to iterate through and iterate over them @@ -2159,12 +2286,9 @@ class PickDlg(QDialog): index, value = min(enumerate([val[0] for val in X]), key=lambda y: abs(y[1] - x)) # unpack the found value pick_rel, phase, picktype = X[index] - # delete the value from corresponding dictionary - allpicks[picktype].pop(phase) - # information output - msg = 'Deleted {} pick for phase {}, at timestamp {} (relative time: {} s)' - print(msg.format(picktype, phase, starttime+pick_rel, pick_rel)) - self.setDirty(True) + return allpicks, pick_rel, phase, picktype + + def drawPhaseText(self): self.drawPicks(picktype='manual', textOnly=True) @@ -2219,11 +2343,8 @@ class PickDlg(QDialog): data.detrend('linear') data.taper(0.02, type='cosine') data.filter(**filtoptions) - title += ' | {} filtered |'.format(filtoptions['type']) - for key, value in filtoptions.items(): - if key == 'type': - continue - title += ' {}: {} |'.format(key, value) + filtops_str = transformFilteroptions2String(filtoptions) + title += ' | Filteroptions: {}'.format(filtops_str) self.multicompfig.plotWFData(wfdata=data, title=title, zoomx=self.getXLims(), zoomy=self.getYLims(), @@ -2258,10 +2379,17 @@ class PickDlg(QDialog): if self.autoFilterAction.isChecked(): self.filterActionP.setChecked(False) self.filterActionS.setChecked(False) - data = self.getWFData().copy() - title = self.getStation() - filter = self.filterActionP.isChecked or self.filterActionS.isChecked() - self.plotWFData(filter=filter) + # data = self.getWFData().copy() + # title = self.getStation() + filter = False + phase = None + if self.filterActionP.isChecked(): + phase = 'P' + filter = True + if self.filterActionS.isChecked(): + phase = 'S' + filter = True + self.plotWFData(phase=phase, filter=filter) def resetZoom(self): ax = self.multicompfig.axes[0] @@ -2866,9 +2994,9 @@ class TuneAutopicker(QWidget): y_top = 0.9 * ax.get_ylim()[1] y_bot = 0.9 * ax.get_ylim()[0] - self._manual_pick_plots.append(ax.vlines(mpp, y_bot, y_top, - color=color, linewidth=2, - label='manual {} Onset (quality: {})'.format(phase, quality))) + self._manual_pick_plots.append(ax.axvline(mpp, y_bot, y_top, + color=color, linewidth=2, + label='manual {} Onset (quality: {})'.format(phase, quality))) self._manual_pick_plots.append(ax.plot([mpp - 0.5, mpp + 0.5], [y_bot, y_bot], linewidth=2, color=color))