diff --git a/QtPyLoT.py b/QtPyLoT.py index 998a6c72..e429307e 100755 --- a/QtPyLoT.py +++ b/QtPyLoT.py @@ -41,6 +41,14 @@ from PySide.QtGui import QMainWindow, QInputDialog, QIcon, QFileDialog, \ QTreeView, QComboBox, QTabWidget, QPushButton, QGridLayout import numpy as np from obspy import UTCDateTime +from obspy.core.event import Magnitude +from obspy.core.util import AttribDict + +try: + import pyqtgraph as pg +except Exception as e: + print('QtPyLoT: Could not import pyqtgraph. {}'.format(e)) + pg = None try: from matplotlib.backends.backend_qt4agg import FigureCanvas @@ -49,25 +57,26 @@ except ImportError: from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure -from pylot.core.analysis.magnitude import RichterMagnitude, MomentMagnitude +from pylot.core.analysis.magnitude import LocalMagnitude, MomentMagnitude from pylot.core.io.data import Data -from pylot.core.io.inputs import FilterOptions, AutoPickParameter +from pylot.core.io.inputs import FilterOptions, PylotParameter from autoPyLoT import autoPyLoT from pylot.core.pick.compare import Comparison from pylot.core.pick.utils import symmetrize_error from pylot.core.io.phases import picksdict_from_picks import pylot.core.loc.nll as nll -from pylot.core.util.defaults import FILTERDEFAULTS, SetChannelComponents +from pylot.core.util.defaults import FILTERDEFAULTS, OUTPUTFORMATS, SetChannelComponents from pylot.core.util.errors import FormatError, DatastructureError, \ OverwriteError, ProcessingError from pylot.core.util.connection import checkurl from pylot.core.util.dataprocessing import read_metadata, restitute_data from pylot.core.util.utils import fnConstructor, getLogin, \ full_range +from pylot.core.util.event import Event from pylot.core.io.location import create_creation_info, create_event from pylot.core.util.widgets import FilterOptionsDialog, NewEventDlg, \ - WaveformWidget, PropertiesDlg, HelpForm, createAction, PickDlg, \ - getDataType, ComparisonDialog, TuneAutopicker, AutoPickParaBox + WaveformWidget, WaveformWidgetPG, PropertiesDlg, HelpForm, createAction, PickDlg, \ + getDataType, ComparisonDialog, TuneAutopicker, PylotParaBox from pylot.core.util.map_projection import map_projection from pylot.core.util.structure import DATASTRUCTURE from pylot.core.util.thread import AutoPickThread, Thread @@ -97,10 +106,12 @@ class MainWindow(QMainWindow): self.infile = infile[0] else: self.infile = infile - self._inputs = AutoPickParameter(infile) + self._inputs = PylotParameter(infile) self._props = None + self.dirty = False self.project = Project() + self.project.parameter = self._inputs self.tap = None self.paraBox = None self.array_map = None @@ -135,13 +146,9 @@ class MainWindow(QMainWindow): self.data = Data(self, lastEvent) else: self.data = Data(self) + self.data._new = False self.autodata = Data(self) - self.dirty = False - - # setup UI - self.setupUi() - if settings.value("user/FullName", None) is None: fulluser = QInputDialog.getText(self, "Enter Name:", "Full name") settings.setValue("user/FullName", fulluser) @@ -165,10 +172,12 @@ class MainWindow(QMainWindow): settings.setValue('compclass', SetChannelComponents()) settings.sync() + # setup UI + self.setupUi() + self.filteroptions = {} - self.pickDlgs = {} - self.picks = {} - self.autopicks = {} + self.pylot_picks = {} + self.pylot_autopicks = {} self.loc = False def setupUi(self): @@ -184,8 +193,6 @@ class MainWindow(QMainWindow): self.setWindowTitle("PyLoT - do seismic processing the python way") self.setWindowIcon(pylot_icon) - xlab = self.startTime.strftime('seconds since %Y/%m/%d %H:%M:%S (%Z)') - _widget = QWidget() self._main_layout = QVBoxLayout() @@ -208,15 +215,13 @@ class MainWindow(QMainWindow): self._main_layout.addWidget(self.tabs) self.tabs.currentChanged.connect(self.refreshTabs) - # create central matplotlib figure canvas widget - plottitle = "Overview: {0} components ".format(self.getComponent()) - self.dataPlot = WaveformWidget(parent=self, xlabel=xlab, ylabel=None, - title=plottitle) - self.dataPlot.setCursor(Qt.CrossCursor) - # add scroll area used in case number of traces gets too high self.wf_scroll_area = QtGui.QScrollArea() + # create central matplotlib figure canvas widget + self.pg = pg + self.init_wfWidget() + # init main widgets for main tabs wf_tab = QtGui.QWidget() array_tab = QtGui.QWidget() @@ -236,7 +241,6 @@ class MainWindow(QMainWindow): self.tabs.addTab(events_tab, 'Eventlist') self.wf_layout.addWidget(self.wf_scroll_area) - self.wf_scroll_area.setWidget(self.dataPlot) self.wf_scroll_area.setWidgetResizable(True) self.init_array_tab() self.init_event_table() @@ -311,21 +315,21 @@ class MainWindow(QMainWindow): # self.createNewEvent, # QKeySequence.New, newIcon, # "Create a new event.") - self.openmanualpicksaction = self.createAction(self, "Load &manual picks ...", + self.openmanualpicksaction = self.createAction(self, "Load event ...", self.load_data, "Ctrl+M", manupicksicon, - "Load manual picks for " + "Load event information for " "the displayed event.") self.openmanualpicksaction.setEnabled(False) self.openmanualpicksaction.setData(None) - self.openautopicksaction = self.createAction(self, "Load &automatic picks ... ", - self.load_autopicks, + self.openautopicksaction = self.createAction(self, "Load event information &automatically ... ", + self.load_multiple_data, "Ctrl+A", autopicksicon, - "Load automatic picks " - "for the displayed event.") + "Load event data automatically " + "for for all events.") self.openautopicksaction.setEnabled(False) self.openautopicksaction.setData(None) @@ -361,10 +365,10 @@ class MainWindow(QMainWindow): QCoreApplication.instance().quit, QKeySequence.Close, quitIcon, "Close event and quit PyLoT") - self.parameterAction = self.createAction(self, "Pick Parameter", - self.pickParameter, + self.parameterAction = self.createAction(self, "Parameter", + self.setParameter, None, QIcon(None), - "Modify Picking Parameter") + "Modify Parameter") self.filterAction = self.createAction(self, "&Filter ...", self.filterWaveformData, "Ctrl+F", filter_icon, @@ -510,6 +514,24 @@ class MainWindow(QMainWindow): self.setCentralWidget(_widget) + def init_wfWidget(self): + settings = QSettings() + xlab = self.startTime.strftime('seconds since %Y/%m/%d %H:%M:%S (%Z)') + plottitle = None#"Overview: {0} components ".format(self.getComponent()) + self.disconnectWFplotEvents() + if str(settings.value('pyqtgraphic')) == 'false' or not pg: + self.pg = False + self.dataPlot = WaveformWidget(parent=self, xlabel=xlab, ylabel=None, + title=plottitle) + else: + self.pg = True + self.dataPlot = WaveformWidgetPG(parent=self, xlabel=xlab, ylabel=None, + title=plottitle) + self.dataPlot.setCursor(Qt.CrossCursor) + self.wf_scroll_area.setWidget(self.dataPlot) + if self.get_current_event(): + self.plotWaveformDataThread() + def init_ref_test_buttons(self): ''' Initiate/create buttons for assigning events containing manual picks to reference or test set. @@ -619,21 +641,66 @@ class MainWindow(QMainWindow): fname_dict = dict(phasfn=fn_phases, locfn=fn_loc) self.load_data(fname_dict, type=type) - def load_data(self, fname=None, type='manual', loc=False): + def load_multiple_data(self, type='manual'): if not self.okToContinue(): return + refresh=False + events = self.project.eventlist + fext = '.xml' + for event in events: + path = event.path + eventname = path.split('/')[-1] + filename = os.path.join(path, 'PyLoT_'+eventname+fext) + if os.path.isfile(filename): + self.load_data(filename, draw=False, event=event, overwrite=True) + refresh=True + if not refresh: + return + if self.get_current_event().pylot_picks: + self.refreshEvents() + self.setDirty(True) + + def load_data(self, fname=None, type='manual', loc=False, draw=True, event=None, overwrite=False): + if not overwrite: + if not self.okToContinue(): + return if fname is None: action = self.sender() if isinstance(action, QAction): fname = self.filename_from_action(action) + if not fname: + return self.set_fname(fname, type) - data = dict(auto=self.autodata, manual=self.data) - data[type] += Data(self, evtdata=fname) + #data = dict(auto=self.autodata, manual=self.data) + if not event: + event = self.get_current_event() + data = Data(self, event) + try: + data_new = Data(self, evtdata=fname) + data += data_new + except ValueError: + qmb = QMessageBox(self, icon=QMessageBox.Question, + text='Warning: Missmatch in event identifiers {} and {}. Continue?'.format( + data_new.get_evt_data().resource_id, + data.get_evt_data().resource_id), + windowTitle='PyLoT - Load data warning') + qmb.setStandardButtons(QMessageBox.Yes | QMessageBox.No) + qmb.setDefaultButton(QMessageBox.No) + ret = qmb.exec_() + if ret == qmb.Yes: + data_new.setNew() + data += data_new + else: + return + + self.data = data + print('Loading {} picks from file {}.'.format(type, fname)) if not loc: - self.updatePicks(type=type) - self.drawPicks(picktype=type) - self.draw() - self.setDirty(True) + self.updatePicks(type=type, event=event) + if draw: + if self.get_current_event().pylot_picks: + self.refreshEvents() + self.setDirty(True) def add_recentfile(self, event): self.recentfiles.insert(0, event) @@ -656,8 +723,8 @@ class MainWindow(QMainWindow): def getWFFnames(self): try: evt = self.get_data().get_evt_data() - if evt.picks: - for pick in evt.picks: + if evt.pylot_picks: + for pick in evt.pylot_picks: try: if pick.waveform_id is not None: fname = pick.waveform_id.getSEEDstring() @@ -695,6 +762,8 @@ class MainWindow(QMainWindow): ''' if self.dataStructure: directory = self.get_current_event_path(eventbox) + if not directory: + return fnames = [os.path.join(directory, f) for f in os.listdir(directory)] else: raise DatastructureError('not specified') @@ -706,17 +775,25 @@ class MainWindow(QMainWindow): ''' if not eventbox: eventbox = self.eventBox - index = eventbox.currentIndex() - return eventbox.itemData(index) + path = eventbox.currentText() + return self.project.getEventFromPath(path) def get_current_event_path(self, eventbox=None): ''' Return event path of event (type QtPylot.Event) currently selected in eventbox. ''' - if not eventbox: - eventbox = self.eventBox - return str(eventbox.currentText().split('|')[0]).strip() + event = self.get_current_event(eventbox) + if event: + return event.path + def get_current_event_name(self, eventbox=None): + ''' + Return event path of event (type QtPylot.Event) currently selected in eventbox. + ''' + path = self.get_current_event_path(eventbox) + if path: + return path.split('/')[-1] + def getLastEvent(self): return self.recentfiles[0] @@ -725,7 +802,7 @@ class MainWindow(QMainWindow): Creates and adds events by user selection of event folders to GUI. ''' if not self.project: - self.project = Project() + self.createNewProject() ed = getExistingDirectories(self, 'Select event directories...') if ed.exec_(): eventlist = ed.selectedFiles() @@ -733,11 +810,59 @@ class MainWindow(QMainWindow): eventlist = [item for item in eventlist if item.split('/')[-1].startswith('e') and len(item.split('/')[-1].split('.')) == 3 and len(item.split('/')[-1]) == 12] + if not eventlist: + print('No events found! Expected structure for event folders: [evID.DOY.YR]') + return else: return if not self.project: print('No project found.') return + + #get path from first event in list and split them + path = eventlist[0] + try: + dirs = { + 'database': path.split('/')[-2], + 'datapath': path.split('/')[-3], + 'rootpath': '/'+os.path.join(*path.split('/')[:-3]) + } + except Exception as e: + dirs = { + 'database': '', + 'datapath': '', + 'rootpath': '' + } + print('Warning: Could not automatically init folder structure. ({})'.format(e)) + + settings = QSettings() + settings.setValue("data/dataRoot", dirs['rootpath']) + settings.sync() + + if not self.project.eventlist: + #init parameter object + self.setParameter(show=False) + #hide all parameter (show all needed parameter later) + self.paraBox.hide_parameter() + for directory in dirs.keys(): + #set parameter + box = self.paraBox.boxes[directory] + self.paraBox.setValue(box, dirs[directory]) + #show needed parameter in box + self.paraBox.show_parameter(directory) + dirs_box = self.paraBox.get_groupbox_dialog('Directories') + if not dirs_box.exec_(): + return + self.project.rootpath = dirs['rootpath'] + else: + if hasattr(self.project, 'rootpath'): + if not self.project.rootpath == dirs['rootpath']: + QMessageBox.warning(self, "PyLoT Warning", + 'Rootpath missmatch to current project!') + return + else: + self.project.rootpath = dirs['rootpath'] + self.project.add_eventlist(eventlist) self.init_events() self.setDirty(True) @@ -806,10 +931,10 @@ class MainWindow(QMainWindow): event_path = event.path event_npicks = 0 event_nautopicks = 0 - if event.picks: - event_npicks = len(event.picks) - if event.autopicks: - event_nautopicks = len(event.autopicks) + if event.pylot_picks: + event_npicks = len(event.pylot_picks) + if event.pylot_autopicks: + event_nautopicks = len(event.pylot_autopicks) event_ref = event.isRefEvent() event_test = event.isTestEvent() @@ -855,7 +980,8 @@ class MainWindow(QMainWindow): '{} unequal {}.' .format(event.path, self.eventBox.itemText(id))) raise ValueError(message) - eventBox.setItemData(id, event) + #not working with obspy events + #eventBox.setItemData(id, event) eventBox.setCurrentIndex(index) self.refreshRefTestButtons() @@ -866,7 +992,7 @@ class MainWindow(QMainWindow): caption = "Open an event file" fname = QFileDialog().getOpenFileName(self, caption=caption, filter=filt, - dir=self.getRoot()) + dir=self.get_current_event_path()) fname = fname[0] else: fname = str(action.data().toString()) @@ -889,41 +1015,45 @@ class MainWindow(QMainWindow): def getSavePath(e): print('warning: {0}'.format(e)) - directory = os.path.realpath(self.getRoot()) + directory = self.get_current_event_path() + eventname = self.get_current_event_name() + filename = 'PyLoT_'+eventname + outpath = os.path.join(directory, filename) file_filter = "QuakeML file (*.xml);;VELEST observation file " \ "format (*.cnv);;NonLinLoc observation file (*.obs)" title = 'Save pick data ...' fname, selected_filter = QFileDialog.getSaveFileName(self, title, - directory, + outpath, file_filter) fbasename, exform = os.path.splitext(fname) - if not exform and selected_filter: + if not exform and selected_filter or not exform in OUTPUTFORMATS: exform = selected_filter.split('*')[1][:-1] - + return fname, exform return fbasename, exform settings = QSettings() fbasename = self.getEventFileName() exform = settings.value('data/exportFormat', 'QUAKEML') try: - self.get_data().applyEVTData(self.getPicks()) + self.get_data().applyEVTData(self.get_current_event(), typ='event')#getPicks()) except OverwriteError: - msgBox = QMessageBox() - msgBox.setText("Picks have been modified!") - msgBox.setInformativeText( - "Do you want to save the changes and overwrite the picks?") - msgBox.setDetailedText(self.get_data().getPicksStr()) - msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) - msgBox.setDefaultButton(QMessageBox.Save) - ret = msgBox.exec_() - if ret == QMessageBox.Save: - self.get_data().resetPicks() - return self.saveData() - elif ret == QMessageBox.Cancel: - return False + # msgBox = QMessageBox() + # msgBox.setText("Picks have been modified!") + # msgBox.setInformativeText( + # "Do you want to save the changes and overwrite the picks?") + # msgBox.setDetailedText(self.get_data().getPicksStr()) + # msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) + # msgBox.setDefaultButton(QMessageBox.Save) + # ret = msgBox.exec_() + # if ret == QMessageBox.Save: + self.get_data().resetPicks() + return self.saveData() + # elif ret == QMessageBox.Cancel: + # return False + # MP MP changed to suppress unnecessary user prompt try: self.get_data().exportEvent(fbasename, exform) except FormatError as e: @@ -949,7 +1079,6 @@ class MainWindow(QMainWindow): # export to given path self.get_data().exportEvent(fbasename, exform) # all files save (ui clean) - self.setDirty(False) self.update_status('Picks saved as %s' % (fbasename + exform)) self.disableSaveManualPicksAction() return True @@ -979,7 +1108,7 @@ class MainWindow(QMainWindow): return self.get_current_event().getPicks() if type == 'auto': return self.get_current_event().getAutopicks() - # rdict = dict(auto=self.autopicks, manual=self.picks) + # rdict = dict(auto=self.pylot_autopicks, manual=self.pylot_picks) # return rdict[type] def getPicksOnStation(self, station, type='manual'): @@ -998,10 +1127,7 @@ class MainWindow(QMainWindow): return self.dataPlot @staticmethod - def getWFID(gui_event): - - ycoord = gui_event.ydata - + def getWFID(ycoord): try: statID = int(round(ycoord)) except TypeError as e: @@ -1056,7 +1182,7 @@ class MainWindow(QMainWindow): if event: self.ref_event_button.setChecked(event.isRefEvent()) self.test_event_button.setChecked(event.isTestEvent()) - self.enableRefTestButtons(bool(self.get_current_event().picks)) + self.enableRefTestButtons(bool(self.get_current_event().pylot_picks)) return self.ref_event_button.setChecked(False) self.test_event_button.setChecked(False) @@ -1115,14 +1241,14 @@ class MainWindow(QMainWindow): if not event: return # update picks saved in GUI mainwindow (to be changed in future!!) MP MP - if not event.picks: - self.picks = {} + if not event.pylot_picks: + self.pylot_picks = {} else: - self.picks = event.picks - if not event.autopicks: - self.autopicks = {} + self.pylot_picks = event.pylot_picks + if not event.pylot_autopicks: + self.pylot_autopicks = {} else: - self.autopicks = event.autopicks + self.pylot_autopicks = event.pylot_autopicks # if current tab is waveformPlot-tab and the data in this tab was not yet refreshed if self.tabs.currentIndex() == 0: if self._eventChanged[0]: @@ -1187,6 +1313,15 @@ class MainWindow(QMainWindow): ''' Connect signals refering to WF-Dataplot (select station, tutor_user, scrolling) ''' + if self.pg: + self.connect_pg() + else: + self.connect_mpl() + + def connect_pg(self): + self.poS_id = self.dataPlot.plotWidget.scene().sigMouseClicked.connect(self.pickOnStation) + + def connect_mpl(self): if not self.poS_id: self.poS_id = self.dataPlot.mpl_connect('button_press_event', self.pickOnStation) @@ -1198,11 +1333,23 @@ class MainWindow(QMainWindow): self.scroll_id = self.dataPlot.mpl_connect('scroll_event', self.scrollPlot) - def disconnectWFplotEvents(self): ''' Disconnect all signals refering to WF-Dataplot (select station, tutor_user, scrolling) ''' + if self.pg: + self.disconnect_pg() + else: + self.disconnect_mpl() + + def disconnect_pg(self): + if self.poS_id: + try: + self.dataPlot.plotWidget.scene().sigMouseClicked.disconnect() + except: + pass + + def disconnect_mpl(self): if self.poS_id: self.dataPlot.mpl_disconnect(self.poS_id) if self.ae_id: @@ -1213,7 +1360,29 @@ class MainWindow(QMainWindow): self.ae_id = None self.scroll_id = None + def finish_pg_plot(self): + self.getPlotWidget().updateWidget() + plots = self.wfp_thread.data + for times, data in plots: + self.dataPlot.plotWidget.getPlotItem().plot(times, data, pen='k') + self.dataPlot.reinitMoveProxy() + self.dataPlot.plotWidget.showAxis('left') + self.dataPlot.plotWidget.showAxis('bottom') + def finishWaveformDataPlot(self): + if self.pg: + self.finish_pg_plot() + else: + self._max_xlims = self.dataPlot.getXLims() + plotWidget = self.getPlotWidget() + plotDict = plotWidget.getPlotDict() + pos = plotDict.keys() + labels = [plotDict[n][2]+'.'+plotDict[n][0] for n in pos] + plotWidget.setYTickLabels(pos, labels) + try: + plotWidget.figure.tight_layout() + except: + pass self.connectWFplotEvents() self.loadlocationaction.setEnabled(True) self.auto_tune.setEnabled(True) @@ -1225,19 +1394,24 @@ class MainWindow(QMainWindow): self.openautopicksaction.setEnabled(True) self.loadpilotevent.setEnabled(True) event = self.get_current_event() - if event.picks: - self.picks = event.picks + if event.pylot_picks: + self.pylot_picks = event.pylot_picks self.drawPicks(picktype='manual') self.enableSaveManualPicksAction() - if event.autopicks: - self.autopicks = event.autopicks + if event.pylot_autopicks: + self.pylot_autopicks = event.pylot_autopicks self.drawPicks(picktype='auto') self.compare_action.setEnabled(True) self.draw() def clearWaveformDataPlot(self): self.disconnectWFplotEvents() - self.dataPlot.getAxes().cla() + if self.pg: + self.dataPlot.plotWidget.getPlotItem().clear() + self.dataPlot.plotWidget.hideAxis('bottom') + self.dataPlot.plotWidget.hideAxis('left') + else: + self.dataPlot.getAxes().cla() self.loadlocationaction.setEnabled(False) self.auto_tune.setEnabled(False) self.auto_pick.setEnabled(False) @@ -1254,10 +1428,11 @@ class MainWindow(QMainWindow): ''' Open a modal thread to plot current waveform data. ''' - wfp_thread = Thread(self, self.plotWaveformData, - progressText='Plotting waveform data...') - wfp_thread.finished.connect(self.finishWaveformDataPlot) - wfp_thread.start() + self.clearWaveformDataPlot() + self.wfp_thread = Thread(self, self.plotWaveformData, + progressText='Plotting waveform data...') + self.wfp_thread.finished.connect(self.finishWaveformDataPlot) + self.wfp_thread.start() def plotWaveformData(self): ''' @@ -1275,18 +1450,12 @@ class MainWindow(QMainWindow): # wfst += self.get_data().getWFData().select(component=alter_comp) plotWidget = self.getPlotWidget() self.adjustPlotHeight() - plotWidget.plotWFData(wfdata=wfst, title=title, mapping=False, component=comp, nth_sample=int(nth_sample)) - plotDict = plotWidget.getPlotDict() - pos = plotDict.keys() - labels = [plotDict[n][2]+'.'+plotDict[n][0] for n in pos] - plotWidget.setYTickLabels(pos, labels) - try: - plotWidget.figure.tight_layout() - except: - pass - self._max_xlims = self.dataPlot.getXLims() + plots = plotWidget.plotWFData(wfdata=wfst, title=title, mapping=False, component=comp, nth_sample=int(nth_sample)) + return plots def adjustPlotHeight(self): + if self.pg: + return height_need = len(self.data.getWFData())*self.height_factor plotWidget = self.getPlotWidget() if self.tabs.widget(0).frameSize().height() < height_need: @@ -1297,20 +1466,20 @@ class MainWindow(QMainWindow): def plotZ(self): self.setComponent('Z') self.plotWaveformDataThread() - self.drawPicks() - self.draw() + # self.drawPicks() + # self.draw() def plotN(self): self.setComponent('N') self.plotWaveformDataThread() - self.drawPicks() - self.draw() + # self.drawPicks() + # self.draw() def plotE(self): self.setComponent('E') self.plotWaveformDataThread() - self.drawPicks() - self.draw() + # self.drawPicks() + # self.draw() def pushFilterWF(self, param_args): self.get_data().filterWFData(param_args) @@ -1382,7 +1551,9 @@ class MainWindow(QMainWindow): return self.seismicPhase def getStationName(self, wfID): - return self.getPlotWidget().getPlotDict()[wfID][0] + plot_dict = self.getPlotWidget().getPlotDict() + if wfID in plot_dict.keys(): + return plot_dict[wfID][0] def alterPhase(self): pass @@ -1429,14 +1600,27 @@ class MainWindow(QMainWindow): self.dataPlot.draw() def pickOnStation(self, gui_event): - if not gui_event.button == 1: - return - - wfID = self.getWFID(gui_event) + if self.pg: + if not gui_event.button() == 1: + return + else: + if not gui_event.button == 1: + return + + if self.pg: + ycoord = self.dataPlot.plotWidget.getPlotItem().vb.mapSceneToView(gui_event.scenePos()).y() + else: + ycoord = gui_event.ydata + + wfID = self.getWFID(ycoord) if wfID is None: return - + self.pickDialog(wfID) + + def pickDialog(self, wfID, nextStation=False): station = self.getStationName(wfID) + if not station: + return self.update_status('picking on station {0}'.format(station)) data = self.get_data().getWFData() pickDlg = PickDlg(self, parameter=self._inputs, @@ -1444,21 +1628,23 @@ class MainWindow(QMainWindow): station=station, picks=self.getPicksOnStation(station, 'manual'), autopicks=self.getPicksOnStation(station, 'auto')) + pickDlg.nextStation.setChecked(nextStation) if pickDlg.exec_(): - if not pickDlg.getPicks(): - return - self.setDirty(True) - self.update_status('picks accepted ({0})'.format(station)) - replot = self.addPicks(station, pickDlg.getPicks()) - self.get_current_event().setPick(station, pickDlg.getPicks()) - self.enableSaveManualPicksAction() - if replot: - self.plotWaveformData() - self.drawPicks() - self.draw() - else: - self.drawPicks(station) - self.draw() + if pickDlg.getPicks(): + self.setDirty(True) + self.update_status('picks accepted ({0})'.format(station)) + replot = self.addPicks(station, pickDlg.getPicks()) + self.get_current_event().setPick(station, pickDlg.getPicks()) + self.enableSaveManualPicksAction() + if replot: + self.plotWaveformDataThread() + self.drawPicks() + self.draw() + else: + self.drawPicks(station) + self.draw() + if pickDlg.nextStation.isChecked(): + self.pickDialog(wfID - 1, nextStation=pickDlg.nextStation.isChecked()) else: self.update_status('picks discarded ({0})'.format(station)) if not self.get_loc_flag() and self.check4Loc(): @@ -1557,40 +1743,47 @@ class MainWindow(QMainWindow): def addPicks(self, station, picks, type='manual'): stat_picks = self.getPicksOnStation(station, type) - rval = False if not stat_picks: - stat_picks = picks + rval = False else: - msgBox = QMessageBox(self) - msgBox.setText("The picks for station {0} have been " - "changed.".format(station)) - msgBox.setDetailedText("Old picks:\n" - "{old_picks}\n\n" - "New picks:\n" - "{new_picks}".format(old_picks=stat_picks, - new_picks=picks)) - msgBox.setInformativeText("Do you want to save your changes?") - msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) - msgBox.setDefaultButton(QMessageBox.Save) - ret = msgBox.exec_() - if ret == QMessageBox.Save: - stat_picks = picks - rval = True - elif ret == QMessageBox.Cancel: - pass - else: - raise Exception('FATAL: Should never occur!') - self.getPicks(type=type)[station] = stat_picks + #set picks (ugly syntax?) + self.getPicks(type=type)[station] = picks + rval = True return rval + # if not stat_picks: + # stat_picks = picks + # else: + # msgBox = QMessageBox(self) + # msgBox.setText("The picks for station {0} have been " + # "changed.".format(station)) + # msgBox.setDetailedText("Old picks:\n" + # "{old_picks}\n\n" + # "New picks:\n" + # "{new_picks}".format(old_picks=stat_picks, + # new_picks=picks)) + # msgBox.setInformativeText("Do you want to save your changes?") + # msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) + # msgBox.setDefaultButton(QMessageBox.Save) + # ret = msgBox.exec_() + # if ret == QMessageBox.Save: + # stat_picks = picks + # rval = True + # elif ret == QMessageBox.Cancel: + # pass + # else: + # raise Exception('FATAL: Should never occur!') + # MP MP prompt redundant because new picks have to be accepted in the first place closing PickDlg - def updatePicks(self, type='manual'): + def updatePicks(self, type='manual', event=None): + if not event: + event = self.get_current_event() picks = picksdict_from_picks(evt=self.get_data(type).get_evt_data()) if type == 'manual': - self.get_current_event().addPicks(picks) - self.picks.update(picks) + event.addPicks(picks) + self.pylot_picks.update(picks) elif type == 'auto': - self.get_current_event().addAutopicks(picks) - self.autopicks.update(picks) + event.addAutopicks(picks) + self.pylot_autopicks.update(picks) self.check4Comparison() def drawPicks(self, station=None, picktype='manual'): @@ -1603,12 +1796,23 @@ class MainWindow(QMainWindow): plotID = self.getStationID(station) if plotID is None: return - ax = self.getPlotWidget().axes + if self.pg: + pw = self.getPlotWidget().plotWidget + else: + ax = self.getPlotWidget().axes ylims = np.array([-.5, +.5]) + plotID - phase_col = { - 'P': ('c', 'c--', 'b-', 'bv', 'b^', 'b'), - 'S': ('m', 'm--', 'r-', 'rv', 'r^', 'r') - } + if self.pg: + dashed = QtCore.Qt.DashLine + dotted = QtCore.Qt.DotLine + phase_col = { + 'P': (pg.mkPen('c'), pg.mkPen((0, 255, 255, 100), style=dashed), pg.mkPen('b', style=dashed), pg.mkPen('b', style=dotted)), + 'S': (pg.mkPen('m'), pg.mkPen((255, 0, 255, 100), style=dashed), pg.mkPen('r', style=dashed), pg.mkPen('r', style=dotted)) + } + else: + phase_col = { + 'P': ('c', 'c--', 'b-', 'bv', 'b^', 'b'), + 'S': ('m', 'm--', 'r-', 'rv', 'r^', 'r') + } stat_picks = self.getPicks(type=picktype)[station] @@ -1616,7 +1820,7 @@ class MainWindow(QMainWindow): for phase in stat_picks: picks = stat_picks[phase] - if type(stat_picks[phase]) is not dict: + if type(stat_picks[phase]) is not dict and type(stat_picks[phase]) is not AttribDict: return colors = phase_col[phase[0].upper()] @@ -1629,22 +1833,47 @@ class MainWindow(QMainWindow): if not spe and epp and lpp: spe = symmetrize_error(mpp - epp, lpp - mpp) - if picktype == 'manual': - if picks['epp'] and picks['lpp']: - ax.fill_between([epp, lpp], ylims[0], ylims[1], - alpha=.25, color=colors[0], label='EPP, LPP') - if spe: - ax.plot([mpp - spe, mpp - spe], ylims, colors[1], label='{}-SPE'.format(phase)) - ax.plot([mpp + spe, mpp + spe], ylims, colors[1]) - ax.plot([mpp, mpp], ylims, colors[2], label='{}-Pick'.format(phase)) + if self.pg: + if picktype == 'manual': + if picks['epp'] and picks['lpp']: + pw.plot([epp, epp], ylims, + alpha=.25, pen=colors[0], name='EPP') + pw.plot([lpp, lpp], ylims, + alpha=.25, pen=colors[0], name='LPP') + if spe: + spe_l = pg.PlotDataItem([mpp - spe, mpp - spe], ylims, pen=colors[1], name='{}-SPE'.format(phase)) + spe_r = pg.PlotDataItem([mpp + spe, mpp + spe], ylims, pen=colors[1]) + pw.addItem(spe_l) + pw.addItem(spe_r) + try: + fill = pg.FillBetweenItem(spe_l, spe_r, brush=colors[1].brush()) + fb = pw.addItem(fill) + except: + print('Warning: drawPicks: Could not create fill for symmetric pick error.') + pw.plot([mpp, mpp], ylims, pen=colors[2], name='{}-Pick'.format(phase)) + else: + pw.plot([mpp, mpp], ylims, pen=colors[0], name='{}-Pick (NO PICKERROR)'.format(phase)) + elif picktype == 'auto': + pw.plot([mpp, mpp], ylims, pen=colors[3]) else: - ax.plot([mpp, mpp], ylims, colors[6], label='{}-Pick (NO PICKERROR)'.format(phase)) - elif picktype == 'auto': - ax.plot(mpp, ylims[1], colors[3], - mpp, ylims[0], colors[4]) - ax.vlines(mpp, ylims[0], ylims[1], colors[5], linestyles='dotted') + raise TypeError('Unknown picktype {0}'.format(picktype)) else: - raise TypeError('Unknown picktype {0}'.format(picktype)) + if picktype == 'manual': + if picks['epp'] and picks['lpp']: + ax.fill_between([epp, lpp], ylims[0], ylims[1], + alpha=.25, color=colors[0], label='EPP, LPP') + if spe: + ax.plot([mpp - spe, mpp - spe], ylims, colors[1], label='{}-SPE'.format(phase)) + ax.plot([mpp + spe, mpp + spe], ylims, colors[1]) + ax.plot([mpp, mpp], ylims, colors[2], label='{}-Pick'.format(phase)) + else: + ax.plot([mpp, mpp], ylims, colors[6], label='{}-Pick (NO PICKERROR)'.format(phase)) + elif picktype == 'auto': + ax.plot(mpp, ylims[1], colors[3], + mpp, ylims[0], colors[4]) + ax.vlines(mpp, ylims[0], ylims[1], colors[5], linestyles='dotted') + else: + raise TypeError('Unknown picktype {0}'.format(picktype)) def locate_event(self): """ @@ -1705,8 +1934,8 @@ class MainWindow(QMainWindow): finally: os.remove(phasepath) - self.get_data().applyEVTData(lt.read_location(locpath), type='event') - self.get_data().applyEVTData(self.calc_magnitude(), type='event') + self.get_data().applyEVTData(lt.read_location(locpath), typ='event') + self.get_data().applyEVTData(self.calc_magnitude(), typ='event') def init_array_tab(self): ''' @@ -1791,6 +2020,12 @@ class MainWindow(QMainWindow): if not self.array_map: return # refresh with new picks here!!! + event = self.get_current_event() + if hasattr(event, 'origins'): + if event.origins: + lat = event.origins[0].latitude + lon = event.origins[0].longitude + self.array_map.eventLoc = (lat, lon) self.array_map.refresh_drawings(self.get_current_event().getPicks()) self._eventChanged[1] = False @@ -1817,19 +2052,19 @@ class MainWindow(QMainWindow): # changes attributes of the corresponding event table = self.project._table event = self.project.getEventFromPath(table[row][0].text()) - if column == 3 or column == 4: + if column == 8 or column == 9: #toggle checked states (exclusive) - item_ref = table[row][3] - item_test = table[row][4] - if column == 3 and item_ref.checkState(): + item_ref = table[row][8] + item_test = table[row][9] + if column == 8 and item_ref.checkState(): item_test.setCheckState(QtCore.Qt.Unchecked) event.setRefEvent(True) - elif column == 3 and not item_ref.checkState(): + elif column == 8 and not item_ref.checkState(): event.setRefEvent(False) - elif column == 4 and item_test.checkState(): + elif column == 9 and item_test.checkState(): item_ref.setCheckState(QtCore.Qt.Unchecked) event.setTestEvent(True) - elif column == 4 and not item_test.checkState(): + elif column == 9 and not item_test.checkState(): event.setTestEvent(False) self.fill_eventbox() elif column == 5: @@ -1845,22 +2080,35 @@ class MainWindow(QMainWindow): # init new qtable self.event_table = QtGui.QTableWidget() - self.event_table.setColumnCount(6) + self.event_table.setColumnCount(11) self.event_table.setRowCount(len(eventlist)) - self.event_table.setHorizontalHeaderLabels(['Event', '[N] MP', - '[N] AP', 'Tuning Set', - 'Test Set', 'Notes']) + self.event_table.setHorizontalHeaderLabels(['Event', + 'Time', + 'Lat', + 'Lon', + 'Depth', + 'Mag', + '[N] MP', + '[N] AP', + 'Tuning Set', + 'Test Set', + 'Notes']) # iterate through eventlist and generate items for table rows self.project._table = [] for index, event in enumerate(eventlist): event_npicks = 0 event_nautopicks = 0 - if event.picks: - event_npicks = len(event.picks) - if event.autopicks: - event_nautopicks = len(event.autopicks) + if event.pylot_picks: + event_npicks = len(event.pylot_picks) + if event.pylot_autopicks: + event_nautopicks = len(event.pylot_autopicks) item_path = QtGui.QTableWidgetItem() + item_time = QtGui.QTableWidgetItem() + item_lat = QtGui.QTableWidgetItem() + item_lon = QtGui.QTableWidgetItem() + item_depth = QtGui.QTableWidgetItem() + item_mag = QtGui.QTableWidgetItem() item_nmp = QtGui.QTableWidgetItem(str(event_npicks)) item_nmp.setIcon(self.manupicksicon_small) item_nap = QtGui.QTableWidgetItem(str(event_nautopicks)) @@ -1872,11 +2120,23 @@ class MainWindow(QMainWindow): item_ref.setBackground(self._colors['ref']) item_test.setBackground(self._colors['test']) item_path.setText(event.path) - item_notes.setText(event.notes) + if hasattr(event, 'origins'): + if event.origins: + origin = event.origins[0] + item_time.setText(str(origin.time).split('.')[0]) + item_lon.setText(str(origin.longitude)) + item_lat.setText(str(origin.latitude)) + item_depth.setText(str(origin.depth)) + if hasattr(event, 'magnitudes'): + if event.magnitudes: + magnitude = event.magnitudes[0] + item_mag.setText(str(magnitude.mag)) + item_notes.setText(event.notes) + set_enabled(item_path, True, False) set_enabled(item_nmp, True, False) set_enabled(item_nap, True, False) - if event.picks: + if event.pylot_picks: set_enabled(item_ref, True, True) set_enabled(item_test, True, True) else: @@ -1892,7 +2152,8 @@ class MainWindow(QMainWindow): else: item_test.setCheckState(QtCore.Qt.Unchecked) - column=[item_path, item_nmp, item_nap, item_ref, item_test, item_notes] + column=[item_path, item_time, item_lat, item_lon, item_depth, item_mag, + item_nmp, item_nap, item_ref, item_test, item_notes] self.project._table.append(column) for r_index, row in enumerate(self.project._table): @@ -1974,7 +2235,7 @@ class MainWindow(QMainWindow): # if not rest_flag: # raise ProcessingError('Restitution of waveform data failed!') if type == 'ML': - local_mag = RichterMagnitude(corr_wf, self.get_data().get_evt_data(), self.inputs.get('sstop'), verbosity = True) + local_mag = LocalMagnitude(corr_wf, self.get_data().get_evt_data(), self.inputs.get('sstop'), verbosity = True) return local_mag.updated_event() elif type == 'Mw': moment_mag = MomentMagnitude(corr_wf, self.get_data().get_evt_data(), self.inputs.get('vp'), self.inputs.get('Qp'), self.inputs.get('rho'), verbosity = True) @@ -2048,28 +2309,18 @@ class MainWindow(QMainWindow): self.data = Data(self, evtdata=event) self.setDirty(True) - def createNewProject(self, exists=False): + def createNewProject(self): ''' Create new project file. ''' - if not exists: - if not self.okToContinue(): - return - dlg = QFileDialog() - fnm = dlg.getSaveFileName(self, 'Create a new project file...', filter='Pylot project (*.plp)') - filename = fnm[0] - if not len(fnm[0]): - return False - if not filename.split('.')[-1] == 'plp': - filename = fnm[0] + '.plp' - if not exists: - self.project = Project() - self.init_events(new=True) - self.setDirty(True) - self.project.parameter=self._inputs - self.project.save(filename) + if not self.okToContinue(): + return + self.project = Project() + self.init_events(new=True) self.setDirty(False) - self.update_status('Creating new project...', duration=1000) + self.project.parameter=self._inputs + self.saveProjectAsAction.setEnabled(True) + self.update_status('Created new project...', duration=1000) return True def loadProject(self, fnm=None): @@ -2098,8 +2349,26 @@ class MainWindow(QMainWindow): return self.init_array_tab() - def saveProjectAs(self): - self.saveProject(new=True) + def saveProjectAs(self, exists=False): + ''' + Save back project to new pickle file. + ''' + if not exists: + if not self.okToContinue(): + return + dlg = QFileDialog() + fnm = dlg.getSaveFileName(self, 'Create a new project file...', filter='Pylot project (*.plp)') + filename = fnm[0] + if not len(fnm[0]): + return False + if not filename.split('.')[-1] == 'plp': + filename = fnm[0] + '.plp' + self.project.parameter=self._inputs + self.project.save(filename) + self.setDirty(False) + self.saveProjectAsAction.setEnabled(True) + self.update_status('Saved new project to {}'.format(filename), duration=5000) + return True def saveProject(self, new=False): ''' @@ -2107,14 +2376,14 @@ class MainWindow(QMainWindow): ''' if self.project and not new: if not self.project.location: - if not self.createNewProject(exists=True): + if not self.saveProjectAs(exists=True): self.setDirty(True) return False else: self.project.parameter=self._inputs self.project.save() if not self.project.dirty: - print('Saved back project to file:\n{}'.format(self.project.location)) + self.update_status('Saved back project to file:\n{}'.format(self.project.location), duration=5000) self.setDirty(False) return True else: @@ -2122,7 +2391,7 @@ class MainWindow(QMainWindow): qmb = QMessageBox.warning(self,'Could not save project', 'Could not save back to original file.\nChoose new file') self.setDirty(True) - return self.createNewProject(exists=True) + return self.saveProjectAs(exists=True) def draw(self): self.fill_eventbox() @@ -2132,8 +2401,8 @@ class MainWindow(QMainWindow): self.setDirty(True) def setDirty(self, value): - self.saveProjectAction.setEnabled(value) - self.saveProjectAsAction.setEnabled(value) + self.saveProjectAction.setEnabled(bool(self.get_current_event().picks)) + self.saveProjectAsAction.setEnabled(True) self.project.setDirty(value) self.dirty = value @@ -2145,18 +2414,20 @@ class MainWindow(QMainWindow): # self.closing.emit() # QMainWindow.closeEvent(self, event) - def pickParameter(self): + def setParameter(self, show=True): if not self.paraBox: - self.paraBox = AutoPickParaBox(self._inputs) + self.paraBox = PylotParaBox(self._inputs) self.paraBox._apply.clicked.connect(self._setDirty) - self.paraBox._okay.clicked.connect(self._setDirty) - self.paraBox.show() + self.paraBox._okay.clicked.connect(self._setDirty) + if show: + self.paraBox.show() def PyLoTprefs(self): if not self._props: self._props = PropertiesDlg(self, infile=self.infile) if self._props.exec_(): + self.init_wfWidget() return def helpHelp(self): @@ -2175,6 +2446,7 @@ class Project(object): def __init__(self): self.eventlist = [] self.location = None + self.rootpath = None self.dirty = False self.parameter = None self._table = None @@ -2188,11 +2460,71 @@ class Project(object): return for item in eventlist: event = Event(item) + event.rootpath = self.parameter['rootpath'] + event.database = self.parameter['database'] + event.datapath = self.parameter['datapath'] if not event.path in self.getPaths(): self.eventlist.append(event) self.setDirty() else: print('Skipping event with path {}. Already part of project.'.format(event.path)) + self.search_eventfile_info() + + def read_eventfile_info(self, filename, separator=','): + ''' + Try to read event information from file (:param:filename) comparing specific event datetimes. + File structure (each row): event, date, time, magnitude, latitude, longitude, depth + separated by :param:separator each. + ''' + infile = open(filename, 'r') + for line in infile.readlines(): + event, date, time, mag, lat, lon, depth = line.split(separator)[:7] + #skip first line + try: + month, day, year = date.split('/') + except: + continue + year = int(year) + #hardcoded, if year only consists of 2 digits (e.g. 16 instead of 2016) + if year<100: + year += 2000 + datetime = '{}-{}-{}T{}'.format(year, month, day, time) + try: + datetime = UTCDateTime(datetime) + except Exception as e: + print(e, datetime, filename) + continue + for event in self.eventlist: + if not event.origins: + continue + origin = event.origins[0] #should have only one origin + if origin.time == datetime: + origin.latitude = float(lat) + origin.longitude = float(lon) + origin.depth = float(depth) + event.magnitudes.append(Magnitude(resource_id=event.resource_id, + mag=float(mag), + mag_type='M')) + + def search_eventfile_info(self): + ''' + Search all datapaths in rootpath for filenames with given file extension fext + and try to read event info from it + ''' + datapaths = [] + fext='.csv' + for event in self.eventlist: + if not event.datapath in datapaths: + datapaths.append(event.datapath) + for datapath in datapaths: + datapath = os.path.join(self.rootpath, datapath) + for filename in os.listdir(datapath): + filename = os.path.join(datapath, filename) + if os.path.isfile(filename) and filename.endswith(fext): + try: + self.read_eventfile_info(filename) + except Exception as e: + print('Failed on reading eventfile info from file {}: {}'.format(filename, e)) def getPaths(self): ''' @@ -2253,75 +2585,6 @@ class Project(object): return project -class Event(object): - ''' - Pickable class containing information on a single event. - ''' - def __init__(self, path): - self.path = path - self.autopicks = {} - self.picks = {} - self.notes = '' - self._testEvent = False - self._refEvent = False - - def addNotes(self, notes): - self.notes = notes - - def clearNotes(self): - self.notes = None - - def isRefEvent(self): - return self._refEvent - - def isTestEvent(self): - return self._testEvent - - def setRefEvent(self, bool): - self._refEvent = bool - if bool: self._testEvent = False - - def setTestEvent(self, bool): - self._testEvent = bool - if bool: self._refEvent = False - - def addPicks(self, picks): - for station in picks: - self.picks[station] = picks[station] - - def addAutopicks(self, autopicks): - for station in autopicks: - self.autopicks[station] = autopicks[station] - - def setPick(self, station, pick): - if pick: - self.picks[station] = pick - - def setPicks(self, picks): - self.picks = picks - - def getPick(self, station): - if station in self.picks.keys(): - return self.picks[station] - - def getPicks(self): - return self.picks - - def setAutopick(self, station, autopick): - if autopick: - self.autopicks[station] = autopick - - def setAutopicks(self, autopicks): - self.autopicks = autopicks - - def getAutopick(self, station): - if station in self.autopicks.keys(): - return self.autopicks[station] - - def getAutopicks(self): - return self.autopicks - - class getExistingDirectories(QFileDialog): ''' File dialog with possibility to select multiple folders. @@ -2343,6 +2606,9 @@ def create_window(): if app is None: app = QApplication(sys.argv) app_created = True + app.setOrganizationName("QtPyLoT"); + app.setOrganizationDomain("rub.de"); + app.setApplicationName("RUB"); app.references = set() #app.references.add(window) #window.show() diff --git a/autoPyLoT.py b/autoPyLoT.py index dc597110..96e26e73 100755 --- a/autoPyLoT.py +++ b/autoPyLoT.py @@ -16,9 +16,9 @@ import pylot.core.loc.focmec as focmec import pylot.core.loc.hash as hash import pylot.core.loc.nll as nll #from PySide.QtGui import QWidget, QInputDialog -from pylot.core.analysis.magnitude import MomentMagnitude, RichterMagnitude +from pylot.core.analysis.magnitude import MomentMagnitude, LocalMagnitude from pylot.core.io.data import Data -from pylot.core.io.inputs import AutoPickParameter +from pylot.core.io.inputs import PylotParameter from pylot.core.pick.autopick import autopickevent, iteratepicker from pylot.core.util.dataprocessing import restitute_data, read_metadata, \ remove_underscores @@ -35,7 +35,7 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even :param inputfile: path to the input file containing all parameter information for automatic picking (for formatting details, see. - `~pylot.core.io.inputs.AutoPickParameter` + `~pylot.core.io.inputs.PylotParameter` :type inputfile: str :return: @@ -71,13 +71,13 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even if not parameter: if inputfile: - parameter = AutoPickParameter(inputfile) + parameter = PylotParameter(inputfile) iplot = parameter['iplot'] else: print('No parameters set and no input file given. Choose either of both.') return else: - if not type(parameter) == AutoPickParameter: + if not type(parameter) == PylotParameter: print('Wrong input type for parameter: {}'.format(type(parameter))) return if inputfile: @@ -98,8 +98,8 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even exf = ['root', 'dpath', 'dbase'] - if parameter.hasParam('eventID') and fnames == 'None': - dsfields['eventID'] = parameter.get('eventID') + if parameter['eventID'] is not '*' and fnames == 'None': + dsfields['eventID'] = parameter['eventID'] exf.append('eventID') datastructure.modifyFields(**dsfields) @@ -130,21 +130,35 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even print("!!No source parameter estimation possible!!") print(" !!! ") - datapath = datastructure.expandDataPath() - if fnames == 'None' and not parameter.hasParam('eventID'): - # multiple event processing - # read each event in database - events = [events for events in glob.glob(os.path.join(datapath, '*')) if os.path.isdir(events)] - elif fnames == 'None' and parameter.hasParam('eventID'): - # single event processing - events = glob.glob(os.path.join(datapath, parameter.get('eventID'))) + if not input_dict: + # started in production mode + datapath = datastructure.expandDataPath() + if fnames == 'None' and parameter['eventID'] is '*': + # multiple event processing + # read each event in database + events = [events for events in glob.glob(os.path.join(datapath, '*')) if os.path.isdir(events)] + elif fnames == 'None' and parameter['eventID'] is not '*': + # single event processing + events = glob.glob(os.path.join(datapath, parameter['eventID'])) + else: + # autoPyLoT was initialized from GUI + events = [] + events.append(eventid) + evID = os.path.split(eventid)[-1] + locflag = 2 else: - # autoPyLoT was initialized from GUI + # started in tune mode + datapath = os.path.join(parameter['rootpath'], + parameter['datapath']) events = [] - events.append(eventid) - evID = os.path.split(eventid)[-1] - locflag = 2 + events.append(os.path.join(datapath, + parameter['database'], + parameter['eventID'])) + if not events: + print('autoPyLoT: No events given. Return!') + return + for event in events: if fnames == 'None': data.setWFData(glob.glob(os.path.join(datapath, event, '*'))) @@ -229,7 +243,7 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even # get latest NLLoc-location file if several are available nllocfile = max(glob.glob(locsearch), key=os.path.getctime) evt = read_events(nllocfile)[0] - # calculating seismic moment Mo and moment magnitude Mw + # calculate seismic moment Mo and moment magnitude Mw moment_mag = MomentMagnitude(corr_dat, evt, parameter.get('vp'), parameter.get('Qp'), parameter.get('rho'), True, \ @@ -238,15 +252,29 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even for station, props in moment_mag.moment_props.items(): picks[station]['P'].update(props) evt = moment_mag.updated_event() - local_mag = RichterMagnitude(corr_dat, evt, - parameter.get('sstop'), True,\ - iplot) + net_mw = moment_mag.net_magnitude() + print("Network moment magnitude: %4.1f" % net_mw.mag) + # calculate local (Richter) magntiude + WAscaling = parameter.get('WAscaling') + magscaling = parameter.get('magscaling') + local_mag = LocalMagnitude(corr_dat, evt, + parameter.get('sstop'), + WAscaling, True, iplot) for station, amplitude in local_mag.amplitudes.items(): picks[station]['S']['Ao'] = amplitude.generic_amplitude - evt = local_mag.updated_event() + print("Local station magnitudes scaled with:") + print("log(Ao) + %f * log(r) + %f * r + %f" % (WAscaling[0], + WAscaling[1], + WAscaling[2])) + evt = local_mag.updated_event(magscaling) + net_ml = local_mag.net_magnitude(magscaling) + print("Network local magnitude: %4.1f" % net_ml.mag) + print("Network local magnitude scaled with:") + print("%f * Ml + %f" % (magscaling[0], magscaling[1])) else: print("autoPyLoT: No NLLoc-location file available!") print("No source parameter estimation possible!") + locflag = 9 else: # get theoretical P-onset times from NLLoc-location file locsearch = '%s/loc/%s.????????.??????.grid?.loc.hyp' % (nllocroot, nllocout) @@ -287,7 +315,7 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even nlloccounter = maxnumit evt = read_events(nllocfile)[0] if locflag < 2: - # calculating seismic moment Mo and moment magnitude Mw + # calculate seismic moment Mo and moment magnitude Mw moment_mag = MomentMagnitude(corr_dat, evt, parameter.get('vp'), parameter.get('Qp'), parameter.get('rho'), True, \ @@ -296,14 +324,25 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even for station, props in moment_mag.moment_props.items(): picks[station]['P'].update(props) evt = moment_mag.updated_event() - local_mag = RichterMagnitude(corr_dat, evt, - parameter.get('sstop'), True, \ - iplot) - for station, amplitude in local_mag.amplitudes.items(): - picks[station]['S']['Ao'] = amplitude.generic_amplitude - evt = local_mag.updated_event() net_mw = moment_mag.net_magnitude() print("Network moment magnitude: %4.1f" % net_mw.mag) + # calculate local (Richter) magntiude + WAscaling = parameter.get('WAscaling') + magscaling = parameter.get('magscaling') + local_mag = LocalMagnitude(corr_dat, evt, + parameter.get('sstop'), + WAscaling, True, iplot) + for station, amplitude in local_mag.amplitudes.items(): + picks[station]['S']['Ao'] = amplitude.generic_amplitude + print("Local station magnitudes scaled with:") + print("log(Ao) + %f * log(r) + %f * r + %f" % (WAscaling[0], + WAscaling[1], + WAscaling[2])) + evt = local_mag.updated_event(magscaling) + net_ml = local_mag.net_magnitude(magscaling) + print("Network local magnitude: %4.1f" % net_ml.mag) + print("Network local magnitude scaled with:") + print("%f * Ml + %f" % (magscaling[0], magscaling[1])) else: print("autoPyLoT: No NLLoc-location file available! Stop iteration!") locflag = 9 @@ -314,26 +353,26 @@ def autoPyLoT(input_dict=None, parameter=None, inputfile=None, fnames=None, even data.applyEVTData(picks) if evt is not None: data.applyEVTData(evt, 'event') - fnqml = '%s/autoPyLoT' % event - data.exportEvent(fnqml) - # HYPO71 - hypo71file = '%s/autoPyLoT_HYPO71_phases' % event - hypo71.export(picks, hypo71file, parameter) - # HYPOSAT - hyposatfile = '%s/autoPyLoT_HYPOSAT_phases' % event - hyposat.export(picks, hyposatfile, parameter) + fnqml = '%s/PyLoT_%s' % (event, evID) + data.exportEvent(fnqml, fnext='.xml', fcheck='manual') if locflag == 1: + # HYPO71 + hypo71file = '%s/PyLoT_%s_HYPO71_phases' % (event, evID) + hypo71.export(picks, hypo71file, parameter) + # HYPOSAT + hyposatfile = '%s/PyLoT_%s_HYPOSAT_phases' % (event, evID) + hyposat.export(picks, hyposatfile, parameter) # VELEST - velestfile = '%s/autoPyLoT_VELEST_phases.cnv' % event + velestfile = '%s/PyLoT_%s_VELEST_phases.cnv' % (event, evID) velest.export(picks, velestfile, parameter, evt) # hypoDD - hypoddfile = '%s/autoPyLoT_hypoDD_phases.pha' % event + hypoddfile = '%s/PyLoT_%s_hypoDD_phases.pha' % (event, evID) hypodd.export(picks, hypoddfile, parameter, evt) # FOCMEC - focmecfile = '%s/autoPyLoT_FOCMEC.in' % event + focmecfile = '%s/PyLoT_%s_FOCMEC.in' % (event, evID) focmec.export(picks, focmecfile, parameter, evt) # HASH - hashfile = '%s/autoPyLoT_HASH' % event + hashfile = '%s/PyLoT_%s_HASH' % (event, evID) hash.export(picks, hashfile, parameter, evt) endsplash = '''------------------------------------------\n' @@ -385,9 +424,5 @@ if __name__ == "__main__": cla = parser.parse_args() - try: - picks, mainFig = autoPyLoT(inputfile=str(cla.inputfile), fnames=str(cla.fnames), - eventid=str(cla.eventid), savepath=str(cla.spath)) - except ValueError: - print("autoPyLoT was running in production mode.") - + picks = autoPyLoT(inputfile=str(cla.inputfile), fnames=str(cla.fnames), + eventid=str(cla.eventid), savepath=str(cla.spath)) diff --git a/pylot/RELEASE-VERSION b/pylot/RELEASE-VERSION deleted file mode 100644 index 8fc5044c..00000000 --- a/pylot/RELEASE-VERSION +++ /dev/null @@ -1 +0,0 @@ -0af79-dirty diff --git a/pylot/core/analysis/magnitude.py b/pylot/core/analysis/magnitude.py index dd5e2d6b..c1eee9cd 100644 --- a/pylot/core/analysis/magnitude.py +++ b/pylot/core/analysis/magnitude.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- """ Created autumn/winter 2015. +Revised/extended summer 2017. :author: Ludger Küperkoch / MAGS2 EP3 working group """ @@ -17,10 +18,17 @@ from pylot.core.pick.utils import getsignalwin, crossings_nonzero_all, \ from pylot.core.util.utils import common_range, fit_curve def richter_magnitude_scaling(delta): - relation = np.loadtxt(os.path.join(os.path.expanduser('~'), - '.pylot', 'richter_scaling.data')) + 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, + 260, 270, 280, 290, 300, 310, 320, 330, 340, 350, 360, 370, 380, + 390, 400, 430, 470, 510, 560, 600, 700, 800, 900, 1000]) + richter_scaling = np.array([1.4, 1.5, 1.7, 1.9, 2.1, 2.3, 2.4, 2.5, 2.6, 2.8, 2.8, 2.9, + 2.9, 3.0, 3.1, 3.1, 3.2, 3.2, 3.3, 3.3, 3.4, 3.4, 3.5, 3.5, + 3.6, 3.7, 3.7, 3.8, 3.8, 3.9, 3.9, 4.0, 4.0, 4.1, 4.2, 4.2, + 4.2, 4.2, 4.3, 4.3, 4.3, 4.4, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, + 5.1, 5.2, 5.4, 5.5, 5.7]) # prepare spline interpolation to calculate return value - func, params = fit_curve(relation[:, 0], relation[:, 1]) + func, params = fit_curve(distance, richter_scaling) return func(delta, params) @@ -31,10 +39,10 @@ class Magnitude(object): def __init__(self, stream, event, verbosity=False, iplot=0): self._type = "M" + self._stream = stream self._plot_flag = iplot self._verbosity = verbosity self._event = event - self._stream = stream self._magnitudes = dict() def __str__(self): @@ -108,28 +116,35 @@ class Magnitude(object): def calc(self): pass - def updated_event(self): - self.event.magnitudes.append(self.net_magnitude()) + def updated_event(self, magscaling=None): + self.event.magnitudes.append(self.net_magnitude(magscaling)) return self.event - def net_magnitude(self): + def net_magnitude(self, magscaling=None): if self: - # TODO if an average Magnitude instead of the median is calculated - # StationMagnitudeContributions should be added to the returned - # Magnitude object - # mag_error => weights (magnitude error estimate from peak_to_peak, calcsourcespec?) - # weights => StationMagnitdeContribution - mag = ope.Magnitude( - mag=np.median([M.mag for M in self.magnitudes.values()]), - magnitude_type=self.type, - origin_id=self.origin_id, - station_count=len(self.magnitudes), - azimuthal_gap=self.origin_id.get_referred_object().quality.azimuthal_gap) + if magscaling is not None and str(magscaling) is not '[0.0, 0.0]': + # scaling necessary + print("Scaling network magnitude ...") + mag = ope.Magnitude( + mag=np.median([M.mag for M in self.magnitudes.values()]) *\ + magscaling[0] + magscaling[1], + magnitude_type=self.type, + origin_id=self.origin_id, + station_count=len(self.magnitudes), + azimuthal_gap=self.origin_id.get_referred_object().quality.azimuthal_gap) + else: + # no saling necessary + mag = ope.Magnitude( + mag=np.median([M.mag for M in self.magnitudes.values()]), + magnitude_type=self.type, + origin_id=self.origin_id, + station_count=len(self.magnitudes), + azimuthal_gap=self.origin_id.get_referred_object().quality.azimuthal_gap) return mag return None -class RichterMagnitude(Magnitude): +class LocalMagnitude(Magnitude): """ Method to derive peak-to-peak amplitude as seen on a Wood-Anderson- seismograph. Has to be derived from instrument corrected traces! @@ -146,10 +161,11 @@ class RichterMagnitude(Magnitude): _amplitudes = dict() - def __init__(self, stream, event, calc_win, verbosity=False, iplot=0): - super(RichterMagnitude, self).__init__(stream, event, verbosity, iplot) + def __init__(self, stream, event, calc_win, wascaling, verbosity=False, iplot=0): + super(LocalMagnitude, self).__init__(stream, event, verbosity, iplot) self._calc_win = calc_win + self._wascaling = wascaling self._type = 'ML' self.calc() @@ -161,6 +177,10 @@ class RichterMagnitude(Magnitude): def calc_win(self, value): self._calc_win = value + @property + def wascaling(self): + return self._wascaling + @property def amplitudes(self): return self._amplitudes @@ -195,6 +215,8 @@ class RichterMagnitude(Magnitude): th = np.arange(0, len(sqH) * dt, dt) # get maximum peak within pick window iwin = getsignalwin(th, t0 - stime, self.calc_win) + ii = min([iwin[len(iwin)-1], len(th)]) + iwin = iwin[0:ii] wapp = np.max(sqH[iwin]) if self.verbose: print("Determined Wood-Anderson peak-to-peak amplitude for station {0}: {1} " @@ -244,10 +266,17 @@ class RichterMagnitude(Magnitude): self.event.amplitudes.append(amplitude) self.amplitudes = (station, amplitude) # using standard Gutenberg-Richter relation - # TODO make the ML calculation more flexible by allowing - # use of custom relation functions - magnitude = ope.StationMagnitude( - mag=np.log10(a0) + richter_magnitude_scaling(delta)) + # or scale WA amplitude with given scaling relation + if str(self.wascaling) == '[0.0, 0.0, 0.0]': + print("Calculating original Richter magnitude ...") + magnitude = ope.StationMagnitude(mag=np.log10(a0) \ + + richter_magnitude_scaling(delta)) + else: + print("Calculating scaled local magnitude ...") + a0 = a0 * 1e03 # mm to nm (see Havskov & Ottemöller, 2010) + magnitude = ope.StationMagnitude(mag=np.log10(a0) \ + + self.wascaling[0] * np.log10(delta) + self.wascaling[1] + * delta + self.wascaling[2]) magnitude.origin_id = self.origin_id magnitude.waveform_id = pick.waveform_id magnitude.amplitude_id = amplitude.resource_id diff --git a/pylot/core/io/data.py b/pylot/core/io/data.py index 358c597f..47e74053 100644 --- a/pylot/core/io/data.py +++ b/pylot/core/io/data.py @@ -6,11 +6,12 @@ import os from obspy import read_events from obspy.core import read, Stream, UTCDateTime from obspy.io.sac import SacIOError -from obspy.core.event import Event +from obspy.core.event import Event as ObsPyEvent 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.utils import fnConstructor, full_range +from pylot.core.util.event import Event class Data(object): """ @@ -33,7 +34,7 @@ class Data(object): self.comp = 'Z' self.wfdata = Stream() self._new = False - if isinstance(evtdata, Event): + if isinstance(evtdata, ObsPyEvent) or isinstance(evtdata, Event): pass elif isinstance(evtdata, dict): evt = readPILOTEvent(**evtdata) @@ -49,7 +50,7 @@ class Data(object): if 'Unknown format for file' in e.message: if 'PHASES' in evtdata: picks = picksdict_from_pilot(evtdata) - evtdata = Event() + evtdata = ObsPyEvent() evtdata.picks = picks_from_picksdict(picks) elif 'LOC' in evtdata: raise NotImplementedError('PILOT location information ' @@ -61,7 +62,7 @@ class Data(object): raise e else: # create an empty Event object self.setNew() - evtdata = Event() + evtdata = ObsPyEvent() evtdata.picks = [] self.evtdata = evtdata self.wforiginal = None @@ -73,6 +74,8 @@ class Data(object): def __add__(self, other): assert isinstance(other, Data), "operands must be of same type 'Data'" + rs_id = self.get_evt_data().get('resource_id') + rs_id_other = other.get_evt_data().get('resource_id') if other.isNew() and not self.isNew(): picks_to_add = other.get_evt_data().picks old_picks = self.get_evt_data().picks @@ -84,7 +87,7 @@ class Data(object): self.evtdata = new.get_evt_data() elif self.isNew() and other.isNew(): pass - elif self.get_evt_data().get('id') == other.get_evt_data().get('id'): + elif rs_id == rs_id_other: other.setNew() return self + other else: @@ -95,7 +98,7 @@ class Data(object): def getPicksStr(self): picks_str = '' for pick in self.get_evt_data().picks: - picks_str += str(pick) + '\n' + picks_str += str(PyLoT) + '\n' return picks_str def getParent(self): @@ -144,12 +147,13 @@ class Data(object): # handle forbidden filenames especially on windows systems return fnConstructor(str(ID)) - def exportEvent(self, fnout, fnext='.xml'): + def exportEvent(self, fnout, fnext='.xml', fcheck='auto'): """ :param fnout: :param fnext: + :param fcheck: :raise KeyError: """ from pylot.core.util.defaults import OUTPUTFORMATS @@ -160,13 +164,35 @@ class Data(object): errmsg = '{0}; selected file extension {1} not ' \ 'supported'.format(e, fnext) raise FormatError(errmsg) + + # check for already existing xml-file + if fnext == '.xml': + if os.path.isfile(fnout + fnext): + print("xml-file already exists! Check content ...") + cat_old = read_events(fnout + fnext) + checkflag = 0 + for j in range(len(cat_old.events[0].picks)): + if cat_old.events[0].picks[j].method_id.id.split('/')[1] == fcheck: + print("Found %s pick(s), append to new catalog." % fcheck) + checkflag = 1 + break + if checkflag == 1: + self.get_evt_data().write(fnout + fnext, format=evtformat) + cat_new = read_events(fnout + fnext) + cat_new.append(cat_old.events[0]) + cat_new.write(fnout + fnext, format=evtformat) + else: + self.get_evt_data().write(fnout + fnext, format=evtformat) + else: + self.get_evt_data().write(fnout + fnext, format=evtformat) # try exporting event via ObsPy - try: - self.get_evt_data().write(fnout + fnext, format=evtformat) - except KeyError as e: - raise KeyError('''{0} export format - not implemented: {1}'''.format(evtformat, e)) + else: + try: + self.get_evt_data().write(fnout + fnext, format=evtformat) + except KeyError as e: + raise KeyError('''{0} export format + not implemented: {1}'''.format(evtformat, e)) def getComp(self): """ @@ -279,12 +305,12 @@ class Data(object): def setEvtData(self, event): self.evtdata = event - def applyEVTData(self, data, type='pick', authority_id='rub'): + def applyEVTData(self, data, typ='pick', authority_id='rub'): """ :param data: - :param type: + :param typ: :param authority_id: :raise OverwriteError: """ @@ -326,19 +352,27 @@ class Data(object): information on the event to the actual data :param event: """ - if not self.isNew(): + if self.isNew(): self.setEvtData(event) else: # prevent overwriting original pick information - picks = copy.deepcopy(self.get_evt_data().picks) + event_old = self.get_evt_data() + print(event_old.resource_id, event.resource_id) + if not event_old.resource_id == event.resource_id: + print("WARNING: Missmatch in event resource id's: {} and {}".format( + event_old.resource_id, + event.resource_id)) + picks = copy.deepcopy(event_old.picks) event = merge_picks(event, picks) # apply event information from location - self.get_evt_data().update(event) + event_old.update(event) applydata = {'pick': applyPicks, 'event': applyEvent} - applydata[type](data) + applydata[typ](data) + self._new = False + class GenericDataStructure(object): diff --git a/pylot/core/io/default_parameters.py b/pylot/core/io/default_parameters.py index f304c8e0..35a3a619 100644 --- a/pylot/core/io/default_parameters.py +++ b/pylot/core/io/default_parameters.py @@ -14,7 +14,7 @@ defaults = {'rootpath': {'type': str, 'value': ''}, 'eventID': {'type': str, - 'tooltip': 'event ID for single event processing', + 'tooltip': 'event ID for single event processing (* for all events found in database)', 'value': ''}, 'extent': {'type': str, @@ -275,7 +275,17 @@ defaults = {'rootpath': {'type': str, 'wdttolerance': {'type': float, 'tooltip': 'maximum allowed deviation from Wadati-diagram', - 'value': 1.0} + 'value': 1.0}, + + 'WAscaling': {'type': (float, float, float), + 'tooltip': 'Scaling relation (log(Ao)+Alog(r)+Br+C) of Wood-Anderson amplitude Ao [nm] \ + If zeros are set, original Richter magnitude is calculated!', + 'value': (0., 0., 0.)}, + + 'magscaling': {'type': (float, float), + 'tooltip': 'Scaling relation for derived local magnitude [a*Ml+b]. \ + If zeros are set, no scaling of network magnitude is applied!', + 'value': (0., 0.)} } settings_main={ @@ -298,6 +308,9 @@ settings_main={ 'vp', 'rho', 'Qp'], + 'localmag':[ + 'WAscaling', + 'magscaling'], 'pick':[ 'extent', 'pstart', diff --git a/pylot/core/io/inputs.py b/pylot/core/io/inputs.py index f70b8caf..9aa9b5d2 100644 --- a/pylot/core/io/inputs.py +++ b/pylot/core/io/inputs.py @@ -4,9 +4,9 @@ from pylot.core.util.errors import ParameterError import default_parameters -class AutoPickParameter(object): +class PylotParameter(object): ''' - AutoPickParameters is a parameter type object capable to read and/or write + PylotParameter is a parameter type object capable to read and/or write parameter ASCII. :param fn str: Filename of the input file @@ -78,7 +78,7 @@ class AutoPickParameter(object): # String representation of the object def __repr__(self): - return "AutoPickParameter('%s')" % self.__filename + return "PylotParameter('%s')" % self.__filename # Boolean test def __nonzero__(self): @@ -140,7 +140,8 @@ class AutoPickParameter(object): all_names += self.get_main_para_names()['dirs'] all_names += self.get_main_para_names()['nlloc'] all_names += self.get_main_para_names()['smoment'] - all_names += self.get_main_para_names()['pick'] + all_names += self.get_main_para_names()['localmag'] + all_names += self.get_main_para_names()['pick'] all_names += self.get_special_para_names()['z'] all_names += self.get_special_para_names()['h'] all_names += self.get_special_para_names()['fm'] @@ -220,22 +221,23 @@ class AutoPickParameter(object): # for key, value in self.iteritems(): # lines.append('{key}\t{value}\n'.format(key=key, value=value)) # fid_out.writelines(lines) - header = ('%This is a parameter input file for PyLoT/autoPyLoT.\n'+ '%All main and special settings regarding data handling\n'+ '%and picking are to be set here!\n'+ - '%Parameters are optimized for local data sets!\n') - seperator = '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n' + '%Parameters are optimized for %{} data sets!\n'.format(self.get_main_para_names()['pick'][0])) + separator = '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n' fid_out.write(header) self.write_section(fid_out, self.get_main_para_names()['dirs'], - 'main settings', seperator) + 'main settings', separator) self.write_section(fid_out, self.get_main_para_names()['nlloc'], - 'NLLoc settings', seperator) + 'NLLoc settings', separator) self.write_section(fid_out, self.get_main_para_names()['smoment'], - 'parameters for seismic moment estimation', seperator) + 'parameters for seismic moment estimation', separator) + self.write_section(fid_out, self.get_main_para_names()['localmag'], + 'settings local magnitude', separator) self.write_section(fid_out, self.get_main_para_names()['pick'], - 'common settings picker', seperator) + 'common settings picker', separator) fid_out.write(('#special settings for calculating CF#\n'+ '%!!Edit the following only if you know what you are doing!!%\n')) self.write_section(fid_out, self.get_special_para_names()['z'], @@ -247,9 +249,9 @@ class AutoPickParameter(object): self.write_section(fid_out, self.get_special_para_names()['quality'], 'quality assessment', None) - def write_section(self, fid, names, title, seperator): - if seperator: - fid.write(seperator) + def write_section(self, fid, names, title, separator): + if separator: + fid.write(separator) fid.write('#{}#\n'.format(title)) l_val = 50 l_name = 15 diff --git a/pylot/core/io/phases.py b/pylot/core/io/phases.py index 54f30695..f737e630 100644 --- a/pylot/core/io/phases.py +++ b/pylot/core/io/phases.py @@ -7,8 +7,9 @@ import os import scipy.io as sio import warnings from obspy.core import UTCDateTime +from obspy.core.util import AttribDict -from pylot.core.io.inputs import AutoPickParameter +from pylot.core.io.inputs import PylotParameter from pylot.core.io.location import create_arrival, create_event, \ create_magnitude, create_origin, create_pick from pylot.core.pick.utils import select_for_phase @@ -116,7 +117,7 @@ def picksdict_from_pilot(fn): picks = dict() phases_pilot = sio.loadmat(fn) stations = stations_from_pilot(phases_pilot['stat']) - params = AutoPickParameter(TIMEERROR_DEFAULTS) + params = PylotParameter(TIMEERROR_DEFAULTS) timeerrors = dict(P=params.get('timeerrorsP'), S=params.get('timeerrorsS')) for n, station in enumerate(stations): @@ -195,6 +196,7 @@ def picksdict_from_picks(evt): phase = {} station = pick.waveform_id.station_code channel = pick.waveform_id.channel_code + network = pick.waveform_id.network_code try: onsets = picks[station] except KeyError as e: @@ -215,6 +217,7 @@ def picksdict_from_picks(evt): phase['lpp'] = lpp phase['spe'] = spe phase['channel'] = channel + phase['network'] = network try: picker = str(pick.method_id) if picker.startswith('smi:local/'): @@ -231,7 +234,7 @@ def picks_from_picksdict(picks, creation_info=None): picks_list = list() for station, onsets in picks.items(): for label, phase in onsets.items(): - if not isinstance(phase, dict): + if not isinstance(phase, dict) and not isinstance(phase, AttribDict): continue onset = phase['mpp'] try: @@ -295,14 +298,14 @@ def reassess_pilot_db(root_dir, db_dir, out_dir=None, fn_param=None, verbosity=0 def reassess_pilot_event(root_dir, db_dir, event_id, out_dir=None, fn_param=None, verbosity=0): from obspy import read - from pylot.core.io.inputs import AutoPickParameter + from pylot.core.io.inputs import PylotParameter from pylot.core.pick.utils import earllatepicker if fn_param is None: import pylot.core.util.defaults as defaults fn_param = defaults.AUTOMATIC_DEFAULTS - default = AutoPickParameter(fn_param, verbosity) + default = PylotParameter(fn_param, verbosity) search_base = os.path.join(root_dir, db_dir, event_id) phases_file = glob.glob(os.path.join(search_base, 'PHASES.mat')) @@ -382,12 +385,12 @@ def reassess_pilot_event(root_dir, db_dir, event_id, out_dir=None, fn_param=None evt.picks = picks_from_picksdict(picks_dict) # write phase information to file if not out_dir: - fnout_prefix = os.path.join(root_dir, db_dir, event_id, '{0}.'.format(event_id)) + fnout_prefix = os.path.join(root_dir, db_dir, event_id, 'PyLoT_{0}.'.format(event_id)) else: out_dir = os.path.join(out_dir, db_dir) if not os.path.isdir(out_dir): os.makedirs(out_dir) - fnout_prefix = os.path.join(out_dir, '{0}.'.format(event_id)) + fnout_prefix = os.path.join(out_dir, 'PyLoT_{0}.'.format(event_id)) evt.write(fnout_prefix + 'xml', format='QUAKEML') #evt.write(fnout_prefix + 'cnv', format='VELEST') @@ -835,9 +838,10 @@ def merge_picks(event, picks): err = pick.time_errors phase = pick.phase_hint station = pick.waveform_id.station_code + network = pick.waveform_id.network_code method = pick.method_id for p in event.picks: if p.waveform_id.station_code == station and p.phase_hint == phase: - p.time, p.time_errors, p.method_id = time, err, method - del time, err, phase, station, method + p.time, p.time_errors, p.waveform_id.network_code, p.method_id = time, err, network, method + del time, err, phase, station, network, method return event diff --git a/pylot/core/pick/autopick.py b/pylot/core/pick/autopick.py index 8f43444b..a8437763 100644 --- a/pylot/core/pick/autopick.py +++ b/pylot/core/pick/autopick.py @@ -11,7 +11,7 @@ function conglomerate utils. import matplotlib.pyplot as plt import numpy as np -from pylot.core.io.inputs import AutoPickParameter +from pylot.core.io.inputs import PylotParameter from pylot.core.pick.picker import AICPicker, PragPicker from pylot.core.pick.charfuns import CharacteristicFunction from pylot.core.pick.charfuns import HOScf, AICcf, ARZcf, ARHcf, AR3Ccf @@ -81,7 +81,7 @@ def autopickstation(wfstream, pickparam, verbose=False, iplot=0, fig_dict=None): :param pickparam: container of picking parameters from input file, usually autoPyLoT.in - :type pickparam: AutoPickParameter + :type pickparam: PylotParameter :param verbose: :type verbose: bool diff --git a/pylot/core/pick/picker.py b/pylot/core/pick/picker.py index c0b0eedb..ebf737a5 100644 --- a/pylot/core/pick/picker.py +++ b/pylot/core/pick/picker.py @@ -212,6 +212,14 @@ class AICPicker(AutoPicker): self.Data[0].data = self.Data[0].data * 1000000 # get signal window isignal = getsignalwin(self.Tcf, self.Pick, self.TSNR[2]) + ii = min([isignal[len(isignal)-1], len(self.Tcf)]) + isignal = isignal[0:ii] + try: + aic[isignal] + except IndexError as e: + msg = "Time series out of bounds! {}".format(e) + print(msg) + return # calculate SNR from CF self.SNR = max(abs(aic[isignal] - np.mean(aic[isignal]))) / \ max(abs(aic[inoise] - np.mean(aic[inoise]))) @@ -223,28 +231,29 @@ class AICPicker(AutoPicker): # find maximum within slope determination window # 'cause slope should be calculated up to first local minimum only! imax = np.argmax(self.Data[0].data[islope]) - if imax == 0: - print('AICPicker: Maximum for slope determination right at the beginning of the window!') - print('Choose longer slope determination window!') - if self.iplot > 1: - if not self.fig: - fig = plt.figure() #self.iplot) ### WHY? MP MP - else: - fig = self.fig - ax = fig.add_subplot(111) - x = self.Data[0].data - ax.plot(self.Tcf, x / max(x), 'k', legend='(HOS-/AR-) Data') - ax.plot(self.Tcf, aicsmooth / max(aicsmooth), 'r', legend='Smoothed AIC-CF') - ax.legend() - ax.set_xlabel('Time [s] since %s' % self.Data[0].stats.starttime) - ax.set_yticks([]) - ax.set_title(self.Data[0].stats.station) - return - iislope = islope[0][0:imax] - if len(iislope) <= 3: + if len(iislope) <= 2: # calculate slope from initial onset to maximum of AIC function + print("AICPicker: Not enough data samples left for slope calculation!") + print("Calculating slope from initial onset to maximum of AIC function ...") imax = np.argmax(aicsmooth[islope]) + if imax == 0: + print("AICPicker: Maximum for slope determination right at the beginning of the window!") + print("Choose longer slope determination window!") + if self.iplot > 1: + if not self.fig: + fig = plt.figure() #self.iplot) ### WHY? MP MP + else: + fig = self.fig + ax = fig.add_subplot(111) + x = self.Data[0].data + ax.plot(self.Tcf, x / max(x), 'k', label='(HOS-/AR-) Data') + ax.plot(self.Tcf, aicsmooth / max(aicsmooth), 'r', label='Smoothed AIC-CF') + ax.legend() + ax.set_xlabel('Time [s] since %s' % self.Data[0].stats.starttime) + ax.set_yticks([]) + ax.set_title(self.Data[0].stats.station) + return iislope = islope[0][0:imax] dataslope = self.Data[0].data[iislope] # calculate slope as polynomal fit of order 1 diff --git a/pylot/core/util/event.py b/pylot/core/util/event.py new file mode 100644 index 00000000..1db5fac7 --- /dev/null +++ b/pylot/core/util/event.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os + +from obspy import UTCDateTime +from obspy.core.event import Event as ObsPyEvent +from obspy.core.event import Origin, Magnitude, ResourceIdentifier + +from pylot.core.io.phases import picks_from_picksdict + + +class Event(ObsPyEvent): + ''' + Pickable class derived from ~obspy.core.event.Event containing information on a single event. + ''' + def __init__(self, path): + self.pylot_id = path.split('/')[-1] + # initialize super class + super(Event, self).__init__(resource_id=ResourceIdentifier('smi:local/'+self.pylot_id)) + self.path = path + self.database = path.split('/')[-2] + self.datapath = path.split('/')[-3] + self.rootpath = '/' + os.path.join(*path.split('/')[:-3]) + self.pylot_autopicks = {} + self.pylot_picks = {} + self.notes = '' + self._testEvent = False + self._refEvent = False + self.get_notes() + + def get_notes_path(self): + notesfile = os.path.join(self.path, 'notes.txt') + return notesfile + + def get_notes(self): + notesfile = self.get_notes_path() + if os.path.isfile(notesfile): + with open(notesfile) as infile: + path = str(infile.readlines()[0].split('\n')[0]) + text = '[eventInfo: '+path+']' + self.addNotes(text) + try: + datetime = UTCDateTime(path.split('/')[-1]) + origin = Origin(resource_id=self.resource_id, time=datetime, latitude=0, longitude=0, depth=0) + self.origins.append(origin) + except: + pass + + def addNotes(self, notes): + self.notes = str(notes) + + def clearNotes(self): + self.notes = None + + def isRefEvent(self): + return self._refEvent + + def isTestEvent(self): + return self._testEvent + + def setRefEvent(self, bool): + self._refEvent = bool + if bool: self._testEvent = False + + def setTestEvent(self, bool): + self._testEvent = bool + if bool: self._refEvent = False + + def addPicks(self, picks): + ''' + add pylot picks and overwrite existing + ''' + for station in picks: + self.pylot_picks[station] = picks[station] + #add ObsPy picks + self.picks = picks_from_picksdict(self.pylot_picks) + + def addAutopicks(self, autopicks): + for station in autopicks: + self.pylot_autopicks[station] = autopicks[station] + + def setPick(self, station, pick): + if pick: + self.pylot_picks[station] = pick + self.picks = picks_from_picksdict(self.pylot_picks) + + def setPicks(self, picks): + ''' + set pylot picks and delete and overwrite all existing + ''' + self.pylot_picks = picks + self.picks = picks_from_picksdict(self.pylot_picks) + + def getPick(self, station): + if station in self.pylot_picks.keys(): + return self.pylot_picks[station] + + def getPicks(self): + return self.pylot_picks + + def setAutopick(self, station, autopick): + if autopick: + self.pylot_autopicks[station] = autopick + + def setAutopicks(self, autopicks): + self.pylot_autopicks = autopicks + + def getAutopick(self, station): + if station in self.pylot_autopicks.keys(): + return self.pylot_autopicks[station] + + def getAutopicks(self): + return self.pylot_autopicks + + def save(self, filename): + ''' + Save PyLoT Event to a file. + Can be loaded by using event.load(filename). + ''' + try: + import cPickle + except ImportError: + import _pickle as cPickle + + try: + outfile = open(filename, 'wb') + cPickle.dump(self, outfile, -1) + except Exception as e: + print('Could not pickle PyLoT event. Reason: {}'.format(e)) + + @staticmethod + def load(filename): + ''' + Load project from filename. + ''' + try: + import cPickle + except ImportError: + import _pickle as cPickle + infile = open(filename, 'rb') + event = cPickle.load(infile) + print('Loaded %s' % filename) + return event diff --git a/pylot/core/util/map_projection.py b/pylot/core/util/map_projection.py index 64fd0444..8a577c83 100644 --- a/pylot/core/util/map_projection.py +++ b/pylot/core/util/map_projection.py @@ -22,6 +22,7 @@ class map_projection(QtGui.QWidget): self.parser = parent.metadata[1] self.picks = None self.picks_dict = None + self.eventLoc = None self.figure = figure self.init_graphics() self.init_stations() @@ -244,6 +245,10 @@ class map_projection(QtGui.QWidget): self.sc = self.basemap.scatter(self.lon, self.lat, s=50, facecolor='none', latlon=True, zorder=10, picker=True, edgecolor='m', label='Not Picked') self.cid = self.canvas.mpl_connect('pick_event', self.onpick) + if self.eventLoc: + lat, lon = self.eventLoc + self.sc_event = self.basemap.scatter(lon, lat, s=100, facecolor='red', + latlon=True, zorder=11, label='Event (might be outside map region)') def scatter_picked_stations(self): lon = self.lon_no_nan @@ -274,8 +279,7 @@ class map_projection(QtGui.QWidget): def refresh_drawings(self, picks=None): self.picks_dict = picks - self.remove_drawings() - self.draw_everything() + self._refresh_drawings() def _refresh_drawings(self): self.remove_drawings() @@ -303,6 +307,9 @@ class map_projection(QtGui.QWidget): if hasattr(self, 'sc_picked'): self.sc_picked.remove() del(self.sc_picked) + if hasattr(self, 'sc_event'): + self.sc_event.remove() + del(self.sc_event) if hasattr(self, 'cbar'): self.cbar.remove() del(self.cbar) diff --git a/pylot/core/util/thread.py b/pylot/core/util/thread.py index 609cf9df..5928ace3 100644 --- a/pylot/core/util/thread.py +++ b/pylot/core/util/thread.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -import sys +import sys, os from PySide.QtCore import QThread, Signal, Qt from PySide.QtGui import QDialog, QProgressBar, QLabel, QHBoxLayout @@ -64,7 +64,9 @@ class Thread(QThread): except Exception as e: self._executed = False self._executedError = e - print(e) + exc_type, exc_obj, exc_tb = sys.exc_info() + fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + print('Exception: {}, file: {}, line: {}'.format(exc_type, fname, exc_tb.tb_lineno)) sys.stdout = sys.__stdout__ def __del__(self): diff --git a/pylot/core/util/utils.py b/pylot/core/util/utils.py index 905073e1..586a54c3 100644 --- a/pylot/core/util/utils.py +++ b/pylot/core/util/utils.py @@ -10,9 +10,9 @@ import re import warnings import subprocess from obspy import UTCDateTime, read -from pylot.core.io.inputs import AutoPickParameter - +from pylot.core.io.inputs import PylotParameter + def _pickle_method(m): if m.im_self is None: return getattr, (m.im_class, m.im_func.func_name) @@ -497,7 +497,7 @@ def which(program, infile=None): bpath = os.path.join(os.path.expanduser('~'), '.pylot', infile) if os.path.exists(bpath): - nllocpath = ":" + AutoPickParameter(bpath).get('nllocbin') + nllocpath = ":" + PylotParameter(bpath).get('nllocbin') os.environ['PATH'] += nllocpath except ImportError as e: print(e.message) diff --git a/pylot/core/util/widgets.py b/pylot/core/util/widgets.py index f28d2679..6eda72b8 100644 --- a/pylot/core/util/widgets.py +++ b/pylot/core/util/widgets.py @@ -12,6 +12,11 @@ import copy import datetime import numpy as np +try: + import pyqtgraph as pg +except: + pg = None + from matplotlib.figure import Figure from pylot.core.util.utils import find_horizontals @@ -31,7 +36,7 @@ from PySide.QtCore import QSettings, Qt, QUrl, Signal, Slot from PySide.QtWebKit import QWebView from obspy import Stream, UTCDateTime from pylot.core.io.data import Data -from pylot.core.io.inputs import FilterOptions, AutoPickParameter +from pylot.core.io.inputs import FilterOptions, PylotParameter from pylot.core.pick.utils import getSNR, earllatepicker, getnoisewin, \ getResolutionWindow from pylot.core.pick.compare import Comparison @@ -42,6 +47,12 @@ from autoPyLoT import autoPyLoT from pylot.core.util.thread import Thread import icons_rc +if pg: + pg.setConfigOption('background', 'w') + pg.setConfigOption('foreground', 'k') + pg.setConfigOptions(antialias=True) + #pg.setConfigOption('leftButtonPan', False) + def getDataType(parent): type = QInputDialog().getItem(parent, "Select phases type", "Type:", ["manual", "automatic"]) @@ -393,6 +404,174 @@ class PlotWidget(FigureCanvas): return self._parent +class WaveformWidgetPG(QtGui.QWidget): + def __init__(self, parent=None, xlabel='x', ylabel='y', title='Title'): + QtGui.QWidget.__init__(self, parent)#, 1) + self.setParent(parent) + self._parent = parent + # attribute plotdict is a dictionary connecting position and a name + self.plotdict = dict() + # create plot + self.main_layout = QtGui.QVBoxLayout() + self.label = QtGui.QLabel() + self.setLayout(self.main_layout) + self.plotWidget = pg.PlotWidget(title=title, autoDownsample=True) + self.main_layout.addWidget(self.plotWidget) + self.main_layout.addWidget(self.label) + self.plotWidget.showGrid(x=False, y=True, alpha=0.2) + self.plotWidget.hideAxis('bottom') + self.plotWidget.hideAxis('left') + self.reinitMoveProxy() + self._proxy = pg.SignalProxy(self.plotWidget.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved) + + def reinitMoveProxy(self): + self.vLine = pg.InfiniteLine(angle=90, movable=False) + self.hLine = pg.InfiniteLine(angle=0, movable=False) + self.plotWidget.addItem(self.vLine, ignoreBounds=True) + self.plotWidget.addItem(self.hLine, ignoreBounds=True) + + def mouseMoved(self, evt): + pos = evt[0] ## using signal proxy turns original arguments into a tuple + if self.plotWidget.sceneBoundingRect().contains(pos): + mousePoint = self.plotWidget.getPlotItem().vb.mapSceneToView(pos) + x, y, = (mousePoint.x(), mousePoint.y()) + #if x > 0:# and index < len(data1): + wfID = self._parent.getWFID(y) + station = self._parent.getStationName(wfID) + if self._parent.get_current_event(): + self.label.setText("station = {}, t = {} [s]".format(station, x)) + self.vLine.setPos(mousePoint.x()) + self.hLine.setPos(mousePoint.y()) + + def getPlotDict(self): + return self.plotdict + + def setPlotDict(self, key, value): + self.plotdict[key] = value + + def clearPlotDict(self): + self.plotdict = dict() + + def getParent(self): + return self._parent + + def setParent(self, parent): + self._parent = parent + + def plotWFData(self, wfdata, title=None, zoomx=None, zoomy=None, + noiselevel=None, scaleddata=False, mapping=True, + component='*', nth_sample=1, iniPick=None): + self.title = title + self.clearPlotDict() + wfstart, wfend = full_range(wfdata) + nmax = 0 + + settings = QSettings() + compclass = settings.value('compclass') + if not compclass: + print('Warning: No settings for channel components found. Using default') + compclass = SetChannelComponents() + + if not component == '*': + alter_comp = compclass.getCompPosition(component) + #alter_comp = str(alter_comp[0]) + + st_select = wfdata.select(component=component) + st_select += wfdata.select(component=alter_comp) + else: + st_select = wfdata + + # list containing tuples of network, station, channel (for sorting) + nsc = [] + for trace in st_select: + nsc.append((trace.stats.network, trace.stats.station, trace.stats.channel)) + nsc.sort() + nsc.reverse() + plots = [] + + try: + self.plotWidget.getPlotItem().vb.setLimits(xMin=float(0), + xMax=float(wfend-wfstart), + yMin=-0.5, + yMax=len(nsc)+0.5) + except: + print('Warning: Could not set zoom limits') + + for n, (network, station, channel) in enumerate(nsc): + st = st_select.select(network=network, station=station, channel=channel) + trace = st[0] + if mapping: + comp = channel[-1] + n = compclass.getPlotPosition(str(comp)) + #n = n[0] + if n > nmax: + nmax = n + msg = 'plotting %s channel of station %s' % (channel, station) + print(msg) + stime = trace.stats.starttime - wfstart + time_ax = prepTimeAxis(stime, trace) + if time_ax is not None: + if not scaleddata: + trace.detrend('constant') + trace.normalize(np.max(np.abs(trace.data)) * 2) + times = [time for index, time in enumerate(time_ax) if not index%nth_sample] + data = [datum + n for index, datum in enumerate(trace.data) if not index%nth_sample] + plots.append((times, data)) + self.setPlotDict(n, (station, channel, network)) + self.xlabel = 'seconds since {0}'.format(wfstart) + self.ylabel = '' + self.setXLims([0, wfend - wfstart]) + self.setYLims([-0.5, nmax + 0.5]) + return plots + + # def getAxes(self): + # return self.axes + + # def getXLims(self): + # return self.getAxes().get_xlim() + + # def getYLims(self): + # return self.getAxes().get_ylim() + + def setXLims(self, lims): + vb = self.plotWidget.getPlotItem().getViewBox() + vb.setXRange(float(lims[0]), float(lims[1]), padding=0) + + def setYLims(self, lims): + vb = self.plotWidget.getPlotItem().getViewBox() + vb.setYRange(float(lims[0]), float(lims[1]), padding=0) + + def setYTickLabels(self, pos, labels): + ticks = zip(pos, labels) + minorTicks = [(0, 0) for item in labels] + # leftAx.tickLength = 5 + # leftAx.orientation = 'right' + self.getAxItem('left').setTicks([ticks, minorTicks]) + + def updateXLabel(self, text): + self.getAxItem('bottom').setLabel(text) + self.draw() + + def updateYLabel(self, text): + self.getAxItem('left').setLabel(text) + self.draw() + + def getAxItem(self, position): + return self.plotWidget.getPlotItem().axes[position]['item'] + + def updateTitle(self, text): + self.plotWidget.getPlotItem().setTitle(text) + self.draw() + + def updateWidget(self):#, xlabel, ylabel, title): + self.updateXLabel(self.xlabel) + self.updateYLabel(self.ylabel) + self.updateTitle(self.title) + + def draw(self): + pass + + class WaveformWidget(FigureCanvas): def __init__(self, parent=None, xlabel='x', ylabel='y', title='Title'): @@ -446,18 +625,20 @@ class WaveformWidget(FigureCanvas): alter_comp = compclass.getCompPosition(component) #alter_comp = str(alter_comp[0]) - wfdata = wfdata.select(component=component) - wfdata += wfdata.select(component=alter_comp) + st_select = wfdata.select(component=component) + st_select += wfdata.select(component=alter_comp) + else: + st_select = wfdata # list containing tuples of network, station, channel (for sorting) nsc = [] - for trace in wfdata: + for trace in st_select: nsc.append((trace.stats.network, trace.stats.station, trace.stats.channel)) nsc.sort() nsc.reverse() for n, (network, station, channel) in enumerate(nsc): - st = wfdata.select(network=network, station=station, channel=channel) + st = st_select.select(network=network, station=station, channel=channel) trace = st[0] if mapping: comp = channel[-1] @@ -571,6 +752,7 @@ class PickDlg(QDialog): self._init_autopicks = {} self.filteroptions = FILTERDEFAULTS self.pick_block = False + self.nextStation = QtGui.QCheckBox('Continue with next station.') # initialize panning attributes self.press = None @@ -664,17 +846,21 @@ class PickDlg(QDialog): self.s_button = QPushButton('S', self) self.p_button.setCheckable(True) self.s_button.setCheckable(True) - # button shortcuts (1 for P-button, 2 for S-button) - self.p_button.setShortcut(QKeySequence('1')) - self.s_button.setShortcut(QKeySequence('2')) # set button tooltips self.p_button.setToolTip('Hotkey: "1"') self.s_button.setToolTip('Hotkey: "2"') - + # create accept/reject button self.accept_button = QPushButton('&Accept Picks') self.reject_button = QPushButton('&Reject Picks') self.disable_ar_buttons() + + # add hotkeys + self._shortcut_space = QtGui.QShortcut(QtGui.QKeySequence(' '), self) + self._shortcut_space.activated.connect(self.accept_button.clicked) + # button shortcuts (1 for P-button, 2 for S-button) + self.p_button.setShortcut(QKeySequence('1')) + self.s_button.setShortcut(QKeySequence('2')) # layout the outermost appearance of the Pick Dialog _outerlayout = QVBoxLayout() @@ -691,7 +877,9 @@ class PickDlg(QDialog): _dialtoolbar.addAction(self.resetPicksAction) if self._embedded: _dialtoolbar.addWidget(self.accept_button) - _dialtoolbar.addWidget(self.reject_button) + _dialtoolbar.addWidget(self.reject_button) + else: + _dialtoolbar.addWidget(self.nextStation) # layout the innermost widget _innerlayout = QVBoxLayout() @@ -1429,7 +1617,6 @@ class TuneAutopicker(QWidget): self.eventBox = self.parent.createEventBox() self.eventBox.setMaxVisibleItems(20) self.fill_eventbox() - self.eventBox.setCurrentIndex(0) self.trace_layout.addWidget(self.eventBox) def init_stationlist(self): @@ -1459,7 +1646,7 @@ class TuneAutopicker(QWidget): model = self.stationBox.model() for network, station in stations: item = QtGui.QStandardItem(network+'.'+station) - if station in self.get_current_event().picks: + if station in self.get_current_event().pylot_picks: item.setBackground(self.parent._colors['ref']) model.appendRow(item) @@ -1475,7 +1662,7 @@ class TuneAutopicker(QWidget): self.stb_names = ['aicARHfig', 'refSpick', 'el_S1pick', 'el_S2pick'] def add_parameters(self): - self.paraBox = AutoPickParaBox(self.parameter) + self.paraBox = PylotParaBox(self.parameter) self.paraBox.set_tune_mode(True) self.update_eventID() self.parameter_layout.addWidget(self.paraBox) @@ -1500,8 +1687,8 @@ class TuneAutopicker(QWidget): self.listWidget.scrollToBottom() def get_current_event(self): - index = self.eventBox.currentIndex() - return self.eventBox.itemData(index) + path = self.eventBox.currentText() + return self.parent.project.getEventFromPath(path) def get_current_event_name(self): return self.eventBox.currentText().split('/')[-1] @@ -1511,13 +1698,13 @@ class TuneAutopicker(QWidget): def get_current_event_picks(self, station): event = self.get_current_event() - if station in event.picks.keys(): - return event.picks[station] + if station in event.pylot_picks.keys(): + return event.pylot_picks[station] def get_current_event_autopicks(self, station): event = self.get_current_event() - if event.autopicks: - return event.autopicks[station] + if event.pylot_autopicks: + return event.pylot_autopicks[station] def get_current_station(self): return str(self.stationBox.currentText()).split('.')[-1] @@ -1531,6 +1718,9 @@ class TuneAutopicker(QWidget): return widget def gen_pick_dlg(self): + if not self.get_current_event(): + self.pickDlg = None + return station = self.get_current_station() data = self.data.getWFData() pickDlg = PickDlg(self, data=data.select(station=station), @@ -1541,7 +1731,6 @@ class TuneAutopicker(QWidget): pickDlg.update_picks.connect(self.picks_from_pickdlg) pickDlg.update_picks.connect(self.fill_eventbox) pickDlg.update_picks.connect(self.fill_stationbox) - pickDlg.update_picks.connect(self.parent.drawPicks) pickDlg.update_picks.connect(lambda: self.parent.setDirty(True)) pickDlg.update_picks.connect(self.parent.enableSaveManualPicksAction) self.pickDlg = QtGui.QWidget() @@ -1551,7 +1740,15 @@ class TuneAutopicker(QWidget): def picks_from_pickdlg(self, picks=None): station = self.get_current_station() + replot = self.parent.addPicks(station, picks) self.get_current_event().setPick(station, picks) + if self.get_current_event() == self.parent.get_current_event(): + if replot: + self.parent.plotWaveformDataThread() + self.parent.drawPicks() + else: + self.parent.drawPicks(station) + self.parent.draw() def plot_manual_picks_to_figs(self): picks = self.get_current_event_picks(self.get_current_station()) @@ -1619,7 +1816,7 @@ class TuneAutopicker(QWidget): id1 = self.figure_tabs.insertTab(1, self.overview, 'Overview') id2 = self.figure_tabs.insertTab(2, self.p_tabs, 'P') id3 = self.figure_tabs.insertTab(3, self.s_tabs, 'S') - if picked: + if picked and self.get_current_event(): self.fill_p_tabs(canvas_dict) self.fill_s_tabs(canvas_dict) self.toggle_autopickTabs(bool(self.fig_dict['mainFig'].axes)) @@ -1658,7 +1855,30 @@ class TuneAutopicker(QWidget): self.init_tab_names() def fill_eventbox(self): + project = self.parent.project + if not project: + return + # update own list self.parent.fill_eventbox(eventBox=self.eventBox, select_events='ref') + index_start = self.parent.eventBox.currentIndex() + index = index_start + if index == -1: + index += 1 + nevents = self.eventBox.model().rowCount() + path = self.eventBox.itemText(index) + if project.getEventFromPath(path).isTestEvent(): + for index in range(nevents): + path = self.eventBox.itemText(index) + if project.getEventFromPath(index): + if not project.getEventFromPath(index).isTestEvent(): + break + #in case all events are marked as test events and last event is reached + if index == nevents - 1: + index = -1 + self.eventBox.setCurrentIndex(index) + if not index == index_start: + self.eventBox.activated.emit(index) + # update parent self.parent.fill_eventbox() def update_eventID(self): @@ -1698,8 +1918,8 @@ class TuneAutopicker(QWidget): self._warn('Could not execute picker:\n{}'.format( self.ap_thread._executedError)) return - self.picks = self.ap_thread.data - if not self.picks: + self.pylot_picks = self.ap_thread.data + if not self.pylot_picks: self._warn('No picks found. See terminal output.') return #renew tabs @@ -1729,7 +1949,8 @@ class TuneAutopicker(QWidget): def clear_all(self): if hasattr(self, 'pickDlg'): - self.pickDlg.setParent(None) + if self.pickDlg: + self.pickDlg.setParent(None) del(self.pickDlg) if hasattr(self, 'overview'): self.overview.setParent(None) @@ -1754,13 +1975,13 @@ class TuneAutopicker(QWidget): self.qmb.show() -class AutoPickParaBox(QtGui.QWidget): +class PylotParaBox(QtGui.QWidget): def __init__(self, parameter, parent=None): ''' Generate Widget containing parameters for automatic picking algorithm. :param: parameter - :type: AutoPickParameter (object) + :type: PylotParameter (object) ''' QtGui.QWidget.__init__(self, parent) @@ -1773,13 +1994,15 @@ class AutoPickParaBox(QtGui.QWidget): self.labels = {} self.boxes = {} self.groupboxes = {} + self._exclusive_widgets = [] self._init_sublayouts() self.setLayout(self.layout) self.add_main_parameters_tab() self.add_special_pick_parameters_tab() self.params_to_gui() self._toggle_advanced_settings() - self.resize(720, 1280) + self.resize(720, 1280) + self.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal) def _init_sublayouts(self): self._main_layout = QtGui.QVBoxLayout() @@ -1819,7 +2042,7 @@ class AutoPickParaBox(QtGui.QWidget): def _create_advanced_cb(self): self._advanced_cb = QtGui.QCheckBox('Enable Advanced Settings') - self._advanced_layout.addWidget(self._advanced_cb) + self._advanced_layout.insertWidget(0, self._advanced_cb) self._advanced_cb.toggled.connect(self._toggle_advanced_settings) def _toggle_advanced_settings(self): @@ -1907,31 +2130,31 @@ class AutoPickParaBox(QtGui.QWidget): scrollA = QtGui.QScrollArea() scrollA.setWidgetResizable(True) scrollA.setWidget(widget) - widget.setLayout(layout) - self.tabs.addTab(scrollA, name) def add_main_parameters_tab(self): self.add_to_layout(self._main_layout, 'Directories', - self.parameter.get_main_para_names()['dirs']) + self.parameter.get_main_para_names()['dirs'], 0) self.add_to_layout(self._main_layout, 'NLLoc', - self.parameter.get_main_para_names()['nlloc']) + self.parameter.get_main_para_names()['nlloc'], 1) self.add_to_layout(self._main_layout, 'Seismic Moment', - self.parameter.get_main_para_names()['smoment']) + self.parameter.get_main_para_names()['smoment'], 2) + self.add_to_layout(self._main_layout, 'Local Magnitude', + self.parameter.get_main_para_names()['localmag'], 3) self.add_to_layout(self._main_layout, 'Common Settings Characteristic Function', - self.parameter.get_main_para_names()['pick']) + self.parameter.get_main_para_names()['pick'], 4) self.add_tab(self._main_layout, 'Main Settings') def add_special_pick_parameters_tab(self): self.add_to_layout(self._advanced_layout, 'Z-component', - self.parameter.get_special_para_names()['z']) + self.parameter.get_special_para_names()['z'], 1) self.add_to_layout(self._advanced_layout, 'H-components', - self.parameter.get_special_para_names()['h']) + self.parameter.get_special_para_names()['h'], 2) self.add_to_layout(self._advanced_layout, 'First-motion picker', - self.parameter.get_special_para_names()['fm']) + self.parameter.get_special_para_names()['fm'], 3) self.add_to_layout(self._advanced_layout, 'Quality assessment', - self.parameter.get_special_para_names()['quality']) + self.parameter.get_special_para_names()['quality'], 4) self.add_tab(self._advanced_layout, 'Advanced Settings') # def gen_h_seperator(self): @@ -1945,12 +2168,46 @@ class AutoPickParaBox(QtGui.QWidget): # font.setBold(True) # label.setFont(font) # return label + + def refresh(self): + for groupbox in self.groupboxes.values(): + layout = groupbox._parentLayout + position = groupbox._position + layout.insertWidget(position, groupbox) + + def get_groupbox_exclusive(self, name): + widget = QtGui.QWidget(self, 1) + layout = QtGui.QVBoxLayout() + widget.setLayout(layout) + layout.addWidget(self.groupboxes[name]) + self._exclusive_widgets.append(widget) + return widget + + def get_groupbox_dialog(self, name): + widget = self.get_groupbox_exclusive(name) + dialog = QtGui.QDialog(self.parent()) + layout = QtGui.QVBoxLayout() + dialog.setLayout(layout) + buttonbox = QtGui.QDialogButtonBox(QDialogButtonBox.Ok | + QDialogButtonBox.Cancel) + buttonbox.accepted.connect(dialog.accept) + buttonbox.accepted.connect(self.refresh) + buttonbox.accepted.connect(self.params_from_gui) + buttonbox.rejected.connect(dialog.reject) + buttonbox.rejected.connect(self.refresh) + buttonbox.rejected.connect(self.params_to_gui) + layout.addWidget(widget) + layout.addWidget(buttonbox) + self._exclusive_dialog = dialog + return dialog - def add_to_layout(self, layout, name, items): + def add_to_layout(self, layout, name, items, position): groupbox = QtGui.QGroupBox(name) + groupbox._position = position + groupbox._parentLayout = layout self.groupboxes[name] = groupbox groupbox.setLayout(self.init_boxes(items)) - layout.addWidget(groupbox) + layout.insertWidget(position, groupbox) def show_groupboxes(self): for name in self.groupboxes.keys(): @@ -1974,6 +2231,16 @@ class AutoPickParaBox(QtGui.QWidget): else: print('Groupbox {} not part of object.'.format(name)) + def show_file_buttons(self): + self.saveButton.show() + self.loadButton.show() + self.defaultsButton.show() + + def hide_file_buttons(self): + self.saveButton.hide() + self.loadButton.hide() + self.defaultsButton.hide() + def show_parameter(self, name=None): if not name: for name in self.boxes.keys(): @@ -2028,6 +2295,8 @@ class AutoPickParaBox(QtGui.QWidget): if type(box) == QtGui.QLineEdit: box.setText(str(value)) elif type(box) == QtGui.QSpinBox or type(box) == QtGui.QDoubleSpinBox: + if not value: + value = 0. box.setValue(value) elif type(box) == QtGui.QCheckBox: if value == 'True': @@ -2084,6 +2353,14 @@ class AutoPickParaBox(QtGui.QWidget): except Exception as e: self._warn('Could not restore defaults:\n{}'.format(e)) return + + def show(self): + self.refresh() + self.show_parameter() + if hasattr(self, '_exclusive_dialog'): + self._exclusive_dialog.close() + self._exclusive_widgets = [] + QtGui.QWidget.show(self) def _warn(self, message): self.qmb = QtGui.QMessageBox(QtGui.QMessageBox.Icon.Warning, @@ -2196,6 +2473,7 @@ class PropTab(QWidget): def resetValues(self, infile=None): return None + class InputsTab(PropTab): def __init__(self, parent, infile=None): @@ -2250,7 +2528,7 @@ class InputsTab(PropTab): return values def resetValues(self, infile): - para = AutoPickParameter(infile) + para = PylotParameter(infile) datstruct = para.get('datastructure') if datstruct == 'SeisComp': index = 0 @@ -2307,6 +2585,7 @@ class GraphicsTab(PropTab): def __init__(self, parent=None): super(GraphicsTab, self).__init__(parent) self.init_layout() + self.add_pg_cb() self.add_nth_sample() self.setLayout(self.main_layout) @@ -2321,15 +2600,28 @@ class GraphicsTab(PropTab): self.spinbox_nth_sample = QtGui.QSpinBox() label = QLabel('nth sample') + label.setToolTip('Plot every nth sample (to speed up plotting)') self.spinbox_nth_sample.setMinimum(1) self.spinbox_nth_sample.setMaximum(10e3) self.spinbox_nth_sample.setValue(int(nth_sample)) - label.setToolTip('Plot every nth sample (to speed up plotting)') - self.main_layout.addWidget(label, 0, 0) - self.main_layout.addWidget(self.spinbox_nth_sample, 0, 1) + self.main_layout.addWidget(label, 1, 0) + self.main_layout.addWidget(self.spinbox_nth_sample, 1, 1) + def add_pg_cb(self): + text = {True: 'Use pyqtgraphic library for plotting', + False: 'Cannot use library: pyqtgraphic not found on system'} + label = QLabel('PyQt graphic') + label.setToolTip(text[bool(pg)]) + label.setEnabled(bool(pg)) + self.checkbox_pg = QtGui.QCheckBox() + self.checkbox_pg.setEnabled(bool(pg)) + self.checkbox_pg.setChecked(bool(pg)) + self.main_layout.addWidget(label, 0, 0) + self.main_layout.addWidget(self.checkbox_pg, 0, 1) + def getValues(self): - values = {'nth_sample': self.spinbox_nth_sample.value()} + values = {'nth_sample': self.spinbox_nth_sample.value(), + 'pyqtgraphic': self.checkbox_pg.isChecked()} return values @@ -2492,7 +2784,7 @@ class LocalisationTab(PropTab): return values def resetValues(self, infile): - para = AutoPickParameter(infile) + para = PylotParameter(infile) nllocroot = para.get('nllocroot') nllocbin = para.get('nllocbin') loctool = self.locToolComboBox.setCurrentIndex(3)