From e31890d8fcbc07b0f0e31a7119f21b178cc05ecd Mon Sep 17 00:00:00 2001 From: Sebastian Wehling-Benatelli Date: Mon, 13 Jul 2015 09:24:16 +0200 Subject: [PATCH 1/6] export and save picks to hard drive --- pylot/RELEASE-VERSION | 2 +- pylot/core/read/data.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/pylot/RELEASE-VERSION b/pylot/RELEASE-VERSION index 52c00f77..d034b5be 100644 --- a/pylot/RELEASE-VERSION +++ b/pylot/RELEASE-VERSION @@ -1 +1 @@ -1abc-dirty +108d-dirty diff --git a/pylot/core/read/data.py b/pylot/core/read/data.py index 2ca981ed..cd24eccd 100644 --- a/pylot/core/read/data.py +++ b/pylot/core/read/data.py @@ -3,9 +3,9 @@ import os -from obspy.core import (read, Stream, UTCDateTime) +from obspy.core import read, Stream, UTCDateTime from obspy import readEvents, read_inventory -from obspy.core.event import (Event, Catalog) +from obspy.core.event import Event, Catalog, ResourceIdentifier from pylot.core.read.io import readPILOTEvent from pylot.core.util.utils import fnConstructor, getGlobalTimes @@ -156,10 +156,20 @@ class Data(object): def getEvtData(self): return self.evtdata - def applyEVTData(self, data, type='pick'): + def applyEVTData(self, data, type='pick', authority_id='rub'): def applyPicks(picks): - pass + firstonset = None + for phase in picks: + onset = picks[phase]['epp'] + if firstonset is None or firstonset > onset: + firstonset = onset + + if 'smi:local' in self.getID(): + fonset_str = firstonset.strftime('%Y_%m_%d_%H_%M_%S') + ID = ResourceIdentifier(fonset_str) + ID.convertIDToQuakeMLURI(authority_id=authority_id) + def applyArrivals(arrivals): pass def applyEvent(event): From 2115864d5c33e86e9f968efe1070f25afb961573 Mon Sep 17 00:00:00 2001 From: Sebastian Wehling-Benatelli Date: Tue, 14 Jul 2015 08:32:05 +0200 Subject: [PATCH 2/6] revert RELEASE-VERSION manually --- pylot/RELEASE-VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylot/RELEASE-VERSION b/pylot/RELEASE-VERSION index d034b5be..52c00f77 100644 --- a/pylot/RELEASE-VERSION +++ b/pylot/RELEASE-VERSION @@ -1 +1 @@ -108d-dirty +1abc-dirty From 17933c75f0ee8a4e220e1b0fc7bf78906cb5dcff Mon Sep 17 00:00:00 2001 From: Sebastian Wehling-Benatelli Date: Sat, 18 Jul 2015 15:59:42 +0200 Subject: [PATCH 3/6] file format for exporting event data is controlled by the extension used; this behavior is more convenient for a GUI driven file selection --- pylot/core/util/defaults.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pylot/core/util/defaults.py b/pylot/core/util/defaults.py index 1fde1c3a..b2bf2a6d 100644 --- a/pylot/core/util/defaults.py +++ b/pylot/core/util/defaults.py @@ -13,6 +13,6 @@ FILTERDEFAULTS = {'P': {'filtertype': None, 'order': 4, 'freq': [.5, 5]}} -OUTPUTFORMATS = {'QuakeML':'QUAKEML', - 'VelEst':'CNV', - 'NonLinLoc':'NLLOC_OBS'} +OUTPUTFORMATS = {'.xml':'QUAKEML', + '.cnv':'CNV', + '.obs':'NLLOC_OBS'} From fc86179c39950b09ccf0572075be4a9cc964cba6 Mon Sep 17 00:00:00 2001 From: Sebastian Wehling-Benatelli Date: Sat, 18 Jul 2015 16:09:50 +0200 Subject: [PATCH 4/6] [closes #145], [addresses #146] this commit introduces the handling of picks as obspy event objects --- pylot/core/read/data.py | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/pylot/core/read/data.py b/pylot/core/read/data.py index cd24eccd..fcd33226 100644 --- a/pylot/core/read/data.py +++ b/pylot/core/read/data.py @@ -5,7 +5,7 @@ import os from obspy.core import read, Stream, UTCDateTime from obspy import readEvents, read_inventory -from obspy.core.event import Event, Catalog, ResourceIdentifier +from obspy.core.event import Event, ResourceIdentifier, Pick, WaveformStreamID from pylot.core.read.io import readPILOTEvent from pylot.core.util.utils import fnConstructor, getGlobalTimes @@ -46,6 +46,7 @@ class Data(object): else: # create an empty Event object self.newevent = True self.evtdata = Event() + self.getEvtData().picks = [] self.wforiginal = None self.cuttimes = None self.dirty = False @@ -160,27 +161,47 @@ class Data(object): def applyPicks(picks): firstonset = None - for phase in picks: - onset = picks[phase]['epp'] - if firstonset is None or firstonset > onset: - firstonset = onset + for station, onsets in picks.items(): + print 'Reading picks on station %s' % station + for label, phase in onsets.items(): + onset = phase['mpp'] + epp = phase['epp'] + lpp = phase['lpp'] + error = phase['spe'] + pick = Pick() + pick.time = onset + pick.time_errors.lower_uncertainty = onset - epp + pick.time_errors.upper_uncertainty = lpp - onset + pick.time_errors.uncertainty = error + pick.phase_hint = label + pick.waveform_id = WaveformStreamID(station_code=station) + self.getEvtData().picks.append(pick) + try: + polarity = phase['fm'] + except KeyError, e: + print 'No polarity information found for %s' % phase + if firstonset is None or firstonset > onset: + firstonset = onset if 'smi:local' in self.getID(): fonset_str = firstonset.strftime('%Y_%m_%d_%H_%M_%S') - ID = ResourceIdentifier(fonset_str) + ID = ResourceIdentifier('event/' + fonset_str) ID.convertIDToQuakeMLURI(authority_id=authority_id) + self.getEvtData().resource_id = ID def applyArrivals(arrivals): pass + def applyEvent(event): pass - applydata = {'pick':applyPicks, - 'arrival':applyArrivals, - 'event':applyEvent} + applydata = {'pick': applyPicks, + 'arrival': applyArrivals, + 'event': applyEvent} applydata[type](data) + class GenericDataStructure(object): ''' GenericDataBase type holds all information about the current data- From f4f744e22cb184511ee806ce94fd28b6d6ceafe0 Mon Sep 17 00:00:00 2001 From: Sebastian Wehling-Benatelli Date: Sat, 18 Jul 2015 16:11:20 +0200 Subject: [PATCH 5/6] export and save picks implemented successfully --- QtPyLoT.py | 35 ++++++++++++++++++++++++++--------- pylot/core/read/data.py | 28 ++++++++++++---------------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/QtPyLoT.py b/QtPyLoT.py index df9241a9..4cabe758 100755 --- a/QtPyLoT.py +++ b/QtPyLoT.py @@ -23,7 +23,7 @@ https://www.iconfinder.com/iconsets/flavour (http://www.gnu.org/copyleft/lesser.html) """ -import sys +import os, sys import matplotlib matplotlib.use('Qt4Agg') @@ -64,7 +64,6 @@ class MainWindow(QMainWindow): super(MainWindow, self).__init__(parent) self.createAction = createAction - self.dirty = False settings = QSettings() if settings.value("user/FullName", None) is None: fulluser = QInputDialog.getText(self, "Enter Name:", "Full name") @@ -102,8 +101,8 @@ class MainWindow(QMainWindow): self.data = Data(self) # load and display waveform data - self.loadWaveformData() self.dirty = False + self.loadWaveformData() self.loadData() self.updateFilterOptions() @@ -333,6 +332,10 @@ class MainWindow(QMainWindow): self.fileMenu.addSeparator() self.fileMenu.addAction(self.fileMenuActions[-1]) + def getRoot(self): + settings = QSettings() + return settings.value("data/dataRoot") + def loadData(self, fname=None): if fname is None: try: @@ -390,18 +393,27 @@ class MainWindow(QMainWindow): else: return + def getEventFileName(self): + return self.getData().getEventFileName() + def saveData(self): settings = QSettings() exform = settings.value('data/exportFormat', 'QUAKEML') + self.getData().applyEVTData(self.getPicks()) try: - self.data.exportEvent(self.fname, exform) + self.getData().exportEvent(self.fname, exform) except FormatError: return False except AttributeError, e: print 'warning: {0}'.format(e) - fname = QFileDialog.getSaveFileName(self, 'Save event') - fname = fname[0] - self.getData().exportEvent(fname, exform) + directory = os.path.join(self.getRoot(), self.getEventFileName()) + file_filter = "Seismic observation files (*.cnv *.obs *.xml)" + fname = QFileDialog.getSaveFileName(self, 'Save event data ...', + directory, file_filter) + fbasename, exform = os.path.splitext(fname[0]) + if not fbasename: + return False + self.getData().exportEvent(fbasename, exform) return True def getComponent(self): @@ -455,7 +467,7 @@ class MainWindow(QMainWindow): def loadWaveformData(self): if self.fnames and self.okToContinue(): - self.dirty = True + self.setDirty(True) self.data.setWFData(self.fnames) elif self.fnames is None and self.okToContinue(): self.data.setWFData(self.getWFFnames()) @@ -585,6 +597,7 @@ class MainWindow(QMainWindow): station=station, picks=self.getPicksOnStation(station)) if pickDlg.exec_(): + self.setDirty(True) self.updateStatus('picks accepted ({0})'.format(station)) self.addPicks(station, pickDlg.getPicks()) self.drawPicks(station) @@ -593,6 +606,7 @@ class MainWindow(QMainWindow): def autoPick(self): list = QListWidget() + self.setDirty(True) logDockWidget = QDockWidget("AutoPickLog", self) logDockWidget.setObjectName("LogDockWidget") logDockWidget.setAllowedAreas(Qt.LeftDockWidgetArea) @@ -700,11 +714,14 @@ class MainWindow(QMainWindow): cinfo = createCreationInfo(agency_id=self.agency) event = createEvent(evtpar['origintime'], cinfo) self.data = Data(self, evtdata=event) - self.dirty = True + self.setDirty(True) def draw(self): self.getPlotWidget().draw() + def setDirty(self, value): + self.dirty = value + def closeEvent(self, event): if self.okToContinue(): self.closing.emit() diff --git a/pylot/core/read/data.py b/pylot/core/read/data.py index fcd33226..9cf2c8d7 100644 --- a/pylot/core/read/data.py +++ b/pylot/core/read/data.py @@ -68,29 +68,25 @@ class Data(object): def updateCutTimes(self): self.cuttimes = getGlobalTimes(self.getWFData()) - def exportEvent(self, fnout=None, evtformat='QUAKEML'): + def getEventFileName(self): + ID = self.getID() + # handle forbidden filenames especially on windows systems + return fnConstructor(str(ID)) + + def exportEvent(self, fnout, fnext='.xml'): from pylot.core.util.defaults import OUTPUTFORMATS - if evtformat.strip() not in OUTPUTFORMATS.values(): - errmsg = 'selected format {0} not available'.format(evtformat) + try: + evtformat = OUTPUTFORMATS[fnext] + except KeyError, e: + errmsg = '{0}; selected file extension {1} not ' \ + 'supported'.format(e, fnext) raise FormatError(errmsg) - if fnout is None: - ID = self.getID() - # handle forbidden filenames especially on windows systems - fnout = fnConstructor(str(ID)) - else: - fnout = fnConstructor(str(fnout)) - - evtformat = evtformat.upper().strip() - - # establish catalog object (event object has no write method) - cat = Catalog() - cat.append(self.getEvtData()) # try exporting event via ObsPy try: - cat.write(fnout + evtformat.lower(), format=evtformat) + self.getEvtData().write(fnout + fnext, format=evtformat) except KeyError, e: raise KeyError('''{0} export format not implemented: {1}'''.format(evtformat, e)) From 0b6fbd22c5dd95ca48ec5f3ba9830e354cc39c61 Mon Sep 17 00:00:00 2001 From: Sebastian Wehling-Benatelli Date: Sat, 18 Jul 2015 16:13:11 +0200 Subject: [PATCH 6/6] reformatting code to meet coding conventions --- pylot/core/read/data.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/pylot/core/read/data.py b/pylot/core/read/data.py index 9cf2c8d7..dad3bf56 100644 --- a/pylot/core/read/data.py +++ b/pylot/core/read/data.py @@ -116,7 +116,7 @@ class Data(object): assert isinstance(fnames, list), "input parameter 'fnames' is " \ "supposed to be of type 'list' " \ "but is actually {0}".format(type( - fnames)) + fnames)) if self.dirty: self.resetWFData() @@ -147,7 +147,7 @@ class Data(object): st = self.getWFData() inv = read_inventory(fninventory) st.attach_response(inv) - pre_filt = (0.005, 0.006, 30.0, 35.0) # set in autoPyLoT.in + pre_filt = (0.005, 0.006, 30.0, 35.0) # set in autoPyLoT.in st.remove_response(output='VEL', pre_filt=pre_filt) def getEvtData(self): @@ -307,14 +307,13 @@ class PilotDataStructure(GenericDataStructure): ''' def __init__(self, **fields): - if not fields: - fields = {'database':'2006.01', - 'root':'/data/Egelados/EVENT_DATA/LOCAL'} + fields = {'database': '2006.01', + 'root': '/data/Egelados/EVENT_DATA/LOCAL'} GenericDataStructure.__init__(self, **fields) - self.setExpandFields(['root','database']) + self.setExpandFields(['root', 'database']) class SeiscompDataStructure(GenericDataStructure): @@ -331,7 +330,6 @@ class SeiscompDataStructure(GenericDataStructure): filesuffix=None, **kwargs): super(GenericDataStructure, self).__init__() - edate = UTCDateTime() halfyear = UTCDateTime('1970-07-01') sdate = UTCDateTime(edate - halfyear) @@ -351,9 +349,9 @@ class SeiscompDataStructure(GenericDataStructure): # http://www.seiscomp3.org/wiki/doc/applications/slarchive/SDS self.dsFields = {'root': '/data/SDS', 'YEAR': year, 'NET': '??', - 'STA': '????', 'CHAN': 'HH?', 'TYPE': 'D', 'LOC': '', - 'DAY': '{0:03d}'.format(sdate.julday) - } + 'STA': '????', 'CHAN': 'HH?', 'TYPE': 'D', 'LOC': '', + 'DAY': '{0:03d}'.format(sdate.julday) + } self.modifiyFields(**kwargs) def modifiyFields(self, **kwargs):