1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
5'''
6Effective seismological trace viewer.
7'''
8from __future__ import absolute_import
10import os
11import sys
12import logging
13import gc
14import tempfile
15import shutil
16import glob
18from os.path import join as pjoin
19from optparse import OptionParser
22from pyrocko import pile as pile_mod
23from pyrocko import util
24from pyrocko import model
25from pyrocko import config
26from pyrocko import io
27from pyrocko.gui import marker
28from pyrocko.io import stationxml
31logger = logging.getLogger('pyrocko.gui.snuffler')
33app = None
36def get_snuffler_instance():
37 from .snuffler_app import Snuffler
38 import locale
39 locale.setlocale(locale.LC_ALL, 'C')
40 global app
41 if app is None:
42 app = Snuffler()
43 return app
46def extend_paths(paths):
47 paths_r = []
48 for p in paths:
49 paths_r.extend(glob.glob(p))
50 return paths_r
53def snuffle(pile=None, **kwargs):
54 '''
55 View pile in a snuffler window.
57 :param pile: :py:class:`pile.Pile` object to be visualized
58 :param stations: list of `pyrocko.model.Station` objects or ``None``
59 :param events: list of `pyrocko.model.Event` objects or ``None``
60 :param markers: list of `pyrocko.gui.util.Marker` objects or ``None``
61 :param ntracks: float, number of tracks to be shown initially (default: 12)
62 :param marker_editor_sortable: bool, whether to allow sorting in marker
63 table (default True). Disabling this will give better performance
64 when working with many markers.
65 :param follow: time interval (in seconds) for real time follow mode or
66 ``None``
67 :param controls: bool, whether to show the main controls (default:
68 ``True``)
69 :param opengl: bool, whether to use opengl (default: ``False``)
70 :param paths: list of files and directories to search for trace files
71 :param pattern: regex which filenames must match
72 :param format: format of input files
73 :param cache_dir: cache directory with trace meta information
74 :param force_cache: bool, whether to use the cache when attribute spoofing
75 is active
76 :param store_path: filename template, where to store trace data from input
77 streams
78 :param store_interval: float, time interval (in seconds) between stream
79 buffer dumps
80 :param want_markers: bool, whether markers should be returned
81 :param launch_hook: callback function called before snuffler window is
82 shown
83 :param instant_close: bool, whether to bypass close window confirmation
84 dialog
85 '''
86 from .snuffler_app import SnufflerWindow, \
87 setup_acquisition_sources, PollInjector
89 if pile is None:
90 pile = pile_mod.make_pile()
92 app = get_snuffler_instance()
94 kwargs_load = {}
95 for k in ('paths', 'regex', 'format', 'cache_dir', 'force_cache'):
96 try:
97 kwargs_load[k] = kwargs.pop(k)
98 except KeyError:
99 pass
101 store_path = kwargs.pop('store_path', None)
102 store_interval = kwargs.pop('store_interval', 600)
103 want_markers = kwargs.pop('want_markers', False)
104 launch_hook = kwargs.pop('launch_hook', None)
106 win = SnufflerWindow(pile, **kwargs)
107 if launch_hook:
108 if not isinstance(launch_hook, list):
109 launch_hook = [launch_hook]
110 for hook in launch_hook:
111 hook(win)
113 sources = []
114 pollinjector = None
115 tempdir = None
116 if 'paths' in kwargs_load:
117 sources.extend(setup_acquisition_sources(kwargs_load['paths']))
118 if sources:
119 if store_path is None:
120 tempdir = tempfile.mkdtemp('', 'snuffler-tmp-')
121 store_path = pjoin(
122 tempdir,
123 'trace-%(network)s.%(station)s.%(location)s.%(channel)s.'
124 '%(tmin_ms)s.mseed')
125 elif os.path.isdir(store_path):
126 store_path = pjoin(
127 store_path,
128 'trace-%(network)s.%(station)s.%(location)s.%(channel)s.'
129 '%(tmin_ms)s.mseed')
131 pollinjector = PollInjector(
132 pile,
133 fixation_length=store_interval,
134 path=store_path)
136 for source in sources:
137 source.start()
138 pollinjector.add_source(source)
140 win.get_view().load(**kwargs_load)
142 if not win.is_closing():
143 app.install_sigint_handler()
144 app.exec_()
145 app.uninstall_sigint_handler()
147 for source in sources:
148 source.stop()
150 if pollinjector:
151 pollinjector.fixate_all()
153 ret = win.return_tag()
155 if want_markers:
156 markers = win.get_view().get_markers()
158 del win
159 gc.collect()
161 if tempdir:
162 shutil.rmtree(tempdir)
164 if want_markers:
165 return ret, markers
166 else:
167 return ret
170def snuffler_from_commandline(args=None):
171 if args is None:
172 args = sys.argv[1:]
174 usage = '''usage: %prog [options] waveforms ...'''
175 parser = OptionParser(usage=usage)
177 parser.add_option(
178 '--format',
179 dest='format',
180 default='detect',
181 choices=io.allowed_formats('load'),
182 help='assume input files are of given FORMAT. Choices: %s'
183 % io.allowed_formats('load', 'cli_help', 'detect'))
185 parser.add_option(
186 '--pattern',
187 dest='regex',
188 metavar='REGEX',
189 help='only include files whose paths match REGEX')
191 parser.add_option(
192 '--stations',
193 dest='station_fns',
194 action='append',
195 default=[],
196 metavar='STATIONS',
197 help='read station information from file STATIONS')
199 parser.add_option(
200 '--stationxml',
201 dest='stationxml_fns',
202 action='append',
203 default=[],
204 metavar='STATIONSXML',
205 help='read station information from XML file STATIONSXML')
207 parser.add_option(
208 '--event', '--events',
209 dest='event_fns',
210 action='append',
211 default=[],
212 metavar='EVENT',
213 help='read event information from file EVENT')
215 parser.add_option(
216 '--markers',
217 dest='marker_fns',
218 action='append',
219 default=[],
220 metavar='MARKERS',
221 help='read marker information file MARKERS')
223 parser.add_option(
224 '--follow',
225 type='float',
226 dest='follow',
227 metavar='N',
228 help='follow real time with a window of N seconds')
230 parser.add_option(
231 '--cache',
232 dest='cache_dir',
233 default=config.config().cache_dir,
234 metavar='DIR',
235 help='use directory DIR to cache trace metadata '
236 '(default=\'%default\')')
238 parser.add_option(
239 '--force-cache',
240 dest='force_cache',
241 action='store_true',
242 default=False,
243 help='use the cache even when trace attribute spoofing is active '
244 '(may have silly consequences)')
246 parser.add_option(
247 '--store-path',
248 dest='store_path',
249 metavar='PATH_TEMPLATE',
250 help='store data received through streams to PATH_TEMPLATE')
252 parser.add_option(
253 '--store-interval',
254 type='float',
255 dest='store_interval',
256 default=600,
257 metavar='N',
258 help='dump stream data to file every N seconds [default: %default]')
260 parser.add_option(
261 '--ntracks',
262 type='int',
263 dest='ntracks',
264 default=24,
265 metavar='N',
266 help='initially use N waveform tracks in viewer [default: %default]')
268 parser.add_option(
269 '--disable-marker-sorting',
270 action='store_false',
271 dest='marker_editor_sortable',
272 default=True,
273 help='disable sorting in marker table for improved performance with '
274 '100000+ markers')
276 parser.add_option(
277 '--hptime',
278 choices=('on', 'off', 'config'),
279 dest='hp_time',
280 default='config',
281 metavar='on|off|config',
282 help='set high precision time mode [default: %default]')
284 parser.add_option(
285 '--opengl',
286 dest='opengl',
287 action='store_true',
288 default=False,
289 help='use OpenGL for drawing')
291 parser.add_option(
292 '--qt5',
293 dest='gui_toolkit_qt5',
294 action='store_true',
295 default=False,
296 help='use Qt5 for the GUI')
298 parser.add_option(
299 '--qt4',
300 dest='gui_toolkit_qt4',
301 action='store_true',
302 default=False,
303 help='use Qt4 for the GUI')
305 parser.add_option(
306 '--debug',
307 dest='debug',
308 action='store_true',
309 default=False,
310 help='print debugging information to stderr')
312 options, args = parser.parse_args(list(args))
314 if options.debug:
315 util.setup_logging('snuffler', 'debug')
316 else:
317 util.setup_logging('snuffler', 'warning')
319 if options.hp_time in ('on', 'off'):
320 util.use_high_precision_time(options.hp_time == 'on')
322 if options.gui_toolkit_qt4:
323 config.override_gui_toolkit = 'qt4'
325 if options.gui_toolkit_qt5:
326 config.override_gui_toolkit = 'qt5'
328 this_pile = pile_mod.Pile()
329 stations = []
330 for stations_fn in extend_paths(options.station_fns):
331 stations.extend(model.station.load_stations(stations_fn))
333 for stationxml_fn in extend_paths(options.stationxml_fns):
334 stations.extend(
335 stationxml.load_xml(
336 filename=stationxml_fn).get_pyrocko_stations())
338 events = []
339 for event_fn in extend_paths(options.event_fns):
340 events.extend(model.load_events(event_fn))
342 markers = []
343 for marker_fn in extend_paths(options.marker_fns):
344 markers.extend(marker.load_markers(marker_fn))
346 return snuffle(
347 this_pile,
348 stations=stations,
349 events=events,
350 markers=markers,
351 ntracks=options.ntracks,
352 marker_editor_sortable=options.marker_editor_sortable,
353 follow=options.follow,
354 controls=True,
355 opengl=options.opengl,
356 paths=args,
357 cache_dir=options.cache_dir,
358 regex=options.regex,
359 format=options.format,
360 force_cache=options.force_cache,
361 store_path=options.store_path,
362 store_interval=options.store_interval)
365if __name__ == '__main__':
366 snuffler_from_commandline()