#!/usr/bin/env python # -*- coding: utf-8 -*- """ PyLoT: Main program =================== PyLoT is a seismic data processing software capable of picking seismic phases (symmetric and asymmetric error assignment), exporting these to several common phase data formats and post process the data, e.g. locating events, via external localization software. Additionally PyLoT is meant as an interface to autoPyLoT which can automatically pick seismic phases, if the parameters have properly been chosen for the particular data set. Some icons are out of a free of charge icon set, which can be found here: https://www.iconfinder.com/iconsets/flavour :authors: Sebastian Wehling-Benatelli / Ludger Küperkoch / Marcel Paffrath :copyright: The PyLoT Development Team (https://ariadne.geophysik.rub.de/trac/PyLoT) :license: GNU Lesser General Public License, Version 3 (http://www.gnu.org/copyleft/lesser.html) """ import os import sys import argparse import matplotlib matplotlib.use('Qt4Agg') matplotlib.rcParams['backend.qt4'] = 'PySide' from PySide import QtGui, QtCore from PySide.QtCore import QCoreApplication, QSettings, Signal, QFile, \ QFileInfo, Qt, QSize from PySide.QtGui import QMainWindow, QInputDialog, QIcon, QFileDialog, \ QWidget, QHBoxLayout, QVBoxLayout, QStyle, QKeySequence, QLabel, QFrame, QAction, \ QDialog, QErrorMessage, QApplication, QPixmap, QMessageBox, QSplashScreen, \ QActionGroup, QListWidget, QDockWidget, QLineEdit, QListView, QAbstractItemView, \ 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 except ImportError: from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure from pylot.core.analysis.magnitude import LocalMagnitude, MomentMagnitude from pylot.core.io.data import Data from pylot.core.io.inputs import FilterOptions, PylotParameter from autoPyLoT import autoPyLoT 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, 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, 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 from pylot.core.util.version import get_git_version as _getVersionString import icons_rc locateTool = dict(nll=nll) class MainWindow(QMainWindow): __version__ = _getVersionString() closing = Signal() def __init__(self, parent=None, infile=None): super(MainWindow, self).__init__(parent) # check for default pylot.in-file if not infile: infile = os.path.join(os.path.expanduser('~'), '.pylot', 'pylot.in') print('Using default input file {}'.format(infile)) if os.path.isfile(infile)== False: infile = QFileDialog().getOpenFileName(caption='Choose PyLoT-input file') if not os.path.exists(infile[0]): QMessageBox.warning(self, "PyLoT Warning", "No PyLoT-input file declared!") sys.exit(0) self.infile = infile[0] else: self.infile = 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 self._metadata = None self._eventChanged = [False, False] self.poS_id = None self.ae_id = None self.scroll_id = None self._ctrl = False # control key pressed self._shift = False # shift key pressed # default factor for dataplot e.g. enabling/disabling scrollarea self.height_factor = 12 # default colors for ref/test event self._colors = { 'ref': QtGui.QColor(200, 210, 230, 255), 'test': QtGui.QColor(200, 230, 200, 255) } # UI has to be set up before(!) children widgets are about to show up self.createAction = createAction # read settings settings = QSettings() self.recentfiles = settings.value("data/recentEvents", []) self.dispComponent = str(settings.value("plotting/dispComponent", "Z")) # initialize event data if self.recentfiles: lastEvent = self.getLastEvent() self.data = Data(self, lastEvent) else: self.data = Data(self) self.data._new = False self.autodata = Data(self) if settings.value("user/FullName", None) is None: fulluser = QInputDialog.getText(self, "Enter Name:", "Full name") settings.setValue("user/FullName", fulluser) settings.setValue("user/Login", getLogin()) if settings.value("agency_id", None) is None: agency = QInputDialog.getText(self, "Enter authority/institution name:", "Authority") settings.setValue("agency_id", agency) self.fname = dict(manual=None, auto=None, loc=None) self.fnames = None self._stime = None structure_setting = settings.value("data/Structure", "PILOT") self.dataStructure = DATASTRUCTURE[structure_setting]() self.seismicPhase = str(settings.value("phase", "P")) if settings.value("data/dataRoot", None) is None: dirname = QFileDialog().getExistingDirectory( caption='Choose data root ...') settings.setValue("data/dataRoot", dirname) if settings.value('compclass', None) is None: settings.setValue('compclass', SetChannelComponents()) settings.sync() # setup UI self.setupUi() self.filteroptions = {} self.pylot_picks = {} self.pylot_autopicks = {} self.loc = False def setupUi(self): try: self.startTime = min( [tr.stats.starttime for tr in self.data.wfdata]) except: self.startTime = UTCDateTime() pylot_icon = QIcon() pylot_icon.addPixmap(QPixmap(':/icons/pylot.png')) self.setWindowTitle("PyLoT - do seismic processing the python way") self.setWindowIcon(pylot_icon) _widget = QWidget() self._main_layout = QVBoxLayout() # add event combo box and ref/test buttons self.eventBox = self.createEventBox() self.eventBox.setMaxVisibleItems(30) self.eventBox.setEnabled(False) self.init_ref_test_buttons() self._event_layout = QHBoxLayout() self._event_layout.addWidget(QLabel('Event: ')) self._event_layout.addWidget(self.eventBox) self._event_layout.addWidget(self.ref_event_button) self._event_layout.addWidget(self.test_event_button) self._event_layout.setStretch(1,1) #set stretch of item 1 to 1 self._main_layout.addLayout(self._event_layout) self.eventBox.activated.connect(self.refreshEvents) # add main tab widget self.tabs = QTabWidget() self._main_layout.addWidget(self.tabs) self.tabs.currentChanged.connect(self.refreshTabs) # 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() events_tab = QtGui.QWidget() # init main widgets layouts self.wf_layout = QtGui.QVBoxLayout() self.array_layout = QtGui.QVBoxLayout() self.events_layout = QtGui.QVBoxLayout() wf_tab.setLayout(self.wf_layout) array_tab.setLayout(self.array_layout) events_tab.setLayout(self.events_layout) #add tabs to main tab widget self.tabs.addTab(wf_tab, 'Waveform Plot') self.tabs.addTab(array_tab, 'Array Map') self.tabs.addTab(events_tab, 'Eventlist') self.wf_layout.addWidget(self.wf_scroll_area) self.wf_scroll_area.setWidgetResizable(True) self.init_array_tab() self.init_event_table() self.tabs.setCurrentIndex(0) quitIcon = self.style().standardIcon(QStyle.SP_MediaStop) helpIcon = self.style().standardIcon(QStyle.SP_DialogHelpButton) newIcon = self.style().standardIcon(QStyle.SP_FileIcon) newFolderIcon = self.style().standardIcon(QStyle.SP_FileDialogNewFolder) # create resource icons addIcon = QIcon() addIcon.addPixmap(QPixmap(':/icons/add.png')) saveIcon = QIcon() saveIcon.addPixmap(QPixmap(':/icons/save.png')) saveasIcon = QIcon() saveasIcon.addPixmap(QPixmap(':/icons/saveas.png')) saveProjectIcon = QIcon() saveProjectIcon.addPixmap(QPixmap(':/icons/saveproject.png')) saveProjectAsIcon = QIcon() saveProjectAsIcon.addPixmap(QPixmap(':/icons/saveprojectas.png')) openIcon = QIcon() openIcon.addPixmap(QPixmap(':/icons/openfile.png')) openProjectIcon = QIcon() openProjectIcon.addPixmap(QPixmap(':/icons/openproject.png')) openLocIcon = QIcon() openLocIcon.addPixmap(QPixmap(':/icons/openloc.png')) openEventIcon = QIcon() openEventIcon.addPixmap(QPixmap(':/icons/openpick.png')) openEventsIcon = QIcon() openEventsIcon.addPixmap(QPixmap(':/icons/openpicks.png')) saveEventsIcon = QIcon() saveEventsIcon.addPixmap(QPixmap(':/icons/savepicks.png')) self.autopicksicon_small = QIcon() self.autopicksicon_small.addPixmap(QPixmap(':/icons/autopicksicon_small.png')) self.manupicksicon_small = QIcon() self.manupicksicon_small.addPixmap(QPixmap(':/icons/manupicksicon_small.png')) loadpiloticon = QIcon() loadpiloticon.addPixmap(QPixmap(':/icons/Matlab_PILOT_icon.png')) p_icon = QIcon() p_icon.addPixmap(QPixmap(':/icons/key_P.png')) s_icon = QIcon() s_icon.addPixmap(QPixmap(':/icons/key_S.png')) print_icon = QIcon() print_icon.addPixmap(QPixmap(':/icons/printer.png')) filter_icon = QIcon() filter_icon.addPixmap(QPixmap(':/icons/filter.png')) z_icon = QIcon() z_icon.addPixmap(QPixmap(':/icons/key_Z.png')) n_icon = QIcon() n_icon.addPixmap(QPixmap(':/icons/key_N.png')) e_icon = QIcon() e_icon.addPixmap(QPixmap(':/icons/key_E.png')) autotune_icon = QIcon() autotune_icon.addPixmap(QPixmap(':/icons/tune.png')) autopylot_icon = QIcon() autopylot_icon.addPixmap(QPixmap(':/icons/autopylot_button')) locate_icon = QIcon() locate_icon.addPixmap(QPixmap(':/icons/locate_button.png')) compare_icon = QIcon() compare_icon.addPixmap(QPixmap(':/icons/compare_button.png')) self.newProjectAction = self.createAction(self, "&New project ...", self.createNewProject, QKeySequence.New, newIcon, "Create a new Project.") self.openProjectAction = self.createAction(self, "&Open project ...", self.loadProject, QKeySequence.Open, openProjectIcon, "Load project file") self.saveProjectAction = self.createAction(self, "&Save project ...", self.saveProject, QKeySequence.Save, saveProjectIcon, "Save project file") self.saveProjectAction.setEnabled(False) self.saveProjectAsAction = self.createAction(self, "Save project as ...", self.saveProjectAs, QKeySequence.SaveAs, saveProjectAsIcon, "Save project file as...") self.saveProjectAsAction.setEnabled(False) # newEventAction = self.createAction(self, "&New event ...", # self.createNewEvent, # QKeySequence.New, newIcon, # "Create a new event.") self.openmanualpicksaction = self.createAction(self, "Load event ...", self.load_data, "Ctrl+M", openEventIcon, "Load event information for " "the displayed event.") self.openmanualpicksaction.setEnabled(False) self.openmanualpicksaction.setData(None) self.openautopicksaction = self.createAction(self, "Load event information &automatically ... ", self.load_multiple_data, "Ctrl+A", openEventsIcon, "Load event data automatically " "for for all events.") self.openautopicksaction.setEnabled(False) self.openautopicksaction.setData(None) self.loadlocationaction = self.createAction(self, "Load &location ...", self.load_loc, "Ctrl+L", openLocIcon, "Load location information on " "the displayed event.") self.loadlocationaction.setEnabled(False) self.loadpilotevent = self.createAction(self, "Load PILOT &event ...", self.load_pilotevent, None, loadpiloticon, "Load PILOT event from information " "MatLab binary collections (created" " in former MatLab based version).") self.loadpilotevent.setEnabled(False) self.saveManualPicksAction = self.createAction(self, "Save &picks ...", self.saveData, "Ctrl+P", saveEventsIcon, "Save event pick data.") self.disableSaveManualPicksAction() self.addEventDataAction = self.createAction(self, "Add &events ...", self.add_events, "Ctrl+E", addIcon, "Add event data") prefsEventAction = self.createAction(self, "Preferences", self.PyLoTprefs, QKeySequence.Preferences, QIcon(None), "Edit PyLoT app preferences.") quitAction = self.createAction(self, "&Quit", QCoreApplication.instance().quit, QKeySequence.Close, quitIcon, "Close event and quit PyLoT") self.parameterAction = self.createAction(self, "Parameter", self.setParameter, None, QIcon(None), "Modify Parameter") self.filterAction = self.createAction(self, "&Filter ...", self.filterWaveformData, "Ctrl+F", filter_icon, """Toggle un-/filtered waveforms to be displayed, according to the desired seismic phase.""", True) filterEditAction = self.createAction(self, "&Filter parameter ...", self.adjustFilterOptions, "Alt+F", QIcon(None), """Adjust filter parameters.""") self.selectPAction = self.createAction(self, "&P", self.alterPhase, "Alt+P", p_icon, "Toggle P phase.", True) self.selectSAction = self.createAction(self, "&S", self.alterPhase, "Alt+S", s_icon, "Toggle S phase", True) self.compare_action = self.createAction(self, "&Compare picks...", self.comparePicks, "Alt+C", compare_icon, "Comparison of " "manual and " "automatic pick " "data.", False) self.compare_action.setEnabled(False) printAction = self.createAction(self, "&Print event ...", self.show_event_information, QKeySequence.Print, print_icon, "Print waveform section.") helpAction = self.createAction(self, "&Help ...", self.helpHelp, QKeySequence.HelpContents, helpIcon, """Show either the documentation homepage (internet connection available), or shipped documentation files.""") self.fileMenu = self.menuBar().addMenu('&File') self.fileMenuActions = (self.newProjectAction, self.addEventDataAction, self.openProjectAction, self.saveProjectAction, self.saveProjectAsAction, self.openmanualpicksaction, self.saveManualPicksAction, None, prefsEventAction, self.parameterAction, quitAction) self.fileMenu.aboutToShow.connect(self.updateFileMenu) self.updateFileMenu() self.editMenu = self.menuBar().addMenu('&Edit') editActions = (self.filterAction, filterEditAction, None, self.selectPAction, self.selectSAction, None, printAction) self.addActions(self.editMenu, editActions) self.helpMenu = self.menuBar().addMenu('&Help') helpActions = (helpAction,) self.addActions(self.helpMenu, helpActions) fileToolBar = self.addToolBar("FileTools") fileToolActions = (self.newProjectAction, self.addEventDataAction, self.openProjectAction, self.saveProjectAction, self.saveProjectAsAction, self.openmanualpicksaction, self.openautopicksaction, self.saveManualPicksAction, self.loadlocationaction, self.loadpilotevent) fileToolBar.setObjectName("FileTools") self.addActions(fileToolBar, fileToolActions) # phaseToolBar = self.addToolBar("PhaseTools") # phaseToolActions = (self.selectPAction, self.selectSAction) # phaseToolBar.setObjectName("PhaseTools") # self.addActions(phaseToolBar, phaseToolActions) # create button group for component selection componentGroup = QActionGroup(self) componentGroup.setExclusive(True) self.z_action = self.createAction(parent=componentGroup, text='Z', slot=self.plotZ, shortcut='Alt+Z', icon=z_icon, tip='Display the vertical (Z)' ' component.', checkable=True) self.z_action.setChecked(True) self.z_action.setEnabled(False) self.n_action = self.createAction(parent=componentGroup, text='N', slot=self.plotN, shortcut='Alt+N', icon=n_icon, tip='Display the north-south (N) ' 'component.', checkable=True) self.n_action.setEnabled(False) self.e_action = self.createAction(parent=componentGroup, text='E', slot=self.plotE, shortcut='Alt+E', icon=e_icon, tip='Display the east-west (E) component.', checkable=True) self.e_action.setEnabled(False) componentToolBar = self.addToolBar("ComponentSelection") componentActions = (self.z_action, self.n_action, self.e_action) componentToolBar.setObjectName("PhaseTools") self.addActions(componentToolBar, componentActions) self.auto_tune = self.createAction(parent=self, text='autoTune', slot=self.tune_autopicker, shortcut='Alt+Ctrl+A', icon=autotune_icon, tip='Tune autopicking algorithm.') self.auto_tune.setEnabled(False) self.auto_pick = self.createAction(parent=self, text='autoPick', slot=self.autoPick, shortcut='Alt+Ctrl+A', icon=autopylot_icon, tip='Automatically pick' ' the displayed waveforms.') self.auto_pick.setEnabled(False) autoPickToolBar = self.addToolBar("autoPyLoT") autoPickActions = (self.auto_tune, self.auto_pick, self.compare_action) self.addActions(autoPickToolBar, autoPickActions) # pickToolBar = self.addToolBar("PickTools") # pickToolActions = (selectStation, ) # pickToolBar.setObjectName("PickTools") # self.addActions(pickToolBar, pickToolActions) self.locateEvent = self.createAction(parent=self, text='locate the event', slot=self.locate_event, shortcut='Alt+Ctrl+L', icon=locate_icon, tip='Locate the event using ' 'the displayed manual arrivals.') self.locateEvent.setEnabled(False) locationToolBar = self.addToolBar("LocationTools") locationToolActions = (self.locateEvent,) locationToolBar.setObjectName("LocationTools") self.addActions(locationToolBar, locationToolActions) self.eventLabel = QLabel() self.eventLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) status = self.statusBar() status.setSizeGripEnabled(False) status.addPermanentWidget(self.eventLabel) status.showMessage("Ready", 500) _widget.setLayout(self._main_layout) _widget.showFullScreen() 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. ''' self.ref_event_button = QtGui.QPushButton('Ref') self.test_event_button = QtGui.QPushButton('Test') self.ref_event_button.setToolTip('Set manual picks of current '+ 'event as reference picks for autopicker tuning.') self.test_event_button.setToolTip('Set manual picks of current '+ 'event as test picks for autopicker testing.') self.ref_event_button.setCheckable(True) self.test_event_button.setCheckable(True) self.set_button_color(self.ref_event_button, self._colors['ref']) self.set_button_color(self.test_event_button, self._colors['test']) self.ref_event_button.clicked.connect(self.toggleRef) self.test_event_button.clicked.connect(self.toggleTest) self.ref_event_button.setEnabled(False) self.test_event_button.setEnabled(False) def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key.Key_Control: self._ctrl = True if event.key() == QtCore.Qt.Key.Key_Shift: self._shift = True def keyReleaseEvent(self, event): if event.key() == QtCore.Qt.Key.Key_Control: self._ctrl = False if event.key() == QtCore.Qt.Key.Key_Shift: self._shift = False @property def metadata(self): return self._metadata @metadata.setter def metadata(self, value): self._metadata = value def updateFileMenu(self): self.fileMenu.clear() for action in self.fileMenuActions[:-1]: if action is None: self.fileMenu.addSeparator() else: self.fileMenu.addAction(action) try: current = self.data.getID() except AttributeError: current = None recentEvents = [] for eventID in self.recentfiles: fname = fnConstructor(eventID) if eventID != current and QFile.exists(fname): recentEvents.append(eventID) recentEvents.reverse() self.recentfiles = recentEvents[0:5] settings = QSettings() settings.setValue() if recentEvents: for i, eventID in enumerate(recentEvents): fname = fnConstructor(eventID) action = QAction(self.windowIcon(), "&{0} {1}".format(i + 1, QFileInfo(fname).fileName()), self) action.setData(fname) self.connect(action, Signal("triggered()"), self.load_data) self.fileMenu.addAction(action) self.fileMenu.addSeparator() self.fileMenu.addAction(self.fileMenuActions[-1]) @property def inputs(self): return self._inputs def getRoot(self): settings = QSettings() return settings.value("data/dataRoot") def load_autopicks(self, fname=None): self.load_data(fname, type='auto') def load_loc(self, fname=None): type = getDataType(self) self.load_data(fname, type=type, loc=True) def load_pilotevent(self): filt = "PILOT location files (*LOC*.mat)" caption = "Select PILOT location file" fn_loc = QFileDialog().getOpenFileName(self, caption=caption, filter=filt, dir=self.getRoot()) fn_loc = fn_loc[0] loc_dir = os.path.split(fn_loc)[0] filt = "PILOT phases files (*PHASES*.mat)" caption = "Select PILOT phases file" fn_phases = QFileDialog().getOpenFileName(self, caption=caption, filter=filt, dir=loc_dir) fn_phases = fn_phases[0] type = getDataType(self) fname_dict = dict(phasfn=fn_phases, locfn=fn_loc) self.load_data(fname_dict, type=type) 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) 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, 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) def set_button_color(self, button, color = None): ''' Set background color of a button. button: type = QtGui.QAbstractButton color: type = QtGui.QColor or type = str (RGBA) ''' if type(color) == QtGui.QColor: palette = button.palette() role = button.backgroundRole() palette.setColor(role, color) button.setPalette(palette) button.setAutoFillBackground(True) elif type(color) == str or not color: button.setStyleSheet("background-color: {}".format(color)) def getWFFnames(self): try: evt = self.get_data().get_evt_data() if evt.pylot_picks: for pick in evt.pylot_picks: try: if pick.waveform_id is not None: fname = pick.waveform_id.getSEEDstring() if fname not in self.fnames: self.fnames.append(fname) except: continue else: if self.dataStructure: searchPath = self.dataStructure.expandDataPath() fnames = QFileDialog.getOpenFileNames(self, "Select waveform " "files:", dir=searchPath) self.fnames = fnames[0] else: raise DatastructureError('not specified') if not self.fnames: return None return self.fnames except DatastructureError as e: print(e) if not self._props: self._props = PropertiesDlg(self, infile=self.infile) if self._props.exec_() == QDialog.Accepted: return self.getWFFnames() else: return def getWFFnames_from_eventbox(self, eventbox=None): ''' Return waveform filenames from event in eventbox. ''' 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') return fnames def get_current_event(self, eventbox=None): ''' Return event (type QtPylot.Event) currently selected in eventbox. ''' if not eventbox: eventbox = self.eventBox 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. ''' 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] def add_events(self): ''' Creates and adds events by user selection of event folders to GUI. ''' if not self.project: self.createNewProject() ed = getExistingDirectories(self, 'Select event directories...') if ed.exec_(): eventlist = ed.selectedFiles() # select only folders that start with 'e', containin two dots and have length 12 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) def createEventBox(self): ''' Eventbox generator. ''' qcb = QComboBox() palette = qcb.palette() # change highlight color: # palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Highlight, # QtGui.QBrush(QtGui.QColor(0,0,127,127))) # palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Highlight, # QtGui.QBrush(QtGui.QColor(0,0,127,127))) qcb.setPalette(palette) return qcb def init_events(self, new=False): ''' Initiate GUI widgets in case of changed or newly added events. ''' nitems = self.eventBox.count() if len(self.project.eventlist) == 0: print('No events to init.') self.clearWaveformDataPlot() return self.eventBox.setEnabled(True) self.fill_eventbox() if new: self.eventBox.setCurrentIndex(0) else: self.eventBox.setCurrentIndex(nitems) self.refreshEvents() tabindex = self.tabs.currentIndex() def fill_eventbox(self, event=None, eventBox=None, select_events='all'): ''' (Re)fill the selected eventBox (type = QtGui.QComboBox). :param: select_events, can be 'all', 'ref' :type: str ''' if not eventBox: eventBox = self.eventBox index=eventBox.currentIndex() tv=QtGui.QTableView() header = tv.horizontalHeader() header.setResizeMode(QtGui.QHeaderView.ResizeToContents) header.setStretchLastSection(True) header.hide() tv.verticalHeader().hide() tv.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) eventBox.setView(tv) eventBox.clear() model = eventBox.model() plmax=0 #set maximum length of path string for event in self.project.eventlist: pl = len(event.path) if pl > plmax: plmax=pl for id, event in enumerate(self.project.eventlist): event_path = event.path event_npicks = 0 event_nautopicks = 0 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() # text = '{path:{plen}} | manual: [{p:3d}] | auto: [{a:3d}]' # text = text.format(path=event_path, # plen=plmax, # p=event_npicks, # a=event_nautopicks) item_path = QtGui.QStandardItem('{path:{plen}}'.format(path=event_path, plen=plmax)) item_nmp = QtGui.QStandardItem(str(event_npicks)) item_nmp.setIcon(self.manupicksicon_small) item_nap = QtGui.QStandardItem(str(event_nautopicks)) item_nap.setIcon(self.autopicksicon_small) item_ref = QtGui.QStandardItem()#str(event_ref)) item_test = QtGui.QStandardItem()#str(event_test)) if event_ref: item_ref.setBackground(self._colors['ref']) if event_test: item_test.setBackground(self._colors['test']) item_notes = QtGui.QStandardItem(event.notes) openIcon = self.style().standardIcon(QStyle.SP_DirOpenIcon) item_path.setIcon(openIcon) # if ref: set different color e.g. # if event_ref: # item.setBackground(self._colors['ref']) # if event_test: # itemt.setBackground(self._colors['test']) # item.setForeground(QtGui.QColor('black')) # font = item.font() # font.setPointSize(10) # item.setFont(font) # item2.setForeground(QtGui.QColor('black')) # item2.setFont(font) itemlist = [item_path, item_nmp, item_nap, item_ref, item_test, item_notes] if event_test and select_events == 'ref': for item in itemlist: item.setEnabled(False) model.appendRow(itemlist) if not event.path == self.eventBox.itemText(id).strip(): message = ('Path missmatch creating eventbox.\n' '{} unequal {}.' .format(event.path, self.eventBox.itemText(id))) raise ValueError(message) #not working with obspy events #eventBox.setItemData(id, event) eventBox.setCurrentIndex(index) self.refreshRefTestButtons() def filename_from_action(self, action): if action.data() is None: filt = "Supported file formats" \ " (*.mat *.qml *.xml *.kor *.evt)" caption = "Open an event file" fname = QFileDialog().getOpenFileName(self, caption=caption, filter=filt, dir=self.get_current_event_path()) fname = fname[0] else: fname = str(action.data().toString()) return fname def get_fnames(self, type='manual'): return self.fname[type] def set_fname(self, fname, type): if self.get_fnames(type) is not None: self.add_recentfile(self.get_fnames(type)) self.fname[type] = fname def getEventFileName(self, type='manual'): if self.get_fnames(type) is None: self.set_fname(self.get_data().getEventFileName(), type) return self.get_fnames(type) def saveData(self): def getSavePath(e): print('warning: {0}'.format(e)) 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, outpath, file_filter) fbasename, exform = os.path.splitext(fname) 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.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 # MP MP changed to suppress unnecessary user prompt try: self.get_data().exportEvent(fbasename, exform) except FormatError as e: fbasename, exform = getSavePath(e) except AttributeError as e: fbasename, exform = getSavePath(e) # catch all possible cases before going on if not fbasename: return False # warn overwriting # elif os.path.exists(fbasename + exform): # ans = QMessageBox.question(self, self.tr("Overwrite file..."), # self.tr("File already exists: {0}\n".format(fbasename + exform) + \ # "Overwrite file anyway?"), # QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) # # only negative answers have to be caught # if ans == QMessageBox.No: # self.saveData() # elif ans == QMessageBox.Cancel: # return False # export to given path self.get_data().exportEvent(fbasename, exform) # all files save (ui clean) self.update_status('Picks saved as %s' % (fbasename + exform)) self.disableSaveManualPicksAction() return True def enableSaveManualPicksAction(self): self.saveManualPicksAction.setEnabled(True) def disableSaveManualPicksAction(self): self.saveManualPicksAction.setEnabled(False) def getinfile(self): return self.infile def getComponent(self): return self.dispComponent def setComponent(self, component): self.dispComponent = component def get_data(self, type='manual'): if type == 'auto': return self.autodata return self.data def getPicks(self, type='manual'): if type == 'manual': return self.get_current_event().getPicks() if type == 'auto': return self.get_current_event().getAutopicks() # rdict = dict(auto=self.pylot_autopicks, manual=self.pylot_picks) # return rdict[type] def getPicksOnStation(self, station, type='manual'): try: return self.getPicks(type)[station] except KeyError: return None def comparePicks(self): if self.check4Comparison(): co = Comparison(auto=self.getPicks('auto'), manu=self.getPicks()) compare_dlg = ComparisonDialog(co, self) compare_dlg.exec_() def getPlotWidget(self): return self.dataPlot @staticmethod def getWFID(ycoord): try: statID = int(round(ycoord)) except TypeError as e: if 'a float is required' in e.message: return None else: raise e return statID def getStationID(self, station): for wfID in self.getPlotWidget().getPlotDict().keys(): actual_station = self.getPlotWidget().getPlotDict()[wfID][0] if station == actual_station: return wfID return None def getStime(self): return self._stime def addActions(self, target, actions): for action in actions: if action is None: target.addSeparator() else: target.addAction(action) def okToContinue(self): if self.dirty: qmb = QMessageBox(self, icon=QMessageBox.Question, text='Do you wish to save changes in the current project?') qmb.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) qmb.setDefaultButton(QMessageBox.Save) ret = qmb.exec_() if ret == qmb.Save: return self.saveProject() elif ret == qmb.Cancel: return False return True def enableRefTestButtons(self, bool): ''' Enable/disable reference and test event selection buttons. ''' self.ref_event_button.setEnabled(bool) self.test_event_button.setEnabled(bool) def refreshRefTestButtons(self): ''' Refresh state of reference and test event selection buttons depending on current event. ''' event = self.get_current_event() if event: self.ref_event_button.setChecked(event.isRefEvent()) self.test_event_button.setChecked(event.isTestEvent()) self.enableRefTestButtons(bool(self.get_current_event().pylot_picks)) return self.ref_event_button.setChecked(False) self.test_event_button.setChecked(False) self.enableRefTestButtons(False) def toggleRef(self): ''' Toggle ref/test buttons when reference button gets clicked. ''' ref = self.ref_event_button.isChecked() self.test_event_button.setChecked(False) self.get_current_event().setTestEvent(False) self.get_current_event().setRefEvent(ref) self.fill_eventbox() self.refreshTabs() if self.tap: self.tap.fill_eventbox() def toggleTest(self): ''' Toggle ref/test buttons when test button gets clicked. ''' test = self.test_event_button.isChecked() self.ref_event_button.setChecked(False) self.get_current_event().setRefEvent(False) self.get_current_event().setTestEvent(test) self.fill_eventbox() self.refreshTabs() if self.tap: self.tap.fill_eventbox() def refreshEvents(self): ''' Refresh GUI when events get changed. ''' # Attribute _eventChanged refers to change in first _eventChanged[0] # and second _eventChanged[1] main tabs. # E.g. plotting is not necessary when changing event in second tab and # array_map refresh is not necessary when changing event in waveform plot tab, # but gets necessary when switching from one to another after changing an event. self._eventChanged = [True, True] self.refreshTabs() def refreshTabs(self): ''' Refresh main tabs depending on change of the current event in first/second tab. ''' # Logical problem appearing somewhere when calling this function: # Loading an existing project from array_tab leads to two calls of newWF # which will read in data input twice. Therefore current tab is changed to 0 # in loadProject before calling this function. plotted=False # only refresh first/second tab when an event was changed. if self._eventChanged[0] or self._eventChanged[1]: event = self.get_current_event() if not event: return # update picks saved in GUI mainwindow (to be changed in future!!) MP MP if not event.pylot_picks: self.pylot_picks = {} else: self.pylot_picks = event.pylot_picks if not event.pylot_autopicks: self.pylot_autopicks = {} else: 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]: if hasattr(self.project, 'eventlist'): if len(self.project.eventlist) > 0: self.newWF() # keep track whether event was already plotted plotted=True # if current tab is array_map-tab and the data in this tab was not yet refreshed if self.tabs.currentIndex() == 1: if self._eventChanged[1]: self.refresh_array_map() if not plotted and self._eventChanged[0]: # newWF(False) = load data without plotting self.newWF(plot=False) if self.tabs.currentIndex() == 2: self.init_event_table() self.refreshRefTestButtons() def newWF(self, plot=True): ''' Load new data and plot if necessary. ''' self.loadWaveformDataThread(plot=plot) if plot: self._eventChanged[0] = False def loadWaveformDataThread(self, plot=True, load=True): ''' Generates a modal thread to load waveform data and call modal plot thread method when finished. ''' if load: wfd_thread = Thread(self, self.loadWaveformData, progressText='Reading data input...') if load and plot: wfd_thread.finished.connect(self.plotWaveformDataThread) if load: wfd_thread.start() if plot and not load: self.plotWaveformDataThread() def loadWaveformData(self): ''' Load waveform data corresponding to current selected event. ''' # if self.fnames and self.okToContinue(): # self.setDirty(True) # ans = self.data.setWFData(self.fnames) # elif self.fnames is None and self.okToContinue(): # ans = self.data.setWFData(self.getWFFnames()) # else: # ans = False self.fnames = self.getWFFnames_from_eventbox() self.data.setWFData(self.fnames) self._stime = full_range(self.get_data().getWFData())[0] def connectWFplotEvents(self): ''' 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) if not self.ae_id: self.ae_id = self.dataPlot.mpl_connect('axes_enter_event', lambda event: self.tutor_user()) if not self.scroll_id: 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: self.dataPlot.mpl_disconnect(self.ae_id) if self.scroll_id: self.dataPlot.mpl_disconnect(self.scroll_id) self.poS_id = None 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) self.auto_pick.setEnabled(True) self.z_action.setEnabled(True) self.e_action.setEnabled(True) self.n_action.setEnabled(True) self.openmanualpicksaction.setEnabled(True) self.openautopicksaction.setEnabled(True) self.loadpilotevent.setEnabled(True) event = self.get_current_event() if event.pylot_picks: self.pylot_picks = event.pylot_picks self.drawPicks(picktype='manual') self.enableSaveManualPicksAction() 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() 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) self.z_action.setEnabled(False) self.e_action.setEnabled(False) self.n_action.setEnabled(False) self.openmanualpicksaction.setEnabled(False) self.openautopicksaction.setEnabled(False) self.loadpilotevent.setEnabled(False) self.disableSaveManualPicksAction() self.draw() def plotWaveformDataThread(self): ''' Open a modal thread to plot current waveform data. ''' 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): ''' Plot waveform data to current plotWidget. ''' settings = QSettings() nth_sample = settings.value('nth_sample') if not nth_sample: nth_sample = 1 zne_text = {'Z': 'vertical', 'N': 'north-south', 'E': 'east-west'} comp = self.getComponent() title = 'section: {0} components'.format(zne_text[comp]) wfst = self.get_data().getWFData() # wfst = self.get_data().getWFData().select(component=comp) # wfst += self.get_data().getWFData().select(component=alter_comp) plotWidget = self.getPlotWidget() self.adjustPlotHeight() 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: plotWidget.setMinimumHeight(height_need) else: plotWidget.setMinimumHeight(0) def plotZ(self): self.setComponent('Z') self.plotWaveformDataThread() # self.drawPicks() # self.draw() def plotN(self): self.setComponent('N') self.plotWaveformDataThread() # self.drawPicks() # self.draw() def plotE(self): self.setComponent('E') self.plotWaveformDataThread() # self.drawPicks() # self.draw() def pushFilterWF(self, param_args): self.get_data().filterWFData(param_args) def filterWaveformData(self): if self.get_data(): if self.getFilterOptions() and self.filterAction.isChecked(): kwargs = self.getFilterOptions().parseFilterOptions() self.pushFilterWF(kwargs) elif self.filterAction.isChecked(): self.adjustFilterOptions() else: self.get_data().resetWFData() self.plotWaveformData() self.drawPicks() self.draw() def adjustFilterOptions(self): fstring = "Filter Options ({0})".format(self.getSeismicPhase()) filterDlg = FilterOptionsDialog(titleString=fstring, parent=self) if filterDlg.exec_(): filteroptions = filterDlg.getFilterOptions() self.setFilterOptions(filteroptions) if self.filterAction.isChecked(): kwargs = self.getFilterOptions().parseFilterOptions() self.pushFilterWF(kwargs) self.plotWaveformData() def getFilterOptions(self): try: return self.filteroptions[self.getSeismicPhase()] except AttributeError as e: print(e) return FilterOptions(None, None, None) def getFilters(self): return self.filteroptions def setFilterOptions(self, filterOptions, seismicPhase=None): if seismicPhase is None: self.getFilters()[self.getSeismicPhase()] = filterOptions else: self.getFilters()[seismicPhase] = filterOptions def updateFilterOptions(self): try: settings = QSettings() if settings.value("filterdefaults", None) is None and not self.getFilters(): for key, value in FILTERDEFAULTS.items(): self.setFilterOptions(FilterOptions(**value), key) elif settings.value("filterdefaults", None) is not None: for key, value in settings.value("filterdefaults"): self.setFilterOptions(FilterOptions(**value), key) except Exception as e: self.update_status('Error ...') emsg = QErrorMessage(self) emsg.showMessage('Error: {0}'.format(e)) else: self.update_status('Filter loaded ... ' '[{0}: {1} Hz]'.format( self.getFilterOptions().getFilterType(), self.getFilterOptions().getFreq())) if self.filterAction.isChecked(): self.filterWaveformData() def getSeismicPhase(self): return self.seismicPhase def getStationName(self, wfID): plot_dict = self.getPlotWidget().getPlotDict() if wfID in plot_dict.keys(): return plot_dict[wfID][0] def alterPhase(self): pass def setSeismicPhase(self, phase): self.seismicPhase = self.seismicPhaseButtonGroup.getValue() self.update_status('Seismic phase changed to ' '{0}'.format(self.getSeismicPhase())) def scrollPlot(self, gui_event): ''' Function connected to mouse wheel scrolling inside WFdataPlot. Only used if amount of traces exceeds a certain limit creating a scroll area. ''' button = gui_event.button x, y = gui_event.xdata, gui_event.ydata if not button == 'up' and not button == 'down': return if not self._ctrl and not self._shift: vbar = self.wf_scroll_area.verticalScrollBar() up_down = {'up': -60, 'down': 60} if vbar.maximum(): vbar.setValue(vbar.value() + up_down[button]) if self._ctrl: factor = {'up': 5./4., 'down': 4./5.} self.height_factor *= factor[button] self.adjustPlotHeight() if self._shift: factor = {'up': 1./2., 'down': 2.} xlims = self.dataPlot.getXLims() xdiff = xlims[1] - xlims[0] xdiff *= factor[button] xl = x - 0.5 * xdiff xr = x + 0.5 * xdiff if xl < self._max_xlims[0]: xl = self._max_xlims[0] if xr > self._max_xlims[1]: xr = self._max_xlims[1] self.dataPlot.setXLims((xl, xr)) self.dataPlot.draw() def pickOnStation(self, 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, data=data.select(station=station), station=station, picks=self.getPicksOnStation(station, 'manual'), autopicks=self.getPicksOnStation(station, 'auto')) pickDlg.nextStation.setChecked(nextStation) if pickDlg.exec_(): 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(): self.locateEvent.setEnabled(True) self.set_loc_flag(True) elif self.get_loc_flag() and not self.check4Loc(): self.set_loc_flag(False) def addListItem(self, text): self.listWidget.addItem(text) self.listWidget.scrollToBottom() def tune_autopicker(self): ''' Initiates TuneAutopicker widget use to interactively tune parameters for autopicking algorithm. ''' # figures and canvas have to be iniated from the main GUI # thread to prevent handling of QPixmap objects outside of # the main thread self.fig_dict = {} self.canvas_dict = {} self.fig_keys = [ 'mainFig', 'aicFig', 'slength', 'checkZ4s', 'refPpick', 'el_Ppick', 'fm_picker', 'el_S1pick', 'el_S2pick', 'refSpick', 'aicARHfig', ] for key in self.fig_keys: fig = Figure() self.fig_dict[key] = fig if not self.tap: # init TuneAutopicker object self.tap = TuneAutopicker(self) # first call of update to init tabs with empty canvas self.update_autopicker() # connect update signal of TuneAutopicker with update function # creating and filling figure canvas self.tap.update.connect(self.update_autopicker) self.tap.figure_tabs.setCurrentIndex(0) else: self.update_autopicker() self.tap.fill_eventbox() self.tap.show() def update_autopicker(self): ''' Create and fill TuneAutopicker tabs with figure canvas. ''' for key in self.fig_keys: self.canvas_dict[key] = FigureCanvas(self.fig_dict[key]) self.tap.fill_tabs(picked=True) def autoPick(self): self.autosave = QFileDialog().getExistingDirectory(caption='Select autoPyLoT output') if not os.path.exists(self.autosave): QMessageBox.warning(self, "PyLoT Warning", "No autoPyLoT output declared!") return self.listWidget = QListWidget() self.setDirty(True) self.logDockWidget = QDockWidget("AutoPickLog", self) self.logDockWidget.setObjectName("LogDockWidget") self.logDockWidget.setAllowedAreas( Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.logDockWidget.setWidget(self.listWidget) self.addDockWidget(Qt.LeftDockWidgetArea, self.logDockWidget) self.addListItem('Loading default values from PyLoT-input file %s' % self.infile) autopick_parameter = self._inputs self.addListItem(str(autopick_parameter)) receventid = self.get_current_event_path() self.thread = AutoPickThread(parent=self, func=autoPyLoT, infile = self.infile, fnames=self.fnames, eventid=receventid, savepath=self.autosave) self.thread.message.connect(self.addListItem) self.thread.start() self.thread.finished.connect(self.finalizeAutoPick) def finalizeAutoPick(self): self.drawPicks(picktype='auto') self.draw() self.thread.quit() def addPicks(self, station, picks, type='manual'): stat_picks = self.getPicksOnStation(station, type) if not stat_picks: rval = False else: #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', 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': event.addPicks(picks) self.pylot_picks.update(picks) elif type == 'auto': event.addAutopicks(picks) self.pylot_autopicks.update(picks) self.check4Comparison() def drawPicks(self, station=None, picktype='manual'): # if picks to draw not specified, draw all picks available if not station: for station in self.getPicks(type=picktype): self.drawPicks(station, picktype=picktype) return # plotting picks plotID = self.getStationID(station) if plotID is None: return if self.pg: pw = self.getPlotWidget().plotWidget else: ax = self.getPlotWidget().axes ylims = np.array([-.5, +.5]) + plotID 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] stime = self.getStime() for phase in stat_picks: picks = stat_picks[phase] if type(stat_picks[phase]) is not dict and type(stat_picks[phase]) is not AttribDict: return colors = phase_col[phase[0].upper()] mpp = picks['mpp'] - stime if picks['epp'] and picks['lpp']: epp = picks['epp'] - stime lpp = picks['lpp'] - stime spe = picks['spe'] if not spe and epp and lpp: spe = symmetrize_error(mpp - epp, lpp - mpp) 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: raise TypeError('Unknown picktype {0}'.format(picktype)) else: 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): """ locate event using the manually picked phases :return: """ if not self.okToContinue(): return settings = QSettings() # get location tool hook loctool = settings.value("loc/tool", "nll") lt = locateTool[loctool] # get working directory locroot = settings.value("{0}/rootPath".format(loctool), None) if locroot is None: self.PyLoTprefs() self.locate_event() infile = settings.value("{0}/inputFile".format(loctool), None) if not infile: caption = 'Select {0} input file'.format(loctool) filt = "Supported file formats" \ " (*.in *.ini *.conf *.cfg)" ans = QFileDialog().getOpenFileName(self, caption=caption, filter=filt, dir=locroot) if ans[0]: infile = ans[0] else: QMessageBox.information(self, self.tr('No infile selected'), self.tr('Inputfile necessary for localization.')) return settings.setValue("{0}/inputFile".format(loctool), infile) settings.sync() if loctool == 'nll': ttt = settings.value("{0}/travelTimeTables", None) ok = False if ttt is None: while not ok: text, ok = QInputDialog.getText(self, 'Pattern for travel time tables', 'Base name of travel time tables', echo=QLineEdit.Normal, text="ttime") ttt = text outfile = settings.value("{0}/outputFile".format(loctool), os.path.split(os.tempnam())[-1]) phasefile = os.path.split(os.tempnam())[-1] phasepath = os.path.join(locroot, 'obs', phasefile) locpath = os.path.join(locroot, 'loc', outfile) lt.export(self.getPicks(), phasepath) lt.modify_inputs(infile, locroot, outfile, phasefile, ttt) try: lt.locate(infile) except RuntimeError as e: print(e.message) finally: os.remove(phasepath) self.get_data().applyEVTData(lt.read_location(locpath), typ='event') self.get_data().applyEVTData(self.calc_magnitude(), typ='event') def init_array_tab(self): ''' Init second main tab if neither metadata nor array map are given. A button will be show calling self.get_metadata. ''' if hasattr(self, 'metadata_widget'): if self.metadata_widget: self.metadata_widget.setParent(None) self.array_layout.removeWidget(self.metadata_widget) if hasattr(self, 'array_map'): if self.array_map: self.array_map.setParent(None) self.array_layout.removeWidget(self.array_map) self.metadata_widget = QWidget(self) grid_layout = QGridLayout() grid_layout.setColumnStretch(0, 1) grid_layout.setColumnStretch(2, 1) grid_layout.setRowStretch(0, 1) grid_layout.setRowStretch(3, 1) label = QLabel('No inventory set...') new_inv_button = QPushButton('Set &inventory file') new_inv_button.clicked.connect(self.get_metadata) grid_layout.addWidget(label, 1, 1) grid_layout.addWidget(new_inv_button, 2, 1) self.metadata = None self.metadata_widget.setLayout(grid_layout) self.array_layout.addWidget(self.metadata_widget) def init_array_map(self, index=1): ''' Try to init array map widget. If no metadata are given, self.get_metadata will be called. ''' self.tabs.setCurrentIndex(1) if hasattr(self, 'metadata_widget'): if self.metadata_widget: self.metadata_widget.setParent(None) self.array_layout.removeWidget(self.metadata_widget) if hasattr(self, 'array_map'): if self.array_map: self.array_map.setParent(None) self.array_layout.removeWidget(self.array_map) if not self.array_map: self.get_metadata() if not self.metadata: return self.am_figure = Figure() self.am_canvas = FigureCanvas(self.am_figure) self.am_toolbar = NavigationToolbar(self.am_canvas, self) self.array_map = map_projection(self) #self.array_map_thread() self.array_layout.addWidget(self.array_map) self.tabs.setCurrentIndex(index) self.refresh_array_map() def array_map_thread(self): ''' Start modal thread to init the array_map object. ''' # Note: basemap generation freezes GUI but cannot be threaded as it generates a Pixmap. self.amt = Thread(self, self.array_map.init_map, arg=None, progressText='Generating map...') self.amt.finished.connect(self.finish_array_map) self.amt.start() def finish_array_map(self): ''' Add array_map to GUI tab when array_map_thread has finished. ''' self.array_map = self.amt.data self.array_layout.addWidget(self.array_map) #self.tabs.setCurrentIndex(index) #self.refresh_array_map() def refresh_array_map(self): ''' Refresh array map when current event is changed. ''' 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 def init_event_table(self, tabindex=2): ''' Build and initiate event table (3rd tab [index=2]) containing information of every event. ''' def set_enabled(item, enabled=True, checkable=False): # modify item flags depending on case needed if enabled and not checkable: item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable) elif enabled and checkable: item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsSelectable) else: item.setFlags(QtCore.Qt.ItemIsSelectable) if self.project: eventlist = self.project.eventlist else: eventlist = [] def cell_changed(row=None, column=None): # connected to cell changes in event table # changes attributes of the corresponding event table = self.project._table event = self.project.getEventFromPath(table[row][0].text()) if column == 8 or column == 9: #toggle checked states (exclusive) 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 == 8 and not item_ref.checkState(): event.setRefEvent(False) elif column == 9 and item_test.checkState(): item_ref.setCheckState(QtCore.Qt.Unchecked) event.setTestEvent(True) elif column == 9 and not item_test.checkState(): event.setTestEvent(False) self.fill_eventbox() elif column == 5: #update event notes notes = table[row][5].text() event.addNotes(notes) self.fill_eventbox() # remove old table if hasattr(self, 'event_table'): self.event_table.setParent(None) self.events_layout.removeWidget(self.event_table) # init new qtable self.event_table = QtGui.QTableWidget() self.event_table.setColumnCount(11) self.event_table.setRowCount(len(eventlist)) 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.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)) item_nap.setIcon(self.autopicksicon_small) item_ref = QtGui.QTableWidgetItem() item_test = QtGui.QTableWidgetItem() item_notes = QtGui.QTableWidgetItem() item_ref.setBackground(self._colors['ref']) item_test.setBackground(self._colors['test']) item_path.setText(event.path) 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.pylot_picks: set_enabled(item_ref, True, True) set_enabled(item_test, True, True) else: set_enabled(item_ref, False, True) set_enabled(item_test, False, True) if event.isRefEvent(): item_ref.setCheckState(QtCore.Qt.Checked) else: item_ref.setCheckState(QtCore.Qt.Unchecked) if event.isTestEvent(): item_test.setCheckState(QtCore.Qt.Checked) else: item_test.setCheckState(QtCore.Qt.Unchecked) 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): for c_index, item in enumerate(row): self.event_table.setItem(r_index, c_index, item) header = self.event_table.horizontalHeader() header.setResizeMode(QtGui.QHeaderView.ResizeToContents) header.setStretchLastSection(True) self.event_table.cellChanged[int, int].connect(cell_changed) self.events_layout.addWidget(self.event_table) self.tabs.setCurrentIndex(tabindex) def read_metadata_thread(self, fninv): self.rm_thread = Thread(self, read_metadata, arg=fninv, progressText='Reading metadata...') self.rm_thread.finished.connect(self.set_metadata) self.rm_thread.start() def set_metadata(self): settings = QSettings() self.metadata = self.rm_thread.data if settings.value('saveMetadata'): self.project.metadata = self.rm_thread.data self.project.inv_path = settings.value("inventoryFile") self.init_array_map() def get_metadata(self): def set_inv(settings): fninv, _ = QFileDialog.getOpenFileName(self, self.tr( "Select inventory..."), self.tr("Select file")) if not fninv: return False ans = QMessageBox.question(self, self.tr("Make default..."), self.tr( "New inventory filename set.\n" + \ "Do you want to make it the default value?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if ans == QMessageBox.Yes: settings.setValue("inventoryFile", fninv) settings.sync() self.read_metadata_thread(fninv) return True settings = QSettings() if hasattr(self.project, 'metadata'): self.metadata = self.project.metadata return True if hasattr(self.project, 'invPath'): settings.setValue("inventoryFile", self.project.inv_path) fninv = settings.value("inventoryFile", None) if fninv is None and not self.metadata: if not set_inv(settings): return None elif fninv is not None and not self.metadata: if not hasattr(self.project, 'invPath'): ans = QMessageBox.question(self, self.tr("Use default metadata..."), self.tr( "Do you want to use the default value for metadata?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if ans == QMessageBox.No: if not set_inv(settings): return None else: self.read_metadata_thread(fninv) def calc_magnitude(self, type='ML'): self.get_metadata() if not self.metadata: return None wf_copy = self.get_data().getWFData().copy() corr_wf = restitute_data(wf_copy, *self.metadata) # if not rest_flag: # raise ProcessingError('Restitution of waveform data failed!') if type == 'ML': 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) return moment_mag.updated_event() else: return None def check4Loc(self): return self.picksNum() >= 4 def check4Comparison(self): mpicks = self.getPicks() apicks = self.getPicks('auto') for station, phases in mpicks.items(): try: aphases = apicks[station] for phase in phases.keys(): if phase in aphases.keys(): return True except KeyError: continue return False def picksNum(self, type='manual'): num = 0 for phases in self.getPicks(type).values(): num += len(phases) return num def get_loc_flag(self): return self.loc def set_loc_flag(self, value): self.loc = value def check_loc_plt(self): evt = self.get_data().get_evt_data() if evt.origins and evt.magnitudes: return True return False def update_status(self, message, duration=5000): self.statusBar().showMessage(message, duration) if self.get_data() is not None: if not self.get_current_event() or not self.project.location: self.setWindowTitle("PyLoT - New project [*]") elif self.get_current_event(): self.setWindowTitle("PyLoT - {} [*]".format(self.project.location)) else: self.setWindowTitle( "PyLoT - seismic processing the python way[*]") self.setWindowModified(self.dirty) def tutor_user(self): trace_pick = ' select trace to pick on station ...' strg_key = ' - [CTRL + mousewheel] vertical spacing' shift_key = ' - [SHIFT + mousewheel] horizontal zoom' message = trace_pick + strg_key + shift_key self.update_status(message, 10000) def show_event_information(self): pass def createNewEvent(self): if self.okToContinue(): new = NewEventDlg() if new.exec_() != QDialog.Rejected: evtpar = new.getValues() cinfo = create_creation_info(agency_id=self.agency) event = create_event(evtpar['origintime'], cinfo) self.data = Data(self, evtdata=event) self.setDirty(True) def createNewProject(self): ''' Create new project file. ''' if not self.okToContinue(): return self.project = Project() self.init_events(new=True) self.setDirty(False) self.project.parameter=self._inputs self.saveProjectAsAction.setEnabled(True) self.update_status('Created new project...', duration=1000) return True def loadProject(self, fnm=None): ''' Load an existing project file. ''' if not self.okToContinue(): return if not fnm: dlg = QFileDialog() fnm = dlg.getOpenFileName(self, 'Open project file...', filter='Pylot project (*.plp)') if not fnm: return fnm = fnm[0] if fnm: self.project = Project.load(fnm) if hasattr(self.project, 'parameter'): if self.project.parameter: self._inputs = self.project.parameter self.tabs.setCurrentIndex(0) # implemented to prevent double-loading of waveform data self.init_events(new=True) self.setDirty(False) if hasattr(self.project, 'metadata'): if self.project.metadata: self.init_array_map(index=0) return self.init_array_tab() 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): ''' Save back project to pickle file. ''' if self.project and not new: if not self.project.location: 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: self.update_status('Saved back project to file:\n{}'.format(self.project.location), duration=5000) self.setDirty(False) return True else: # if still dirty because saving failed qmb = QMessageBox.warning(self,'Could not save project', 'Could not save back to original file.\nChoose new file') self.setDirty(True) return self.saveProjectAs(exists=True) def draw(self): self.fill_eventbox() self.getPlotWidget().draw() def _setDirty(self): self.setDirty(True) def setDirty(self, value): self.saveProjectAction.setEnabled(value) self.saveProjectAsAction.setEnabled(True) self.project.setDirty(value) self.dirty = value def closeEvent(self, event): if self.okToContinue(): event.accept() else: event.ignore() # self.closing.emit() # QMainWindow.closeEvent(self, event) def setParameter(self, show=True): if not self.paraBox: self.paraBox = PylotParaBox(self._inputs) self.paraBox._apply.clicked.connect(self._setDirty) 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): if checkurl(): form = HelpForm( 'https://ariadne.geophysik.ruhr-uni-bochum.de/trac/PyLoT/wiki') else: form = HelpForm(':/help.html') form.show() class Project(object): ''' Pickable class containing information of a QtPyLoT project, like event lists and file locations. ''' def __init__(self): self.eventlist = [] self.location = None self.rootpath = None self.dirty = False self.parameter = None self._table = None def add_eventlist(self, eventlist): ''' Add events from an eventlist containing paths to event directories. Will skip existing paths. ''' if len(eventlist) == 0: 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): ''' Returns paths (eventlist) of all events saved in the project. ''' paths = [] for event in self.eventlist: paths.append(event.path) return paths def setDirty(self, value=True): self.dirty = value def getEventFromPath(self, path): ''' Search for an event in the project by event path. ''' for event in self.eventlist: if event.path == path: return event def save(self, filename=None): ''' Save PyLoT Project to a file. Can be loaded by using project.load(filename). ''' try: import cPickle except ImportError: import _pickle as cPickle if filename: self.location = filename else: filename = self.location try: outfile = open(filename, 'wb') cPickle.dump(self, outfile, -1) self.setDirty(False) except Exception as e: print('Could not pickle PyLoT project. Reason: {}'.format(e)) self.setDirty() @staticmethod def load(filename): ''' Load project from filename. ''' try: import cPickle except ImportError: import _pickle as cPickle infile = open(filename, 'rb') project = cPickle.load(infile) project.location = filename print('Loaded %s' % filename) return project class getExistingDirectories(QFileDialog): ''' File dialog with possibility to select multiple folders. ''' def __init__(self, *args): super(getExistingDirectories, self).__init__(*args) self.setOption(self.DontUseNativeDialog, True) self.setOption(self.ReadOnly, True) self.setFileMode(self.Directory) self.setOption(self.ShowDirsOnly, True) self.findChildren(QListView)[0].setSelectionMode(QAbstractItemView.ExtendedSelection) self.findChildren(QTreeView)[0].setSelectionMode(QAbstractItemView.ExtendedSelection) def create_window(): app_created = False app = QCoreApplication.instance() #check for existing app (when using ipython) 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() return app, app_created def main(args=None): project_filename = None pylot_infile = None if args: if args.project_filename: project_filename = args.project_filename if args.input_filename: pylot_infile = args.input_filename # create the Qt application pylot_app, app_created = create_window() #pylot_app = QApplication(sys.argv) pixmap = QPixmap(":/splash/splash.png") splash = QSplashScreen(pixmap) splash.show() app_icon = QIcon() app_icon.addPixmap(QPixmap(':/icons/pylot.png')) # create the main window pylot_form = MainWindow(infile=pylot_infile) icon = QIcon() pylot_form.setWindowIcon(icon) pylot_form.setIconSize(QSize(60, 60)) splash.showMessage('Loading. Please wait ...') pylot_app.processEvents() # set Application Information pylot_app.setOrganizationName("Ruhr-University Bochum / BESTEC") pylot_app.setOrganizationDomain("rub.de") pylot_app.processEvents() pylot_app.setApplicationName("PyLoT") pylot_app.setApplicationVersion(pylot_form.__version__) pylot_app.setWindowIcon(app_icon) pylot_app.processEvents() # Show main window and run the app pylot_form.showMaximized() pylot_app.processEvents() splash.finish(pylot_form) if project_filename: pylot_form.loadProject(args.project_filename) if app_created: pylot_app.exec_() else: return pylot_form if __name__ == "__main__": parser = argparse.ArgumentParser(description='Welcome to PyLoT.') parser.add_argument('-p', dest='project_filename', help='load project file', default=None) parser.add_argument('-in', dest='input_filename', help='set pylot input file', default=None) args = parser.parse_args() sys.exit(main(args))