pylot/QtPyLoT.py

478 lines
18 KiB
Python
Raw Normal View History

#!/usr/bin/env python
2014-03-31 12:57:08 +02:00
# -*- 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.
2014-11-28 09:19:16 +01:00
Some icons are out of a free of charge icon set, which can be found here:
https://www.iconfinder.com/iconsets/flavour
2014-03-31 12:57:08 +02:00
:author:
Sebastian Wehling-Benatelli
: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)
"""
2014-12-08 10:26:14 +01:00
import os
import sys
2015-02-16 10:24:17 +01:00
from PySide.QtCore import QCoreApplication, QSettings, Signal, QFile, QFileInfo
from PySide.QtGui import QMainWindow, QInputDialog, QIcon, QFileDialog, \
QWidget, QHBoxLayout, QStyle, QKeySequence, QLabel, QFrame, QAction, \
QDialog, QErrorMessage, QApplication
from obspy.core import UTCDateTime
from pylot.core.read import Data, FilterOptions
from pylot.core.util import _getVersionString, FILTERDEFAULTS, fnConstructor, \
checkurl, FormatError, layoutStationButtons, FilterOptionsDialog, \
NewEventDlg, createEvent, MPLWidget, PropertiesDlg, HelpForm
2015-02-13 11:12:47 +01:00
from pylot.core.util.structure import DATASTRUCTURE
2015-02-16 10:24:17 +01:00
# Version information
__version__ = _getVersionString()
class MainWindow(QMainWindow):
2014-12-08 10:26:14 +01:00
closing = Signal()
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.dirty = False
2014-11-28 11:15:49 +01:00
settings = QSettings()
2014-12-08 10:26:14 +01:00
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", os.getlogin())
settings.sync()
self.recentEvents = settings.value("data/recentEvents", [])
self.fnames = None
2015-02-13 11:12:47 +01:00
self.dataStructure = DATASTRUCTURE[
settings.value("data/Structure", None)]()
2014-12-08 10:26:14 +01:00
self.setWindowTitle("PyLoT - do seismic processing the python way")
2014-11-28 11:15:49 +01:00
self.setWindowIcon(QIcon(":/icon.ico"))
self.seismicPhase = str(settings.value("phase", "P"))
self.dispComponent = str(settings.value("plotting/dispComponent", "Z"))
2014-12-08 10:26:14 +01:00
if settings.value("data/dataRoot", None) is None:
2015-01-29 08:53:01 +01:00
dirname = QFileDialog().getExistingDirectory(
caption='Choose data root ...')
2014-12-08 10:26:14 +01:00
settings.setValue("data/dataRoot", dirname)
2014-12-01 12:36:23 +01:00
settings.sync()
2014-11-28 11:15:49 +01:00
# initialize filter parameter
filterOptionsP = FILTERDEFAULTS['P']
filterOptionsS = FILTERDEFAULTS['S']
2015-01-29 08:53:01 +01:00
self.filterOptionsP = FilterOptions(**filterOptionsP)
self.filterOptionsS = FilterOptions(**filterOptionsS)
# UI has to be set up before(!) children widgets are
self.setupUi()
# initialize event data
if self.recentEvents:
lastEvent = self.getLastEvent()
self.data = Data(self, lastEvent)
else:
self.data = Data(self)
# load and display waveform data
self.loadWaveformData()
2014-12-08 10:26:14 +01:00
self.dirty = False
self.loadData()
self.updateFilterOptions()
try:
2015-01-29 08:53:01 +01:00
self.startTime = min(
[tr.stats.starttime for tr in self.data.wfdata])
except:
self.startTime = UTCDateTime()
def setupUi(self):
self.setWindowIcon(QIcon(":/icon.ico"))
xlab = self.startTime.strftime('seconds since %d %b %Y %H:%M:%S (%Z)')
_widget = QWidget()
_layout = QHBoxLayout()
plottitle = "Overview: {0} components ".format(self.getComponent())
# create central matplotlib figure canvas widget
self.DataPlot = MPLWidget(parent=self, xlabel=xlab, ylabel=None,
title=plottitle)
statsButtons = layoutStationButtons(self.getData(), self.getComponent())
_layout.addLayout(statsButtons)
_layout.addWidget(self.DataPlot)
openIcon = self.style().standardIcon(QStyle.SP_DirOpenIcon)
quitIcon = self.style().standardIcon(QStyle.SP_MediaStop)
saveIcon = self.style().standardIcon(QStyle.SP_DriveHDIcon)
helpIcon = self.style().standardIcon(QStyle.SP_DialogHelpButton)
newIcon = self.style().standardIcon(QStyle.SP_FileIcon)
newEventAction = self.createAction("&New event ...",
self.createNewEvent,
QKeySequence.New, newIcon,
"Create a new event.")
openEventAction = self.createAction("&Open event ...", self.loadData,
QKeySequence.Open, openIcon,
"Open an event.")
openEventAction.setData(None)
saveEventAction = self.createAction("&Save event ...", self.saveData,
QKeySequence.Save, saveIcon,
"Save actual event data.")
openWFDataAction = self.createAction("Open &waveforms ...",
self.loadWaveformData,
"Ctrl+W", QIcon(":/wfIcon.png"),
"""Open waveform data (event will
be closed).""")
prefsEventAction = self.createAction("Preferences", self.PyLoTprefs,
QKeySequence.Preferences,
QIcon(None),
"Edit PyLoT app preferences.")
quitAction = self.createAction("&Quit",
QCoreApplication.instance().quit,
QKeySequence.Close, quitIcon,
"Close event and quit PyLoT")
filterAction = self.createAction("&Filter ...", self.filterData,
"Ctrl+F", QIcon(":/filter.png"),
"""Toggle un-/filtered waveforms
to be displayed, according to the
desired seismic phase.""", True)
filterEditAction = self.createAction("&Filter parameter ...",
self.adjustFilterOptions,
"Alt+F", QIcon(None),
"""Adjust filter parameters.""")
selectPAction = self.createAction("&P", self.alterPhase, "Alt+P",
QIcon(":/picon.png"),
"Toggle P phase.", True)
selectSAction = self.createAction("&S", self.alterPhase, "Alt+S",
QIcon(":/sicon.png"),
"Toggle S phase", True)
printAction = self.createAction("&Print event ...",
self.printEvent, QKeySequence.Print,
QIcon(":/printer.png"),
"Print waveform overview.")
helpAction = self.createAction("&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 = (newEventAction, openEventAction,
saveEventAction, openWFDataAction, None,
prefsEventAction, quitAction)
self.fileMenu.aboutToShow.connect(self.updateFileMenu)
self.updateFileMenu()
self.editMenu = self.menuBar().addMenu('&Edit')
editActions = (filterAction, filterEditAction, None, selectPAction,
selectSAction, None, printAction)
self.addMenuActions(self.editMenu, editActions)
self.helpMenu = self.menuBar().addMenu('&Help')
helpActions = (helpAction, )
self.addMenuActions(self.helpMenu, helpActions)
self.eventLabel = QLabel()
self.eventLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
status = self.statusBar()
status.setSizeGripEnabled(False)
status.addPermanentWidget(self.eventLabel)
status.showMessage("Ready", 500)
statsButtons = layoutStationButtons(self.getData(), self.getComponent())
_layout.addLayout(statsButtons)
_layout.addWidget(self.DataPlot)
_widget.setLayout(_layout)
self.setCentralWidget(_widget)
def createAction(self, text, slot=None, shortcut=None, icon=None,
tip=None, checkable=False):
"""
:rtype : ~PySide.QtGui.QAction
"""
action = QAction(text, self)
if icon is not None:
action.setIcon(icon)
if shortcut is not None:
action.setShortcut(shortcut)
if tip is not None:
action.setToolTip(tip)
if slot is not None:
action.triggered.connect(slot)
if checkable:
action.setCheckable(True)
return action
def updateFileMenu(self):
self.fileMenu.clear()
2015-01-20 13:44:35 +01:00
for action in self.fileMenuActions[:-1]:
if action is None:
self.fileMenu.addSeparator()
else:
self.fileMenu.addAction(action)
try:
2015-02-13 11:15:48 +01:00
current = self.data.getID()
2015-01-20 13:44:35 +01:00
except AttributeError:
current = None
recentEvents = []
for eventID in self.recentEvents:
fname = fnConstructor(eventID)
if eventID != current and QFile.exists(fname):
recentEvents.append(eventID)
2015-02-13 11:16:20 +01:00
recentEvents.reverse()
self.recentEvents = recentEvents[0:5]
settings = QSettings()
settings.setValue()
if recentEvents:
for i, eventID in enumerate(recentEvents):
fname = fnConstructor(eventID)
action = QAction(QIcon(":/icon.png"),
"&{0} {1}".format(i + 1,
QFileInfo(fname).fileName()),
self)
action.setData(fname)
self.connect(action, Signal("triggered()"),
self.loadData)
self.fileMenu.addAction(action)
self.fileMenu.addSeparator()
self.fileMenu.addAction(self.fileMenuActions[-1])
def loadData(self, fname=None):
if fname is None:
try:
self.data = Data(self, evtdata=self.fname)
except AttributeError:
action = self.sender()
if isinstance(action, QAction):
if action.data() is None:
filt = "Supported event formats (*.mat *.qml *.xml " \
"*.kor *.evt)"
caption = 'Select event to open'
fname, = QFileDialog().getOpenFileName(self,
caption=caption,
filter=filt)
self.fname = fname
else:
self.fname = unicode(action.data().toString())
if not self.okToContinue():
return
else:
2014-12-17 12:16:32 +01:00
self.fname = fname
self.data = Data(self, evtdata=self.fname)
def getLastEvent(self):
return self.recentEvents[0]
def getWFFnames(self):
try:
evt = self.getData().getEvtData()
if evt.picks:
for pick in evt.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()
2015-02-13 11:19:10 +01:00
fnames, = QFileDialog.getOpenFileNames(self,
"Select waveform "
"files:",
2015-02-13 11:19:10 +01:00
dir=searchPath)
self.fnames = fnames
else:
raise ValueError('dataStructure not specified')
return self.fnames
except ValueError:
props = PropertiesDlg(self)
if props.exec_() == QDialog.Accepted:
return self.getWFFnames()
else:
return
2014-11-28 11:15:49 +01:00
def saveData(self):
2014-12-17 12:16:32 +01:00
settings = QSettings()
exform = settings.value('data/exportFormat', 'None')
try:
self.data.exportEvent(self.fname, exform)
except FormatError:
return False
except AttributeError:
fname, = QFileDialog.getSaveFileName(self, 'Save event')
self.data.exportEvent(fname, exform)
2014-12-08 10:26:14 +01:00
return True
2014-11-28 11:15:49 +01:00
def getComponent(self):
return self.dispComponent
def getData(self):
return self.data
def getDataWidget(self):
return self.DataPlot
def addMenuActions(self, menu, actions):
for action in actions:
if action is None:
menu.addSeparator()
else:
menu.addAction(action)
def okToContinue(self):
if self.dirty:
return self.saveData()
return True
def loadWaveformData(self):
if self.fnames and self.okToContinue():
self.dirty = True
self.data.setWFData(self.fnames)
elif self.fnames is None and self.okToContinue():
self.data.setWFData(self.getWFFnames())
self.plotData()
def plotData(self):
self.getData().plotData(self.getDataWidget())
2014-03-19 12:24:41 +01:00
2014-11-28 11:15:49 +01:00
def filterData(self):
if self.getData():
kwargs = {}
freq = self.getFilterOptions().getFreq()
if len(freq) > 1:
kwargs['freqmin'] = freq[0]
kwargs['freqmax'] = freq[1]
else:
kwargs['freq'] = freq
kwargs['type'] = self.getFilterOptions().getFilterType()
kwargs['corners'] = self.filteroptions.getOrder()
self.getData().filter(kwargs)
2014-11-28 11:15:49 +01:00
def adjustFilterOptions(self):
filteroptions = None
fstring = "Filter Options ({0})".format(self.getSeismicPhase())
filterDlg = FilterOptionsDialog(titleString=fstring,
parent=self,
filterOptions=self.getFilterOptions())
if filterDlg.exec_():
filteroptions = filterDlg.getFilterOptions()
assert isinstance(filteroptions, FilterOptions)
self.setFilterOptions(filteroptions)
def getFilterOptions(self):
return self.filteroptions
def setFilterOptions(self, filterOptions):
2015-01-29 08:53:01 +01:00
cases = {'P': self.filterOptionsP,
'S': self.filterOptionsS}
cases[self.getSeismicPhase()] = filterOptions
self.updateFilterOptions()
def updateFilterOptions(self):
try:
self.filteroptions = [self.filterOptionsP
if not self.seismicPhase == 'S'
2014-11-28 11:15:49 +01:00
else self.filterOptionsS][0]
except Exception, e:
2014-11-28 09:19:16 +01:00
self.updateStatus('Error ...')
2014-11-28 11:15:49 +01:00
emsg = QErrorMessage(self)
emsg.showMessage('Error: {0}'.format(e))
else:
2014-11-28 09:19:16 +01:00
self.updateStatus('Filter loaded ...')
def getSeismicPhase(self):
return self.seismicPhase
2014-11-28 11:15:49 +01:00
def alterPhase(self):
pass
def setSeismicPhase(self, phase):
2014-11-28 09:19:16 +01:00
self.seismicPhase = self.seismicPhaseButtonGroup.getValue()
def updateStatus(self, message):
self.statusBar().showMessage(message, 5000)
2014-12-08 10:26:14 +01:00
if self.getData() is not None:
if not self.getData().isNew():
2015-01-29 08:53:01 +01:00
self.setWindowTitle(
"PyLoT - processing event %s[*]" % self.getData().getID())
2014-12-08 10:26:14 +01:00
elif self.getData().isNew():
self.setWindowTitle("PyLoT - New event [*]")
else:
2015-01-29 08:53:01 +01:00
self.setWindowTitle(
"PyLoT - seismic processing the python way[*]")
2014-12-08 10:26:14 +01:00
self.setWindowTitle("PyLoT - seismic processing the python way[*]")
self.setWindowModified(self.dirty)
self.statusBar().showMessage(message, 5000)
2014-11-28 11:15:49 +01:00
def printEvent(self):
pass
def createNewEvent(self):
if self.okToContinue():
new = NewEventDlg()
if new.exec_() != QDialog.Rejected:
evtpar = new.getValues()
self.data = Data(self, evtdata=createEvent(**evtpar))
self.dirty = True
2014-12-08 10:26:14 +01:00
def closeEvent(self, event):
if self.okToContinue():
self.closing.emit()
QMainWindow.closeEvent(self, event)
2014-11-28 11:15:49 +01:00
def PyLoTprefs(self):
props = PropertiesDlg(self)
if props.exec_():
return
def helpHelp(self):
if checkurl():
2015-01-29 08:53:01 +01:00
form = HelpForm(
'https://ariadne.geophysik.ruhr-uni-bochum.de/trac/PyLoT/wiki')
else:
form = HelpForm(':/help.html')
form.show()
def main():
# create the Qt application
2014-12-01 08:40:35 +01:00
pylot_app = QApplication(sys.argv[0])
# set Application Information
pylot_app.setOrganizationName("Ruhr-University Bochum / MAGS2")
pylot_app.setOrganizationDomain("rub.de")
pylot_app.setApplicationName("PyLoT")
2014-11-28 09:19:16 +01:00
pylot_app.setWindowIcon(QIcon(":/icon.ico"))
# create the main window
pylot_form = MainWindow()
# Show main window and run the app
pylot_form.show()
2014-12-08 10:26:14 +01:00
pylot_app.exec_()
2015-01-29 08:53:01 +01:00
if __name__ == "__main__":
2014-12-08 10:26:14 +01:00
sys.exit(main())