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 pollinjector = PollInjector(
137 pile,
138 fixation_length=store_interval,
139 path=store_path)
141 for source in sources:
142 source.start()
143 pollinjector.add_source(source)
145 win.get_view().load(**kwargs_load)
147 if not win.is_closing():
148 app.install_sigint_handler()
149 try:
150 app.exec_()
152 finally:
153 app.uninstall_sigint_handler()
155 for source in sources:
156 source.stop()
158 if pollinjector:
159 pollinjector.fixate_all()
161 ret = win.return_tag()
163 if want_markers:
164 markers = win.get_view().get_markers()
166 del win
167 gc.collect()
169 if tempdir:
170 shutil.rmtree(tempdir)
172 if want_markers:
173 return ret, markers
174 else:
175 return ret
178def snuffler_from_commandline(args=None):
179 if args is None:
180 args = sys.argv[1:]
182 usage = '''usage: %prog [options] waveforms ...'''
183 parser = OptionParser(usage=usage)
185 parser.add_option(
186 '--format',
187 dest='format',
188 default='detect',
189 choices=io.allowed_formats('load'),
190 help='assume input files are of given FORMAT. Choices: %s'
191 % io.allowed_formats('load', 'cli_help', 'detect'))
193 parser.add_option(
194 '--pattern',
195 dest='regex',
196 metavar='REGEX',
197 help='only include files whose paths match REGEX')
199 parser.add_option(
200 '--stations',
201 dest='station_fns',
202 action='append',
203 default=[],
204 metavar='STATIONS',
205 help='read station information from file STATIONS')
207 parser.add_option(
208 '--stationxml',
209 dest='stationxml_fns',
210 action='append',
211 default=[],
212 metavar='STATIONSXML',
213 help='read station information from XML file STATIONSXML')
215 parser.add_option(
216 '--event', '--events',
217 dest='event_fns',
218 action='append',
219 default=[],
220 metavar='EVENT',
221 help='read event information from file EVENT')
223 parser.add_option(
224 '--markers',
225 dest='marker_fns',
226 action='append',
227 default=[],
228 metavar='MARKERS',
229 help='read marker information file MARKERS')
231 parser.add_option(
232 '--follow',
233 type='float',
234 dest='follow',
235 metavar='N',
236 help='follow real time with a window of N seconds')
238 parser.add_option(
239 '--cache',
240 dest='cache_dir',
241 default=config.config().cache_dir,
242 metavar='DIR',
243 help='use directory DIR to cache trace metadata '
244 '(default=\'%default\')')
246 parser.add_option(
247 '--force-cache',
248 dest='force_cache',
249 action='store_true',
250 default=False,
251 help='use the cache even when trace attribute spoofing is active '
252 '(may have silly consequences)')
254 parser.add_option(
255 '--store-path',
256 dest='store_path',
257 metavar='PATH_TEMPLATE',
258 help='store data received through streams to PATH_TEMPLATE')
260 parser.add_option(
261 '--store-interval',
262 type='float',
263 dest='store_interval',
264 default=600,
265 metavar='N',
266 help='dump stream data to file every N seconds [default: %default]')
268 parser.add_option(
269 '--ntracks',
270 type='int',
271 dest='ntracks',
272 default=24,
273 metavar='N',
274 help='initially use N waveform tracks in viewer [default: %default]')
276 parser.add_option(
277 '--disable-marker-sorting',
278 action='store_false',
279 dest='marker_editor_sortable',
280 default=True,
281 help='disable sorting in marker table for improved performance with '
282 '100000+ markers')
284 parser.add_option(
285 '--hptime',
286 choices=('on', 'off', 'config'),
287 dest='hp_time',
288 default='config',
289 metavar='on|off|config',
290 help='set high precision time mode [default: %default]')
292 parser.add_option(
293 '--opengl',
294 dest='opengl',
295 action='store_true',
296 default=None,
297 help='use OpenGL for drawing')
299 parser.add_option(
300 '--no-opengl',
301 dest='opengl',
302 action='store_false',
303 default=None,
304 help='do not use OpenGL for drawing')
306 parser.add_option(
307 '--debug',
308 dest='debug',
309 action='store_true',
310 default=False,
311 help='print debugging information to stderr')
313 options, args = parser.parse_args(list(args))
315 if options.debug:
316 util.setup_logging('snuffler', 'debug')
317 else:
318 util.setup_logging('snuffler', 'warning')
320 if options.hp_time in ('on', 'off'):
321 util.use_high_precision_time(options.hp_time == 'on')
323 this_pile = pile_mod.Pile()
324 stations = []
325 for stations_fn in extend_paths(options.station_fns):
326 stations.extend(model.station.load_stations(stations_fn))
328 for stationxml_fn in extend_paths(options.stationxml_fns):
329 stations.extend(
330 stationxml.load_xml(
331 filename=stationxml_fn).get_pyrocko_stations())
333 events = []
334 for event_fn in extend_paths(options.event_fns):
335 events.extend(model.load_events(event_fn))
337 markers = []
338 for marker_fn in extend_paths(options.marker_fns):
339 markers.extend(marker.load_markers(marker_fn))
341 return snuffle(
342 this_pile,
343 stations=stations,
344 events=events,
345 markers=markers,
346 ntracks=options.ntracks,
347 marker_editor_sortable=options.marker_editor_sortable,
348 follow=options.follow,
349 controls=True,
350 opengl=options.opengl,
351 paths=args,
352 cache_dir=options.cache_dir,
353 regex=options.regex,
354 format=options.format,
355 force_cache=options.force_cache,
356 store_path=options.store_path,
357 store_interval=options.store_interval)
360if __name__ == '__main__':
361 snuffler_from_commandline()