diff --git a/QtPyLoT.py b/QtPyLoT.py index e8709d0c..8209d464 100755 --- a/QtPyLoT.py +++ b/QtPyLoT.py @@ -42,6 +42,7 @@ from obspy import UTCDateTime from pylot.core.io.data import Data from pylot.core.io.inputs import FilterOptions, AutoPickParameter from pylot.core.pick.autopick import autopickevent +from pylot.core.pick.compare import Comparison from pylot.core.io.phases import picksdict_from_picks from pylot.core.loc.nll import locate as locateNll from pylot.core.util.defaults import FILTERDEFAULTS, COMPNAME_MAP, \ @@ -53,7 +54,8 @@ from pylot.core.util.utils import fnConstructor, getLogin, \ getGlobalTimes from pylot.core.io.location import create_creation_info, create_event from pylot.core.util.widgets import FilterOptionsDialog, NewEventDlg, \ - WaveformWidget, PropertiesDlg, HelpForm, createAction, PickDlg, getDataType + WaveformWidget, PropertiesDlg, HelpForm, createAction, PickDlg, \ + getDataType, ComparisonDialog from pylot.core.util.structure import DATASTRUCTURE from pylot.core.util.thread import AutoPickThread from pylot.core.util.version import get_git_version as _getVersionString @@ -578,7 +580,9 @@ class MainWindow(QMainWindow): def comparePicks(self): if self.check4Comparison(): - compare_dlg = ComparisonDialog(self) + co = Comparison(auto=self.getPicks('auto'), manu=self.getPicks()) + compare_dlg = ComparisonDialog(co, self) + compare_dlg.exec_() def getPlotWidget(self): return self.DataPlot @@ -749,7 +753,7 @@ class MainWindow(QMainWindow): wfID = self.getWFID(gui_event) - if not wfID: return + if wfID is None: return station = self.getStationName(wfID) self.updateStatus('picking on station {0}'.format(station)) diff --git a/pylot/core/pick/compare.py b/pylot/core/pick/compare.py index d246be67..b39f8ef8 100644 --- a/pylot/core/pick/compare.py +++ b/pylot/core/pick/compare.py @@ -29,10 +29,12 @@ class Comparison(object): names = list() self._pdfs = dict() for name, fn in kwargs.items(): - if not isinstance(PDFDictionary, fn): - self._pdfs[name] = PDFDictionary.from_quakeml(fn) - else: + if isinstance(fn, PDFDictionary): self._pdfs[name] = fn + elif isinstance(fn, dict): + self._pdfs[name] = PDFDictionary(fn) + else: + self._pdfs[name] = PDFDictionary.from_quakeml(fn) names.append(name) if len(names) > 2: raise ValueError('Comparison is only defined for two ' @@ -138,29 +140,28 @@ class Comparison(object): plt.show() - def get_expectation_array(self, phase): + def get_all(self, phasename): pdf_dict = self.comparison - exp_array = list() - for station, phases in pdf_dict.items(): + rlist = list() + for phases in pdf_dict.values(): try: - exp_array.append(phases[phase].expectation()) - except KeyError as e: - print('{err_msg}; station = {station}, phase = {phase}'.format( - err_msg=str(e), station=station, phase=phase)) + rlist.append(phases[phasename]) + except KeyError: continue - return exp_array + return rlist + + def get_array(self, phase, method_name): + import operator + method = operator.methodcaller(method_name) + pdf_list = self.get_all(phase) + rarray = map(method, pdf_list) + return np.array(rarray) + + def get_expectation_array(self, phase): + return self.get_array(phase, 'expectation') def get_std_array(self, phase): - pdf_dict = self.comparison - std_array = list() - for station, phases in pdf_dict.items(): - try: - std_array.append(phases[phase].standard_deviation()) - except KeyError as e: - print('{err_msg}; station = {station}, phase = {phase}'.format( - err_msg=str(e), station=station, phase=phase)) - continue - return std_array + return self.get_array(phase, 'standard_deviation') def hist_expectation(self, phases='all', bins=20, normed=False): phases.strip() @@ -222,6 +223,9 @@ class PDFDictionary(object): else: return True + def __getitem__(self, item): + return self.pdf_data[item] + @property def pdf_data(self): return self._pdfdata @@ -268,6 +272,8 @@ class PDFDictionary(object): for station, phases in pdf_picks.items(): for phase, values in phases.items(): + if phase not in 'PS': + continue phases[phase] = ProbabilityDensityFunction.from_pick( values['epp'], values['mpp'], diff --git a/pylot/core/util/widgets.py b/pylot/core/util/widgets.py index 0b30c0c8..5548746b 100644 --- a/pylot/core/util/widgets.py +++ b/pylot/core/util/widgets.py @@ -17,17 +17,18 @@ except ImportError: from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT from matplotlib.widgets import MultiCursor -from PySide.QtGui import QAction, QApplication, QComboBox, QDateTimeEdit, \ - QDialog, QDialogButtonBox, QDoubleSpinBox, QGroupBox, QGridLayout, \ - QIcon, QKeySequence, QLabel, QLineEdit, QMessageBox, QPixmap, QSpinBox, \ - QTabWidget, QToolBar, QVBoxLayout, QWidget, QPushButton, QFileDialog, \ - QInputDialog +from PySide.QtGui import QAction, QApplication, QCheckBox, QComboBox, \ + QDateTimeEdit, QDialog, QDialogButtonBox, QDoubleSpinBox, QGroupBox, \ + QGridLayout, QIcon, QKeySequence, QLabel, QLineEdit, QMessageBox, \ + QPixmap, QSpinBox, QTabWidget, QToolBar, QVBoxLayout, QWidget, \ + QPushButton, QFileDialog, QInputDialog from PySide.QtCore import QSettings, Qt, QUrl, Signal, Slot from PySide.QtWebKit import QWebView from obspy import Stream, UTCDateTime from pylot.core.io.inputs import FilterOptions from pylot.core.pick.utils import getSNR, earllatepicker, getnoisewin, \ getResolutionWindow +from pylot.core.pick.compare import Comparison from pylot.core.util.defaults import OUTPUTFORMATS, FILTERDEFAULTS, LOCTOOLS, \ COMPPOSITION_MAP from pylot.core.util.utils import prepTimeAxis, getGlobalTimes, scaleWFData, \ @@ -64,21 +65,70 @@ def createAction(parent, text, slot=None, shortcut=None, icon=None, action.setCheckable(True) return action -class ComparsionDialog(QDialog): +class ComparisonDialog(QDialog): def __init__(self, c, parent=None): self._data = c - self._stats = c.keys() - self._canvas = PlotWidget(parent) - super(ComparsionDialog, self).__init__(parent) - self + self._stats = c.stations + self._canvas = PlotWidget(self) + self._widgets = dict(stationsComboBox=None, + phasesComboBox=None, + histCheckBox=None) + self._phases = 'PS' + self._plotprops = dict(station=self.stations[0], phase=self.phases[0]) + super(ComparisonDialog, self).__init__(parent) + self.setupUI() + self.plotcomparison() def setupUI(self): - pass + + _outerlayout = QVBoxLayout(self) + _innerlayout = QVBoxLayout(self) + + _stats_combobox = QComboBox(self) + _stats_combobox.setObjectName('stationsComboBox') + _stats_combobox.setEditable(True) + _stats_combobox.setInsertPolicy(QComboBox.NoInsert) + _stats_combobox.addItems(self.stations) + _stats_combobox.editTextChanged.connect(self.prepareplot) + self.widgets = _stats_combobox + + _phases_combobox = QComboBox(self) + _phases_combobox.setObjectName('phasesComboBox') + _phases_combobox.addItems(['P', 'S']) + _phases_combobox.currentIndexChanged.connect(self.prepareplot) + self.widgets = _phases_combobox + + _hist_checkbox = QCheckBox('Show histograms', self) + _hist_checkbox.setObjectName('histCheckBox') + _hist_checkbox.stateChanged.connect(self.plothist) + self.widgets = _hist_checkbox + + _toolbar = QToolBar(self) + _toolbar.addWidget(_stats_combobox) + _toolbar.addWidget(_phases_combobox) + _toolbar.addWidget(_hist_checkbox) + + _buttonbox = QDialogButtonBox(QDialogButtonBox.Close) + + _innerlayout.addWidget(self.canvas) + _innerlayout.addWidget(_buttonbox) + + _outerlayout.addWidget(_toolbar) + _outerlayout.addLayout(_innerlayout) + + _buttonbox.rejected.connect(self.reject) + + # finally layout the entire dialog + self.setLayout(_outerlayout) @property def canvas(self): return self._canvas + @canvas.setter + def canvas(self, canvas_obj): + self._canvas = canvas_obj + @property def stations(self): return self._stats @@ -87,15 +137,163 @@ class ComparsionDialog(QDialog): def stations(self, stations): self._stats = stations + @property + def phases(self): + return self._phases + + @phases.setter + def phases(self, value): + self._phases = value + + @property + def plotprops(self): + return self._plotprops + + @plotprops.setter + def plotprops(self, values): + try: + key, value = values + if key not in self.plotprops.keys(): + raise KeyError("'key' {0} not found in " + "ComparisonDialog.plotprops keys.".format(key)) + except ValueError: + raise ValueError("Pass an iterable with two items") + else: + self._plotprops[key] = value + @property def data(self): return self._data @data.setter def data(self, data): - self.stations = data.keys() + assert not isinstance(data, Comparison) + self.stations = data.stations self._data = data + @property + def widgets(self): + return self._widgets + + @widgets.setter + def widgets(self, widget): + name = widget.objectName() + if name in self.widgets.keys(): + self._widgets[name] = widget + + def hasvalue(self, sender): + text = sender.currentText() + index = sender.findText(text.upper()) + return index + + def prepareplot(self): + try: + _widget = self.sender() + name = _widget.objectName() + text = _widget.currentText().upper() + index = self.hasvalue(_widget) + if name == 'stationsComboBox' and index is not -1: + _widget.setCurrentIndex(index) + self.plotprops = ('station', text) + elif name == 'phasesComboBox': + self.plotprops = ('phase', text) + except ValueError: + raise ValueError('No sender widget given!') + finally: + self.plotcomparison() + + def plotcomparison(self): + from matplotlib import gridspec + _axes = self.canvas.figure.add_subplot(111) + + _gs = gridspec.GridSpec(3, 2) + _axes = self.canvas.figure.add_subplot(_gs[0:2, :]) + _ax1 = self.canvas.figure.add_subplot(_gs[2, 0]) + _ax2 = self.canvas.figure.add_subplot(_gs[2, 1]) + + _axes.cla() + station = self.plotprops['station'] + phase = self.plotprops['phase'] + pdf = self.data.comparison[station][phase] + x, y, std, exp = pdf.axis, pdf.data, pdf.standard_deviation(), \ + pdf.expectation() + _axes.plot(x, y) + _axes.set_title(phase) + _axes.set_ylabel('propability density [-]') + _axes.set_xlabel('time difference [s]') + annotation = "{phase} difference on {station}\n" \ + "expectation: {exp}\n" \ + "std: {std}".format(station=station, phase=phase, + std=std, exp=exp) + _anno = _axes.annotate(annotation, xy=(.05, .5), xycoords='axes ' + 'fraction') + bbox_props = dict(boxstyle='round', facecolor='lightgrey', alpha=.7) + _anno.set_bbox(bbox_props) + + pdf_a = self.data.get('auto')[station][phase] + pdf_m = self.data.get('manu')[station][phase] + xauto, yauto, stdauto, expauto = pdf_a.axis, pdf_a.data, \ + pdf_a.standard_deviation(), \ + pdf_a.expectation() + xmanu, ymanu, stdmanu, expmanu = pdf_m.axis, pdf_m.data, \ + pdf_m.standard_deviation(), \ + pdf_m.expectation() + + _ax1.plot(xauto, yauto) + + _ax2.plot(xmanu, ymanu) + + _gs.update(wspace=0.5, hspace=0.5) + + self.canvas.draw() + + def plothist(self): + name = self.sender().objectName() + if self.widgets[name].isChecked(): + for wname, widget in self.widgets.items(): + if wname != name: + self.widgets[wname].setEnabled(False) + self.canvas.figure.clf() + _axPstd, _axPexp = self.canvas.figure.add_subplot(221), self.canvas.figure.add_subplot(223) + _axSstd, _axSexp = self.canvas.figure.add_subplot(222), self.canvas.figure.add_subplot(224) + axes_dict = dict(P=dict(std=_axPstd, exp=_axPexp), + S=dict(std=_axSstd, exp=_axSexp)) + bbox_props = dict(boxstyle='round', facecolor='lightgrey', alpha=.7) + for phase in self.phases: + std = self.data.get_std_array(phase) + std = std[np.isfinite(std)] + stdxlims = [0., 1.2 * max(std)] + exp = self.data.get_expectation_array(phase) + exp = exp[np.isfinite(exp)] + eps_exp = 0.05 * (max(exp) - min(exp)) + expxlims = [min(exp) - eps_exp, max(exp) + eps_exp] + axes_dict[phase]['std'].hist(std, range=stdxlims, bins=20, normed=False) + axes_dict[phase]['exp'].hist(exp, range=expxlims, bins=20, + normed=False) + std_annotation = "Distribution curve for {phase} differences'\n" \ + "standard deviations (all stations)\n" \ + "number of samples: {nsamples}".format(phase=phase, nsamples=len(std)) + _anno_std = axes_dict[phase]['std'].annotate(std_annotation, xy=(.05, .8), xycoords='axes fraction') + _anno_std.set_bbox(bbox_props) + exp_annotation = "Distribution curve for {phase} differences'\n" \ + "expectations (all stations)\n" \ + "number of samples: {nsamples}".format(phase=phase, nsamples=len(exp)) + _anno_exp = axes_dict[phase]['exp'].annotate(exp_annotation, xy=(.05, .8), xycoords='axes fraction') + _anno_exp.set_bbox(bbox_props) + axes_dict[phase]['exp'].set_xlabel('expectation [s]') + axes_dict[phase]['std'].set_xlabel('standard deviation [s]') + + for ax in axes_dict['P'].values(): + ax.set_ylabel('frequency [-]') + + self.canvas.draw() + else: + for wname, widget in self.widgets.items(): + if wname != name: + self.widgets[wname].setEnabled(True) + self.canvas.figure.clf() + self.plotcomparison() + class PlotWidget(FigureCanvas): def __init__(self, parent=None, xlabel='x', ylabel='y', title='Title'):