# http://pyrocko.org - GPLv3 # # The Pyrocko Developers, 21st Century # ---|P------/S----------~Lg---------- Snuffling infrastructure
This module provides the base class :py:class:`Snuffling` for user-defined snufflings and some utilities for their handling. '''
VTKFrame, PixmapFrame, Marker, EventMarker, PhaseMarker, load_markers, save_markers)
if use_pyqt5: return x else: return x, None
self.widgetVisibilityChanged.emit(True)
self.widgetVisibilityChanged.emit(False)
''' Definition of an adjustable floating point parameter for the snuffling. The snuffling may display controls for user input for such parameters.
:param name: labels the parameter on the snuffling's control panel :param ident: identifier of the parameter :param default: default value :param minimum: minimum value for the parameter :param maximum: maximum value for the parameter :param low_is_none: if ``True``: parameter is set to None at lowest value of parameter range (optional) :param high_is_none: if ``True``: parameter is set to None at highest value of parameter range (optional) :param low_is_zero: if ``True``: parameter is set to value 0 at lowest value of parameter range (optional) '''
self, name, ident, default, minimum, maximum, low_is_none=None, high_is_none=None, low_is_zero=False):
default = None default = None
''' Definition of a boolean switch for the snuffling. The snuffling may display a checkbox for such a switch.
:param name: labels the switch on the snuffling's control panel :param ident: identifier of the parameter :param default: default value '''
''' Definition of a string choice for the snuffling. The snuffling may display a menu for such a choice.
:param name: labels the menu on the snuffling's control panel :param ident: identifier of the parameter :param default: default value :param choices: tuple of other options '''
''' Base class for user snufflings.
Snufflings are plugins for snuffler (and other applications using the :py:class:`pyrocko.pile_viewer.PileOverview` class defined in ``pile_viewer.py``). They can be added, removed and reloaded at runtime and should provide a simple way of extending the functionality of snuffler.
A snuffling has access to all data available in a pile viewer, can process this data and can create and add new traces and markers to the viewer. '''
''' Setup the snuffling.
This method should be implemented in subclass and contain e.g. calls to :py:meth:`set_name` and :py:meth:`add_parameter`. '''
pass
''' Returns the path of the directory where snufflings are stored.
The default path is ``$HOME/.snufflings``. '''
return self._path
''' Set parent viewer and hooks to add panel and menu entry.
This method is called from the :py:class:`pyrocko.pile_viewer.PileOverview` object. Calls :py:meth:`setup_gui`. '''
''' Create and add gui elements to the viewer.
This method is initially called from :py:meth:`init_gui`. It is also called, e.g. when new parameters have been added or if the name of the snuffling has been changed. '''
self.get_name(), self._panel, reloaded)
self._helpmenuitem)
''' Force to create a panel.
:param bool: if ``True`` will create a panel with Help, Clear and Run button. '''
self._force_panel = bool
import optparse
class MyOptionParser(optparse.OptionParser): def error(self, msg): logger.error(msg) self.exit(1)
parser = MyOptionParser()
parser.add_option( '--format', dest='format', default='from_extension', choices=( 'mseed', 'sac', 'kan', 'segy', 'seisan', 'seisan.l', 'seisan.b', 'gse1', 'gcf', 'yaff', 'datacube', 'from_extension', 'detect'), help='assume files are of given FORMAT [default: \'%default\']')
parser.add_option( '--pattern', dest='regex', metavar='REGEX', help='only include files whose paths match REGEX')
self.add_params_to_cli_parser(parser) self.configure_cli_parser(parser) return parser
pass
return None
for param in self._parameters: if isinstance(param, Param): parser.add_option( '--' + param.ident, dest=param.ident, default=param.default, type='float', help=param.name)
self.setup() parser = self.make_cli_parser1() (options, args) = parser.parse_args()
for param in self._parameters: if isinstance(param, Param): setattr(self, param.ident, getattr(options, param.ident))
self._cli_params['regex'] = options.regex self._cli_params['format'] = options.format self._cli_params['sources'] = args
return options, args, parser
''' Remove the gui elements of the snuffling.
This removes the panel and menu entry of the widget from the viewer and also removes all traces and markers added with the :py:meth:`add_traces` and :py:meth:`add_markers` methods. '''
self._helpmenuitem)
''' Set the snuffling's name.
The snuffling's name is shown as a menu entry and in the panel header. '''
''' Get the snuffling's name. '''
self._have_pre_process_hook = bool self._live_update = False self._pre_process_hook_enabled = False self.reset_gui()
self._have_post_process_hook = bool self._live_update = False self._post_process_hook_enabled = False self.reset_gui()
self._pile_ = False
''' Get informed when pile changed.
When activated, the :py:meth:`pile_changed` method is called on every update in the viewer's pile. '''
viewer = self.get_viewer() viewer.pile_has_changed_signal.connect( self.pile_changed)
''' Stop getting informed about changes in viewer's pile. '''
viewer = self.get_viewer() viewer.pile_has_changed_signal.disconnect( self.pile_changed)
''' Called when the connected viewer's pile has changed.
Must be activated with a call to :py:meth:`enable_pile_changed_notifications`. '''
pass
''' Delete and recreate the snuffling's panel. '''
sett = self.get_settings() self.delete_gui() self.setup_gui(reloaded=reloaded) self.set_settings(sett)
''' Display a message box.
:param kind: string defining kind of message :param message: the message to be displayed '''
try: box = qw.QMessageBox(self.get_viewer()) box.setText('%s: %s' % (kind.capitalize(), message)) box.exec_() except NoViewerSet: pass
''' Show an error message box.
:param message: specifying the error '''
logger.error('%s: %s' % (self._name, message)) self.show_message('error', message)
''' Display a warning message.
:param message: specifying the warning '''
logger.warning('%s: %s' % (self._name, message)) self.show_message('warning', message)
''' Show an error message box and raise :py:exc:`SnufflingCallFailed` exception.
:param message: specifying the error '''
self.error(message) raise SnufflingCallFailed(message)
''' Create a :py:class:`FigureFrame` and return either the frame, a :py:class:`matplotlib.figure.Figure` instance or a :py:class:`matplotlib.axes.Axes` instance.
:param name: labels the figure frame's tab :param get: 'axes'|'figure'|'frame' (optional) '''
if name is None: self._iplot += 1 name = 'Plot %i (%s)' % (self._iplot, self.get_name())
fframe = FigureFrame() self._panel_parent.add_tab(name, fframe) if get == 'axes': return fframe.gca() elif get == 'figure': return fframe.gcf() elif get == 'figure_frame': return fframe
''' Returns a :py:class:`matplotlib.figure.Figure` instance which can be displayed within snuffler by calling :py:meth:`canvas.draw`.
:param name: labels the tab of the figure '''
return self.pylab(name=name, get='figure')
''' Returns a :py:class:`matplotlib.axes.Axes` instance.
:param name: labels the tab of axes '''
return self.pylab(name=name, get='axes')
''' Create a :py:class:`pyrocko.gui.util.FigureFrame`.
:param name: labels the tab figure frame '''
return self.pylab(name=name, get='figure_frame')
''' Create a :py:class:`pyrocko.gui.util.PixmapFrame`.
:param name: labels the tab :param filename: name of file to be displayed '''
f = PixmapFrame(filename)
scroll_area = qw.QScrollArea() scroll_area.setWidget(f) scroll_area.setWidgetResizable(True)
self._panel_parent.add_tab(name or "Pixmap", scroll_area) return f
''' Creates a :py:class:`WebKitFrame` which can be used as a browser within snuffler.
:param url: url to open :param name: labels the tab '''
if name is None: self._iplot += 1 name = 'Web browser %i (%s)' % (self._iplot, self.get_name())
f = WebKitFrame(url) self._panel_parent.add_tab(name, f) return f
''' Create a :py:class:`pyrocko.gui.util.VTKFrame` to render interactive 3D graphics.
:param actors: list of VTKActors :param name: labels the tab
Initialize the interactive rendering by calling the frames' :py:meth`initialize` method after having added all actors to the frames renderer.
Requires installation of vtk including python wrapper. ''' if name is None: self._iplot += 1 name = 'VTK %i (%s)' % (self._iplot, self.get_name())
try: f = VTKFrame(actors=actors) except ImportError as e: self.fail(e)
self._panel_parent.add_tab(name, f) return f
''' Create a temporary directory and return its absolute path.
The directory and all its contents are removed when the Snuffling instance is deleted. '''
''' Enable/disable live updating.
When live updates are enabled, the :py:meth:`call` method is called whenever the user changes a parameter. If it is disabled, the user has to initiate such a call manually by triggering the snuffling's menu item or pressing the call button. '''
self._pre_process_hook_enabled = live_update self._post_process_hook_enabled = live_update
''' Add an adjustable parameter to the snuffling.
:param param: object of type :py:class:`Param`, :py:class:`Switch`, or :py:class:`Choice`.
For each parameter added, controls are added to the snuffling's panel, so that the parameter can be adjusted from the gui. '''
self.delete_gui() self.setup_gui()
''' Add a button to the snuffling's panel.
:param name: string that labels the button :param method: method associated with the button '''
self.delete_gui() self.setup_gui()
''' Get the snuffling's adjustable parameter definitions.
Returns a list of objects of type :py:class:`Param`. '''
''' Get one of the snuffling's adjustable parameter definitions.
:param ident: identifier of the parameter
Returns an object of type :py:class:`Param` or ``None``. '''
for param in self._parameters: if param.ident == ident: return param return None
''' Set one of the snuffling's adjustable parameters.
:param ident: identifier of the parameter :param value: new value of the parameter
Adjusts the control of a parameter without calling :py:meth:`call`. '''
self._set_parameter_value(ident, value)
control = self._param_controls.get(ident, None) if control: control.set_value(value)
''' Set the range of one of the snuffling's adjustable parameters.
:param ident: identifier of the parameter :param vmin,vmax: new minimum and maximum value for the parameter
Adjusts the control of a parameter without calling :py:meth:`call`. '''
control = self._param_controls.get(ident, None) if control: control.set_range(vmin, vmax)
''' Update the choices of a Choice parameter.
:param ident: identifier of the parameter :param choices: list of strings '''
control = self._param_controls.get(ident, None) if control: selected_choice = control.set_choices(choices) self._set_parameter_value(ident, selected_choice)
''' Get the current value of a parameter.
:param ident: identifier of the parameter ''' return getattr(self, ident)
''' Returns a dictionary with identifiers of all parameters as keys and their values as the dictionaries values. '''
params = self.get_parameters() settings = {} for param in params: settings[param.ident] = self.get_parameter_value(param.ident)
return settings
params = self.get_parameters() dparams = dict([(param.ident, param) for param in params]) for k, v in settings.items(): if k in dparams: self._set_parameter_value(k, v) if k in self._param_controls: control = self._param_controls[k] control.set_value(v)
''' Get the parent viewer.
Returns a reference to an object of type :py:class:`PileOverview`, which is the main viewer widget.
If no gui has been initialized for the snuffling, a :py:exc:`NoViewerSet` exception is raised. '''
raise NoViewerSet()
''' Get the pile.
If a gui has been initialized, a reference to the viewer's internal pile is returned. If not, the :py:meth:`make_pile` method (which may be overloaded in subclass) is called to create a pile. This can be utilized to make hybrid snufflings, which may work also in a standalone mode. '''
try: p = self.get_viewer().get_pile() except NoViewerSet: if self._no_viewer_pile is None: self._no_viewer_pile = self.make_pile()
p = self._no_viewer_pile
return p
self, trange=(-3600., 3600.), missing='warn'):
''' Get event and stations with available data for active event.
:param trange: (begin, end), time range around event origin time to query for available data :param missing: string, what to do in case of missing station information: ``'warn'``, ``'raise'`` or ``'ignore'``.
:returns: ``(event, stations)`` '''
p = self.get_pile() v = self.get_viewer()
event = v.get_active_event() if event is None: self.fail( 'No active event set. Select an event and press "e" to make ' 'it the "active event"')
stations = {} for traces in p.chopper( event.time+trange[0], event.time+trange[1], load_data=False, degap=False):
for tr in traces: try: for skey in v.station_keys(tr): if skey in stations: continue
station = v.get_station(skey) stations[skey] = station
except KeyError: s = 'No station information for station key "%s".' \ % '.'.join(skey)
if missing == 'warn': logger.warning(s) elif missing == 'raise': raise MissingStationInformation(s) elif missing == 'ignore': pass else: assert False, 'invalid argument to "missing"'
stations[skey] = None
return event, list(set( st for st in stations.values() if st is not None))
''' Get all stations known to the viewer. '''
v = self.get_viewer() stations = list(v.stations.values()) return stations
''' Get all markers from the viewer. '''
return self.get_viewer().get_markers()
''' Get all event markers from the viewer. '''
return [m for m in self.get_viewer().get_markers() if isinstance(m, EventMarker)]
''' Get all selected markers from the viewer. '''
return self.get_viewer().selected_markers()
''' Get all selected event markers from the viewer. '''
return [m for m in self.get_viewer().selected_markers() if isinstance(m, EventMarker)]
''' Get the marker of the active event and any associated phase markers '''
viewer = self.get_viewer() markers = viewer.get_markers() event_marker = viewer.get_active_event_marker() if event_marker is None: self.fail( 'No active event set. ' 'Select an event and press "e" to make it the "active event"')
event = event_marker.get_event()
selection = [] for m in markers: if isinstance(m, PhaseMarker): if m.get_event() is event: selection.append(m)
return ( event_marker, [m for m in markers if isinstance(m, PhaseMarker) and m.get_event() == event])
''' Get currently active trace selector from viewer.
:param mode: set to ``'inview'`` (default) to only include selections currently shown in the viewer, ``'visible' to include all traces not currenly hidden by hide or quick-select commands, or ``'all'`` to disable any restrictions. '''
viewer = self.get_viewer()
def rtrue(tr): return True
if mode == 'inview': return viewer.trace_selector or rtrue elif mode == 'visible': return viewer.trace_filter or rtrue elif mode == 'all': return rtrue else: raise Exception('invalid mode argument')
mode='inview', main_bandpass=False, progress=None, responsive=False, *args, **kwargs): ''' Iterate over selected traces.
Shortcut to get all trace data contained in the selected markers in the running snuffler. For each selected marker, :py:meth:`pyrocko.pile.Pile.chopper` is called with the arguments *tmin*, *tmax*, and *trace_selector* set to values according to the marker. Additional arguments to the chopper are handed over from *\\*args* and *\\*\\*kwargs*.
:param fallback: If ``True``, if no selection has been marked, use the content currently visible in the viewer.
:param marker_selector: If not ``None`` a callback to filter markers.
:param mode: Set to ``'inview'`` (default) to only include selections currently shown in the viewer (excluding traces accessible through vertical scrolling), ``'visible'`` to include all traces not currently hidden by hide or quick-select commands (including traces accessible through vertical scrolling), or ``'all'`` to disable any restrictions.
:param main_bandpass: If ``True``, apply main control high- and lowpass filters to traces. Note: use with caution. Processing is fixed to use 4th order Butterworth highpass and lowpass and the signal is always demeaned before filtering. FFT filtering, rotation, demean and bandpass settings from the graphical interface are not respected here. Padding is not automatically adjusted so results may include artifacts.
:param progress: If given a string a progress bar is shown to the user. The string is used as the label for the progress bar.
:param responsive: If set to ``True``, occasionally allow UI events to be processed. If used in combination with ``progress``, this allows the iterator to be aborted by the user. '''
try: viewer = self.get_viewer() markers = [ m for m in viewer.selected_markers() if not isinstance(m, EventMarker)]
if marker_selector is not None: markers = [ marker for marker in markers if marker_selector(marker)]
pile = self.get_pile()
def rtrue(tr): return True
trace_selector_arg = kwargs.pop('trace_selector', rtrue) trace_selector_viewer = self.get_viewer_trace_selector(mode)
style_arg = kwargs.pop('style', None)
if main_bandpass: def apply_filters(traces): for tr in traces: if viewer.highpass is not None: tr.highpass(4, viewer.highpass) if viewer.lowpass is not None: tr.lowpass(4, viewer.lowpass) return traces else: def apply_filters(traces): return traces
pb = viewer.parent().get_progressbars()
time_last = [time.time()]
def update_progress(label, batch): time_now = time.time() if responsive: # start processing events with one second delay, so that # e.g. cleanup actions at startup do not cause track number # changes etc. if time_last[0] + 1. < time_now: qw.qApp.processEvents() else: # redraw about once a second if time_last[0] + 1. < time_now: viewer.repaint()
time_last[0] = time.time() # use time after drawing
abort = pb.set_status( label, batch.i*100./batch.n, responsive) abort |= viewer.window().is_closing()
return abort
if markers: for imarker, marker in enumerate(markers): try: if progress: label = '%s: %i/%i' % ( progress, imarker+1, len(markers))
pb.set_status(label, 0, responsive)
if not marker.nslc_ids: trace_selector_marker = rtrue else: def trace_selector_marker(tr): return marker.match_nslc(tr.nslc_id)
def trace_selector(tr): return trace_selector_arg(tr) \ and trace_selector_viewer(tr) \ and trace_selector_marker(tr)
for batch in pile.chopper( tmin=marker.tmin, tmax=marker.tmax, trace_selector=trace_selector, style='batch', *args, **kwargs):
if progress: abort = update_progress(label, batch) if abort: return
batch.traces = apply_filters(batch.traces) if style_arg == 'batch': yield batch else: yield batch.traces
finally: if progress: pb.set_status(label, 100., responsive)
elif fallback: def trace_selector(tr): return trace_selector_arg(tr) \ and trace_selector_viewer(tr)
tmin, tmax = viewer.get_time_range()
if not pile.is_empty(): ptmin = pile.get_tmin() tpad = kwargs.get('tpad', 0.0) if ptmin > tmin: tmin = ptmin + tpad ptmax = pile.get_tmax() if ptmax < tmax: tmax = ptmax - tpad
try: if progress: label = progress pb.set_status(label, 0, responsive)
for batch in pile.chopper( tmin=tmin, tmax=tmax, trace_selector=trace_selector, style='batch', *args, **kwargs):
if progress: abort = update_progress(label, batch)
if abort: return
batch.traces = apply_filters(batch.traces)
if style_arg == 'batch': yield batch else: yield batch.traces
finally: if progress: pb.set_status(label, 100., responsive)
else: raise NoTracesSelected()
except NoViewerSet: pile = self.get_pile() return pile.chopper(*args, **kwargs)
''' Get the time range spanning all selected markers.
:param fallback: if ``True`` and no marker is selected return begin and end of visible time range '''
viewer = self.get_viewer() markers = viewer.selected_markers() mins = [marker.tmin for marker in markers] maxs = [marker.tmax for marker in markers]
if mins and maxs: tmin = min(mins) tmax = max(maxs)
elif fallback: tmin, tmax = viewer.get_time_range()
else: raise NoTracesSelected()
return tmin, tmax
''' Called when the snuffling's panel becomes visible or is hidden.
Can be overloaded in subclass, e.g. to perform additional setup actions when the panel is activated the first time. '''
pass
''' Create a pile.
To be overloaded in subclass. The default implementation just calls :py:func:`pyrocko.pile.make_pile` to create a pile from command line arguments. '''
cachedirname = config.config().cache_dir sources = self._cli_params.get('sources', sys.argv[1:]) return pile.make_pile( sources, cachedirname=cachedirname, regex=self._cli_params['regex'], fileformat=self._cli_params['format'])
''' Create a widget for the snuffling's control panel.
Normally called from the :py:meth:`setup_gui` method. Returns ``None`` if no panel is needed (e.g. if the snuffling has no adjustable parameters). '''
qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding)) self.panel_visibility_changed)
qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum))
high_is_none=param.high_is_none, low_is_none=param.low_is_none) else: high_is_none=param.high_is_none, low_is_none=param.low_is_none, low_is_zero=param.low_is_zero)
param.name, param.minimum, param.maximum, param.default, iparam)
self.modified_snuffling_panel)
param.ident, param.default, param.choices, param.name) self.choose_on_snuffling_panel)
qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum))
param.ident, param.default, param.name) self.switch_on_snuffling_panel)
qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum))
qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum))
self.live_update_toggled)
self.help_button_triggered)
self.clear_button_triggered)
self.call_button_triggered)
try: method() except SnufflingError as e: if not isinstance(e, SnufflingCallFailed): # those have logged within error() logger.error('%s: %s' % (self._name, e)) logger.error( '%s: Snuffling action failed' % self._name)
self.get_viewer().update()
call_and_update(method))
0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding)
else:
''' Create the help menu item for the snuffling. '''
self.help_button_triggered)
''' Create the menu item for the snuffling.
This method may be overloaded in subclass and return ``None``, if no menu entry is wanted. '''
self._have_pre_process_hook or self._have_post_process_hook)
self.menuitem_triggered)
self, caption='Save File', dir='', filter='', selected_filter=None):
''' Query user for an output filename.
This is currently a wrapper to :py:func:`QFileDialog.getSaveFileName`. A :py:exc:`UserCancelled` exception is raised if the user cancels the dialog. '''
if not dir and self._previous_output_filename: dir = self._previous_output_filename
fn = getSaveFileName( self.get_viewer(), caption, dir, filter, selected_filter) if not fn: raise UserCancelled()
self._previous_output_filename = fn return str(fn)
''' Query user for an input directory.
This is a wrapper to :py:func:`QFileDialog.getExistingDirectory`. A :py:exc:`UserCancelled` exception is raised if the user cancels the dialog. '''
if not dir and self._previous_input_directory: dir = self._previous_input_directory
dn = qw.QFileDialog.getExistingDirectory( None, caption, dir, qw.QFileDialog.ShowDirsOnly)
if not dn: raise UserCancelled()
self._previous_input_directory = dn return str(dn)
selected_filter=None): ''' Query user for an input filename.
This is currently a wrapper to :py:func:`QFileDialog.getOpenFileName`. A :py:exc:`UserCancelled` exception is raised if the user cancels the dialog. '''
if not dir and self._previous_input_filename: dir = self._previous_input_filename
fn, _ = fnpatch(qw.QFileDialog.getOpenFileName( self.get_viewer(), caption, dir, filter)) # selected_filter)
if not fn: raise UserCancelled()
self._previous_input_filename = fn return str(fn)
''' Query user for a text input.
This is currently a wrapper to :py:func:`QInputDialog.getText`. A :py:exc:`UserCancelled` exception is raised if the user cancels the dialog. '''
inp, ok = qw.QInputDialog.getText(self.get_viewer(), 'Input', caption)
if not ok: raise UserCancelled()
return inp
''' Called when the user has played with an adjustable parameter.
The default implementation sets the parameter, calls the snuffling's :py:meth:`call` method and finally triggers an update on the viewer widget. '''
param = self.get_parameters()[iparam] self._set_parameter_value(param.ident, value) if self._live_update: self.check_call() self.get_viewer().update()
''' Called when the user has toggled a switchable parameter. '''
self._set_parameter_value(ident, state) if self._live_update: self.check_call() self.get_viewer().update()
''' Called when the user has made a choice about a choosable parameter. '''
self._set_parameter_value(ident, state) if self._live_update: self.check_call() self.get_viewer().update()
''' Called when the user has triggered the snuffling's menu.
The default implementation calls the snuffling's :py:meth:`call` method and triggers an update on the viewer widget. '''
self.check_call()
if self._have_pre_process_hook: self._pre_process_hook_enabled = arg
if self._have_post_process_hook: self._post_process_hook_enabled = arg
if self._have_pre_process_hook or self._have_post_process_hook: self.get_viewer().clean_update() else: self.get_viewer().update()
''' Called when the user has clicked the snuffling's call button.
The default implementation calls the snuffling's :py:meth:`call` method and triggers an update on the viewer widget. '''
self.check_call() self.get_viewer().update()
''' Called when the user has clicked the snuffling's clear button.
This calls the :py:meth:`cleanup` method and triggers an update on the viewer widget. '''
self.cleanup() self.get_viewer().update()
''' Creates a :py:class:`QLabel` which contains the documentation as given in the snufflings' __doc__ string. '''
if self.__doc__: if self.__doc__.strip().startswith('<html>'): doc = qw.QLabel(self.__doc__) else: try: import markdown doc = qw.QLabel(markdown.markdown(self.__doc__))
except ImportError: logger.error( 'Install Python module "markdown" for pretty help ' 'formatting.')
doc = qw.QLabel(self.__doc__) else: doc = qw.QLabel('This snuffling does not provide any online help.')
labels = [doc]
if self._filename: from html import escape
code = open(self._filename, 'r').read()
doc_src = qw.QLabel( '''<html><body> <hr /> <center><em>May the source be with you, young Skywalker!</em><br /><br /> <a href="file://%s"><code>%s</code></a></center> <br /> <p style="margin-left: 2em; margin-right: 2em; background-color:#eed;"> <pre style="white-space: pre-wrap"><code>%s </code></pre></p></body></html>''' % ( quote(self._filename), escape(self._filename), escape(code)))
labels.append(doc_src)
for h in labels: h.setAlignment(qc.Qt.AlignTop | qc.Qt.AlignLeft) h.setWordWrap(True)
self._viewer.show_doc('Help: %s' % self._name, labels, target='panel')
''' Called when the checkbox for live-updates has been toggled. '''
self.set_live_update(on)
''' Add traces to the viewer.
:param traces: list of objects of type :py:class:`pyrocko.trace.Trace`
The traces are put into a :py:class:`pyrocko.pile.MemTracesFile` and added to the viewer's internal pile for display. Note, that unlike with the traces from the files given on the command line, these traces are kept in memory and so may quickly occupy a lot of ram if a lot of traces are added.
This method should be preferred over modifying the viewer's internal pile directly, because this way, the snuffling has a chance to automatically remove its private traces again (see :py:meth:`cleanup` method). '''
''' Add a trace to the viewer.
See :py:meth:`add_traces`. '''
self.add_traces([tr])
''' Add some markers to the display.
Takes a list of objects of type :py:class:`pyrocko.gui.util.Marker` and adds these to the viewer. '''
self.get_viewer().add_markers(markers) self._markers.extend(markers)
''' Add a marker to the display.
See :py:meth:`add_markers`. '''
self.add_markers([marker])
''' Remove all traces and markers which have been added so far by the snuffling. '''
except NoViewerSet: pass
if self._call_in_progress: self.show_message('error', 'Previous action still in progress.') return
try: self._call_in_progress = True self.call() return 0
except SnufflingError as e: if not isinstance(e, SnufflingCallFailed): # those have logged within error() logger.error('%s: %s' % (self._name, e)) logger.error('%s: Snuffling action failed' % self._name) return 1
except Exception as e: message = '%s: Snuffling action raised an exception: %s' % ( self._name, str(e))
logger.exception(message) self.show_message('error', message)
finally: self._call_in_progress = False
''' Main work routine of the snuffling.
This method is called when the snuffling's menu item has been triggered or when the user has played with the panel controls. To be overloaded in subclass. The default implementation does nothing useful. '''
pass
return traces
return traces
''' Return current amount of extra padding needed by live processing hooks. '''
return 0.0
''' Called when the snuffling instance is about to be deleted.
Can be overloaded to do user-defined cleanup actions. The default implementation calls :py:meth:`cleanup` and deletes the snuffling`s tempory directory, if needed. '''
''' This exception is raised, when no viewer has been set on a Snuffling. '''
def __str__(self): return 'No GUI available. ' \ 'Maybe this Snuffling cannot be run in command line mode?'
''' Raised when station information is missing. '''
''' This exception is raised, when no traces have been selected in the viewer and we cannot fallback to using the current view. '''
def __str__(self): return 'No traces have been selected / are available.'
''' This exception is raised, when the user has cancelled a snuffling dialog. '''
def __str__(self): return 'The user has cancelled a dialog.'
''' This exception is raised, when :py:meth:`Snuffling.fail` is called from :py:meth:`Snuffling.call`. '''
''' Utility class to load/reload snufflings from a file.
The snufflings are created by user modules which have the special function :py:func:`__snufflings__` which return the snuffling instances to be exported. The snuffling module is attached to a handler class, which makes use of the snufflings (e.g. :py:class:`pyrocko.pile_viewer.PileOverwiew` from ``pile_viewer.py``). The handler class must implement the methods ``add_snuffling()`` and ``remove_snuffling()`` which are used as callbacks. The callbacks are utilized from the methods :py:meth:`load_if_needed` and :py:meth:`remove_snufflings` which may be called from the handler class, when needed. '''
self._path = path self._name = name self._mtime = None self._module = None self._snufflings = [] self._handler = handler
filename = os.path.join(self._path, self._name+'.py')
try: mtime = os.stat(filename)[8] except OSError as e: if e.errno == 2: logger.error(e) raise BrokenSnufflingModule(filename)
if self._module is None: sys.path[0:0] = [self._path] try: logger.debug('Loading snuffling module %s' % filename) if self._name in sys.modules: raise InvalidSnufflingFilename(self._name)
self._module = __import__(self._name) del sys.modules[self._name]
for snuffling in self._module.__snufflings__(): snuffling._filename = filename self.add_snuffling(snuffling)
except Exception: logger.error(traceback.format_exc()) raise BrokenSnufflingModule(filename)
finally: sys.path[0:1] = []
elif self._mtime != mtime: logger.warning('Reloading snuffling module %s' % filename) settings = self.remove_snufflings() sys.path[0:0] = [self._path] try:
sys.modules[self._name] = self._module
reload(self._module) del sys.modules[self._name]
for snuffling in self._module.__snufflings__(): snuffling._filename = filename self.add_snuffling(snuffling, reloaded=True)
if len(self._snufflings) == len(settings): for sett, snuf in zip(settings, self._snufflings): snuf.set_settings(sett)
except Exception: logger.error(traceback.format_exc()) raise BrokenSnufflingModule(filename)
finally: sys.path[0:1] = []
self._mtime = mtime
snuffling._path = self._path snuffling.setup() self._snufflings.append(snuffling) self._handler.add_snuffling(snuffling, reloaded=reloaded)
settings = [] for snuffling in self._snufflings: settings.append(snuffling.get_settings()) self._handler.remove_snuffling(snuffling)
self._snufflings = [] return settings
self.sw_toggled.emit(self.ident, state)
self.blockSignals(True) self.setChecked(state) self.blockSignals(False)
icur = self.cbox.currentIndex() if icur != -1: selected_choice = choices[icur] else: selected_choice = None
self.choices = choices self.cbox.clear() for ichoice, choice in enumerate(choices): self.cbox.addItem(qc.QString(choice))
if selected_choice is not None and selected_choice in choices: self.set_value(selected_choice) return selected_choice else: self.set_value(choices[0]) return choices[0]
self.choosen.emit( self.ident, self.choices[i])
|