1# https://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5 

6''' 

7Effective seismological trace viewer. 

8''' 

9 

10import os 

11import sys 

12import logging 

13import gc 

14import tempfile 

15import shutil 

16import glob 

17 

18from os.path import join as pjoin 

19from optparse import OptionParser 

20 

21 

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 

28 

29from . import marker 

30 

31 

32logger = logging.getLogger('pyrocko.gui.snuffler.snuffler') 

33 

34app = None 

35 

36 

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 

47 

48 

49def extend_paths(paths): 

50 paths_r = [] 

51 for p in paths: 

52 paths_r.extend(glob.glob(p)) 

53 return paths_r 

54 

55 

56def snuffle(pile=None, **kwargs): 

57 ''' 

58 View pile in a snuffler window. 

59 

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 

93 

94 if pile is None: 

95 pile = pile_mod.make_pile() 

96 

97 app = get_snuffler_instance() 

98 

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 

105 

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) 

110 

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) 

117 

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') 

135 

136 pollinjector = PollInjector( 

137 pile, 

138 fixation_length=store_interval, 

139 path=store_path) 

140 

141 for source in sources: 

142 source.start() 

143 pollinjector.add_source(source) 

144 

145 win.get_view().load(**kwargs_load) 

146 

147 if not win.is_closing(): 

148 app.install_sigint_handler() 

149 try: 

150 app.exec_() 

151 

152 finally: 

153 app.uninstall_sigint_handler() 

154 

155 for source in sources: 

156 source.stop() 

157 

158 if pollinjector: 

159 pollinjector.fixate_all() 

160 

161 ret = win.return_tag() 

162 

163 if want_markers: 

164 markers = win.get_view().get_markers() 

165 

166 del win 

167 gc.collect() 

168 

169 if tempdir: 

170 shutil.rmtree(tempdir) 

171 

172 if want_markers: 

173 return ret, markers 

174 else: 

175 return ret 

176 

177 

178def snuffler_from_commandline(args=None): 

179 if args is None: 

180 args = sys.argv[1:] 

181 

182 usage = '''usage: %prog [options] waveforms ...''' 

183 parser = OptionParser(usage=usage) 

184 

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')) 

192 

193 parser.add_option( 

194 '--pattern', 

195 dest='regex', 

196 metavar='REGEX', 

197 help='only include files whose paths match REGEX') 

198 

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') 

206 

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') 

214 

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') 

222 

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') 

230 

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') 

237 

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\')') 

245 

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)') 

253 

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') 

259 

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]') 

267 

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]') 

275 

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') 

283 

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]') 

291 

292 parser.add_option( 

293 '--opengl', 

294 dest='opengl', 

295 action='store_true', 

296 default=None, 

297 help='use OpenGL for drawing') 

298 

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') 

305 

306 parser.add_option( 

307 '--debug', 

308 dest='debug', 

309 action='store_true', 

310 default=False, 

311 help='print debugging information to stderr') 

312 

313 options, args = parser.parse_args(list(args)) 

314 

315 if options.debug: 

316 util.setup_logging('snuffler', 'debug') 

317 else: 

318 util.setup_logging('snuffler', 'warning') 

319 

320 if options.hp_time in ('on', 'off'): 

321 util.use_high_precision_time(options.hp_time == 'on') 

322 

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)) 

327 

328 for stationxml_fn in extend_paths(options.stationxml_fns): 

329 stations.extend( 

330 stationxml.load_xml( 

331 filename=stationxml_fn).get_pyrocko_stations()) 

332 

333 events = [] 

334 for event_fn in extend_paths(options.event_fns): 

335 events.extend(model.load_events(event_fn)) 

336 

337 markers = [] 

338 for marker_fn in extend_paths(options.marker_fns): 

339 markers.extend(marker.load_markers(marker_fn)) 

340 

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) 

358 

359 

360if __name__ == '__main__': 

361 snuffler_from_commandline()