From ad62284e0e4feb7bf9a650bbdd64f6536fc92062 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 7 Jun 2024 13:09:34 +0200 Subject: [PATCH] [update] improve pickfile selection, give the ability to select only specific files --- PyLoT.py | 11 +- pylot/core/util/widgets.py | 318 +++++++++++++++++++++++++++++++++---- 2 files changed, 294 insertions(+), 35 deletions(-) diff --git a/PyLoT.py b/PyLoT.py index 2c2a2a48..919780f5 100755 --- a/PyLoT.py +++ b/PyLoT.py @@ -1011,13 +1011,12 @@ class MainWindow(QMainWindow): events=events) if not sld.exec_(): return - fext = sld.comboBox.currentText() - # fext = '.xml' + filenames = sld.getChecked() for event in events: - filename = get_pylot_eventfile_with_extension(event, fext) - if filename: - self.load_data(filename, draw=False, event=event, overwrite=True) - refresh = True + for filename in filenames: + if os.path.isfile(filename) and event.pylot_id in filename: + self.load_data(filename, draw=False, event=event, overwrite=True) + refresh = True if not refresh: return if self.get_current_event().pylot_picks: diff --git a/pylot/core/util/widgets.py b/pylot/core/util/widgets.py index f3b9d33a..3b0d0105 100644 --- a/pylot/core/util/widgets.py +++ b/pylot/core/util/widgets.py @@ -7,6 +7,7 @@ Created on Wed Mar 19 11:27:35 2014 import copy import datetime import getpass +import glob import multiprocessing import os import subprocess @@ -16,6 +17,7 @@ import traceback import matplotlib import numpy as np +from pylot.core.io.phases import getQualitiesfromxml matplotlib.use('QT5Agg') @@ -860,13 +862,26 @@ class WaveformWidgetPG(QtWidgets.QWidget): def clearPlotDict(self): self.plotdict = dict() - def plotWFData(self, wfdata, wfsyn=None, title=None, zoomx=None, zoomy=None, - noiselevel=None, scaleddata=False, mapping=True, - component='*', nth_sample=1, iniPick=None, verbosity=0, - method='normal', gain=1.): + def plotWFData(self, wfdata, wfsyn=None, title=None, scaleddata=False, mapping=True, + component='*', nth_sample=1, verbosity=0, method='normal', gain=1., shift_syn=0.2): + def station_sort(nslc): + """Try to sort after station integer in case of a line array (e.g. active seismics)""" + try: + rval = sorted(nslc, key=lambda x: int(x.split('.')[1])) + return rval + except ValueError as e: + # this is the standard case for seismological stations + pass + except Exception as e: + print(f'Sorting by station integer failed with unknown exception: {e}') + + # fallback to default sorting + return sorted(nslc) + if not wfdata: print('Nothing to plot.') return + self.title = title self.clearPlotDict() self.wfstart, self.wfend = full_range(wfdata) @@ -884,14 +899,14 @@ class WaveformWidgetPG(QtWidgets.QWidget): else: st_select = wfdata - st_select, gaps = merge_stream(st_select) + # st_select, gaps = check_for_gaps_and_merge(st_select) #MP MP commented because probably done twice # list containing tuples of network, station, channel (for sorting) nslc = [] for trace in st_select: nslc.append( trace.get_id()) # (trace.stats.network, trace.stats.station, trace.stats.location trace.stats.channel)) - nslc.sort() + nslc = station_sort(nslc) nslc.reverse() plots = [] @@ -955,7 +970,7 @@ class WaveformWidgetPG(QtWidgets.QWidget): self.ylabel = '' self.setXLims([0, self.wfend - self.wfstart]) self.setYLims([0.5, nmax + 0.5]) - return plots, gaps + return plots def minMax(self, trace, time_ax): ''' @@ -977,7 +992,7 @@ class WaveformWidgetPG(QtWidgets.QWidget): min_ = data.min(axis=1) max_ = data.max(axis=1) if remaining_samples: - extreme_values = np.empty((npixel + 1, 2), dtype=np.float) + extreme_values = np.empty((npixel + 1, 2), dtype=float) extreme_values[:-1, 0] = min_ extreme_values[:-1, 1] = max_ extreme_values[-1, 0] = \ @@ -985,7 +1000,7 @@ class WaveformWidgetPG(QtWidgets.QWidget): extreme_values[-1, 1] = \ trace.data[-remaining_samples:].max() else: - extreme_values = np.empty((npixel, 2), dtype=np.float) + extreme_values = np.empty((npixel, 2), dtype=float) extreme_values[:, 0] = min_ extreme_values[:, 1] = max_ data = extreme_values.flatten() @@ -1555,6 +1570,160 @@ class PylotCanvas(FigureCanvas): self.draw() +class SearchFileByExtensionDialog(QtWidgets.QDialog): + def __init__(self, parent=None, label='Text: ', default_text='.xml', events=None): + super(SearchFileByExtensionDialog, self).__init__(parent) + self.events = events + self.filepaths = [] + self.file_extensions = [] + self.check_all_state = True + self.default_text = default_text + self.label = label + self.setButtons() + self.setupUi() + self.connectSignals() + self.showPaths() + self.refreshSelectionBox() + # self.refresh_timer = QTimer(self) + # self.refresh_timer.timeout.connect(self.showPaths) + # self.refresh_timer.start(10000) + + self.resize(800, 450) + + def setupUi(self): + ncol = 4 + self.main_layout = QtWidgets.QVBoxLayout() + self.header_layout = QtWidgets.QHBoxLayout() + self.footer_layout = QtWidgets.QHBoxLayout() + # + self.setLayout(self.main_layout) + + # widgets inside the dialog + self.textLabel = QtWidgets.QLabel(self.label) + self.comboBox = QtWidgets.QComboBox() + self.comboBox.addItem(self.default_text) + self.comboBox.setEditable(True) + + # optional search button, currently disabled. List refreshed when text changes + self.searchButton = QtWidgets.QPushButton('Search') + self.searchButton.setVisible(False) + + # check/uncheck button for table + self.checkAllButton = QtWidgets.QPushButton('Check/Uncheck all') + + # table + self.tableWidget = QtWidgets.QTableWidget() + tableWidget = self.tableWidget + tableWidget.setColumnCount(ncol) + tableWidget.setRowCount(len(self.events)) + tableWidget.setHorizontalHeaderLabels(('', 'Event ID', 'Filename', 'Last modified')) + tableWidget.setEditTriggers(tableWidget.NoEditTriggers) + tableWidget.setSortingEnabled(True) + header = tableWidget.horizontalHeader() + header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) + header.setStretchLastSection(True) + + self.statusText = QtWidgets.QLabel() + + self.header_layout.addWidget(self.textLabel) + self.header_layout.addWidget(self.comboBox) + self.header_layout.addWidget(self.searchButton) + + self.footer_layout.addWidget(self.checkAllButton) + self.footer_layout.addWidget(self.statusText) + self.footer_layout.setStretch(0, 0) + + self.main_layout.addLayout(self.header_layout) + self.main_layout.addWidget(self.tableWidget) + self.main_layout.addLayout(self.footer_layout) + self.main_layout.addWidget(self._buttonbox) + + def showPaths(self): + self.filepaths = [] + fext = self.comboBox.currentText() + self.tableWidget.clearContents() + for index, event in enumerate(self.events): + filename = get_pylot_eventfile_with_extension(event, fext) + pf_selected_item = QtWidgets.QTableWidgetItem() + if filename: + pf_selected_item.setCheckState(QtCore.Qt.Checked) + #check_state = QtCore.Qt.Checked if filename else QtCore.Qt.Unchecked + #pf_selected_item.setCheckState(check_state) + self.tableWidget.setItem(index, 0, pf_selected_item) + self.tableWidget.setItem(index, 1, QtWidgets.QTableWidgetItem(f'{event.pylot_id}')) + if filename: + self.filepaths.append(filename) + ts = int(os.path.getmtime(filename)) + + # create QTableWidgetItems of filepath and last modification time + fname_item = QtWidgets.QTableWidgetItem(f'{os.path.split(filename)[-1]}') + fname_item.setData(3, filename) + ts_item = QtWidgets.QTableWidgetItem(f'{datetime.datetime.fromtimestamp(ts)}') + self.tableWidget.setItem(index, 2, fname_item) + self.tableWidget.setItem(index, 3, ts_item) + + self.update_status() + + def refreshSelectionBox(self): + fext = self.comboBox.currentText() + self.file_extensions = [fext] + + for event in self.events: + extensions = get_possible_pylot_eventfile_extensions(event, '*.xml') + for ext in extensions: + if not ext in self.file_extensions: + self.file_extensions.append(ext) + + self.comboBox.clear() + for ext in sorted(self.file_extensions): + self.comboBox.addItem(ext) + + def setButtons(self): + self._buttonbox = QDialogButtonBox(QDialogButtonBox.Ok | + QDialogButtonBox.Cancel) + + def toggleCheckAll(self): + self.check_all_state = not self.check_all_state + self.checkAll(self.check_all_state) + + def checkAll(self, state): + state = QtCore.Qt.Checked if state else QtCore.Qt.Unchecked + for row_ind in range(self.tableWidget.rowCount()): + item = self.tableWidget.item(row_ind, 0) + item.setCheckState(state) + + def getChecked(self): + filepaths = [] + for row_ind in range(self.tableWidget.rowCount()): + item_check = self.tableWidget.item(row_ind, 0) + if item_check.checkState() == QtCore.Qt.Checked: + item_fname = self.tableWidget.item(row_ind, 2) + filepath = item_fname.data(3) + filepaths.append(filepath) + return filepaths + + def update_status(self, row=None, col=None): + if col is not None and col != 0: + return + filepaths = self.getChecked() + if len(filepaths) > 0: + status_text = f'Found {len(filepaths)} eventfiles. Do you want to load them?' + else: + status_text = 'Did not find any files for specified file mask.' + self.statusText.setText(status_text) + + + def connectSignals(self): + self._buttonbox.accepted.connect(self.accept) + self._buttonbox.rejected.connect(self.reject) + self.comboBox.editTextChanged.connect(self.showPaths) + self.searchButton.clicked.connect(self.showPaths) + self.checkAllButton.clicked.connect(self.toggleCheckAll) + self.checkAllButton.clicked.connect(self.update_status) + self.tableWidget.cellClicked.connect(self.update_status) + + + class SingleTextLineDialog(QtWidgets.QDialog): def __init__(self, parent=None, label='Text: ', default_text='.xml'): super(SingleTextLineDialog, self).__init__(parent) @@ -1786,7 +1955,7 @@ class PickDlg(QDialog): # init expected picks using obspy Taup try: - if self.metadata and model is not None: + if self.metadata and model != "None": self.model = TauPyModel(model) self.get_arrivals() self.drawArrivals() @@ -2146,7 +2315,7 @@ class PickDlg(QDialog): # create action and add to menu # phase name transferred using lambda function - slot = lambda phase=phase, phaseID=phaseID: phaseSelect[phaseID](phase) + slot = lambda ph=phase, phID=phaseID: phaseSelect[phID](ph) picksAction = createAction(parent=self, text=phase, slot=slot, shortcut=shortcut) @@ -2513,7 +2682,9 @@ class PickDlg(QDialog): data.normalize() if not data: QtWidgets.QMessageBox.warning(self, 'No channel to plot', - 'No channel to plot for phase: {}.'.format(phase)) + 'No channel to plot for phase: {}. ' + 'Make sure to select the correct channels for P and S ' + 'in the menu in the top panel.'.format(phase)) self.leave_picking_mode() return @@ -2653,7 +2824,7 @@ class PickDlg(QDialog): minFMSNR = parameter.get('minFMSNR') quality = get_quality_class(spe, parameter.get('timeerrorsP')) if quality <= minFMweight and snr >= minFMSNR: - FM = fmpicker(self.getWFData().select(channel=channel), wfdata, parameter.get('fmpickwin'), + FM = fmpicker(self.getWFData().select(channel=channel).copy(), wfdata.copy(), parameter.get('fmpickwin'), pick - stime_diff) # save pick times for actual phase @@ -3566,8 +3737,9 @@ class TuneAutopicker(QWidget): # wfdat = remove_underscores(wfdat) # rotate misaligned stations to ZNE # check for gaps and doubled channels - wfdat, gaps = merge_stream(wfdat) - # check4gaps(wfdat) + wfdat, _ = check_for_gaps_and_merge(wfdat) + # check for nans + check_for_nan(wfdat) check4doubled(wfdat) wfdat = check4rotated(wfdat, self.parent().metadata, verbosity=0) # trim station components to same start value @@ -3611,14 +3783,14 @@ class TuneAutopicker(QWidget): self.listWidget.scrollToBottom() def get_current_event(self): - path = self.eventBox.currentText() + path = self.get_current_event_fp() return self.parent().project.getEventFromPath(path) def get_current_event_name(self): - return self.eventBox.currentText().split('/')[-1].split('*')[0] + return self.eventBox.currentText().split('/')[-1].rstrip('*') def get_current_event_fp(self): - return self.eventBox.currentText().split('*')[0] + return self.eventBox.currentText().rstrip('*') def get_current_event_picks(self, station): event = self.get_current_event() @@ -3722,6 +3894,7 @@ class TuneAutopicker(QWidget): st = self.data.getWFData() tr = st.select(station=self.get_current_station())[0] starttime = tr.stats.starttime + # create two lists with figure names and subindices (for subplots) to get the correct axes p_axes = [ ('mainFig', 0), ('aicFig', 0), @@ -5663,9 +5836,23 @@ class ChooseWaveFormWindow(QWidget): def submit(self): matplotlib.pyplot.close(self.currentSpectro) t = self.chooseBoxTraces.currentText() + " " + self.chooseBoxComponent.currentText() - self.currentSpectro = self.traces[ - self.chooseBoxTraces.currentText()[3:]][self.chooseBoxComponent.currentText()].spectrogram(show=False, title=t) - self.currentSpectro.show() + #self.currentSpectro = self.traces[ + # self.chooseBoxTraces.currentText()[3:]][self.chooseBoxComponent.currentText()].spectrogram(show=False, title=t) + #self.currentSpectro.show() + self.applyFFT() + + def applyFFT(self, trace): + tra = self.traces[self.chooseBoxTraces.currentText()[3:]]['Z'] + transformed = abs(np.fft.rfft(tra.data)) + print ( transformed ) + matplotlib.pyplot.plot ( transformed ) + matplotlib.pyplot.show() + + def applyFFTs(self, tra): + transformed = abs(np.fft.rfft(tra.data)) + print ( transformed ) + matplotlib.pyplot.plot ( transformed ) + matplotlib.pyplot.show() def submitN(self): matplotlib.pyplot.close(self.currentSpectro) @@ -5681,13 +5868,6 @@ class ChooseWaveFormWindow(QWidget): self.chooseBoxTraces.currentText()[3:]]['E'].spectrogram(show=False, title=t) self.currentSpectro.show() - def submitZ(self): - matplotlib.pyplot.close(self.currentSpectro) - t = self.chooseBoxTraces.currentText() + " " + self.chooseBoxComponent.currentText() - self.currentSpectro = self.traces[ - self.chooseBoxTraces.currentText()[3:]]['Z'].spectrogram(show=False, title=t) - self.currentSpectro.show() - # Creates a QComboBox and adds all traces provided def createComboBoxTraces(self): if len(self.wFs) <= 0: @@ -5714,7 +5894,87 @@ class ChooseWaveFormWindow(QWidget): pass +class SpectrogramTab(QWidget): + def __init__(self, traces, wfdata, parent=None): + super(SpectrogramTab, self).__init__(parent) + self.setupUi() + self.traces = traces + self.wfdata = wfdata + + def setupUi(self): + pass + def makeSpecFig(self, direction = 'Z', height = 0, width = 0, parent = None): + + i = 0 + grams = [] + figure, axis = matplotlib.pyplot.subplots(len(self.traces), sharex=True) + + start, end = full_range(self.wfdata) + + if height != 0 and width != 0: + figure.figsize = (width, height) + figure.set_figwidth = width + figure.set_figheight = height + + #figure.tight_layout() + + for t in self.traces: + tra = self.traces[t][direction] + #print(start, end) + + # Set Title + if i == 0: + if direction == 'Z': + figure.suptitle("section: vertical components") + elif direction == 'E': + figure.suptitle("section: east-west components") + elif direction == 'N': + figure.suptitle("section: north-south components") + axis[i].vlines(0, axis[i].get_ylim()[0], axis[i].get_ylim()[1], + colors='m', linestyles='dashed', + linewidth=2) + + # Different axis settings for visual improvements + # axis[i].set_xlim(left=0, right=end - start) + # axis[i].spines['top'].set_visible(False) + # axis[i].spines['right'].set_visible(False) + # # axis[i].spines['left'].set_visible(False) + + # axis[i].tick_params(axis='x', which='both', bottom=False, top=False, labelbottom=False) + # if not (len(self.traces) == i - 1): + # axis[i].spines['bottom'].set_visible(False) + # axis[i].set_yticks([]) + # axis[i].set_ylabel(t, loc='center', rotation='horizontal') + + #ax.axhline(n, color="0.5", lw=0.5) + + + grams.append(tra.spectrogram(show=False, axes=axis[i])) + i+=1 + + #figure.setXLims([0, end - start]) + figure.set_tight_layout(True) + fC = FigureCanvas(figure) + return fC + + #for t in self.traces: + # tra = self.traces[t]['Z'] + # transformed = abs(np.fft.rfft(tra.data)) + # axis[i].plot(transformed, label=t) + # # axis[i].tick_params(labelbottom=False) + # axis[i].spines['top'].set_visible(False) + # axis[i].spines['right'].set_visible(False) + # axis[i].spines['left'].set_visible(False) + # if not (len(self.traces) == i - 1): + # axis[i].spines['bottom'].set_visible(False) + # axis[i].set_yticks([]) + # axis[i].set_ylabel(t, loc='center', rotation='horizontal') + # # axis[i].axis('off') + # i += 1 + # # self.applyFFTs(t) + + if __name__ == '__main__': import doctest - doctest.testmod() \ No newline at end of file + doctest.testmod()