diff --git a/QtPyLoT.py b/QtPyLoT.py index 4f71b66e..909cf8fc 100755 --- a/QtPyLoT.py +++ b/QtPyLoT.py @@ -104,7 +104,7 @@ class MainWindow(QMainWindow): self.setWindowTitle("PyLoT - do seismic processing the python way") self.setWindowIcon(QIcon(":/icon.ico")) - xlab = self.startTime.strftime('seconds since %d %b %Y %H:%M:%S (%Z)') + xlab = self.startTime.strftime('seconds since %Y/%m/%d %H:%M:%S (%Z)') _widget = QWidget() _widget.setCursor(Qt.CrossCursor) @@ -365,7 +365,11 @@ class MainWindow(QMainWindow): self.plotWaveformData() def plotWaveformData(self): - self.getData().plotWFData(self.getPlotWidget()) + zne_text = {'Z': 'vertical', 'N': 'north-south', 'E': 'east-west'} + comp = self.getComponent() + title = 'overview: {0} components'.format(zne_text[comp]) + wfst = self.getData().getWFData().select(component=comp) + self.getPlotWidget().plotWFData(wfdata=wfst, title=title) def filterWaveformData(self): if self.getData(): @@ -455,10 +459,14 @@ class MainWindow(QMainWindow): wfID = self.getWFID(event) station = self.getStationName(wfID) - data = self.getData().getWFData() - pickDlg = PickDlg(self, data.select(station=station), station) print 'picking on station {0}'.format(station) - pickDlg.exec_() + data = self.getData().getWFData() + pickDlg = PickDlg(self, data=data.select(station=station), + station=station) + if pickDlg.exec_(): + print 'picks accepted' + else: + print 'picks not saved and closed dialog' def updateStatus(self, message): diff --git a/pylot/core/read/data.py b/pylot/core/read/data.py index 44249638..874c7aac 100644 --- a/pylot/core/read/data.py +++ b/pylot/core/read/data.py @@ -10,7 +10,8 @@ from obspy.core.event import (Event, Catalog) from pylot.core.read import readPILOTEvent -from pylot.core.util import fnConstructor, createEvent, FormatError +from pylot.core.util import fnConstructor, createEvent, FormatError, \ + prepTimeAxis, getGlobalTimes class Data(object): @@ -63,14 +64,7 @@ class Data(object): return self.cuttimes def updateCutTimes(self): - min_start = UTCDateTime() - max_end = None - for trace in self.getWFData().select(component=self.getComp()): - if trace.stats.starttime < min_start: - min_start = trace.stats.starttime - if max_end is None or trace.stats.endtime > max_end: - max_end = trace.stats.endtime - self.cuttimes = [min_start, max_end] + self.cuttimes = getGlobalTimes(self.getWFData()) def exportEvent(self, fnout=None, evtformat='QUAKEML'): @@ -99,29 +93,6 @@ class Data(object): raise KeyError('''{0} export format not implemented: {1}'''.format(evtformat, e)) - def plotWFData(self, widget): - wfst = self.getWFData().select(component=self.getComp()) - widget.axes.cla() - for n, trace in enumerate(wfst): - stime = trace.stats.starttime - self.getCutTimes()[0] - etime = trace.stats.endtime - self.getCutTimes()[1] - srate = trace.stats.sampling_rate - nsamp = len(trace.data) - tincr = trace.stats.delta - station = trace.stats.station - time_ax = np.arange(stime, nsamp / srate, tincr) - trace.normalize(trace.data.max() * 2) - widget.axes.plot(time_ax, trace.data + n, 'k') - xlabel = 'seconds since {0}'.format(self.getCutTimes()[0]) - ylabel = '' - zne_text = {'Z': 'vertical', 'N': 'north-south', 'E': 'east-west'} - title = 'overview: {0} components'.format(zne_text[self.getComp()]) - widget.updateWidget(xlabel, ylabel, title) - widget.setPlotDict(n, station) - - widget.axes.autoscale(tight=True) - - def getComp(self): return self.comp @@ -146,7 +117,8 @@ class Data(object): def appendWFData(self, fnames): assert isinstance(fnames, list), "input parameter 'fnames' is " \ "supposed to be of type 'list' " \ - "but is actually".format(type(fnames)) + "but is actually {0}".format(type( + fnames)) if self.dirty: self.resetWFData() diff --git a/pylot/core/util/__init__.py b/pylot/core/util/__init__.py index 5248f170..b20bc021 100755 --- a/pylot/core/util/__init__.py +++ b/pylot/core/util/__init__.py @@ -4,7 +4,8 @@ from pylot.core.util.errors import OptionsError, FormatError, DatastructureError from pylot.core.util.layouts import layoutStationButtons from pylot.core.util.utils import fnConstructor, createArrival, createEvent,\ createPick, createAmplitude, createOrigin, createMagnitude, getOwner, \ - getHash, getLogin, createCreationInfo, createResourceID + getHash, getLogin, createCreationInfo, createResourceID, prepTimeAxis, \ + getGlobalTimes from pylot.core.util.widgets import PickDlg, HelpForm, FilterOptionsDialog,\ PropertiesDlg, NewEventDlg, MPLWidget, createAction from pylot.core.util.version import get_git_version as _getVersionString diff --git a/pylot/core/util/utils.py b/pylot/core/util/utils.py index ab37c2b0..a173fe40 100644 --- a/pylot/core/util/utils.py +++ b/pylot/core/util/utils.py @@ -6,6 +6,7 @@ import os import pwd import re import hashlib +import numpy as np from obspy.core import UTCDateTime import obspy.core.event as ope @@ -224,5 +225,30 @@ def createAmplitude(pickID, amp, unit, category, origintime, cinfo, def getOwner(fn): return pwd.getpwuid(os.stat(fn).st_uid).pw_name +def prepTimeAxis(stime, trace): + nsamp = trace.stats.npts + srate = trace.stats.sampling_rate + tincr = trace.stats.delta + etime = stime + nsamp / srate + time_ax = np.arange(stime, etime, tincr) + if len(time_ax) < nsamp: + print 'elongate time axes by one datum' + time_ax = np.arange(stime, etime + tincr, tincr) + elif len(time_ax) > nsamp: + print 'shorten time axes by one datum' + time_ax = np.arange(stime, etime - tincr, tincr) + if len(time_ax) != nsamp: + raise ValueError('{0} samples of data \n ' + '{1} length of time vector \n' + 'delta: {2}'.format(nsamp, len(time_ax), tincr)) + return time_ax - +def getGlobalTimes(stream): + min_start = UTCDateTime() + max_end = None + for trace in stream: + if trace.stats.starttime < min_start: + min_start = trace.stats.starttime + if max_end is None or trace.stats.endtime > max_end: + max_end = trace.stats.endtime + return [min_start, max_end] diff --git a/pylot/core/util/widgets.py b/pylot/core/util/widgets.py index 74cbcf37..7dd35262 100644 --- a/pylot/core/util/widgets.py +++ b/pylot/core/util/widgets.py @@ -43,6 +43,7 @@ from PySide.QtCore import (QSettings, from PySide.QtWebKit import QWebView from pylot.core.read import FilterOptions from pylot.core.util.defaults import OUTPUTFORMATS +from pylot.core.util import prepTimeAxis, getGlobalTimes def createAction(parent, text, slot=None, shortcut=None, icon=None, @@ -70,13 +71,18 @@ class MPLWidget(FigureCanvas): self._parent = None self.setParent(parent) self.figure = Figure() + # attribute plotdict is an dictionary connecting position and a name self.plotdict = dict() - + # create axes self.axes = self.figure.add_subplot(111) - self._statID = None + # clear axes each time plot is called + self.axes.hold(True) + # initialize super class FigureCanvas.__init__(self, self.figure) + # add an cursor for station selection self.multiCursor = MultiCursor(self.figure.canvas, (self.axes,), horizOn=True, color='m', lw=1) + # update labels of the entire widget self.updateWidget(xlabel, ylabel, title) def getPlotDict(self): @@ -91,6 +97,25 @@ class MPLWidget(FigureCanvas): def setParent(self, parent): self._parent = parent + def plotWFData(self, wfdata, title = None): + self.axes.lines = [] + wfstart = getGlobalTimes(wfdata)[0] + for n, trace in enumerate(wfdata): + station = trace.stats.station + print('plotting station: %s' % station) + stime = trace.stats.starttime - wfstart + time_ax = prepTimeAxis(stime, trace) + trace.detrend() + trace.detrend('demean') + trace.normalize(trace.data.max() * 2) + self.axes.plot(time_ax, trace.data + n, 'k') + self.axes.hold(True) + xlabel = 'seconds since {0}'.format(wfstart) + ylabel = '' + self.updateWidget(xlabel, ylabel, title) + self.setPlotDict(n, station) + self.axes.autoscale(tight=True) + def updateXLabel(self, text): self.axes.set_xlabel(text) @@ -105,6 +130,11 @@ class MPLWidget(FigureCanvas): self.updateYLabel(ylabel) self.updateTitle(title) + def insertLabel(self, pos, text): + pos = pos / max(self.axes.ylim) + axann = self.axes.annotate(text, xy=(.03, pos), xycoords='axes fraction') + axann.set_bbox(dict(facecolor='lightgrey', alpha=.6)) + class multiComponentPlot(FigureCanvas): @@ -165,12 +195,8 @@ class multiComponentPlot(FigureCanvas): self.axesdict = dict() # prepare variables for plotting - trace = data[0] - stime = trace.stats.starttime - srate = trace.stats.sampling_rate - npts = trace.stats.npts - tincr = trace.stats.delta - time_ax = np.arange(0, npts / srate, tincr) + stime = getGlobalTimes(self.getData())[0] + xlabel = 'time since {0} [s]'.format(stime) # plot individual component traces in separate subplots @@ -181,8 +207,9 @@ class multiComponentPlot(FigureCanvas): else: subax = self.figure.add_subplot(nsub) subax.autoscale(tight=True) - subset = data.copy().select(component=comp)[0].data - subax.plot(time_ax, subset) + subset = data.copy().select(component=comp)[0] + time_ax = prepTimeAxis(subset.stats.starttime - stime, subset) + subax.plot(time_ax, subset.data) self.axesdict[n] = subax self.updateYLabel(n, comp) if n == self.noc: @@ -190,12 +217,9 @@ class multiComponentPlot(FigureCanvas): else: self.updateXLabel(n, '') - self.multiCursor = MultiCursor(self.figure.canvas, tuple(self.axesdict.values()), color='r', lw=1) - - def insertLabel(self, pos, text): - subax = self.axesdict[pos] - axann = subax.annotate(text, xy=(.03, .97), xycoords='axes fraction') - axann.set_bbox(dict(facecolor='lightgrey', alpha=.6)) + self.multiCursor = MultiCursor(self.figure.canvas, + tuple(self.axesdict.values()), + color='r', lw=1) def updateXLabel(self, pos, text): self.axesdict[pos].set_xlabel(text) @@ -214,6 +238,7 @@ class PickDlg(QDialog): self.rotate = rotate self.components = 'ZNE' + # set attribute holding data if data is None: try: data = parent.getData().getWFData().copy() @@ -225,10 +250,16 @@ class PickDlg(QDialog): else: self.data = data - self.multicompfig = multiComponentPlot(data=data, parent=self, - components=self.getComponents()) + # initialize plotting widget + self.multicompfig = MPLWidget(self) + + # setup ui self.setupUi() + # plot data + self.getPlotWidget().plotWFData(wfdata=self.getWFData(), + title=self.getStation()) + def setupUi(self): # create actions @@ -266,6 +297,9 @@ class PickDlg(QDialog): def getComponents(self): return self.components + def getStation(self): + return self.station + def getPlotWidget(self): return self.multicompfig @@ -273,8 +307,9 @@ class PickDlg(QDialog): return self.data def filterWFData(self): - self.data.filter(type='bandpass', freqmin=.5, freqmax=15.) - self.getPlotWidget().resetPlot(self.getComponents(), self.getWFData()) + data = self.getWFData().copy().filter(type='bandpass', freqmin=.5, freqmax=15.) + title = self.getStation() + ' (filtered)' + self.getPlotWidget().plotWFData(wfdata=data, title=title) class PropertiesDlg(QDialog): @@ -300,12 +335,9 @@ class PropertiesDlg(QDialog): layout.addWidget(self.buttonBox) self.setLayout(layout) - self.connect(self.buttonBox, Signal("accepted()"), self, - Slot("accept()")) - self.connect(self.buttonBox.button(QDialogButtonBox.Apply), - Signal("clicked()"), self.apply) - self.connect(self.buttonBox, Signal("rejected()"), - self, Slot("reject()")) + self.buttonBox.accepted.connect(self.accept) + self.buttonBox.rejected.connect(self.reject) + self.buttonBox.button(QDialogButtonBox.Apply).clicked.connect(self.apply) def accept(self, *args, **kwargs): self.apply()