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 pile as pile_mod
23from pyrocko import util
24from pyrocko import model
25from pyrocko import config
26from pyrocko import io
27from pyrocko.io import stationxml
29from . import marker
32logger = logging.getLogger('pyrocko.gui.snuffler.snuffler')
34app = None
37def get_snuffler_instance():
38 from .snuffler_app import Snuffler
39 from ..qt_compat import qg
40 import locale
41 locale.setlocale(locale.LC_ALL, 'C')
42 global app
43 if app is None:
44 qg.QSurfaceFormat.setDefaultFormat(qg.QSurfaceFormat())
45 app = Snuffler()
46 return app
49def extend_paths(paths):
50 paths_r = []
51 for p in paths:
52 paths_r.extend(glob.glob(p))
53 return paths_r
56def snuffle(pile=None, **kwargs):
57 '''
58 View pile in a snuffler window.
60 :param pile: :py:class:`pile.Pile` object to be visualized
61 :param stations: list of `pyrocko.model.Station` objects or ``None``
62 :param events: list of `pyrocko.model.Event` objects or ``None``
63 :param markers: list of `pyrocko.gui.snuffler.util.Marker` objects or
64 ``None``
65 :param ntracks: float, number of tracks to be shown initially (default: 12)
66 :param marker_editor_sortable: bool, whether to allow sorting in marker
67 table (default True). Disabling this will give better performance
68 when working with many markers.
69 :param follow: time interval (in seconds) for real time follow mode or
70 ``None``
71 :param controls: bool, whether to show the main controls (default:
72 ``True``)
73 :param opengl: bool, whether to use opengl (default: ``None`` - automatic
74 choice).
75 :param paths: list of files and directories to search for trace files
76 :param pattern: regex which filenames must match
77 :param format: format of input files
78 :param cache_dir: cache directory with trace meta information
79 :param force_cache: bool, whether to use the cache when attribute spoofing
80 is active
81 :param store_path: filename template, where to store trace data from input
82 streams
83 :param store_interval: float, time interval (in seconds) between stream
84 buffer dumps
85 :param want_markers: bool, whether markers should be returned
86 :param launch_hook: callback function called before snuffler window is
87 shown
88 :param instant_close: bool, whether to bypass close window confirmation
89 dialog
90 '''
91 from .snuffler_app import SnufflerWindow, \
92 setup_acquisition_sources, PollInjector
94 if pile is None:
95 pile = pile_mod.make_pile()
97 app = get_snuffler_instance()
99 kwargs_load = {}
100 for k in ('paths', 'regex', 'format', 'cache_dir', 'force_cache'):
101 try:
102 kwargs_load[k] = kwargs.pop(k)
103 except KeyError:
104 pass
106 store_path = kwargs.pop('store_path', None)
107 store_interval = kwargs.pop('store_interval', 600)
108 want_markers = kwargs.pop('want_markers', False)
109 launch_hook = kwargs.pop('launch_hook', None)
111 win = SnufflerWindow(pile, **kwargs)
112 if launch_hook:
113 if not isinstance(launch_hook, list):
114 launch_hook = [launch_hook]
115 for hook in launch_hook:
116 hook(win)
118 sources = []
119 pollinjector = None
120 tempdir = None
121 if 'paths' in kwargs_load:
122 sources.extend(setup_acquisition_sources(kwargs_load['paths']))
123 if sources:
124 if store_path is None:
125 tempdir = tempfile.mkdtemp('', 'snuffler-tmp-')
126 store_path = pjoin(
127 tempdir,
128 'trace-%(network)s.%(station)s.%(location)s.%(channel)s.'
129 '%(tmin_ms)s.mseed')
130 elif os.path.isdir(store_path):
131 store_path = pjoin(
132 store_path,
133 'trace-%(network)s.%(station)s.%(location)s.%(channel)s.'
134 '%(tmin_ms)s.mseed')
136 interval = min(
137 source.get_wanted_poll_interval() for source in sources)
139 pollinjector = PollInjector(
140 pile,
141 fixation_length=store_interval,
142 path=store_path,
143 interval=interval)
145 for source in sources:
146 source.start()
147 pollinjector.add_source(source)
149 win.get_view().load(**kwargs_load)
151 if not win.is_closing():
152 app.install_sigint_handler()
153 try:
154 app.exec_()
156 finally:
157 app.uninstall_sigint_handler()
159 for source in sources:
160 source.stop()
162 if pollinjector:
163 pollinjector.fixate_all()
165 ret = win.return_tag()
167 if want_markers:
168 markers = win.get_view().get_markers()
170 del win
171 gc.collect()
173 if tempdir:
174 shutil.rmtree(tempdir)
176 if want_markers:
177 return ret, markers
178 else:
179 return ret
182def snuffler_from_commandline(args=None):
183 if args is None:
184 args = sys.argv[1:]
186 usage = '''usage: %prog [options] waveforms ...'''
187 parser = OptionParser(usage=usage)
189 parser.add_option(
190 '--format',
191 dest='format',
192 default='detect',
193 choices=io.allowed_formats('load'),
194 help='assume input files are of given FORMAT. Choices: %s'
195 % io.allowed_formats('load', 'cli_help', 'detect'))
197 parser.add_option(
198 '--pattern',
199 dest='regex',
200 metavar='REGEX',
201 help='only include files whose paths match REGEX')
203 parser.add_option(
204 '--stations',
205 dest='station_fns',
206 action='append',
207 default=[],
208 metavar='STATIONS',
209 help='read station information from file STATIONS')
211 parser.add_option(
212 '--stationxml',
213 dest='stationxml_fns',
214 action='append',
215 default=[],
216 metavar='STATIONSXML',
217 help='read station information from XML file STATIONSXML')
219 parser.add_option(
220 '--event', '--events',
221 dest='event_fns',
222 action='append',
223 default=[],
224 metavar='EVENT',
225 help='read event information from file EVENT')
227 parser.add_option(
228 '--markers',
229 dest='marker_fns',
230 action='append',
231 default=[],
232 metavar='MARKERS',
233 help='read marker information file MARKERS')
235 parser.add_option(
236 '--follow',
237 type='float',
238 dest='follow',
239 metavar='N',
240 help='follow real time with a window of N seconds')
242 parser.add_option(
243 '--cache',
244 dest='cache_dir',
245 default=config.config().cache_dir,
246 metavar='DIR',
247 help='use directory DIR to cache trace metadata '
248 '(default=\'%default\')')
250 parser.add_option(
251 '--force-cache',
252 dest='force_cache',
253 action='store_true',
254 default=False,
255 help='use the cache even when trace attribute spoofing is active '
256 '(may have silly consequences)')
258 parser.add_option(
259 '--store-path',
260 dest='store_path',
261 metavar='PATH_TEMPLATE',
262 help='store data received through streams to PATH_TEMPLATE')
264 parser.add_option(
265 '--store-interval',
266 type='float',
267 dest='store_interval',
268 default=600,
269 metavar='N',
270 help='dump stream data to file every N seconds [default: %default]')
272 parser.add_option(
273 '--ntracks',
274 type='int',
275 dest='ntracks',
276 default=24,
277 metavar='N',
278 help='initially use N waveform tracks in viewer [default: %default]')
280 parser.add_option(
281 '--disable-marker-sorting',
282 action='store_false',
283 dest='marker_editor_sortable',
284 default=True,
285 help='disable sorting in marker table for improved performance with '
286 '100000+ markers')
288 parser.add_option(
289 '--hptime',
290 choices=('on', 'off', 'config'),
291 dest='hp_time',
292 default='config',
293 metavar='on|off|config',
294 help='set high precision time mode [default: %default]')
296 parser.add_option(
297 '--opengl',
298 dest='opengl',
299 action='store_true',
300 default=None,
301 help='use OpenGL for drawing')
303 parser.add_option(
304 '--no-opengl',
305 dest='opengl',
306 action='store_false',
307 default=None,
308 help='do not use OpenGL for drawing')
310 parser.add_option(
311 '--debug',
312 dest='debug',
313 action='store_true',
314 default=False,
315 help='print debugging information to stderr')
317 options, args = parser.parse_args(list(args))
319 if options.debug:
320 util.setup_logging('snuffler', 'debug')
321 else:
322 util.setup_logging('snuffler', 'info')
324 if options.hp_time in ('on', 'off'):
325 util.use_high_precision_time(options.hp_time == 'on')
327 this_pile = pile_mod.Pile()
328 stations = []
329 for stations_fn in extend_paths(options.station_fns):
330 stations.extend(model.station.load_stations(stations_fn))
332 for stationxml_fn in extend_paths(options.stationxml_fns):
333 stations.extend(
334 stationxml.load_xml(
335 filename=stationxml_fn).get_pyrocko_stations())
337 events = []
338 for event_fn in extend_paths(options.event_fns):
339 events.extend(model.load_events(event_fn))
341 markers = []
342 for marker_fn in extend_paths(options.marker_fns):
343 markers.extend(marker.load_markers(marker_fn))
345 return snuffle(
346 this_pile,
347 stations=stations,
348 events=events,
349 markers=markers,
350 ntracks=options.ntracks,
351 marker_editor_sortable=options.marker_editor_sortable,
352 follow=options.follow,
353 controls=True,
354 opengl=options.opengl,
355 paths=args,
356 cache_dir=options.cache_dir,
357 regex=options.regex,
358 format=options.format,
359 force_cache=options.force_cache,
360 store_path=options.store_path,
361 store_interval=options.store_interval)
364if __name__ == '__main__':
365 snuffler_from_commandline()