1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

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

5''' 

6Snuffling infrastructure 

7 

8This module provides the base class :py:class:`Snuffling` for user-defined 

9snufflings and some utilities for their handling. 

10''' 

11from __future__ import absolute_import 

12 

13import os 

14import sys 

15import time 

16import logging 

17import traceback 

18import tempfile 

19 

20from .qt_compat import qc, qw, getSaveFileName 

21 

22from pyrocko import pile, config 

23from pyrocko.util import quote 

24 

25from .util import (ValControl, LinValControl, FigureFrame, SmartplotFrame, 

26 WebKitFrame, VTKFrame, PixmapFrame, Marker, EventMarker, 

27 PhaseMarker, load_markers, save_markers) 

28 

29 

30from importlib import reload 

31 

32Marker, load_markers, save_markers # noqa 

33 

34logger = logging.getLogger('pyrocko.gui.snuffling') 

35 

36 

37class MyFrame(qw.QFrame): 

38 widgetVisibilityChanged = qc.pyqtSignal(bool) 

39 

40 def showEvent(self, ev): 

41 self.widgetVisibilityChanged.emit(True) 

42 

43 def hideEvent(self, ev): 

44 self.widgetVisibilityChanged.emit(False) 

45 

46 

47class Param(object): 

48 ''' 

49 Definition of an adjustable floating point parameter for the 

50 snuffling. The snuffling may display controls for user input for 

51 such parameters. 

52 

53 :param name: labels the parameter on the snuffling's control panel 

54 :param ident: identifier of the parameter 

55 :param default: default value 

56 :param minimum: minimum value for the parameter 

57 :param maximum: maximum value for the parameter 

58 :param low_is_none: if ``True``: parameter is set to None at lowest value 

59 of parameter range (optional) 

60 :param high_is_none: if ``True``: parameter is set to None at highest value 

61 of parameter range (optional) 

62 :param low_is_zero: if ``True``: parameter is set to value 0 at lowest 

63 value of parameter range (optional) 

64 ''' 

65 

66 def __init__( 

67 self, name, ident, default, minimum, maximum, 

68 low_is_none=None, 

69 high_is_none=None, 

70 low_is_zero=False, 

71 tracking=True, 

72 type=float): 

73 

74 if low_is_none and default == minimum: 

75 default = None 

76 if high_is_none and default == maximum: 

77 default = None 

78 

79 self.name = name 

80 self.ident = ident 

81 self.default = default 

82 self.minimum = minimum 

83 self.maximum = maximum 

84 self.low_is_none = low_is_none 

85 self.high_is_none = high_is_none 

86 self.low_is_zero = low_is_zero 

87 self.tracking = tracking 

88 self.type = type 

89 

90 self._control = None 

91 

92 

93class Switch(object): 

94 ''' 

95 Definition of a boolean switch for the snuffling. The snuffling 

96 may display a checkbox for such a switch. 

97 

98 :param name: labels the switch on the snuffling's control panel 

99 :param ident: identifier of the parameter 

100 :param default: default value 

101 ''' 

102 

103 def __init__(self, name, ident, default): 

104 self.name = name 

105 self.ident = ident 

106 self.default = default 

107 

108 

109class Choice(object): 

110 ''' 

111 Definition of a string choice for the snuffling. The snuffling 

112 may display a menu for such a choice. 

113 

114 :param name: labels the menu on the snuffling's control panel 

115 :param ident: identifier of the parameter 

116 :param default: default value 

117 :param choices: tuple of other options 

118 ''' 

119 

120 def __init__(self, name, ident, default, choices): 

121 self.name = name 

122 self.ident = ident 

123 self.default = default 

124 self.choices = choices 

125 

126 

127class Snuffling(object): 

128 ''' 

129 Base class for user snufflings. 

130 

131 Snufflings are plugins for snuffler (and other applications using the 

132 :py:class:`pyrocko.pile_viewer.PileOverview` class defined in 

133 ``pile_viewer.py``). They can be added, removed and reloaded at runtime and 

134 should provide a simple way of extending the functionality of snuffler. 

135 

136 A snuffling has access to all data available in a pile viewer, can process 

137 this data and can create and add new traces and markers to the viewer. 

138 ''' 

139 

140 def __init__(self): 

141 self._path = None 

142 

143 self._name = 'Untitled Snuffling' 

144 self._viewer = None 

145 self._tickets = [] 

146 self._markers = [] 

147 

148 self._delete_panel = None 

149 self._delete_menuitem = None 

150 

151 self._panel_parent = None 

152 self._menu_parent = None 

153 

154 self._panel = None 

155 self._menuitem = None 

156 self._helpmenuitem = None 

157 self._parameters = [] 

158 self._param_controls = {} 

159 

160 self._triggers = [] 

161 

162 self._live_update = True 

163 self._previous_output_filename = None 

164 self._previous_input_filename = None 

165 self._previous_input_directory = None 

166 

167 self._tempdir = None 

168 self._iplot = 0 

169 

170 self._have_pre_process_hook = False 

171 self._have_post_process_hook = False 

172 self._pre_process_hook_enabled = False 

173 self._post_process_hook_enabled = False 

174 

175 self._no_viewer_pile = None 

176 self._cli_params = {} 

177 self._filename = None 

178 self._force_panel = False 

179 self._call_in_progress = {} 

180 

181 def setup(self): 

182 ''' 

183 Setup the snuffling. 

184 

185 This method should be implemented in subclass and contain e.g. calls to 

186 :py:meth:`set_name` and :py:meth:`add_parameter`. 

187 ''' 

188 

189 pass 

190 

191 def module_dir(self): 

192 ''' 

193 Returns the path of the directory where snufflings are stored. 

194 

195 The default path is ``$HOME/.snufflings``. 

196 ''' 

197 

198 return self._path 

199 

200 def init_gui(self, viewer, panel_parent, menu_parent, reloaded=False): 

201 ''' 

202 Set parent viewer and hooks to add panel and menu entry. 

203 

204 This method is called from the 

205 :py:class:`pyrocko.pile_viewer.PileOverview` object. Calls 

206 :py:meth:`setup_gui`. 

207 ''' 

208 

209 self._viewer = viewer 

210 self._panel_parent = panel_parent 

211 self._menu_parent = menu_parent 

212 

213 self.setup_gui(reloaded=reloaded) 

214 

215 def setup_gui(self, reloaded=False): 

216 ''' 

217 Create and add gui elements to the viewer. 

218 

219 This method is initially called from :py:meth:`init_gui`. It is also 

220 called, e.g. when new parameters have been added or if the name of the 

221 snuffling has been changed. 

222 ''' 

223 

224 if self._panel_parent is not None: 

225 self._panel = self.make_panel(self._panel_parent) 

226 if self._panel: 

227 self._panel_parent.add_panel( 

228 self.get_name(), self._panel, reloaded) 

229 

230 if self._menu_parent is not None: 

231 self._menuitem = self.make_menuitem(self._menu_parent) 

232 self._helpmenuitem = self.make_helpmenuitem(self._menu_parent) 

233 if self._menuitem: 

234 self._menu_parent.add_snuffling_menuitem(self._menuitem) 

235 

236 if self._helpmenuitem: 

237 self._menu_parent.add_snuffling_help_menuitem( 

238 self._helpmenuitem) 

239 

240 def set_force_panel(self, bool=True): 

241 ''' 

242 Force to create a panel. 

243 

244 :param bool: if ``True`` will create a panel with Help, Clear and Run 

245 button. 

246 ''' 

247 

248 self._force_panel = bool 

249 

250 def make_cli_parser1(self): 

251 import optparse 

252 

253 class MyOptionParser(optparse.OptionParser): 

254 def error(self, msg): 

255 logger.error(msg) 

256 self.exit(1) 

257 

258 parser = MyOptionParser() 

259 

260 parser.add_option( 

261 '--format', 

262 dest='format', 

263 default='from_extension', 

264 choices=( 

265 'mseed', 'sac', 'kan', 'segy', 'seisan', 'seisan.l', 

266 'seisan.b', 'gse1', 'gcf', 'yaff', 'datacube', 

267 'from_extension', 'detect'), 

268 help='assume files are of given FORMAT [default: \'%default\']') 

269 

270 parser.add_option( 

271 '--pattern', 

272 dest='regex', 

273 metavar='REGEX', 

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

275 

276 self.add_params_to_cli_parser(parser) 

277 self.configure_cli_parser(parser) 

278 return parser 

279 

280 def configure_cli_parser(self, parser): 

281 pass 

282 

283 def cli_usage(self): 

284 return None 

285 

286 def add_params_to_cli_parser(self, parser): 

287 

288 for param in self._parameters: 

289 if isinstance(param, Param): 

290 parser.add_option( 

291 '--' + param.ident, 

292 dest=param.ident, 

293 default=param.default, 

294 type={float: 'float', int: 'int'}[param.type], 

295 help=param.name) 

296 

297 def setup_cli(self): 

298 self.setup() 

299 parser = self.make_cli_parser1() 

300 (options, args) = parser.parse_args() 

301 

302 for param in self._parameters: 

303 if isinstance(param, Param): 

304 setattr(self, param.ident, getattr(options, param.ident)) 

305 

306 self._cli_params['regex'] = options.regex 

307 self._cli_params['format'] = options.format 

308 self._cli_params['sources'] = args 

309 

310 return options, args, parser 

311 

312 def delete_gui(self): 

313 ''' 

314 Remove the gui elements of the snuffling. 

315 

316 This removes the panel and menu entry of the widget from the viewer and 

317 also removes all traces and markers added with the 

318 :py:meth:`add_traces` and :py:meth:`add_markers` methods. 

319 ''' 

320 

321 self.cleanup() 

322 

323 if self._panel is not None: 

324 self._panel_parent.remove_panel(self._panel) 

325 self._panel = None 

326 

327 if self._menuitem is not None: 

328 self._menu_parent.remove_snuffling_menuitem(self._menuitem) 

329 self._menuitem = None 

330 

331 if self._helpmenuitem is not None: 

332 self._menu_parent.remove_snuffling_help_menuitem( 

333 self._helpmenuitem) 

334 

335 def set_name(self, name): 

336 ''' 

337 Set the snuffling's name. 

338 

339 The snuffling's name is shown as a menu entry and in the panel header. 

340 ''' 

341 

342 self._name = name 

343 self.reset_gui() 

344 

345 def get_name(self): 

346 ''' 

347 Get the snuffling's name. 

348 ''' 

349 

350 return self._name 

351 

352 def set_have_pre_process_hook(self, bool): 

353 self._have_pre_process_hook = bool 

354 self._live_update = False 

355 self._pre_process_hook_enabled = False 

356 self.reset_gui() 

357 

358 def set_have_post_process_hook(self, bool): 

359 self._have_post_process_hook = bool 

360 self._live_update = False 

361 self._post_process_hook_enabled = False 

362 self.reset_gui() 

363 

364 def set_have_pile_changed_hook(self, bool): 

365 self._pile_ = False 

366 

367 def enable_pile_changed_notifications(self): 

368 ''' 

369 Get informed when pile changed. 

370 

371 When activated, the :py:meth:`pile_changed` method is called on every 

372 update in the viewer's pile. 

373 ''' 

374 

375 viewer = self.get_viewer() 

376 viewer.pile_has_changed_signal.connect( 

377 self.pile_changed) 

378 

379 def disable_pile_changed_notifications(self): 

380 ''' 

381 Stop getting informed about changes in viewer's pile. 

382 ''' 

383 

384 viewer = self.get_viewer() 

385 viewer.pile_has_changed_signal.disconnect( 

386 self.pile_changed) 

387 

388 def pile_changed(self): 

389 ''' 

390 Called when the connected viewer's pile has changed. 

391 

392 Must be activated with a call to 

393 :py:meth:`enable_pile_changed_notifications`. 

394 ''' 

395 

396 pass 

397 

398 def reset_gui(self, reloaded=False): 

399 ''' 

400 Delete and recreate the snuffling's panel. 

401 ''' 

402 

403 if self._panel or self._menuitem: 

404 sett = self.get_settings() 

405 self.delete_gui() 

406 self.setup_gui(reloaded=reloaded) 

407 self.set_settings(sett) 

408 

409 def show_message(self, kind, message): 

410 ''' 

411 Display a message box. 

412 

413 :param kind: string defining kind of message 

414 :param message: the message to be displayed 

415 ''' 

416 

417 try: 

418 box = qw.QMessageBox(self.get_viewer()) 

419 box.setText('%s: %s' % (kind.capitalize(), message)) 

420 box.exec_() 

421 except NoViewerSet: 

422 pass 

423 

424 def error(self, message): 

425 ''' 

426 Show an error message box. 

427 

428 :param message: specifying the error 

429 ''' 

430 

431 logger.error('%s: %s' % (self._name, message)) 

432 self.show_message('error', message) 

433 

434 def warn(self, message): 

435 ''' 

436 Display a warning message. 

437 

438 :param message: specifying the warning 

439 ''' 

440 

441 logger.warning('%s: %s' % (self._name, message)) 

442 self.show_message('warning', message) 

443 

444 def fail(self, message): 

445 ''' 

446 Show an error message box and raise :py:exc:`SnufflingCallFailed` 

447 exception. 

448 

449 :param message: specifying the error 

450 ''' 

451 

452 self.error(message) 

453 raise SnufflingCallFailed(message) 

454 

455 def pylab(self, name=None, get='axes', figure_cls=None): 

456 ''' 

457 Create a :py:class:`FigureFrame` and return either the frame, 

458 a :py:class:`matplotlib.figure.Figure` instance or a 

459 :py:class:`matplotlib.axes.Axes` instance. 

460 

461 :param name: labels the figure frame's tab 

462 :param get: 'axes'|'figure'|'frame' (optional) 

463 ''' 

464 

465 if name is None: 

466 self._iplot += 1 

467 name = 'Plot %i (%s)' % (self._iplot, self.get_name()) 

468 

469 fframe = FigureFrame(figure_cls=figure_cls) 

470 self._panel_parent.add_tab(name, fframe) 

471 if get == 'axes': 

472 return fframe.gca() 

473 elif get == 'figure': 

474 return fframe.gcf() 

475 elif get == 'figure_frame': 

476 return fframe 

477 

478 def figure(self, name=None): 

479 ''' 

480 Returns a :py:class:`matplotlib.figure.Figure` instance 

481 which can be displayed within snuffler by calling 

482 :py:meth:`canvas.draw`. 

483 

484 :param name: labels the tab of the figure 

485 ''' 

486 

487 return self.pylab(name=name, get='figure') 

488 

489 def axes(self, name=None): 

490 ''' 

491 Returns a :py:class:`matplotlib.axes.Axes` instance. 

492 

493 :param name: labels the tab of axes 

494 ''' 

495 

496 return self.pylab(name=name, get='axes') 

497 

498 def figure_frame(self, name=None, figure_cls=None): 

499 ''' 

500 Create a :py:class:`pyrocko.gui.util.FigureFrame`. 

501 

502 :param name: labels the tab figure frame 

503 ''' 

504 

505 return self.pylab(name=name, get='figure_frame', figure_cls=figure_cls) 

506 

507 def smartplot_frame(self, name, *args, plot_cls=None, **kwargs): 

508 ''' 

509 Create a :py:class:`pyrocko.gui.util.SmartplotFrame`. 

510 

511 :param name: labels the tab 

512 :param *args, **kwargs: 

513 passed to :py:class:`pyrocko.plot.smartplot.Plot` 

514 :param plot_cls: 

515 if given, subclass to be used instead of 

516 :py:class:`pyrocko.plot.smartplot.Plot` 

517 ''' 

518 frame = SmartplotFrame( 

519 plot_args=args, 

520 plot_cls=plot_cls, 

521 plot_kwargs=kwargs) 

522 

523 self._panel_parent.add_tab(name, frame) 

524 return frame 

525 

526 def pixmap_frame(self, filename=None, name=None): 

527 ''' 

528 Create a :py:class:`pyrocko.gui.util.PixmapFrame`. 

529 

530 :param name: labels the tab 

531 :param filename: name of file to be displayed 

532 ''' 

533 

534 f = PixmapFrame(filename) 

535 

536 scroll_area = qw.QScrollArea() 

537 scroll_area.setWidget(f) 

538 scroll_area.setWidgetResizable(True) 

539 

540 self._panel_parent.add_tab(name or "Pixmap", scroll_area) 

541 return f 

542 

543 def web_frame(self, url=None, name=None): 

544 ''' 

545 Creates a :py:class:`WebKitFrame` which can be used as a browser 

546 within snuffler. 

547 

548 :param url: url to open 

549 :param name: labels the tab 

550 ''' 

551 

552 if name is None: 

553 self._iplot += 1 

554 name = 'Web browser %i (%s)' % (self._iplot, self.get_name()) 

555 

556 f = WebKitFrame(url) 

557 self._panel_parent.add_tab(name, f) 

558 return f 

559 

560 def vtk_frame(self, name=None, actors=None): 

561 ''' 

562 Create a :py:class:`pyrocko.gui.util.VTKFrame` to render interactive 3D 

563 graphics. 

564 

565 :param actors: list of VTKActors 

566 :param name: labels the tab 

567 

568 Initialize the interactive rendering by calling the frames' 

569 :py:meth`initialize` method after having added all actors to the frames 

570 renderer. 

571 

572 Requires installation of vtk including python wrapper. 

573 ''' 

574 if name is None: 

575 self._iplot += 1 

576 name = 'VTK %i (%s)' % (self._iplot, self.get_name()) 

577 

578 try: 

579 f = VTKFrame(actors=actors) 

580 except ImportError as e: 

581 self.fail(e) 

582 

583 self._panel_parent.add_tab(name, f) 

584 return f 

585 

586 def tempdir(self): 

587 ''' 

588 Create a temporary directory and return its absolute path. 

589 

590 The directory and all its contents are removed when the Snuffling 

591 instance is deleted. 

592 ''' 

593 

594 if self._tempdir is None: 

595 self._tempdir = tempfile.mkdtemp('', 'snuffling-tmp-') 

596 

597 return self._tempdir 

598 

599 def set_live_update(self, live_update): 

600 ''' 

601 Enable/disable live updating. 

602 

603 When live updates are enabled, the :py:meth:`call` method is called 

604 whenever the user changes a parameter. If it is disabled, the user has 

605 to initiate such a call manually by triggering the snuffling's menu 

606 item or pressing the call button. 

607 ''' 

608 

609 self._live_update = live_update 

610 if self._have_pre_process_hook: 

611 self._pre_process_hook_enabled = live_update 

612 if self._have_post_process_hook: 

613 self._post_process_hook_enabled = live_update 

614 

615 try: 

616 self.get_viewer().clean_update() 

617 except NoViewerSet: 

618 pass 

619 

620 def add_parameter(self, param): 

621 ''' 

622 Add an adjustable parameter to the snuffling. 

623 

624 :param param: object of type :py:class:`Param`, :py:class:`Switch`, or 

625 :py:class:`Choice`. 

626 

627 For each parameter added, controls are added to the snuffling's panel, 

628 so that the parameter can be adjusted from the gui. 

629 ''' 

630 

631 self._parameters.append(param) 

632 self._set_parameter_value(param.ident, param.default) 

633 

634 if self._panel is not None: 

635 self.delete_gui() 

636 self.setup_gui() 

637 

638 def add_trigger(self, name, method): 

639 ''' 

640 Add a button to the snuffling's panel. 

641 

642 :param name: string that labels the button 

643 :param method: method associated with the button 

644 ''' 

645 

646 self._triggers.append((name, method)) 

647 

648 if self._panel is not None: 

649 self.delete_gui() 

650 self.setup_gui() 

651 

652 def get_parameters(self): 

653 ''' 

654 Get the snuffling's adjustable parameter definitions. 

655 

656 Returns a list of objects of type :py:class:`Param`. 

657 ''' 

658 

659 return self._parameters 

660 

661 def get_parameter(self, ident): 

662 ''' 

663 Get one of the snuffling's adjustable parameter definitions. 

664 

665 :param ident: identifier of the parameter 

666 

667 Returns an object of type :py:class:`Param` or ``None``. 

668 ''' 

669 

670 for param in self._parameters: 

671 if param.ident == ident: 

672 return param 

673 return None 

674 

675 def set_parameter(self, ident, value): 

676 ''' 

677 Set one of the snuffling's adjustable parameters. 

678 

679 :param ident: identifier of the parameter 

680 :param value: new value of the parameter 

681 

682 Adjusts the control of a parameter without calling :py:meth:`call`. 

683 ''' 

684 

685 self._set_parameter_value(ident, value) 

686 

687 control = self._param_controls.get(ident, None) 

688 if control: 

689 control.set_value(value) 

690 

691 def set_parameter_range(self, ident, vmin, vmax): 

692 ''' 

693 Set the range of one of the snuffling's adjustable parameters. 

694 

695 :param ident: identifier of the parameter 

696 :param vmin,vmax: new minimum and maximum value for the parameter 

697 

698 Adjusts the control of a parameter without calling :py:meth:`call`. 

699 ''' 

700 

701 control = self._param_controls.get(ident, None) 

702 if control: 

703 control.set_range(vmin, vmax) 

704 

705 def set_parameter_choices(self, ident, choices): 

706 ''' 

707 Update the choices of a Choice parameter. 

708 

709 :param ident: identifier of the parameter 

710 :param choices: list of strings 

711 ''' 

712 

713 control = self._param_controls.get(ident, None) 

714 if control: 

715 selected_choice = control.set_choices(choices) 

716 self._set_parameter_value(ident, selected_choice) 

717 

718 def _set_parameter_value(self, ident, value): 

719 setattr(self, ident, value) 

720 

721 def get_parameter_value(self, ident): 

722 ''' 

723 Get the current value of a parameter. 

724 

725 :param ident: identifier of the parameter 

726 ''' 

727 return getattr(self, ident) 

728 

729 def get_settings(self): 

730 ''' 

731 Returns a dictionary with identifiers of all parameters as keys and 

732 their values as the dictionaries values. 

733 ''' 

734 

735 params = self.get_parameters() 

736 settings = {} 

737 for param in params: 

738 settings[param.ident] = self.get_parameter_value(param.ident) 

739 

740 return settings 

741 

742 def set_settings(self, settings): 

743 params = self.get_parameters() 

744 dparams = dict([(param.ident, param) for param in params]) 

745 for k, v in settings.items(): 

746 if k in dparams: 

747 self._set_parameter_value(k, v) 

748 if k in self._param_controls: 

749 control = self._param_controls[k] 

750 control.set_value(v) 

751 

752 def get_viewer(self): 

753 ''' 

754 Get the parent viewer. 

755 

756 Returns a reference to an object of type :py:class:`PileOverview`, 

757 which is the main viewer widget. 

758 

759 If no gui has been initialized for the snuffling, a 

760 :py:exc:`NoViewerSet` exception is raised. 

761 ''' 

762 

763 if self._viewer is None: 

764 raise NoViewerSet() 

765 return self._viewer 

766 

767 def get_pile(self): 

768 ''' 

769 Get the pile. 

770 

771 If a gui has been initialized, a reference to the viewer's internal 

772 pile is returned. If not, the :py:meth:`make_pile` method (which may be 

773 overloaded in subclass) is called to create a pile. This can be 

774 utilized to make hybrid snufflings, which may work also in a standalone 

775 mode. 

776 ''' 

777 

778 try: 

779 p = self.get_viewer().get_pile() 

780 except NoViewerSet: 

781 if self._no_viewer_pile is None: 

782 self._no_viewer_pile = self.make_pile() 

783 

784 p = self._no_viewer_pile 

785 

786 return p 

787 

788 def get_active_event_and_stations( 

789 self, trange=(-3600., 3600.), missing='warn'): 

790 

791 ''' 

792 Get event and stations with available data for active event. 

793 

794 :param trange: (begin, end), time range around event origin time to 

795 query for available data 

796 :param missing: string, what to do in case of missing station 

797 information: ``'warn'``, ``'raise'`` or ``'ignore'``. 

798 

799 :returns: ``(event, stations)`` 

800 ''' 

801 

802 p = self.get_pile() 

803 v = self.get_viewer() 

804 

805 event = v.get_active_event() 

806 if event is None: 

807 self.fail( 

808 'No active event set. Select an event and press "e" to make ' 

809 'it the "active event"') 

810 

811 stations = {} 

812 for traces in p.chopper( 

813 event.time+trange[0], 

814 event.time+trange[1], 

815 load_data=False, 

816 degap=False): 

817 

818 for tr in traces: 

819 try: 

820 for skey in v.station_keys(tr): 

821 if skey in stations: 

822 continue 

823 

824 station = v.get_station(skey) 

825 stations[skey] = station 

826 

827 except KeyError: 

828 s = 'No station information for station key "%s".' \ 

829 % '.'.join(skey) 

830 

831 if missing == 'warn': 

832 logger.warning(s) 

833 elif missing == 'raise': 

834 raise MissingStationInformation(s) 

835 elif missing == 'ignore': 

836 pass 

837 else: 

838 assert False, 'invalid argument to "missing"' 

839 

840 stations[skey] = None 

841 

842 return event, list(set( 

843 st for st in stations.values() if st is not None)) 

844 

845 def get_stations(self): 

846 ''' 

847 Get all stations known to the viewer. 

848 ''' 

849 

850 v = self.get_viewer() 

851 stations = list(v.stations.values()) 

852 return stations 

853 

854 def get_markers(self): 

855 ''' 

856 Get all markers from the viewer. 

857 ''' 

858 

859 return self.get_viewer().get_markers() 

860 

861 def get_event_markers(self): 

862 ''' 

863 Get all event markers from the viewer. 

864 ''' 

865 

866 return [m for m in self.get_viewer().get_markers() 

867 if isinstance(m, EventMarker)] 

868 

869 def get_selected_markers(self): 

870 ''' 

871 Get all selected markers from the viewer. 

872 ''' 

873 

874 return self.get_viewer().selected_markers() 

875 

876 def get_selected_event_markers(self): 

877 ''' 

878 Get all selected event markers from the viewer. 

879 ''' 

880 

881 return [m for m in self.get_viewer().selected_markers() 

882 if isinstance(m, EventMarker)] 

883 

884 def get_active_event_and_phase_markers(self): 

885 ''' 

886 Get the marker of the active event and any associated phase markers 

887 ''' 

888 

889 viewer = self.get_viewer() 

890 markers = viewer.get_markers() 

891 event_marker = viewer.get_active_event_marker() 

892 if event_marker is None: 

893 self.fail( 

894 'No active event set. ' 

895 'Select an event and press "e" to make it the "active event"') 

896 

897 event = event_marker.get_event() 

898 

899 selection = [] 

900 for m in markers: 

901 if isinstance(m, PhaseMarker): 

902 if m.get_event() is event: 

903 selection.append(m) 

904 

905 return ( 

906 event_marker, 

907 [m for m in markers if isinstance(m, PhaseMarker) and 

908 m.get_event() == event]) 

909 

910 def get_viewer_trace_selector(self, mode='inview'): 

911 ''' 

912 Get currently active trace selector from viewer. 

913 

914 :param mode: set to ``'inview'`` (default) to only include selections 

915 currently shown in the viewer, ``'visible' to include all traces 

916 not currenly hidden by hide or quick-select commands, or ``'all'`` 

917 to disable any restrictions. 

918 ''' 

919 

920 viewer = self.get_viewer() 

921 

922 def rtrue(tr): 

923 return True 

924 

925 if mode == 'inview': 

926 return viewer.trace_selector or rtrue 

927 elif mode == 'visible': 

928 return viewer.trace_filter or rtrue 

929 elif mode == 'all': 

930 return rtrue 

931 else: 

932 raise Exception('invalid mode argument') 

933 

934 def chopper_selected_traces(self, fallback=False, marker_selector=None, 

935 mode='inview', main_bandpass=False, 

936 progress=None, responsive=False, 

937 *args, **kwargs): 

938 ''' 

939 Iterate over selected traces. 

940 

941 Shortcut to get all trace data contained in the selected markers in the 

942 running snuffler. For each selected marker, 

943 :py:meth:`pyrocko.pile.Pile.chopper` is called with the arguments 

944 *tmin*, *tmax*, and *trace_selector* set to values according to the 

945 marker. Additional arguments to the chopper are handed over from 

946 *\\*args* and *\\*\\*kwargs*. 

947 

948 :param fallback: 

949 If ``True``, if no selection has been marked, use the content 

950 currently visible in the viewer. 

951 

952 :param marker_selector: 

953 If not ``None`` a callback to filter markers. 

954 

955 :param mode: 

956 Set to ``'inview'`` (default) to only include selections currently 

957 shown in the viewer (excluding traces accessible through vertical 

958 scrolling), ``'visible'`` to include all traces not currently 

959 hidden by hide or quick-select commands (including traces 

960 accessible through vertical scrolling), or ``'all'`` to disable any 

961 restrictions. 

962 

963 :param main_bandpass: 

964 If ``True``, apply main control high- and lowpass filters to 

965 traces. Note: use with caution. Processing is fixed to use 4th 

966 order Butterworth highpass and lowpass and the signal is always 

967 demeaned before filtering. FFT filtering, rotation, demean and 

968 bandpass settings from the graphical interface are not respected 

969 here. Padding is not automatically adjusted so results may include 

970 artifacts. 

971 

972 :param progress: 

973 If given a string a progress bar is shown to the user. The string 

974 is used as the label for the progress bar. 

975 

976 :param responsive: 

977 If set to ``True``, occasionally allow UI events to be processed. 

978 If used in combination with ``progress``, this allows the iterator 

979 to be aborted by the user. 

980 ''' 

981 

982 try: 

983 viewer = self.get_viewer() 

984 markers = [ 

985 m for m in viewer.selected_markers() 

986 if not isinstance(m, EventMarker)] 

987 

988 if marker_selector is not None: 

989 markers = [ 

990 marker for marker in markers if marker_selector(marker)] 

991 

992 pile = self.get_pile() 

993 

994 def rtrue(tr): 

995 return True 

996 

997 trace_selector_arg = kwargs.pop('trace_selector', rtrue) 

998 trace_selector_viewer = self.get_viewer_trace_selector(mode) 

999 

1000 style_arg = kwargs.pop('style', None) 

1001 

1002 if main_bandpass: 

1003 def apply_filters(traces): 

1004 for tr in traces: 

1005 if viewer.highpass is not None: 

1006 tr.highpass(4, viewer.highpass) 

1007 if viewer.lowpass is not None: 

1008 tr.lowpass(4, viewer.lowpass) 

1009 return traces 

1010 else: 

1011 def apply_filters(traces): 

1012 return traces 

1013 

1014 pb = viewer.parent().get_progressbars() 

1015 

1016 time_last = [time.time()] 

1017 

1018 def update_progress(label, batch): 

1019 time_now = time.time() 

1020 if responsive: 

1021 # start processing events with one second delay, so that 

1022 # e.g. cleanup actions at startup do not cause track number 

1023 # changes etc. 

1024 if time_last[0] + 1. < time_now: 

1025 qw.qApp.processEvents() 

1026 else: 

1027 # redraw about once a second 

1028 if time_last[0] + 1. < time_now: 

1029 viewer.repaint() 

1030 

1031 time_last[0] = time.time() # use time after drawing 

1032 

1033 abort = pb.set_status( 

1034 label, batch.i*100./batch.n, responsive) 

1035 abort |= viewer.window().is_closing() 

1036 

1037 return abort 

1038 

1039 if markers: 

1040 for imarker, marker in enumerate(markers): 

1041 try: 

1042 if progress: 

1043 label = '%s: %i/%i' % ( 

1044 progress, imarker+1, len(markers)) 

1045 

1046 pb.set_status(label, 0, responsive) 

1047 

1048 if not marker.nslc_ids: 

1049 trace_selector_marker = rtrue 

1050 else: 

1051 def trace_selector_marker(tr): 

1052 return marker.match_nslc(tr.nslc_id) 

1053 

1054 def trace_selector(tr): 

1055 return trace_selector_arg(tr) \ 

1056 and trace_selector_viewer(tr) \ 

1057 and trace_selector_marker(tr) 

1058 

1059 for batch in pile.chopper( 

1060 tmin=marker.tmin, 

1061 tmax=marker.tmax, 

1062 trace_selector=trace_selector, 

1063 style='batch', 

1064 *args, 

1065 **kwargs): 

1066 

1067 if progress: 

1068 abort = update_progress(label, batch) 

1069 if abort: 

1070 return 

1071 

1072 batch.traces = apply_filters(batch.traces) 

1073 if style_arg == 'batch': 

1074 yield batch 

1075 else: 

1076 yield batch.traces 

1077 

1078 finally: 

1079 if progress: 

1080 pb.set_status(label, 100., responsive) 

1081 

1082 elif fallback: 

1083 def trace_selector(tr): 

1084 return trace_selector_arg(tr) \ 

1085 and trace_selector_viewer(tr) 

1086 

1087 tmin, tmax = viewer.get_time_range() 

1088 

1089 if not pile.is_empty(): 

1090 ptmin = pile.get_tmin() 

1091 tpad = kwargs.get('tpad', 0.0) 

1092 if ptmin > tmin: 

1093 tmin = ptmin + tpad 

1094 ptmax = pile.get_tmax() 

1095 if ptmax < tmax: 

1096 tmax = ptmax - tpad 

1097 

1098 try: 

1099 if progress: 

1100 label = progress 

1101 pb.set_status(label, 0, responsive) 

1102 

1103 for batch in pile.chopper( 

1104 tmin=tmin, 

1105 tmax=tmax, 

1106 trace_selector=trace_selector, 

1107 style='batch', 

1108 *args, 

1109 **kwargs): 

1110 

1111 if progress: 

1112 abort = update_progress(label, batch) 

1113 

1114 if abort: 

1115 return 

1116 

1117 batch.traces = apply_filters(batch.traces) 

1118 

1119 if style_arg == 'batch': 

1120 yield batch 

1121 else: 

1122 yield batch.traces 

1123 

1124 finally: 

1125 if progress: 

1126 pb.set_status(label, 100., responsive) 

1127 

1128 else: 

1129 raise NoTracesSelected() 

1130 

1131 except NoViewerSet: 

1132 pile = self.get_pile() 

1133 return pile.chopper(*args, **kwargs) 

1134 

1135 def get_selected_time_range(self, fallback=False): 

1136 ''' 

1137 Get the time range spanning all selected markers. 

1138 

1139 :param fallback: if ``True`` and no marker is selected return begin and 

1140 end of visible time range 

1141 ''' 

1142 

1143 viewer = self.get_viewer() 

1144 markers = viewer.selected_markers() 

1145 mins = [marker.tmin for marker in markers] 

1146 maxs = [marker.tmax for marker in markers] 

1147 

1148 if mins and maxs: 

1149 tmin = min(mins) 

1150 tmax = max(maxs) 

1151 

1152 elif fallback: 

1153 tmin, tmax = viewer.get_time_range() 

1154 

1155 else: 

1156 raise NoTracesSelected() 

1157 

1158 return tmin, tmax 

1159 

1160 def panel_visibility_changed(self, bool): 

1161 ''' 

1162 Called when the snuffling's panel becomes visible or is hidden. 

1163 

1164 Can be overloaded in subclass, e.g. to perform additional setup actions 

1165 when the panel is activated the first time. 

1166 ''' 

1167 

1168 pass 

1169 

1170 def make_pile(self): 

1171 ''' 

1172 Create a pile. 

1173 

1174 To be overloaded in subclass. The default implementation just calls 

1175 :py:func:`pyrocko.pile.make_pile` to create a pile from command line 

1176 arguments. 

1177 ''' 

1178 

1179 cachedirname = config.config().cache_dir 

1180 sources = self._cli_params.get('sources', sys.argv[1:]) 

1181 return pile.make_pile( 

1182 sources, 

1183 cachedirname=cachedirname, 

1184 regex=self._cli_params['regex'], 

1185 fileformat=self._cli_params['format']) 

1186 

1187 def make_panel(self, parent): 

1188 ''' 

1189 Create a widget for the snuffling's control panel. 

1190 

1191 Normally called from the :py:meth:`setup_gui` method. Returns ``None`` 

1192 if no panel is needed (e.g. if the snuffling has no adjustable 

1193 parameters). 

1194 ''' 

1195 

1196 params = self.get_parameters() 

1197 self._param_controls = {} 

1198 if params or self._force_panel: 

1199 sarea = MyScrollArea(parent.get_panel_parent_widget()) 

1200 sarea.setFrameStyle(qw.QFrame.NoFrame) 

1201 sarea.setSizePolicy(qw.QSizePolicy( 

1202 qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding)) 

1203 frame = MyFrame(sarea) 

1204 frame.widgetVisibilityChanged.connect( 

1205 self.panel_visibility_changed) 

1206 

1207 frame.setSizePolicy(qw.QSizePolicy( 

1208 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)) 

1209 frame.setFrameStyle(qw.QFrame.NoFrame) 

1210 sarea.setWidget(frame) 

1211 sarea.setWidgetResizable(True) 

1212 layout = qw.QGridLayout() 

1213 layout.setContentsMargins(0, 0, 0, 0) 

1214 layout.setSpacing(0) 

1215 frame.setLayout(layout) 

1216 

1217 parlayout = qw.QGridLayout() 

1218 

1219 irow = 0 

1220 ipar = 0 

1221 have_switches = False 

1222 have_params = False 

1223 for iparam, param in enumerate(params): 

1224 if isinstance(param, Param): 

1225 if param.minimum <= 0.0: 

1226 param_control = LinValControl( 

1227 high_is_none=param.high_is_none, 

1228 low_is_none=param.low_is_none, 

1229 type=param.type) 

1230 else: 

1231 param_control = ValControl( 

1232 high_is_none=param.high_is_none, 

1233 low_is_none=param.low_is_none, 

1234 low_is_zero=param.low_is_zero, 

1235 type=param.type) 

1236 

1237 param_control.setup( 

1238 param.name, 

1239 param.minimum, 

1240 param.maximum, 

1241 param.default, 

1242 iparam) 

1243 

1244 param_control.set_tracking(param.tracking) 

1245 param_control.valchange.connect( 

1246 self.modified_snuffling_panel) 

1247 

1248 self._param_controls[param.ident] = param_control 

1249 for iw, w in enumerate(param_control.widgets()): 

1250 parlayout.addWidget(w, ipar, iw) 

1251 

1252 ipar += 1 

1253 have_params = True 

1254 

1255 elif isinstance(param, Choice): 

1256 param_widget = ChoiceControl( 

1257 param.ident, param.default, param.choices, param.name) 

1258 param_widget.choosen.connect( 

1259 self.choose_on_snuffling_panel) 

1260 

1261 self._param_controls[param.ident] = param_widget 

1262 parlayout.addWidget(param_widget, ipar, 0, 1, 3) 

1263 ipar += 1 

1264 have_params = True 

1265 

1266 elif isinstance(param, Switch): 

1267 have_switches = True 

1268 

1269 if have_params: 

1270 parframe = qw.QFrame(sarea) 

1271 parframe.setSizePolicy(qw.QSizePolicy( 

1272 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)) 

1273 parframe.setLayout(parlayout) 

1274 layout.addWidget(parframe, irow, 0) 

1275 irow += 1 

1276 

1277 if have_switches: 

1278 swlayout = qw.QGridLayout() 

1279 isw = 0 

1280 for iparam, param in enumerate(params): 

1281 if isinstance(param, Switch): 

1282 param_widget = SwitchControl( 

1283 param.ident, param.default, param.name) 

1284 param_widget.sw_toggled.connect( 

1285 self.switch_on_snuffling_panel) 

1286 

1287 self._param_controls[param.ident] = param_widget 

1288 swlayout.addWidget(param_widget, isw//10, isw % 10) 

1289 isw += 1 

1290 

1291 swframe = qw.QFrame(sarea) 

1292 swframe.setSizePolicy(qw.QSizePolicy( 

1293 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)) 

1294 swframe.setLayout(swlayout) 

1295 layout.addWidget(swframe, irow, 0) 

1296 irow += 1 

1297 

1298 butframe = qw.QFrame(sarea) 

1299 butframe.setSizePolicy(qw.QSizePolicy( 

1300 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)) 

1301 butlayout = qw.QHBoxLayout() 

1302 butframe.setLayout(butlayout) 

1303 

1304 live_update_checkbox = qw.QCheckBox('Auto-Run') 

1305 if self._live_update: 

1306 live_update_checkbox.setCheckState(qc.Qt.Checked) 

1307 

1308 butlayout.addWidget(live_update_checkbox) 

1309 live_update_checkbox.toggled.connect( 

1310 self.live_update_toggled) 

1311 

1312 help_button = qw.QPushButton('Help') 

1313 butlayout.addWidget(help_button) 

1314 help_button.clicked.connect( 

1315 self.help_button_triggered) 

1316 

1317 clear_button = qw.QPushButton('Clear') 

1318 butlayout.addWidget(clear_button) 

1319 clear_button.clicked.connect( 

1320 self.clear_button_triggered) 

1321 

1322 call_button = qw.QPushButton('Run') 

1323 butlayout.addWidget(call_button) 

1324 call_button.clicked.connect( 

1325 self.call_button_triggered) 

1326 

1327 for name, method in self._triggers: 

1328 but = qw.QPushButton(name) 

1329 

1330 def call_and_update(method): 

1331 def f(): 

1332 self.check_call(method) 

1333 self.get_viewer().update() 

1334 return f 

1335 

1336 but.clicked.connect( 

1337 call_and_update(method)) 

1338 

1339 butlayout.addWidget(but) 

1340 

1341 layout.addWidget(butframe, irow, 0) 

1342 

1343 irow += 1 

1344 spacer = qw.QSpacerItem( 

1345 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

1346 

1347 layout.addItem(spacer, irow, 0) 

1348 

1349 return sarea 

1350 

1351 else: 

1352 return None 

1353 

1354 def make_helpmenuitem(self, parent): 

1355 ''' 

1356 Create the help menu item for the snuffling. 

1357 ''' 

1358 

1359 item = qw.QAction(self.get_name(), None) 

1360 

1361 item.triggered.connect( 

1362 self.help_button_triggered) 

1363 

1364 return item 

1365 

1366 def make_menuitem(self, parent): 

1367 ''' 

1368 Create the menu item for the snuffling. 

1369 

1370 This method may be overloaded in subclass and return ``None``, if no 

1371 menu entry is wanted. 

1372 ''' 

1373 

1374 item = qw.QAction(self.get_name(), None) 

1375 item.setCheckable( 

1376 self._have_pre_process_hook or self._have_post_process_hook) 

1377 

1378 item.triggered.connect( 

1379 self.menuitem_triggered) 

1380 

1381 return item 

1382 

1383 def output_filename( 

1384 self, 

1385 caption='Save File', 

1386 dir='', 

1387 filter='', 

1388 selected_filter=None): 

1389 

1390 ''' 

1391 Query user for an output filename. 

1392 

1393 This is currently a wrapper to :py:func:`QFileDialog.getSaveFileName`. 

1394 A :py:exc:`UserCancelled` exception is raised if the user cancels the 

1395 dialog. 

1396 ''' 

1397 

1398 if not dir and self._previous_output_filename: 

1399 dir = self._previous_output_filename 

1400 

1401 fn = getSaveFileName( 

1402 self.get_viewer(), caption, dir, filter, selected_filter) 

1403 if not fn: 

1404 raise UserCancelled() 

1405 

1406 self._previous_output_filename = fn 

1407 return str(fn) 

1408 

1409 def input_directory(self, caption='Open Directory', dir=''): 

1410 ''' 

1411 Query user for an input directory. 

1412 

1413 This is a wrapper to :py:func:`QFileDialog.getExistingDirectory`. 

1414 A :py:exc:`UserCancelled` exception is raised if the user cancels the 

1415 dialog. 

1416 ''' 

1417 

1418 if not dir and self._previous_input_directory: 

1419 dir = self._previous_input_directory 

1420 

1421 dn = qw.QFileDialog.getExistingDirectory( 

1422 None, caption, dir, qw.QFileDialog.ShowDirsOnly) 

1423 

1424 if not dn: 

1425 raise UserCancelled() 

1426 

1427 self._previous_input_directory = dn 

1428 return str(dn) 

1429 

1430 def input_filename(self, caption='Open File', dir='', filter='', 

1431 selected_filter=None): 

1432 ''' 

1433 Query user for an input filename. 

1434 

1435 This is currently a wrapper to :py:func:`QFileDialog.getOpenFileName`. 

1436 A :py:exc:`UserCancelled` exception is raised if the user cancels the 

1437 dialog. 

1438 ''' 

1439 

1440 if not dir and self._previous_input_filename: 

1441 dir = self._previous_input_filename 

1442 

1443 fn, _ = qw.QFileDialog.getOpenFileName( 

1444 self.get_viewer(), 

1445 caption, 

1446 dir, 

1447 filter) 

1448 

1449 if not fn: 

1450 raise UserCancelled() 

1451 

1452 self._previous_input_filename = fn 

1453 return str(fn) 

1454 

1455 def input_dialog(self, caption='', request='', directory=False): 

1456 ''' 

1457 Query user for a text input. 

1458 

1459 This is currently a wrapper to :py:func:`QInputDialog.getText`. 

1460 A :py:exc:`UserCancelled` exception is raised if the user cancels the 

1461 dialog. 

1462 ''' 

1463 

1464 inp, ok = qw.QInputDialog.getText(self.get_viewer(), 'Input', caption) 

1465 

1466 if not ok: 

1467 raise UserCancelled() 

1468 

1469 return inp 

1470 

1471 def modified_snuffling_panel(self, value, iparam): 

1472 ''' 

1473 Called when the user has played with an adjustable parameter. 

1474 

1475 The default implementation sets the parameter, calls the snuffling's 

1476 :py:meth:`call` method and finally triggers an update on the viewer 

1477 widget. 

1478 ''' 

1479 

1480 param = self.get_parameters()[iparam] 

1481 self._set_parameter_value(param.ident, value) 

1482 if self._live_update: 

1483 self.check_call(self.call) 

1484 self.get_viewer().update() 

1485 

1486 def switch_on_snuffling_panel(self, ident, state): 

1487 ''' 

1488 Called when the user has toggled a switchable parameter. 

1489 ''' 

1490 

1491 self._set_parameter_value(ident, state) 

1492 if self._live_update: 

1493 self.check_call(self.call) 

1494 self.get_viewer().update() 

1495 

1496 def choose_on_snuffling_panel(self, ident, state): 

1497 ''' 

1498 Called when the user has made a choice about a choosable parameter. 

1499 ''' 

1500 

1501 self._set_parameter_value(ident, state) 

1502 if self._live_update: 

1503 self.check_call(self.call) 

1504 self.get_viewer().update() 

1505 

1506 def menuitem_triggered(self, arg): 

1507 ''' 

1508 Called when the user has triggered the snuffling's menu. 

1509 

1510 The default implementation calls the snuffling's :py:meth:`call` method 

1511 and triggers an update on the viewer widget. 

1512 ''' 

1513 

1514 self.check_call(self.call) 

1515 

1516 if self._have_pre_process_hook: 

1517 self._pre_process_hook_enabled = arg 

1518 

1519 if self._have_post_process_hook: 

1520 self._post_process_hook_enabled = arg 

1521 

1522 if self._have_pre_process_hook or self._have_post_process_hook: 

1523 self.get_viewer().clean_update() 

1524 else: 

1525 self.get_viewer().update() 

1526 

1527 def call_button_triggered(self): 

1528 ''' 

1529 Called when the user has clicked the snuffling's call button. 

1530 

1531 The default implementation calls the snuffling's :py:meth:`call` method 

1532 and triggers an update on the viewer widget. 

1533 ''' 

1534 

1535 self.check_call(self.call) 

1536 self.get_viewer().update() 

1537 

1538 def clear_button_triggered(self): 

1539 ''' 

1540 Called when the user has clicked the snuffling's clear button. 

1541 

1542 This calls the :py:meth:`cleanup` method and triggers an update on the 

1543 viewer widget. 

1544 ''' 

1545 

1546 self.cleanup() 

1547 self.get_viewer().update() 

1548 

1549 def help_button_triggered(self): 

1550 ''' 

1551 Creates a :py:class:`QLabel` which contains the documentation as 

1552 given in the snufflings' __doc__ string. 

1553 ''' 

1554 

1555 if self.__doc__: 

1556 if self.__doc__.strip().startswith('<html>'): 

1557 doc = qw.QLabel(self.__doc__) 

1558 else: 

1559 try: 

1560 import markdown 

1561 doc = qw.QLabel(markdown.markdown(self.__doc__)) 

1562 

1563 except ImportError: 

1564 logger.error( 

1565 'Install Python module "markdown" for pretty help ' 

1566 'formatting.') 

1567 

1568 doc = qw.QLabel(self.__doc__) 

1569 else: 

1570 doc = qw.QLabel('This snuffling does not provide any online help.') 

1571 

1572 labels = [doc] 

1573 

1574 if self._filename: 

1575 from html import escape 

1576 

1577 code = open(self._filename, 'r').read() 

1578 

1579 doc_src = qw.QLabel( 

1580 '''<html><body> 

1581<hr /> 

1582<center><em>May the source be with you, young Skywalker!</em><br /><br /> 

1583<a href="file://%s"><code>%s</code></a></center> 

1584<br /> 

1585<p style="margin-left: 2em; margin-right: 2em; background-color:#eed;"> 

1586<pre style="white-space: pre-wrap"><code>%s 

1587</code></pre></p></body></html>''' 

1588 % ( 

1589 quote(self._filename), 

1590 escape(self._filename), 

1591 escape(code))) 

1592 

1593 labels.append(doc_src) 

1594 

1595 for h in labels: 

1596 h.setAlignment(qc.Qt.AlignTop | qc.Qt.AlignLeft) 

1597 h.setWordWrap(True) 

1598 

1599 self._viewer.show_doc('Help: %s' % self._name, labels, target='panel') 

1600 

1601 def live_update_toggled(self, on): 

1602 ''' 

1603 Called when the checkbox for live-updates has been toggled. 

1604 ''' 

1605 

1606 self.set_live_update(on) 

1607 

1608 def add_traces(self, traces): 

1609 ''' 

1610 Add traces to the viewer. 

1611 

1612 :param traces: list of objects of type :py:class:`pyrocko.trace.Trace` 

1613 

1614 The traces are put into a :py:class:`pyrocko.pile.MemTracesFile` and 

1615 added to the viewer's internal pile for display. Note, that unlike with 

1616 the traces from the files given on the command line, these traces are 

1617 kept in memory and so may quickly occupy a lot of ram if a lot of 

1618 traces are added. 

1619 

1620 This method should be preferred over modifying the viewer's internal 

1621 pile directly, because this way, the snuffling has a chance to 

1622 automatically remove its private traces again (see :py:meth:`cleanup` 

1623 method). 

1624 ''' 

1625 

1626 ticket = self.get_viewer().add_traces(traces) 

1627 self._tickets.append(ticket) 

1628 return ticket 

1629 

1630 def add_trace(self, tr): 

1631 ''' 

1632 Add a trace to the viewer. 

1633 

1634 See :py:meth:`add_traces`. 

1635 ''' 

1636 

1637 self.add_traces([tr]) 

1638 

1639 def add_markers(self, markers): 

1640 ''' 

1641 Add some markers to the display. 

1642 

1643 Takes a list of objects of type :py:class:`pyrocko.gui.util.Marker` and 

1644 adds these to the viewer. 

1645 ''' 

1646 

1647 self.get_viewer().add_markers(markers) 

1648 self._markers.extend(markers) 

1649 

1650 def add_marker(self, marker): 

1651 ''' 

1652 Add a marker to the display. 

1653 

1654 See :py:meth:`add_markers`. 

1655 ''' 

1656 

1657 self.add_markers([marker]) 

1658 

1659 def cleanup(self): 

1660 ''' 

1661 Remove all traces and markers which have been added so far by the 

1662 snuffling. 

1663 ''' 

1664 

1665 try: 

1666 viewer = self.get_viewer() 

1667 viewer.release_data(self._tickets) 

1668 viewer.remove_markers(self._markers) 

1669 

1670 except NoViewerSet: 

1671 pass 

1672 

1673 self._tickets = [] 

1674 self._markers = [] 

1675 

1676 def check_call(self, method): 

1677 

1678 if method in self._call_in_progress: 

1679 self.show_message('error', 'Previous action still in progress.') 

1680 return 

1681 

1682 try: 

1683 self._call_in_progress[method] = True 

1684 method() 

1685 return 0 

1686 

1687 except SnufflingError as e: 

1688 if not isinstance(e, SnufflingCallFailed): 

1689 # those have logged within error() 

1690 logger.error('%s: %s' % (self._name, e)) 

1691 logger.error('%s: Snuffling action failed' % self._name) 

1692 return 1 

1693 

1694 except Exception as e: 

1695 message = '%s: Snuffling action raised an exception: %s' % ( 

1696 self._name, str(e)) 

1697 

1698 logger.exception(message) 

1699 self.show_message('error', message) 

1700 

1701 finally: 

1702 del self._call_in_progress[method] 

1703 

1704 def call(self): 

1705 ''' 

1706 Main work routine of the snuffling. 

1707 

1708 This method is called when the snuffling's menu item has been triggered 

1709 or when the user has played with the panel controls. To be overloaded 

1710 in subclass. The default implementation does nothing useful. 

1711 ''' 

1712 

1713 pass 

1714 

1715 def pre_process_hook(self, traces): 

1716 return traces 

1717 

1718 def post_process_hook(self, traces): 

1719 return traces 

1720 

1721 def get_tpad(self): 

1722 ''' 

1723 Return current amount of extra padding needed by live processing hooks. 

1724 ''' 

1725 

1726 return 0.0 

1727 

1728 def pre_destroy(self): 

1729 ''' 

1730 Called when the snuffling instance is about to be deleted. 

1731 

1732 Can be overloaded to do user-defined cleanup actions. The 

1733 default implementation calls :py:meth:`cleanup` and deletes 

1734 the snuffling`s tempory directory, if needed. 

1735 ''' 

1736 

1737 self.cleanup() 

1738 if self._tempdir is not None: 

1739 import shutil 

1740 shutil.rmtree(self._tempdir) 

1741 

1742 

1743class SnufflingError(Exception): 

1744 pass 

1745 

1746 

1747class NoViewerSet(SnufflingError): 

1748 ''' 

1749 This exception is raised, when no viewer has been set on a Snuffling. 

1750 ''' 

1751 

1752 def __str__(self): 

1753 return 'No GUI available. ' \ 

1754 'Maybe this Snuffling cannot be run in command line mode?' 

1755 

1756 

1757class MissingStationInformation(SnufflingError): 

1758 ''' 

1759 Raised when station information is missing. 

1760 ''' 

1761 

1762 

1763class NoTracesSelected(SnufflingError): 

1764 ''' 

1765 This exception is raised, when no traces have been selected in the viewer 

1766 and we cannot fallback to using the current view. 

1767 ''' 

1768 

1769 def __str__(self): 

1770 return 'No traces have been selected / are available.' 

1771 

1772 

1773class UserCancelled(SnufflingError): 

1774 ''' 

1775 This exception is raised, when the user has cancelled a snuffling dialog. 

1776 ''' 

1777 

1778 def __str__(self): 

1779 return 'The user has cancelled a dialog.' 

1780 

1781 

1782class SnufflingCallFailed(SnufflingError): 

1783 ''' 

1784 This exception is raised, when :py:meth:`Snuffling.fail` is called from 

1785 :py:meth:`Snuffling.call`. 

1786 ''' 

1787 

1788 

1789class InvalidSnufflingFilename(Exception): 

1790 pass 

1791 

1792 

1793class SnufflingModule(object): 

1794 ''' 

1795 Utility class to load/reload snufflings from a file. 

1796 

1797 The snufflings are created by user modules which have the special function 

1798 :py:func:`__snufflings__` which return the snuffling instances to be 

1799 exported. The snuffling module is attached to a handler class, which makes 

1800 use of the snufflings (e.g. :py:class:`pyrocko.pile_viewer.PileOverwiew` 

1801 from ``pile_viewer.py``). The handler class must implement the methods 

1802 ``add_snuffling()`` and ``remove_snuffling()`` which are used as callbacks. 

1803 The callbacks are utilized from the methods :py:meth:`load_if_needed` and 

1804 :py:meth:`remove_snufflings` which may be called from the handler class, 

1805 when needed. 

1806 ''' 

1807 

1808 mtimes = {} 

1809 

1810 def __init__(self, path, name, handler): 

1811 self._path = path 

1812 self._name = name 

1813 self._mtime = None 

1814 self._module = None 

1815 self._snufflings = [] 

1816 self._handler = handler 

1817 

1818 def load_if_needed(self): 

1819 filename = os.path.join(self._path, self._name+'.py') 

1820 

1821 try: 

1822 mtime = os.stat(filename)[8] 

1823 except OSError as e: 

1824 if e.errno == 2: 

1825 logger.error(e) 

1826 raise BrokenSnufflingModule(filename) 

1827 

1828 if self._module is None: 

1829 sys.path[0:0] = [self._path] 

1830 try: 

1831 logger.debug('Loading snuffling module %s' % filename) 

1832 if self._name in sys.modules: 

1833 raise InvalidSnufflingFilename(self._name) 

1834 

1835 self._module = __import__(self._name) 

1836 del sys.modules[self._name] 

1837 

1838 for snuffling in self._module.__snufflings__(): 

1839 snuffling._filename = filename 

1840 self.add_snuffling(snuffling) 

1841 

1842 except Exception: 

1843 logger.error(traceback.format_exc()) 

1844 raise BrokenSnufflingModule(filename) 

1845 

1846 finally: 

1847 sys.path[0:1] = [] 

1848 

1849 elif self._mtime != mtime: 

1850 logger.warning('Reloading snuffling module %s' % filename) 

1851 settings = self.remove_snufflings() 

1852 sys.path[0:0] = [self._path] 

1853 try: 

1854 

1855 sys.modules[self._name] = self._module 

1856 

1857 reload(self._module) 

1858 del sys.modules[self._name] 

1859 

1860 for snuffling in self._module.__snufflings__(): 

1861 snuffling._filename = filename 

1862 self.add_snuffling(snuffling, reloaded=True) 

1863 

1864 if len(self._snufflings) == len(settings): 

1865 for sett, snuf in zip(settings, self._snufflings): 

1866 snuf.set_settings(sett) 

1867 

1868 except Exception: 

1869 logger.error(traceback.format_exc()) 

1870 raise BrokenSnufflingModule(filename) 

1871 

1872 finally: 

1873 sys.path[0:1] = [] 

1874 

1875 self._mtime = mtime 

1876 

1877 def add_snuffling(self, snuffling, reloaded=False): 

1878 snuffling._path = self._path 

1879 snuffling.setup() 

1880 self._snufflings.append(snuffling) 

1881 self._handler.add_snuffling(snuffling, reloaded=reloaded) 

1882 

1883 def remove_snufflings(self): 

1884 settings = [] 

1885 for snuffling in self._snufflings: 

1886 settings.append(snuffling.get_settings()) 

1887 self._handler.remove_snuffling(snuffling) 

1888 

1889 self._snufflings = [] 

1890 return settings 

1891 

1892 

1893class BrokenSnufflingModule(Exception): 

1894 pass 

1895 

1896 

1897class MyScrollArea(qw.QScrollArea): 

1898 

1899 def sizeHint(self): 

1900 s = qc.QSize() 

1901 s.setWidth(self.widget().sizeHint().width()) 

1902 s.setHeight(self.widget().sizeHint().height()) 

1903 return s 

1904 

1905 

1906class SwitchControl(qw.QCheckBox): 

1907 sw_toggled = qc.pyqtSignal(object, bool) 

1908 

1909 def __init__(self, ident, default, *args): 

1910 qw.QCheckBox.__init__(self, *args) 

1911 self.ident = ident 

1912 self.setChecked(default) 

1913 self.toggled.connect(self._sw_toggled) 

1914 

1915 def _sw_toggled(self, state): 

1916 self.sw_toggled.emit(self.ident, state) 

1917 

1918 def set_value(self, state): 

1919 self.blockSignals(True) 

1920 self.setChecked(state) 

1921 self.blockSignals(False) 

1922 

1923 

1924class ChoiceControl(qw.QFrame): 

1925 choosen = qc.pyqtSignal(object, object) 

1926 

1927 def __init__(self, ident, default, choices, name, *args): 

1928 qw.QFrame.__init__(self, *args) 

1929 self.label = qw.QLabel(name, self) 

1930 self.label.setMinimumWidth(120) 

1931 self.cbox = qw.QComboBox(self) 

1932 self.layout = qw.QHBoxLayout(self) 

1933 self.layout.addWidget(self.label) 

1934 self.layout.addWidget(self.cbox) 

1935 self.layout.setContentsMargins(0, 0, 0, 0) 

1936 self.layout.setSpacing(0) 

1937 self.ident = ident 

1938 self.choices = choices 

1939 for ichoice, choice in enumerate(choices): 

1940 self.cbox.addItem(choice) 

1941 

1942 self.set_value(default) 

1943 self.cbox.activated.connect(self.emit_choosen) 

1944 

1945 def set_choices(self, choices): 

1946 icur = self.cbox.currentIndex() 

1947 if icur != -1: 

1948 selected_choice = choices[icur] 

1949 else: 

1950 selected_choice = None 

1951 

1952 self.choices = choices 

1953 self.cbox.clear() 

1954 for ichoice, choice in enumerate(choices): 

1955 self.cbox.addItem(qc.QString(choice)) 

1956 

1957 if selected_choice is not None and selected_choice in choices: 

1958 self.set_value(selected_choice) 

1959 return selected_choice 

1960 else: 

1961 self.set_value(choices[0]) 

1962 return choices[0] 

1963 

1964 def emit_choosen(self, i): 

1965 self.choosen.emit( 

1966 self.ident, 

1967 self.choices[i]) 

1968 

1969 def set_value(self, v): 

1970 self.cbox.blockSignals(True) 

1971 for i, choice in enumerate(self.choices): 

1972 if choice == v: 

1973 self.cbox.setCurrentIndex(i) 

1974 self.cbox.blockSignals(False)