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

11 

12import os 

13import sys 

14import time 

15import logging 

16import traceback 

17import tempfile 

18 

19from .qt_compat import qc, qw, getSaveFileName 

20 

21from pyrocko import pile, config 

22from pyrocko.util import quote 

23 

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

25 WebKitFrame, VTKFrame, PixmapFrame, Marker, EventMarker, 

26 PhaseMarker, load_markers, save_markers) 

27 

28 

29from importlib import reload 

30 

31Marker, load_markers, save_markers # noqa 

32 

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

34 

35 

36class MyFrame(qw.QFrame): 

37 widgetVisibilityChanged = qc.pyqtSignal(bool) 

38 

39 def showEvent(self, ev): 

40 self.widgetVisibilityChanged.emit(True) 

41 

42 def hideEvent(self, ev): 

43 self.widgetVisibilityChanged.emit(False) 

44 

45 

46class Param(object): 

47 ''' 

48 Definition of an adjustable floating point parameter for the 

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

50 such parameters. 

51 

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

53 :param ident: identifier of the parameter 

54 :param default: default value 

55 :param minimum: minimum value for the parameter 

56 :param maximum: maximum value for the parameter 

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

58 of parameter range (optional) 

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

60 of parameter range (optional) 

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

62 value of parameter range (optional) 

63 ''' 

64 

65 def __init__( 

66 self, name, ident, default, minimum, maximum, 

67 low_is_none=None, 

68 high_is_none=None, 

69 low_is_zero=False, 

70 tracking=True, 

71 type=float): 

72 

73 if low_is_none and default == minimum: 

74 default = None 

75 if high_is_none and default == maximum: 

76 default = None 

77 

78 self.name = name 

79 self.ident = ident 

80 self.default = default 

81 self.minimum = minimum 

82 self.maximum = maximum 

83 self.low_is_none = low_is_none 

84 self.high_is_none = high_is_none 

85 self.low_is_zero = low_is_zero 

86 self.tracking = tracking 

87 self.type = type 

88 

89 self._control = None 

90 

91 

92class Switch(object): 

93 ''' 

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

95 may display a checkbox for such a switch. 

96 

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

98 :param ident: identifier of the parameter 

99 :param default: default value 

100 ''' 

101 

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

103 self.name = name 

104 self.ident = ident 

105 self.default = default 

106 

107 

108class Choice(object): 

109 ''' 

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

111 may display a menu for such a choice. 

112 

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

114 :param ident: identifier of the parameter 

115 :param default: default value 

116 :param choices: tuple of other options 

117 ''' 

118 

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

120 self.name = name 

121 self.ident = ident 

122 self.default = default 

123 self.choices = choices 

124 

125 

126class Snuffling(object): 

127 ''' 

128 Base class for user snufflings. 

129 

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

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

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

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

134 

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

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

137 ''' 

138 

139 def __init__(self): 

140 self._path = None 

141 

142 self._name = 'Untitled Snuffling' 

143 self._viewer = None 

144 self._tickets = [] 

145 self._markers = [] 

146 

147 self._delete_panel = None 

148 self._delete_menuitem = None 

149 

150 self._panel_parent = None 

151 self._menu_parent = None 

152 

153 self._panel = None 

154 self._menuitem = None 

155 self._helpmenuitem = None 

156 self._parameters = [] 

157 self._param_controls = {} 

158 

159 self._triggers = [] 

160 

161 self._live_update = True 

162 self._previous_output_filename = None 

163 self._previous_input_filename = None 

164 self._previous_input_directory = None 

165 

166 self._tempdir = None 

167 self._iplot = 0 

168 

169 self._have_pre_process_hook = False 

170 self._have_post_process_hook = False 

171 self._pre_process_hook_enabled = False 

172 self._post_process_hook_enabled = False 

173 

174 self._no_viewer_pile = None 

175 self._cli_params = {} 

176 self._filename = None 

177 self._force_panel = False 

178 self._call_in_progress = {} 

179 

180 def setup(self): 

181 ''' 

182 Setup the snuffling. 

183 

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

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

186 ''' 

187 

188 pass 

189 

190 def module_dir(self): 

191 ''' 

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

193 

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

195 ''' 

196 

197 return self._path 

198 

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

200 ''' 

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

202 

203 This method is called from the 

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

205 :py:meth:`setup_gui`. 

206 ''' 

207 

208 self._viewer = viewer 

209 self._panel_parent = panel_parent 

210 self._menu_parent = menu_parent 

211 

212 self.setup_gui(reloaded=reloaded) 

213 

214 def setup_gui(self, reloaded=False): 

215 ''' 

216 Create and add gui elements to the viewer. 

217 

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

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

220 snuffling has been changed. 

221 ''' 

222 

223 if self._panel_parent is not None: 

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

225 if self._panel: 

226 self._panel_parent.add_panel( 

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

228 

229 if self._menu_parent is not None: 

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

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

232 if self._menuitem: 

233 self._menu_parent.add_snuffling_menuitem(self._menuitem) 

234 

235 if self._helpmenuitem: 

236 self._menu_parent.add_snuffling_help_menuitem( 

237 self._helpmenuitem) 

238 

239 def set_force_panel(self, bool=True): 

240 ''' 

241 Force to create a panel. 

242 

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

244 button. 

245 ''' 

246 

247 self._force_panel = bool 

248 

249 def make_cli_parser1(self): 

250 import optparse 

251 

252 class MyOptionParser(optparse.OptionParser): 

253 def error(self, msg): 

254 logger.error(msg) 

255 self.exit(1) 

256 

257 parser = MyOptionParser() 

258 

259 parser.add_option( 

260 '--format', 

261 dest='format', 

262 default='from_extension', 

263 choices=( 

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

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

266 'from_extension', 'detect'), 

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

268 

269 parser.add_option( 

270 '--pattern', 

271 dest='regex', 

272 metavar='REGEX', 

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

274 

275 self.add_params_to_cli_parser(parser) 

276 self.configure_cli_parser(parser) 

277 return parser 

278 

279 def configure_cli_parser(self, parser): 

280 pass 

281 

282 def cli_usage(self): 

283 return None 

284 

285 def add_params_to_cli_parser(self, parser): 

286 

287 for param in self._parameters: 

288 if isinstance(param, Param): 

289 parser.add_option( 

290 '--' + param.ident, 

291 dest=param.ident, 

292 default=param.default, 

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

294 help=param.name) 

295 

296 def setup_cli(self): 

297 self.setup() 

298 parser = self.make_cli_parser1() 

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

300 

301 for param in self._parameters: 

302 if isinstance(param, Param): 

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

304 

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

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

307 self._cli_params['sources'] = args 

308 

309 return options, args, parser 

310 

311 def delete_gui(self): 

312 ''' 

313 Remove the gui elements of the snuffling. 

314 

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

316 also removes all traces and markers added with the 

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

318 ''' 

319 

320 self.cleanup() 

321 

322 if self._panel is not None: 

323 self._panel_parent.remove_panel(self._panel) 

324 self._panel = None 

325 

326 if self._menuitem is not None: 

327 self._menu_parent.remove_snuffling_menuitem(self._menuitem) 

328 self._menuitem = None 

329 

330 if self._helpmenuitem is not None: 

331 self._menu_parent.remove_snuffling_help_menuitem( 

332 self._helpmenuitem) 

333 

334 def set_name(self, name): 

335 ''' 

336 Set the snuffling's name. 

337 

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

339 ''' 

340 

341 self._name = name 

342 self.reset_gui() 

343 

344 def get_name(self): 

345 ''' 

346 Get the snuffling's name. 

347 ''' 

348 

349 return self._name 

350 

351 def set_have_pre_process_hook(self, bool): 

352 self._have_pre_process_hook = bool 

353 self._live_update = False 

354 self._pre_process_hook_enabled = False 

355 self.reset_gui() 

356 

357 def set_have_post_process_hook(self, bool): 

358 self._have_post_process_hook = bool 

359 self._live_update = False 

360 self._post_process_hook_enabled = False 

361 self.reset_gui() 

362 

363 def set_have_pile_changed_hook(self, bool): 

364 self._pile_ = False 

365 

366 def enable_pile_changed_notifications(self): 

367 ''' 

368 Get informed when pile changed. 

369 

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

371 update in the viewer's pile. 

372 ''' 

373 

374 viewer = self.get_viewer() 

375 viewer.pile_has_changed_signal.connect( 

376 self.pile_changed) 

377 

378 def disable_pile_changed_notifications(self): 

379 ''' 

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

381 ''' 

382 

383 viewer = self.get_viewer() 

384 viewer.pile_has_changed_signal.disconnect( 

385 self.pile_changed) 

386 

387 def pile_changed(self): 

388 ''' 

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

390 

391 Must be activated with a call to 

392 :py:meth:`enable_pile_changed_notifications`. 

393 ''' 

394 

395 pass 

396 

397 def reset_gui(self, reloaded=False): 

398 ''' 

399 Delete and recreate the snuffling's panel. 

400 ''' 

401 

402 if self._panel or self._menuitem: 

403 sett = self.get_settings() 

404 self.delete_gui() 

405 self.setup_gui(reloaded=reloaded) 

406 self.set_settings(sett) 

407 

408 def show_message(self, kind, message): 

409 ''' 

410 Display a message box. 

411 

412 :param kind: string defining kind of message 

413 :param message: the message to be displayed 

414 ''' 

415 

416 try: 

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

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

419 box.exec_() 

420 except NoViewerSet: 

421 pass 

422 

423 def error(self, message): 

424 ''' 

425 Show an error message box. 

426 

427 :param message: specifying the error 

428 ''' 

429 

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

431 self.show_message('error', message) 

432 

433 def warn(self, message): 

434 ''' 

435 Display a warning message. 

436 

437 :param message: specifying the warning 

438 ''' 

439 

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

441 self.show_message('warning', message) 

442 

443 def fail(self, message): 

444 ''' 

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

446 exception. 

447 

448 :param message: specifying the error 

449 ''' 

450 

451 self.error(message) 

452 raise SnufflingCallFailed(message) 

453 

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

455 ''' 

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

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

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

459 

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

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

462 ''' 

463 

464 if name is None: 

465 self._iplot += 1 

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

467 

468 fframe = FigureFrame(figure_cls=figure_cls) 

469 self._panel_parent.add_tab(name, fframe) 

470 if get == 'axes': 

471 return fframe.gca() 

472 elif get == 'figure': 

473 return fframe.gcf() 

474 elif get == 'figure_frame': 

475 return fframe 

476 

477 def figure(self, name=None): 

478 ''' 

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

480 which can be displayed within snuffler by calling 

481 :py:meth:`canvas.draw`. 

482 

483 :param name: labels the tab of the figure 

484 ''' 

485 

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

487 

488 def axes(self, name=None): 

489 ''' 

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

491 

492 :param name: labels the tab of axes 

493 ''' 

494 

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

496 

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

498 ''' 

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

500 

501 :param name: labels the tab figure frame 

502 ''' 

503 

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

505 

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

507 ''' 

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

509 

510 :param name: labels the tab 

511 :param *args, **kwargs: 

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

513 :param plot_cls: 

514 if given, subclass to be used instead of 

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

516 ''' 

517 frame = SmartplotFrame( 

518 plot_args=args, 

519 plot_cls=plot_cls, 

520 plot_kwargs=kwargs) 

521 

522 self._panel_parent.add_tab(name, frame) 

523 return frame 

524 

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

526 ''' 

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

528 

529 :param name: labels the tab 

530 :param filename: name of file to be displayed 

531 ''' 

532 

533 f = PixmapFrame(filename) 

534 

535 scroll_area = qw.QScrollArea() 

536 scroll_area.setWidget(f) 

537 scroll_area.setWidgetResizable(True) 

538 

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

540 return f 

541 

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

543 ''' 

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

545 within snuffler. 

546 

547 :param url: url to open 

548 :param name: labels the tab 

549 ''' 

550 

551 if name is None: 

552 self._iplot += 1 

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

554 

555 f = WebKitFrame(url) 

556 self._panel_parent.add_tab(name, f) 

557 return f 

558 

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

560 ''' 

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

562 graphics. 

563 

564 :param actors: list of VTKActors 

565 :param name: labels the tab 

566 

567 Initialize the interactive rendering by calling the frames' 

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

569 renderer. 

570 

571 Requires installation of vtk including python wrapper. 

572 ''' 

573 if name is None: 

574 self._iplot += 1 

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

576 

577 try: 

578 f = VTKFrame(actors=actors) 

579 except ImportError as e: 

580 self.fail(e) 

581 

582 self._panel_parent.add_tab(name, f) 

583 return f 

584 

585 def tempdir(self): 

586 ''' 

587 Create a temporary directory and return its absolute path. 

588 

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

590 instance is deleted. 

591 ''' 

592 

593 if self._tempdir is None: 

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

595 

596 return self._tempdir 

597 

598 def set_live_update(self, live_update): 

599 ''' 

600 Enable/disable live updating. 

601 

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

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

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

605 item or pressing the call button. 

606 ''' 

607 

608 self._live_update = live_update 

609 if self._have_pre_process_hook: 

610 self._pre_process_hook_enabled = live_update 

611 if self._have_post_process_hook: 

612 self._post_process_hook_enabled = live_update 

613 

614 try: 

615 self.get_viewer().clean_update() 

616 except NoViewerSet: 

617 pass 

618 

619 def add_parameter(self, param): 

620 ''' 

621 Add an adjustable parameter to the snuffling. 

622 

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

624 :py:class:`Choice`. 

625 

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

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

628 ''' 

629 

630 self._parameters.append(param) 

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

632 

633 if self._panel is not None: 

634 self.delete_gui() 

635 self.setup_gui() 

636 

637 def add_trigger(self, name, method): 

638 ''' 

639 Add a button to the snuffling's panel. 

640 

641 :param name: string that labels the button 

642 :param method: method associated with the button 

643 ''' 

644 

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

646 

647 if self._panel is not None: 

648 self.delete_gui() 

649 self.setup_gui() 

650 

651 def get_parameters(self): 

652 ''' 

653 Get the snuffling's adjustable parameter definitions. 

654 

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

656 ''' 

657 

658 return self._parameters 

659 

660 def get_parameter(self, ident): 

661 ''' 

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

663 

664 :param ident: identifier of the parameter 

665 

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

667 ''' 

668 

669 for param in self._parameters: 

670 if param.ident == ident: 

671 return param 

672 return None 

673 

674 def set_parameter(self, ident, value): 

675 ''' 

676 Set one of the snuffling's adjustable parameters. 

677 

678 :param ident: identifier of the parameter 

679 :param value: new value of the parameter 

680 

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

682 ''' 

683 

684 self._set_parameter_value(ident, value) 

685 

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

687 if control: 

688 control.set_value(value) 

689 

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

691 ''' 

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

693 

694 :param ident: identifier of the parameter 

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

696 

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

698 ''' 

699 

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

701 if control: 

702 control.set_range(vmin, vmax) 

703 

704 def set_parameter_choices(self, ident, choices): 

705 ''' 

706 Update the choices of a Choice parameter. 

707 

708 :param ident: identifier of the parameter 

709 :param choices: list of strings 

710 ''' 

711 

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

713 if control: 

714 selected_choice = control.set_choices(choices) 

715 self._set_parameter_value(ident, selected_choice) 

716 

717 def _set_parameter_value(self, ident, value): 

718 setattr(self, ident, value) 

719 

720 def get_parameter_value(self, ident): 

721 ''' 

722 Get the current value of a parameter. 

723 

724 :param ident: identifier of the parameter 

725 ''' 

726 return getattr(self, ident) 

727 

728 def get_settings(self): 

729 ''' 

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

731 their values as the dictionaries values. 

732 ''' 

733 

734 params = self.get_parameters() 

735 settings = {} 

736 for param in params: 

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

738 

739 return settings 

740 

741 def set_settings(self, settings): 

742 params = self.get_parameters() 

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

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

745 if k in dparams: 

746 self._set_parameter_value(k, v) 

747 if k in self._param_controls: 

748 control = self._param_controls[k] 

749 control.set_value(v) 

750 

751 def get_viewer(self): 

752 ''' 

753 Get the parent viewer. 

754 

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

756 which is the main viewer widget. 

757 

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

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

760 ''' 

761 

762 if self._viewer is None: 

763 raise NoViewerSet() 

764 return self._viewer 

765 

766 def get_pile(self): 

767 ''' 

768 Get the pile. 

769 

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

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

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

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

774 mode. 

775 ''' 

776 

777 try: 

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

779 except NoViewerSet: 

780 if self._no_viewer_pile is None: 

781 self._no_viewer_pile = self.make_pile() 

782 

783 p = self._no_viewer_pile 

784 

785 return p 

786 

787 def get_active_event_and_stations( 

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

789 

790 ''' 

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

792 

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

794 query for available data 

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

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

797 

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

799 ''' 

800 

801 p = self.get_pile() 

802 v = self.get_viewer() 

803 

804 event = v.get_active_event() 

805 if event is None: 

806 self.fail( 

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

808 'it the "active event"') 

809 

810 stations = {} 

811 for traces in p.chopper( 

812 event.time+trange[0], 

813 event.time+trange[1], 

814 load_data=False, 

815 degap=False): 

816 

817 for tr in traces: 

818 try: 

819 for skey in v.station_keys(tr): 

820 if skey in stations: 

821 continue 

822 

823 station = v.get_station(skey) 

824 stations[skey] = station 

825 

826 except KeyError: 

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

828 % '.'.join(skey) 

829 

830 if missing == 'warn': 

831 logger.warning(s) 

832 elif missing == 'raise': 

833 raise MissingStationInformation(s) 

834 elif missing == 'ignore': 

835 pass 

836 else: 

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

838 

839 stations[skey] = None 

840 

841 return event, list(set( 

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

843 

844 def get_stations(self): 

845 ''' 

846 Get all stations known to the viewer. 

847 ''' 

848 

849 v = self.get_viewer() 

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

851 return stations 

852 

853 def get_markers(self): 

854 ''' 

855 Get all markers from the viewer. 

856 ''' 

857 

858 return self.get_viewer().get_markers() 

859 

860 def get_event_markers(self): 

861 ''' 

862 Get all event markers from the viewer. 

863 ''' 

864 

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

866 if isinstance(m, EventMarker)] 

867 

868 def get_selected_markers(self): 

869 ''' 

870 Get all selected markers from the viewer. 

871 ''' 

872 

873 return self.get_viewer().selected_markers() 

874 

875 def get_selected_event_markers(self): 

876 ''' 

877 Get all selected event markers from the viewer. 

878 ''' 

879 

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

881 if isinstance(m, EventMarker)] 

882 

883 def get_active_event_and_phase_markers(self): 

884 ''' 

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

886 ''' 

887 

888 viewer = self.get_viewer() 

889 markers = viewer.get_markers() 

890 event_marker = viewer.get_active_event_marker() 

891 if event_marker is None: 

892 self.fail( 

893 'No active event set. ' 

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

895 

896 event = event_marker.get_event() 

897 

898 selection = [] 

899 for m in markers: 

900 if isinstance(m, PhaseMarker): 

901 if m.get_event() is event: 

902 selection.append(m) 

903 

904 return ( 

905 event_marker, 

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

907 m.get_event() == event]) 

908 

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

910 ''' 

911 Get currently active trace selector from viewer. 

912 

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

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

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

916 to disable any restrictions. 

917 ''' 

918 

919 viewer = self.get_viewer() 

920 

921 def rtrue(tr): 

922 return True 

923 

924 if mode == 'inview': 

925 return viewer.trace_selector or rtrue 

926 elif mode == 'visible': 

927 return viewer.trace_filter or rtrue 

928 elif mode == 'all': 

929 return rtrue 

930 else: 

931 raise Exception('invalid mode argument') 

932 

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

934 mode='inview', main_bandpass=False, 

935 progress=None, responsive=False, 

936 *args, **kwargs): 

937 ''' 

938 Iterate over selected traces. 

939 

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

941 running snuffler. For each selected marker, 

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

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

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

945 *\\*args* and *\\*\\*kwargs*. 

946 

947 :param fallback: 

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

949 currently visible in the viewer. 

950 

951 :param marker_selector: 

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

953 

954 :param mode: 

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

956 shown in the viewer (excluding traces accessible through vertical 

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

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

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

960 restrictions. 

961 

962 :param main_bandpass: 

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

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

965 order Butterworth highpass and lowpass and the signal is always 

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

967 bandpass settings from the graphical interface are not respected 

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

969 artifacts. 

970 

971 :param progress: 

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

973 is used as the label for the progress bar. 

974 

975 :param responsive: 

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

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

978 to be aborted by the user. 

979 ''' 

980 

981 try: 

982 viewer = self.get_viewer() 

983 markers = [ 

984 m for m in viewer.selected_markers() 

985 if not isinstance(m, EventMarker)] 

986 

987 if marker_selector is not None: 

988 markers = [ 

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

990 

991 pile = self.get_pile() 

992 

993 def rtrue(tr): 

994 return True 

995 

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

997 trace_selector_viewer = self.get_viewer_trace_selector(mode) 

998 

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

1000 

1001 if main_bandpass: 

1002 def apply_filters(traces): 

1003 for tr in traces: 

1004 if viewer.highpass is not None: 

1005 tr.highpass(4, viewer.highpass) 

1006 if viewer.lowpass is not None: 

1007 tr.lowpass(4, viewer.lowpass) 

1008 return traces 

1009 else: 

1010 def apply_filters(traces): 

1011 return traces 

1012 

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

1014 

1015 time_last = [time.time()] 

1016 

1017 def update_progress(label, batch): 

1018 time_now = time.time() 

1019 if responsive: 

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

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

1022 # changes etc. 

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

1024 qw.qApp.processEvents() 

1025 else: 

1026 # redraw about once a second 

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

1028 viewer.repaint() 

1029 

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

1031 

1032 abort = pb.set_status( 

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

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

1035 

1036 return abort 

1037 

1038 if markers: 

1039 for imarker, marker in enumerate(markers): 

1040 try: 

1041 if progress: 

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

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

1044 

1045 pb.set_status(label, 0, responsive) 

1046 

1047 if not marker.nslc_ids: 

1048 trace_selector_marker = rtrue 

1049 else: 

1050 def trace_selector_marker(tr): 

1051 return marker.match_nslc(tr.nslc_id) 

1052 

1053 def trace_selector(tr): 

1054 return trace_selector_arg(tr) \ 

1055 and trace_selector_viewer(tr) \ 

1056 and trace_selector_marker(tr) 

1057 

1058 for batch in pile.chopper( 

1059 tmin=marker.tmin, 

1060 tmax=marker.tmax, 

1061 trace_selector=trace_selector, 

1062 style='batch', 

1063 *args, 

1064 **kwargs): 

1065 

1066 if progress: 

1067 abort = update_progress(label, batch) 

1068 if abort: 

1069 return 

1070 

1071 batch.traces = apply_filters(batch.traces) 

1072 if style_arg == 'batch': 

1073 yield batch 

1074 else: 

1075 yield batch.traces 

1076 

1077 finally: 

1078 if progress: 

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

1080 

1081 elif fallback: 

1082 def trace_selector(tr): 

1083 return trace_selector_arg(tr) \ 

1084 and trace_selector_viewer(tr) 

1085 

1086 tmin, tmax = viewer.get_time_range() 

1087 

1088 if not pile.is_empty(): 

1089 ptmin = pile.get_tmin() 

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

1091 if ptmin > tmin: 

1092 tmin = ptmin + tpad 

1093 ptmax = pile.get_tmax() 

1094 if ptmax < tmax: 

1095 tmax = ptmax - tpad 

1096 

1097 try: 

1098 if progress: 

1099 label = progress 

1100 pb.set_status(label, 0, responsive) 

1101 

1102 for batch in pile.chopper( 

1103 tmin=tmin, 

1104 tmax=tmax, 

1105 trace_selector=trace_selector, 

1106 style='batch', 

1107 *args, 

1108 **kwargs): 

1109 

1110 if progress: 

1111 abort = update_progress(label, batch) 

1112 

1113 if abort: 

1114 return 

1115 

1116 batch.traces = apply_filters(batch.traces) 

1117 

1118 if style_arg == 'batch': 

1119 yield batch 

1120 else: 

1121 yield batch.traces 

1122 

1123 finally: 

1124 if progress: 

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

1126 

1127 else: 

1128 raise NoTracesSelected() 

1129 

1130 except NoViewerSet: 

1131 pile = self.get_pile() 

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

1133 

1134 def get_selected_time_range(self, fallback=False): 

1135 ''' 

1136 Get the time range spanning all selected markers. 

1137 

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

1139 end of visible time range 

1140 ''' 

1141 

1142 viewer = self.get_viewer() 

1143 markers = viewer.selected_markers() 

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

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

1146 

1147 if mins and maxs: 

1148 tmin = min(mins) 

1149 tmax = max(maxs) 

1150 

1151 elif fallback: 

1152 tmin, tmax = viewer.get_time_range() 

1153 

1154 else: 

1155 raise NoTracesSelected() 

1156 

1157 return tmin, tmax 

1158 

1159 def panel_visibility_changed(self, bool): 

1160 ''' 

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

1162 

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

1164 when the panel is activated the first time. 

1165 ''' 

1166 

1167 pass 

1168 

1169 def make_pile(self): 

1170 ''' 

1171 Create a pile. 

1172 

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

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

1175 arguments. 

1176 ''' 

1177 

1178 cachedirname = config.config().cache_dir 

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

1180 return pile.make_pile( 

1181 sources, 

1182 cachedirname=cachedirname, 

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

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

1185 

1186 def make_panel(self, parent): 

1187 ''' 

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

1189 

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

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

1192 parameters). 

1193 ''' 

1194 

1195 params = self.get_parameters() 

1196 self._param_controls = {} 

1197 if params or self._force_panel: 

1198 sarea = MyScrollArea(parent.get_panel_parent_widget()) 

1199 sarea.setFrameStyle(qw.QFrame.NoFrame) 

1200 sarea.setSizePolicy(qw.QSizePolicy( 

1201 qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding)) 

1202 frame = MyFrame(sarea) 

1203 frame.widgetVisibilityChanged.connect( 

1204 self.panel_visibility_changed) 

1205 

1206 frame.setSizePolicy(qw.QSizePolicy( 

1207 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)) 

1208 frame.setFrameStyle(qw.QFrame.NoFrame) 

1209 sarea.setWidget(frame) 

1210 sarea.setWidgetResizable(True) 

1211 layout = qw.QGridLayout() 

1212 layout.setContentsMargins(0, 0, 0, 0) 

1213 layout.setSpacing(0) 

1214 frame.setLayout(layout) 

1215 

1216 parlayout = qw.QGridLayout() 

1217 

1218 irow = 0 

1219 ipar = 0 

1220 have_switches = False 

1221 have_params = False 

1222 for iparam, param in enumerate(params): 

1223 if isinstance(param, Param): 

1224 if param.minimum <= 0.0: 

1225 param_control = LinValControl( 

1226 high_is_none=param.high_is_none, 

1227 low_is_none=param.low_is_none, 

1228 type=param.type) 

1229 else: 

1230 param_control = ValControl( 

1231 high_is_none=param.high_is_none, 

1232 low_is_none=param.low_is_none, 

1233 low_is_zero=param.low_is_zero, 

1234 type=param.type) 

1235 

1236 param_control.setup( 

1237 param.name, 

1238 param.minimum, 

1239 param.maximum, 

1240 param.default, 

1241 iparam) 

1242 

1243 param_control.set_tracking(param.tracking) 

1244 param_control.valchange.connect( 

1245 self.modified_snuffling_panel) 

1246 

1247 self._param_controls[param.ident] = param_control 

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

1249 parlayout.addWidget(w, ipar, iw) 

1250 

1251 ipar += 1 

1252 have_params = True 

1253 

1254 elif isinstance(param, Choice): 

1255 param_widget = ChoiceControl( 

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

1257 param_widget.choosen.connect( 

1258 self.choose_on_snuffling_panel) 

1259 

1260 self._param_controls[param.ident] = param_widget 

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

1262 ipar += 1 

1263 have_params = True 

1264 

1265 elif isinstance(param, Switch): 

1266 have_switches = True 

1267 

1268 if have_params: 

1269 parframe = qw.QFrame(sarea) 

1270 parframe.setSizePolicy(qw.QSizePolicy( 

1271 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)) 

1272 parframe.setLayout(parlayout) 

1273 layout.addWidget(parframe, irow, 0) 

1274 irow += 1 

1275 

1276 if have_switches: 

1277 swlayout = qw.QGridLayout() 

1278 isw = 0 

1279 for iparam, param in enumerate(params): 

1280 if isinstance(param, Switch): 

1281 param_widget = SwitchControl( 

1282 param.ident, param.default, param.name) 

1283 param_widget.sw_toggled.connect( 

1284 self.switch_on_snuffling_panel) 

1285 

1286 self._param_controls[param.ident] = param_widget 

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

1288 isw += 1 

1289 

1290 swframe = qw.QFrame(sarea) 

1291 swframe.setSizePolicy(qw.QSizePolicy( 

1292 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)) 

1293 swframe.setLayout(swlayout) 

1294 layout.addWidget(swframe, irow, 0) 

1295 irow += 1 

1296 

1297 butframe = qw.QFrame(sarea) 

1298 butframe.setSizePolicy(qw.QSizePolicy( 

1299 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)) 

1300 butlayout = qw.QHBoxLayout() 

1301 butframe.setLayout(butlayout) 

1302 

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

1304 if self._live_update: 

1305 live_update_checkbox.setCheckState(qc.Qt.Checked) 

1306 

1307 butlayout.addWidget(live_update_checkbox) 

1308 live_update_checkbox.toggled.connect( 

1309 self.live_update_toggled) 

1310 

1311 help_button = qw.QPushButton('Help') 

1312 butlayout.addWidget(help_button) 

1313 help_button.clicked.connect( 

1314 self.help_button_triggered) 

1315 

1316 clear_button = qw.QPushButton('Clear') 

1317 butlayout.addWidget(clear_button) 

1318 clear_button.clicked.connect( 

1319 self.clear_button_triggered) 

1320 

1321 call_button = qw.QPushButton('Run') 

1322 butlayout.addWidget(call_button) 

1323 call_button.clicked.connect( 

1324 self.call_button_triggered) 

1325 

1326 for name, method in self._triggers: 

1327 but = qw.QPushButton(name) 

1328 

1329 def call_and_update(method): 

1330 def f(): 

1331 self.check_call(method) 

1332 self.get_viewer().update() 

1333 return f 

1334 

1335 but.clicked.connect( 

1336 call_and_update(method)) 

1337 

1338 butlayout.addWidget(but) 

1339 

1340 layout.addWidget(butframe, irow, 0) 

1341 

1342 irow += 1 

1343 spacer = qw.QSpacerItem( 

1344 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

1345 

1346 layout.addItem(spacer, irow, 0) 

1347 

1348 return sarea 

1349 

1350 else: 

1351 return None 

1352 

1353 def make_helpmenuitem(self, parent): 

1354 ''' 

1355 Create the help menu item for the snuffling. 

1356 ''' 

1357 

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

1359 

1360 item.triggered.connect( 

1361 self.help_button_triggered) 

1362 

1363 return item 

1364 

1365 def make_menuitem(self, parent): 

1366 ''' 

1367 Create the menu item for the snuffling. 

1368 

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

1370 menu entry is wanted. 

1371 ''' 

1372 

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

1374 item.setCheckable( 

1375 self._have_pre_process_hook or self._have_post_process_hook) 

1376 

1377 item.triggered.connect( 

1378 self.menuitem_triggered) 

1379 

1380 return item 

1381 

1382 def output_filename( 

1383 self, 

1384 caption='Save File', 

1385 dir='', 

1386 filter='', 

1387 selected_filter=None): 

1388 

1389 ''' 

1390 Query user for an output filename. 

1391 

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

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

1394 dialog. 

1395 ''' 

1396 

1397 if not dir and self._previous_output_filename: 

1398 dir = self._previous_output_filename 

1399 

1400 fn = getSaveFileName( 

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

1402 if not fn: 

1403 raise UserCancelled() 

1404 

1405 self._previous_output_filename = fn 

1406 return str(fn) 

1407 

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

1409 ''' 

1410 Query user for an input directory. 

1411 

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

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

1414 dialog. 

1415 ''' 

1416 

1417 if not dir and self._previous_input_directory: 

1418 dir = self._previous_input_directory 

1419 

1420 dn = qw.QFileDialog.getExistingDirectory( 

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

1422 

1423 if not dn: 

1424 raise UserCancelled() 

1425 

1426 self._previous_input_directory = dn 

1427 return str(dn) 

1428 

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

1430 selected_filter=None): 

1431 ''' 

1432 Query user for an input filename. 

1433 

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

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

1436 dialog. 

1437 ''' 

1438 

1439 if not dir and self._previous_input_filename: 

1440 dir = self._previous_input_filename 

1441 

1442 fn, _ = qw.QFileDialog.getOpenFileName( 

1443 self.get_viewer(), 

1444 caption, 

1445 dir, 

1446 filter) 

1447 

1448 if not fn: 

1449 raise UserCancelled() 

1450 

1451 self._previous_input_filename = fn 

1452 return str(fn) 

1453 

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

1455 ''' 

1456 Query user for a text input. 

1457 

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

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

1460 dialog. 

1461 ''' 

1462 

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

1464 

1465 if not ok: 

1466 raise UserCancelled() 

1467 

1468 return inp 

1469 

1470 def modified_snuffling_panel(self, value, iparam): 

1471 ''' 

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

1473 

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

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

1476 widget. 

1477 ''' 

1478 

1479 param = self.get_parameters()[iparam] 

1480 self._set_parameter_value(param.ident, value) 

1481 if self._live_update: 

1482 self.check_call(self.call) 

1483 self.get_viewer().update() 

1484 

1485 def switch_on_snuffling_panel(self, ident, state): 

1486 ''' 

1487 Called when the user has toggled a switchable parameter. 

1488 ''' 

1489 

1490 self._set_parameter_value(ident, state) 

1491 if self._live_update: 

1492 self.check_call(self.call) 

1493 self.get_viewer().update() 

1494 

1495 def choose_on_snuffling_panel(self, ident, state): 

1496 ''' 

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

1498 ''' 

1499 

1500 self._set_parameter_value(ident, state) 

1501 if self._live_update: 

1502 self.check_call(self.call) 

1503 self.get_viewer().update() 

1504 

1505 def menuitem_triggered(self, arg): 

1506 ''' 

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

1508 

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

1510 and triggers an update on the viewer widget. 

1511 ''' 

1512 

1513 self.check_call(self.call) 

1514 

1515 if self._have_pre_process_hook: 

1516 self._pre_process_hook_enabled = arg 

1517 

1518 if self._have_post_process_hook: 

1519 self._post_process_hook_enabled = arg 

1520 

1521 if self._have_pre_process_hook or self._have_post_process_hook: 

1522 self.get_viewer().clean_update() 

1523 else: 

1524 self.get_viewer().update() 

1525 

1526 def call_button_triggered(self): 

1527 ''' 

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

1529 

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

1531 and triggers an update on the viewer widget. 

1532 ''' 

1533 

1534 self.check_call(self.call) 

1535 self.get_viewer().update() 

1536 

1537 def clear_button_triggered(self): 

1538 ''' 

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

1540 

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

1542 viewer widget. 

1543 ''' 

1544 

1545 self.cleanup() 

1546 self.get_viewer().update() 

1547 

1548 def help_button_triggered(self): 

1549 ''' 

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

1551 given in the snufflings' __doc__ string. 

1552 ''' 

1553 

1554 if self.__doc__: 

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

1556 doc = qw.QLabel(self.__doc__) 

1557 else: 

1558 try: 

1559 import markdown 

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

1561 

1562 except ImportError: 

1563 logger.error( 

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

1565 'formatting.') 

1566 

1567 doc = qw.QLabel(self.__doc__) 

1568 else: 

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

1570 

1571 labels = [doc] 

1572 

1573 if self._filename: 

1574 from html import escape 

1575 

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

1577 

1578 doc_src = qw.QLabel( 

1579 '''<html><body> 

1580<hr /> 

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

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

1583<br /> 

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

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

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

1587 % ( 

1588 quote(self._filename), 

1589 escape(self._filename), 

1590 escape(code))) 

1591 

1592 labels.append(doc_src) 

1593 

1594 for h in labels: 

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

1596 h.setWordWrap(True) 

1597 

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

1599 

1600 def live_update_toggled(self, on): 

1601 ''' 

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

1603 ''' 

1604 

1605 self.set_live_update(on) 

1606 

1607 def add_traces(self, traces): 

1608 ''' 

1609 Add traces to the viewer. 

1610 

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

1612 

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

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

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

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

1617 traces are added. 

1618 

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

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

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

1622 method). 

1623 ''' 

1624 

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

1626 self._tickets.append(ticket) 

1627 return ticket 

1628 

1629 def add_trace(self, tr): 

1630 ''' 

1631 Add a trace to the viewer. 

1632 

1633 See :py:meth:`add_traces`. 

1634 ''' 

1635 

1636 self.add_traces([tr]) 

1637 

1638 def add_markers(self, markers): 

1639 ''' 

1640 Add some markers to the display. 

1641 

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

1643 adds these to the viewer. 

1644 ''' 

1645 

1646 self.get_viewer().add_markers(markers) 

1647 self._markers.extend(markers) 

1648 

1649 def add_marker(self, marker): 

1650 ''' 

1651 Add a marker to the display. 

1652 

1653 See :py:meth:`add_markers`. 

1654 ''' 

1655 

1656 self.add_markers([marker]) 

1657 

1658 def cleanup(self): 

1659 ''' 

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

1661 snuffling. 

1662 ''' 

1663 

1664 try: 

1665 viewer = self.get_viewer() 

1666 viewer.release_data(self._tickets) 

1667 viewer.remove_markers(self._markers) 

1668 

1669 except NoViewerSet: 

1670 pass 

1671 

1672 self._tickets = [] 

1673 self._markers = [] 

1674 

1675 def check_call(self, method): 

1676 

1677 if method in self._call_in_progress: 

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

1679 return 

1680 

1681 try: 

1682 self._call_in_progress[method] = True 

1683 method() 

1684 return 0 

1685 

1686 except SnufflingError as e: 

1687 if not isinstance(e, SnufflingCallFailed): 

1688 # those have logged within error() 

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

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

1691 return 1 

1692 

1693 except Exception as e: 

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

1695 self._name, str(e)) 

1696 

1697 logger.exception(message) 

1698 self.show_message('error', message) 

1699 

1700 finally: 

1701 del self._call_in_progress[method] 

1702 

1703 def call(self): 

1704 ''' 

1705 Main work routine of the snuffling. 

1706 

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

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

1709 in subclass. The default implementation does nothing useful. 

1710 ''' 

1711 

1712 pass 

1713 

1714 def pre_process_hook(self, traces): 

1715 return traces 

1716 

1717 def post_process_hook(self, traces): 

1718 return traces 

1719 

1720 def get_tpad(self): 

1721 ''' 

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

1723 ''' 

1724 

1725 return 0.0 

1726 

1727 def pre_destroy(self): 

1728 ''' 

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

1730 

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

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

1733 the snuffling`s tempory directory, if needed. 

1734 ''' 

1735 

1736 self.cleanup() 

1737 if self._tempdir is not None: 

1738 import shutil 

1739 shutil.rmtree(self._tempdir) 

1740 

1741 

1742class SnufflingError(Exception): 

1743 pass 

1744 

1745 

1746class NoViewerSet(SnufflingError): 

1747 ''' 

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

1749 ''' 

1750 

1751 def __str__(self): 

1752 return 'No GUI available. ' \ 

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

1754 

1755 

1756class MissingStationInformation(SnufflingError): 

1757 ''' 

1758 Raised when station information is missing. 

1759 ''' 

1760 

1761 

1762class NoTracesSelected(SnufflingError): 

1763 ''' 

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

1765 and we cannot fallback to using the current view. 

1766 ''' 

1767 

1768 def __str__(self): 

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

1770 

1771 

1772class UserCancelled(SnufflingError): 

1773 ''' 

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

1775 ''' 

1776 

1777 def __str__(self): 

1778 return 'The user has cancelled a dialog.' 

1779 

1780 

1781class SnufflingCallFailed(SnufflingError): 

1782 ''' 

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

1784 :py:meth:`Snuffling.call`. 

1785 ''' 

1786 

1787 

1788class InvalidSnufflingFilename(Exception): 

1789 pass 

1790 

1791 

1792class SnufflingModule(object): 

1793 ''' 

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

1795 

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

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

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

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

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

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

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

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

1804 when needed. 

1805 ''' 

1806 

1807 mtimes = {} 

1808 

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

1810 self._path = path 

1811 self._name = name 

1812 self._mtime = None 

1813 self._module = None 

1814 self._snufflings = [] 

1815 self._handler = handler 

1816 

1817 def load_if_needed(self): 

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

1819 

1820 try: 

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

1822 except OSError as e: 

1823 if e.errno == 2: 

1824 logger.error(e) 

1825 raise BrokenSnufflingModule(filename) 

1826 

1827 if self._module is None: 

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

1829 try: 

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

1831 if self._name in sys.modules: 

1832 raise InvalidSnufflingFilename(self._name) 

1833 

1834 self._module = __import__(self._name) 

1835 del sys.modules[self._name] 

1836 

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

1838 snuffling._filename = filename 

1839 self.add_snuffling(snuffling) 

1840 

1841 except Exception: 

1842 logger.error(traceback.format_exc()) 

1843 raise BrokenSnufflingModule(filename) 

1844 

1845 finally: 

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

1847 

1848 elif self._mtime != mtime: 

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

1850 settings = self.remove_snufflings() 

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

1852 try: 

1853 

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

1855 

1856 reload(self._module) 

1857 del sys.modules[self._name] 

1858 

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

1860 snuffling._filename = filename 

1861 self.add_snuffling(snuffling, reloaded=True) 

1862 

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

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

1865 snuf.set_settings(sett) 

1866 

1867 except Exception: 

1868 logger.error(traceback.format_exc()) 

1869 raise BrokenSnufflingModule(filename) 

1870 

1871 finally: 

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

1873 

1874 self._mtime = mtime 

1875 

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

1877 snuffling._path = self._path 

1878 snuffling.setup() 

1879 self._snufflings.append(snuffling) 

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

1881 

1882 def remove_snufflings(self): 

1883 settings = [] 

1884 for snuffling in self._snufflings: 

1885 settings.append(snuffling.get_settings()) 

1886 self._handler.remove_snuffling(snuffling) 

1887 

1888 self._snufflings = [] 

1889 return settings 

1890 

1891 

1892class BrokenSnufflingModule(Exception): 

1893 pass 

1894 

1895 

1896class MyScrollArea(qw.QScrollArea): 

1897 

1898 def sizeHint(self): 

1899 s = qc.QSize() 

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

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

1902 return s 

1903 

1904 

1905class SwitchControl(qw.QCheckBox): 

1906 sw_toggled = qc.pyqtSignal(object, bool) 

1907 

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

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

1910 self.ident = ident 

1911 self.setChecked(default) 

1912 self.toggled.connect(self._sw_toggled) 

1913 

1914 def _sw_toggled(self, state): 

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

1916 

1917 def set_value(self, state): 

1918 self.blockSignals(True) 

1919 self.setChecked(state) 

1920 self.blockSignals(False) 

1921 

1922 

1923class ChoiceControl(qw.QFrame): 

1924 choosen = qc.pyqtSignal(object, object) 

1925 

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

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

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

1929 self.label.setMinimumWidth(120) 

1930 self.cbox = qw.QComboBox(self) 

1931 self.layout = qw.QHBoxLayout(self) 

1932 self.layout.addWidget(self.label) 

1933 self.layout.addWidget(self.cbox) 

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

1935 self.layout.setSpacing(0) 

1936 self.ident = ident 

1937 self.choices = choices 

1938 for ichoice, choice in enumerate(choices): 

1939 self.cbox.addItem(choice) 

1940 

1941 self.set_value(default) 

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

1943 

1944 def set_choices(self, choices): 

1945 icur = self.cbox.currentIndex() 

1946 if icur != -1: 

1947 selected_choice = choices[icur] 

1948 else: 

1949 selected_choice = None 

1950 

1951 self.choices = choices 

1952 self.cbox.clear() 

1953 for ichoice, choice in enumerate(choices): 

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

1955 

1956 if selected_choice is not None and selected_choice in choices: 

1957 self.set_value(selected_choice) 

1958 return selected_choice 

1959 else: 

1960 self.set_value(choices[0]) 

1961 return choices[0] 

1962 

1963 def emit_choosen(self, i): 

1964 self.choosen.emit( 

1965 self.ident, 

1966 self.choices[i]) 

1967 

1968 def set_value(self, v): 

1969 self.cbox.blockSignals(True) 

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

1971 if choice == v: 

1972 self.cbox.setCurrentIndex(i) 

1973 self.cbox.blockSignals(False)