Extending Snuffler with plugins: Snufflings

Snufflings are small Python scripts which extend the functionality of Snuffler. Snuffler looks into the directory $HOME/.snufflings for snufflings. Snufflings can be reloaded at run-time using the menu entry ‘Reload Snufflings’ in the main menu of Snuffler - no need to restart Snuffler when a snuffling is modified or added.

Already existing snufflings can be downloaded from the snuffling repository

Example Snuffling to show earthquake catalog information within Snuffler

Put the following code into $HOME/.snufflings/geofon.py. It will add four items into the Snufflings sub-menu of Snuffler (Get GEOFON events, Get GEOFON events (> M6), …). When one of these is selected by the user, the GEOFON catalog is queried for earthquake information for the time range visible in Snuffler. For each earthquake found, a marker is shown in the viewer.

from pyrocko.snuffling import Snuffling, Param
from pyrocko.pile_viewer import Marker, EventMarker
from pyrocko import catalog

class GeofonEvents(Snuffling):

    '''
    Get events from GEOFON catalog.
    '''

    def __init__(self, magmin=None):
        self._magmin = magmin
        Snuffling.__init__(self)

    def setup(self):
        '''Customization of the snuffling.'''

        if self._magmin is None:
            self.set_name('Get GEOFON Events')
        else:
            self.set_name('Get GEOFON Events (> M %g)' % self._magmin)

    def call(self):
        '''Main work routine of the snuffling.'''

        # get time range visible in viewer
        viewer = self.get_viewer()
        tmin, tmax = viewer.get_time_range()

        # download event information from GEOFON web page
        # 1) get list of event names
        geofon = catalog.Geofon()
        event_names = geofon.get_event_names(
            time_range=(tmin,tmax),
            magmin=self._magmin)

        # 2) get event information and add a marker in the snuffler window
        for event_name in event_names:
            event = geofon.get_event(event_name)
            marker = EventMarker(event)
            self.add_markers([marker])

def __snufflings__():
    '''Returns a list of snufflings to be exported by this module.'''

    return [ GeofonEvents(),
             GeofonEvents(magmin=6),
             GeofonEvents(magmin=7),
             GeofonEvents(magmin=8) ]

How it works

Snuffler looks into the directory HOME/.snufflings for python scripts (*.py). Within each of these it tries to query the function __snufflings__() which should return a list of snuffling objects, which are instances of a snuffling class. A custom snuffling class is created by subclassing pyrocko.snuffling.Snuffling. Within the derived class implement the methods setup() and call(). setup() is called during initialization of the snuffling object, call() is called when the user selects the menu entry. You may define several snuffling classes within one snuffling source file. You may also return several instances of a single snuffling class from the __snufflings__() function.

The pyrocko.snuffling.Snuffling base class documentation can also be accessed with the command pydoc pyrocko.snuffling.Snuffling from the shell. Example snufflings can be found in src/snufflings/ in the pyrocko source code. More examples may be found in the contrib-snufflings repository repository.

More examples

How to add simple markers to the viewer

from pyrocko.snuffling import Snuffling
from pyrocko.pile_viewer import Marker

class Example1(Snuffling):

    '''Example Snuffling to demonstrate how to add markers to the viewer.

It looks at all selected traces and puts a Marker at the peak amplitude of the
raw traces. If no traces are selected all traces in view are used.  It is not
affected by filter settings of the viewer.

This works well for short continuous traces, but if longer or gappy traces are
in the viewer, there may be some problems which are not
'''

    def setup(self):
        # this sets the name for the menu entry:
        self.set_name('Example 1: mark peak amplitudes')

    def call(self):

        # remove all markers which have been previously added by this snuffling
        self.cleanup()

        # this is a shortcut to get selected traces or all traces in view
        for traces in self.chopper_selected_traces(fallback=True):

            for tr in traces:
                net, sta, loc, cha = tr.nslc_id

                # using a trace method to get time and amplitude
                time, amplitude = tr.absmax()

                # the marker kind sets the color of the marker
                kind = 3

                # create the marker object
                m = Marker([ (net, sta, loc, cha) ], time, time, kind )

                # add it to the viewer
                self.add_marker(m)

def __snufflings__():
    return [ Example1() ]

Synthetic Seismograms of an STS2 seismometer

This snuffling demonstrates the method add_paramter() which extends the snufflings’ panel by scroll bars and options to choose between predefined parameters.

class STS2:

    ''' Apply the STS2's transfer function which is deduced from the
poles, zeros and gain of the transfer tunction. The Green's function
database (gdfb) which is required for synthetic seismograms and the
rake of the focal mechanism can be chosen and changed within snuffler.
Two gfdbs are needed.
Three synthetic seismograms of an STS2 seismometer will be the result.
'''
    # 'evaluate() will apply the transfer function on each frequency.
    def evaluate(self,freqs):

        # transform the frequency to angular frequency.
        w = 2j*pi*freqs

        Poles = array([-3.7e-2+3.7e-2j, -3.7e-2-3.7e-2j,
                       -2.51e2, -1.31e2+4.67e2j, -1.31e2-4.67e2])
        Zeros = array([0,0])
        K = 6.16817e7

        # Multiply factored polynomials of the transfer function's numerator
        # and denominator.
        a = ones(freqs.size,dtype=complex)*K
        for i_z in Zeros:
            a *= w-i_z
        for i_p in Poles:
            a /= w-i_p
        return a

class ParaEditCp_TF_GTTG(Snuffling):

    def setup(self):

        # Give the snuffling a name:
        self.set_name('STS-2.1')

        # Add scrollbars of the parameters that you desire to adjust.
        # 1st argument: Description that appears within the snuffling.
        # 2nd argument: Name of parameter as used in the following code.
        # 3rd-5th argument: default, start, stop.
        self.add_parameter(Param('Strike[deg]', 'strike', 179., -180., 180.))

        # The parameter 'Choice' adds a menu to choose from different options.
        # 1st argument: Description that appears within the snuffling.
        # 2nd argument: Name of paramter as used in the following code.
        # 3rd argument: Default
        # 4th to ... argument: List containing all other options.
        self.add_parameter(Choice('GFDB','database','gemini',['gemini','qseis']))
        self.set_live_update(False)

    def call(self):

        self.cleanup()

        # Set up receiver configuration.
        tab = '''
        HH  53.456  9.9247  0
        '''.strip()

        receivers = []
        station, lat, lon, depth = tab.split()
        r = receiver.Receiver(lat,lon, components='neu', name='.%s.' % station)
        receivers.append(r)

        # Composition of the source
        olat, olon = 36.9800, -3.5400
        otime = util.str_to_time('1954-03-29 06:16:05')

        # The gfdb can be chosen within snuffler.
        # This refers to the 'add_parameter' method.
        if self.database == 'gemini':
            db = gfdb.Gfdb('/scratch/local2/gfdb_workshop_iasp91/gfdb/db')
        else:
            db = gfdb.Gfdb('/scratch/local2/gfdb_building/deep/gfdb_iasp/db')

        seis = seismosizer.Seismosizer(hosts=['localhost'])
        seis.set_database(db)
        seis.set_effective_dt(db.dt)
        seis.set_local_interpolation('bilinear')
        seis.set_receivers(receivers)
        seis.set_source_location( olat, olon, otime)
        seis.set_source_constraints (0, 0, 0, 0 ,0 ,-1)
        self.seis = seis

        # Change strike within snuffler with the added scroll bar.
        strike = self.strike

        # Other focal mechism parameters are constants
        dip = 122; rake = 80; moment = 7.00e20; depth = 650000; risetime = 24
        s = source.Source('bilateral',
        sourceparams_str='0 0 0 %g %g %g %g %g 0 0 0 0 1 %g' % (depth, moment, strike, dip, rake, risetime))
        self.seis.set_source(s)
        recs = self.seis.get_receivers_snapshot( which_seismograms = ('syn',), which_spectra=(), which_processing='tapered')

        trs = []
        for rec in recs:
            rec.save_traces_mseed(filename_tmpl='%(whichset)s_%(network)s_%(station)s_%(location)s_%(channel)s.mseed' )
            trs.extend(rec.get_traces())

        # Define fade in and out, band pass filter and cut off fader for the TF.
        tfade = 8
        freqlimit = (0.005,0.006,1,1.3)
        cut_off_fading = 5
        ntraces = []

        for tr in trs:
            TF = STS2()

            # Save synthetic trace after transfer function was applied.
            trace_filtered = tr.transfer(tfade, freqlimit, TF, cut_off_fading)
            # Set new codes to the filtered trace to make it identifiable.
            rename={'e':'BHE','n':'BHN','u':'BHZ'}
            trace_filtered.set_codes(channel=rename[trace_filtered.channel], network='', station='HHHA', location='syn')
            ntraces.append(trace_filtered)

#             Extract the synthetic trace's data with get_?data() and store them.
#            xval = trace_filtered.get_xdata()
#            yval = trace_filtered.get_ydata()
#            savetxt('synthetic_data_'+trace_filtered.channel,xval)

        self.add_traces(ntraces)
        self.seis = None

def __snufflings__():
    return [ ParaEditCp_TF_GTTG() ]