1# https://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6'''
7Effective seismological trace viewer.
8'''
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 deps
23from pyrocko import pile as pile_mod
24from pyrocko import util
25from pyrocko import model
26from pyrocko import config
27from pyrocko import io
28from pyrocko.io import stationxml
30from . import marker
33logger = logging.getLogger('pyrocko.gui.snuffler.snuffler')
35app = None
38def get_snuffler_instance():
39 from .snuffler_app import Snuffler
40 from ..qt_compat import qg
41 import locale
42 locale.setlocale(locale.LC_ALL, 'C')
43 global app
44 if app is None:
45 qg.QSurfaceFormat.setDefaultFormat(qg.QSurfaceFormat())
46 app = Snuffler()
47 return app
50def extend_paths(paths):
51 paths_r = []
52 for p in paths:
53 paths_r.extend(glob.glob(p))
54 return paths_r
57def snuffle(pile=None, **kwargs):
58 '''
59 View pile in a snuffler window.
61 :param pile: :py:class:`pile.Pile` object to be visualized
62 :param stations: list of `pyrocko.model.Station` objects or ``None``
63 :param events: list of `pyrocko.model.Event` objects or ``None``
64 :param markers: list of `pyrocko.gui.snuffler.util.Marker` objects or
65 ``None``
66 :param ntracks: float, number of tracks to be shown initially (default: 12)
67 :param marker_editor_sortable: bool, whether to allow sorting in marker
68 table (default True). Disabling this will give better performance
69 when working with many markers.
70 :param follow: time interval (in seconds) for real time follow mode or
71 ``None``
72 :param controls: bool, whether to show the main controls (default:
73 ``True``)
74 :param opengl: bool, whether to use opengl (default: ``None`` - automatic
75 choice).
76 :param paths: list of files and directories to search for trace files
77 :param pattern: regex which filenames must match
78 :param format: format of input files
79 :param cache_dir: cache directory with trace meta information
80 :param force_cache: bool, whether to use the cache when attribute spoofing
81 is active
82 :param store_path: filename template, where to store trace data from input
83 streams
84 :param store_interval: float, time interval (in seconds) between stream
85 buffer dumps
86 :param want_markers: bool, whether markers should be returned
87 :param launch_hook: callback function called before snuffler window is
88 shown
89 :param instant_close: bool, whether to bypass close window confirmation
90 dialog
91 '''
92 from .snuffler_app import SnufflerWindow, \
93 setup_acquisition_sources, PollInjector
95 if pile is None:
96 pile = pile_mod.make_pile()
98 app = get_snuffler_instance()
100 kwargs_load = {}
101 for k in ('paths', 'regex', 'format', 'cache_dir', 'force_cache'):
102 try:
103 kwargs_load[k] = kwargs.pop(k)
104 except KeyError:
105 pass
107 store_path = kwargs.pop('store_path', None)
108 store_interval = kwargs.pop('store_interval', 600)
109 want_markers = kwargs.pop('want_markers', False)
110 launch_hook = kwargs.pop('launch_hook', None)
112 win = SnufflerWindow(pile, **kwargs)
113 if launch_hook:
114 if not isinstance(launch_hook, list):
115 launch_hook = [launch_hook]
116 for hook in launch_hook:
117 hook(win)
119 sources = []
120 pollinjector = None
121 tempdir = None
122 if 'paths' in kwargs_load:
123 sources.extend(setup_acquisition_sources(kwargs_load['paths']))
124 if sources:
125 if store_path is None:
126 tempdir = tempfile.mkdtemp('', 'snuffler-tmp-')
127 store_path = pjoin(
128 tempdir,
129 'trace-%(network)s.%(station)s.%(location)s.%(channel)s.'
130 '%(tmin_ms)s.mseed')
131 elif os.path.isdir(store_path):
132 store_path = pjoin(
133 store_path,
134 'trace-%(network)s.%(station)s.%(location)s.%(channel)s.'
135 '%(tmin_ms)s.mseed')
137 interval = min(
138 source.get_wanted_poll_interval() for source in sources)
140 pollinjector = PollInjector(
141 pile,
142 fixation_length=store_interval,
143 path=store_path,
144 interval=interval)
146 for source in sources:
147 source.start()
148 pollinjector.add_source(source)
150 win.get_view().load(**kwargs_load)
152 if not win.is_closing():
153 app.install_sigint_handler()
154 try:
155 app.exec_()
157 finally:
158 app.uninstall_sigint_handler()
160 for source in sources:
161 source.stop()
163 if pollinjector:
164 pollinjector.fixate_all()
166 ret = win.return_tag()
168 if want_markers:
169 markers = win.get_view().get_markers()
171 del win
172 gc.collect()
174 if tempdir:
175 shutil.rmtree(tempdir)
177 if want_markers:
178 return ret, markers
179 else:
180 return ret
183def snuffler_from_commandline(args=None):
184 if args is None:
185 args = sys.argv[1:]
187 usage = '''usage: %prog [options] waveforms ...'''
188 parser = OptionParser(usage=usage)
190 parser.add_option(
191 '--format',
192 dest='format',
193 default='detect',
194 choices=io.allowed_formats('load'),
195 help='assume input files are of given FORMAT. Choices: %s'
196 % io.allowed_formats('load', 'cli_help', 'detect'))
198 parser.add_option(
199 '--pattern',
200 dest='regex',
201 metavar='REGEX',
202 help='only include files whose paths match REGEX')
204 parser.add_option(
205 '--stations',
206 dest='station_fns',
207 action='append',
208 default=[],
209 metavar='STATIONS',
210 help='read station information from file STATIONS')
212 parser.add_option(
213 '--stationxml',
214 dest='stationxml_fns',
215 action='append',
216 default=[],
217 metavar='STATIONSXML',
218 help='read station information from XML file STATIONSXML')
220 parser.add_option(
221 '--event', '--events',
222 dest='event_fns',
223 action='append',
224 default=[],
225 metavar='EVENT',
226 help='read event information from file EVENT')
228 parser.add_option(
229 '--markers',
230 dest='marker_fns',
231 action='append',
232 default=[],
233 metavar='MARKERS',
234 help='read marker information file MARKERS')
236 parser.add_option(
237 '--follow',
238 type='float',
239 dest='follow',
240 metavar='N',
241 help='follow real time with a window of N seconds')
243 parser.add_option(
244 '--cache',
245 dest='cache_dir',
246 default=config.config().cache_dir,
247 metavar='DIR',
248 help='use directory DIR to cache trace metadata '
249 "(default='%default')")
251 parser.add_option(
252 '--force-cache',
253 dest='force_cache',
254 action='store_true',
255 default=False,
256 help='use the cache even when trace attribute spoofing is active '
257 '(may have silly consequences)')
259 parser.add_option(
260 '--store-path',
261 dest='store_path',
262 metavar='PATH_TEMPLATE',
263 help='store data received through streams to PATH_TEMPLATE')
265 parser.add_option(
266 '--store-interval',
267 type='float',
268 dest='store_interval',
269 default=600,
270 metavar='N',
271 help='dump stream data to file every N seconds [default: %default]')
273 parser.add_option(
274 '--ntracks',
275 type='int',
276 dest='ntracks',
277 default=24,
278 metavar='N',
279 help='initially use N waveform tracks in viewer [default: %default]')
281 parser.add_option(
282 '--disable-marker-sorting',
283 action='store_false',
284 dest='marker_editor_sortable',
285 default=True,
286 help='disable sorting in marker table for improved performance with '
287 '100000+ markers')
289 parser.add_option(
290 '--hptime',
291 choices=('on', 'off', 'config'),
292 dest='hp_time',
293 default='config',
294 metavar='on|off|config',
295 help='set high precision time mode [default: %default]')
297 parser.add_option(
298 '--opengl',
299 dest='opengl',
300 action='store_true',
301 default=None,
302 help='use OpenGL for drawing')
304 parser.add_option(
305 '--no-opengl',
306 dest='opengl',
307 action='store_false',
308 default=None,
309 help='do not use OpenGL for drawing')
311 parser.add_option(
312 '--debug',
313 dest='debug',
314 action='store_true',
315 default=False,
316 help='print debugging information to stderr')
318 options, args = parser.parse_args(list(args))
320 if options.debug:
321 util.setup_logging('snuffler', 'debug')
322 else:
323 util.setup_logging('snuffler', 'info')
325 if options.hp_time in ('on', 'off'):
326 util.use_high_precision_time(options.hp_time == 'on')
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 import pyrocko
348 try:
349 return pyrocko.snuffle(
350 this_pile,
351 stations=stations,
352 events=events,
353 markers=markers,
354 ntracks=options.ntracks,
355 marker_editor_sortable=options.marker_editor_sortable,
356 follow=options.follow,
357 controls=True,
358 opengl=options.opengl,
359 paths=args,
360 cache_dir=options.cache_dir,
361 regex=options.regex,
362 format=options.format,
363 force_cache=options.force_cache,
364 store_path=options.store_path,
365 store_interval=options.store_interval)
367 except deps.MissingPyrockoDependency as e:
368 logger.fatal(str(e))
369 sys.exit(1)
372if __name__ == '__main__':
373 snuffler_from_commandline()