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

29 

30from . import marker 

31 

32 

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

34 

35app = None 

36 

37 

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 

48 

49 

50def extend_paths(paths): 

51 paths_r = [] 

52 for p in paths: 

53 paths_r.extend(glob.glob(p)) 

54 return paths_r 

55 

56 

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

58 ''' 

59 View pile in a snuffler window. 

60 

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 

94 

95 if pile is None: 

96 pile = pile_mod.make_pile() 

97 

98 app = get_snuffler_instance() 

99 

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 

106 

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) 

111 

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) 

118 

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

136 

137 interval = min( 

138 source.get_wanted_poll_interval() for source in sources) 

139 

140 pollinjector = PollInjector( 

141 pile, 

142 fixation_length=store_interval, 

143 path=store_path, 

144 interval=interval) 

145 

146 for source in sources: 

147 source.start() 

148 pollinjector.add_source(source) 

149 

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

151 

152 if not win.is_closing(): 

153 app.install_sigint_handler() 

154 try: 

155 app.exec_() 

156 

157 finally: 

158 app.uninstall_sigint_handler() 

159 

160 for source in sources: 

161 source.stop() 

162 

163 if pollinjector: 

164 pollinjector.fixate_all() 

165 

166 ret = win.return_tag() 

167 

168 if want_markers: 

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

170 

171 del win 

172 gc.collect() 

173 

174 if tempdir: 

175 shutil.rmtree(tempdir) 

176 

177 if want_markers: 

178 return ret, markers 

179 else: 

180 return ret 

181 

182 

183def snuffler_from_commandline(args=None): 

184 if args is None: 

185 args = sys.argv[1:] 

186 

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

188 parser = OptionParser(usage=usage) 

189 

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

197 

198 parser.add_option( 

199 '--pattern', 

200 dest='regex', 

201 metavar='REGEX', 

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

203 

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

211 

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

219 

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

227 

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

235 

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

242 

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

250 

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

258 

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

264 

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

272 

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

280 

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

288 

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

296 

297 parser.add_option( 

298 '--opengl', 

299 dest='opengl', 

300 action='store_true', 

301 default=None, 

302 help='use OpenGL for drawing') 

303 

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

310 

311 parser.add_option( 

312 '--debug', 

313 dest='debug', 

314 action='store_true', 

315 default=False, 

316 help='print debugging information to stderr') 

317 

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

319 

320 if options.debug: 

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

322 else: 

323 util.setup_logging('snuffler', 'info') 

324 

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

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

327 

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

332 

333 for stationxml_fn in extend_paths(options.stationxml_fns): 

334 stations.extend( 

335 stationxml.load_xml( 

336 filename=stationxml_fn).get_pyrocko_stations()) 

337 

338 events = [] 

339 for event_fn in extend_paths(options.event_fns): 

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

341 

342 markers = [] 

343 for marker_fn in extend_paths(options.marker_fns): 

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

345 

346 import pyrocko 

347 

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) 

366 

367 except deps.MissingPyrockoDependency as e: 

368 logger.fatal(str(e)) 

369 sys.exit(1) 

370 

371 

372if __name__ == '__main__': 

373 snuffler_from_commandline()