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

21 

22from pyrocko import pile, config 

23from pyrocko.util import quote 

24 

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

26 VTKFrame, PixmapFrame, Marker, EventMarker, PhaseMarker, 

27 load_markers, save_markers) 

28 

29 

30if sys.version_info >= (3, 0): 

31 from importlib import reload 

32 

33 

34Marker, load_markers, save_markers # noqa 

35 

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

37 

38 

39def fnpatch(x): 

40 if use_pyqt5: 

41 return x 

42 else: 

43 return x, None 

44 

45 

46class MyFrame(qw.QFrame): 

47 widgetVisibilityChanged = qc.pyqtSignal(bool) 

48 

49 def showEvent(self, ev): 

50 self.widgetVisibilityChanged.emit(True) 

51 

52 def hideEvent(self, ev): 

53 self.widgetVisibilityChanged.emit(False) 

54 

55 

56class Param(object): 

57 ''' 

58 Definition of an adjustable floating point parameter for the 

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

60 such parameters. 

61 

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

63 :param ident: identifier of the parameter 

64 :param default: default value 

65 :param minimum: minimum value for the parameter 

66 :param maximum: maximum value for the parameter 

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

68 of parameter range (optional) 

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

70 of parameter range (optional) 

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

72 value of parameter range (optional) 

73 ''' 

74 

75 def __init__( 

76 self, name, ident, default, minimum, maximum, 

77 low_is_none=None, 

78 high_is_none=None, 

79 low_is_zero=False, 

80 tracking=True): 

81 

82 if low_is_none and default == minimum: 

83 default = None 

84 if high_is_none and default == maximum: 

85 default = None 

86 

87 self.name = name 

88 self.ident = ident 

89 self.default = default 

90 self.minimum = minimum 

91 self.maximum = maximum 

92 self.low_is_none = low_is_none 

93 self.high_is_none = high_is_none 

94 self.low_is_zero = low_is_zero 

95 self.tracking = tracking 

96 

97 self.type = type(default) 

98 self._control = None 

99 

100 

101class Switch(object): 

102 ''' 

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

104 may display a checkbox for such a switch. 

105 

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

107 :param ident: identifier of the parameter 

108 :param default: default value 

109 ''' 

110 

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

112 self.name = name 

113 self.ident = ident 

114 self.default = default 

115 

116 

117class Choice(object): 

118 ''' 

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

120 may display a menu for such a choice. 

121 

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

123 :param ident: identifier of the parameter 

124 :param default: default value 

125 :param choices: tuple of other options 

126 ''' 

127 

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

129 self.name = name 

130 self.ident = ident 

131 self.default = default 

132 self.choices = choices 

133 

134 

135class Snuffling(object): 

136 ''' 

137 Base class for user snufflings. 

138 

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

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

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

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

143 

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

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

146 ''' 

147 

148 def __init__(self): 

149 self._path = None 

150 

151 self._name = 'Untitled Snuffling' 

152 self._viewer = None 

153 self._tickets = [] 

154 self._markers = [] 

155 

156 self._delete_panel = None 

157 self._delete_menuitem = None 

158 

159 self._panel_parent = None 

160 self._menu_parent = None 

161 

162 self._panel = None 

163 self._menuitem = None 

164 self._helpmenuitem = None 

165 self._parameters = [] 

166 self._param_controls = {} 

167 

168 self._triggers = [] 

169 

170 self._live_update = True 

171 self._previous_output_filename = None 

172 self._previous_input_filename = None 

173 self._previous_input_directory = None 

174 

175 self._tempdir = None 

176 self._iplot = 0 

177 

178 self._have_pre_process_hook = False 

179 self._have_post_process_hook = False 

180 self._pre_process_hook_enabled = False 

181 self._post_process_hook_enabled = False 

182 

183 self._no_viewer_pile = None 

184 self._cli_params = {} 

185 self._filename = None 

186 self._force_panel = False 

187 self._call_in_progress = False 

188 

189 def setup(self): 

190 ''' 

191 Setup the snuffling. 

192 

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

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

195 ''' 

196 

197 pass 

198 

199 def module_dir(self): 

200 ''' 

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

202 

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

204 ''' 

205 

206 return self._path 

207 

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

209 ''' 

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

211 

212 This method is called from the 

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

214 :py:meth:`setup_gui`. 

215 ''' 

216 

217 self._viewer = viewer 

218 self._panel_parent = panel_parent 

219 self._menu_parent = menu_parent 

220 

221 self.setup_gui(reloaded=reloaded) 

222 

223 def setup_gui(self, reloaded=False): 

224 ''' 

225 Create and add gui elements to the viewer. 

226 

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

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

229 snuffling has been changed. 

230 ''' 

231 

232 if self._panel_parent is not None: 

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

234 if self._panel: 

235 self._panel_parent.add_panel( 

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

237 

238 if self._menu_parent is not None: 

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

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

241 if self._menuitem: 

242 self._menu_parent.add_snuffling_menuitem(self._menuitem) 

243 

244 if self._helpmenuitem: 

245 self._menu_parent.add_snuffling_help_menuitem( 

246 self._helpmenuitem) 

247 

248 def set_force_panel(self, bool=True): 

249 ''' 

250 Force to create a panel. 

251 

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

253 button. 

254 ''' 

255 

256 self._force_panel = bool 

257 

258 def make_cli_parser1(self): 

259 import optparse 

260 

261 class MyOptionParser(optparse.OptionParser): 

262 def error(self, msg): 

263 logger.error(msg) 

264 self.exit(1) 

265 

266 parser = MyOptionParser() 

267 

268 parser.add_option( 

269 '--format', 

270 dest='format', 

271 default='from_extension', 

272 choices=( 

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

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

275 'from_extension', 'detect'), 

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

277 

278 parser.add_option( 

279 '--pattern', 

280 dest='regex', 

281 metavar='REGEX', 

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

283 

284 self.add_params_to_cli_parser(parser) 

285 self.configure_cli_parser(parser) 

286 return parser 

287 

288 def configure_cli_parser(self, parser): 

289 pass 

290 

291 def cli_usage(self): 

292 return None 

293 

294 def add_params_to_cli_parser(self, parser): 

295 

296 for param in self._parameters: 

297 if isinstance(param, Param): 

298 parser.add_option( 

299 '--' + param.ident, 

300 dest=param.ident, 

301 default=param.default, 

302 type='float', 

303 help=param.name) 

304 

305 def setup_cli(self): 

306 self.setup() 

307 parser = self.make_cli_parser1() 

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

309 

310 for param in self._parameters: 

311 if isinstance(param, Param): 

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

313 

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

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

316 self._cli_params['sources'] = args 

317 

318 return options, args, parser 

319 

320 def delete_gui(self): 

321 ''' 

322 Remove the gui elements of the snuffling. 

323 

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

325 also removes all traces and markers added with the 

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

327 ''' 

328 

329 self.cleanup() 

330 

331 if self._panel is not None: 

332 self._panel_parent.remove_panel(self._panel) 

333 self._panel = None 

334 

335 if self._menuitem is not None: 

336 self._menu_parent.remove_snuffling_menuitem(self._menuitem) 

337 self._menuitem = None 

338 

339 if self._helpmenuitem is not None: 

340 self._menu_parent.remove_snuffling_help_menuitem( 

341 self._helpmenuitem) 

342 

343 def set_name(self, name): 

344 ''' 

345 Set the snuffling's name. 

346 

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

348 ''' 

349 

350 self._name = name 

351 self.reset_gui() 

352 

353 def get_name(self): 

354 ''' 

355 Get the snuffling's name. 

356 ''' 

357 

358 return self._name 

359 

360 def set_have_pre_process_hook(self, bool): 

361 self._have_pre_process_hook = bool 

362 self._live_update = False 

363 self._pre_process_hook_enabled = False 

364 self.reset_gui() 

365 

366 def set_have_post_process_hook(self, bool): 

367 self._have_post_process_hook = bool 

368 self._live_update = False 

369 self._post_process_hook_enabled = False 

370 self.reset_gui() 

371 

372 def set_have_pile_changed_hook(self, bool): 

373 self._pile_ = False 

374 

375 def enable_pile_changed_notifications(self): 

376 ''' 

377 Get informed when pile changed. 

378 

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

380 update in the viewer's pile. 

381 ''' 

382 

383 viewer = self.get_viewer() 

384 viewer.pile_has_changed_signal.connect( 

385 self.pile_changed) 

386 

387 def disable_pile_changed_notifications(self): 

388 ''' 

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

390 ''' 

391 

392 viewer = self.get_viewer() 

393 viewer.pile_has_changed_signal.disconnect( 

394 self.pile_changed) 

395 

396 def pile_changed(self): 

397 ''' 

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

399 

400 Must be activated with a call to 

401 :py:meth:`enable_pile_changed_notifications`. 

402 ''' 

403 

404 pass 

405 

406 def reset_gui(self, reloaded=False): 

407 ''' 

408 Delete and recreate the snuffling's panel. 

409 ''' 

410 

411 if self._panel or self._menuitem: 

412 sett = self.get_settings() 

413 self.delete_gui() 

414 self.setup_gui(reloaded=reloaded) 

415 self.set_settings(sett) 

416 

417 def show_message(self, kind, message): 

418 ''' 

419 Display a message box. 

420 

421 :param kind: string defining kind of message 

422 :param message: the message to be displayed 

423 ''' 

424 

425 try: 

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

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

428 box.exec_() 

429 except NoViewerSet: 

430 pass 

431 

432 def error(self, message): 

433 ''' 

434 Show an error message box. 

435 

436 :param message: specifying the error 

437 ''' 

438 

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

440 self.show_message('error', message) 

441 

442 def warn(self, message): 

443 ''' 

444 Display a warning message. 

445 

446 :param message: specifying the warning 

447 ''' 

448 

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

450 self.show_message('warning', message) 

451 

452 def fail(self, message): 

453 ''' 

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

455 exception. 

456 

457 :param message: specifying the error 

458 ''' 

459 

460 self.error(message) 

461 raise SnufflingCallFailed(message) 

462 

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

464 ''' 

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

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

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

468 

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

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

471 ''' 

472 

473 if name is None: 

474 self._iplot += 1 

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

476 

477 fframe = FigureFrame() 

478 self._panel_parent.add_tab(name, fframe) 

479 if get == 'axes': 

480 return fframe.gca() 

481 elif get == 'figure': 

482 return fframe.gcf() 

483 elif get == 'figure_frame': 

484 return fframe 

485 

486 def figure(self, name=None): 

487 ''' 

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

489 which can be displayed within snuffler by calling 

490 :py:meth:`canvas.draw`. 

491 

492 :param name: labels the tab of the figure 

493 ''' 

494 

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

496 

497 def axes(self, name=None): 

498 ''' 

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

500 

501 :param name: labels the tab of axes 

502 ''' 

503 

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

505 

506 def figure_frame(self, name=None): 

507 ''' 

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

509 

510 :param name: labels the tab figure frame 

511 ''' 

512 

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

514 

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

516 ''' 

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

518 

519 :param name: labels the tab 

520 :param filename: name of file to be displayed 

521 ''' 

522 

523 f = PixmapFrame(filename) 

524 

525 scroll_area = qw.QScrollArea() 

526 scroll_area.setWidget(f) 

527 scroll_area.setWidgetResizable(True) 

528 

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

530 return f 

531 

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

533 ''' 

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

535 within snuffler. 

536 

537 :param url: url to open 

538 :param name: labels the tab 

539 ''' 

540 

541 if name is None: 

542 self._iplot += 1 

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

544 

545 f = WebKitFrame(url) 

546 self._panel_parent.add_tab(name, f) 

547 return f 

548 

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

550 ''' 

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

552 graphics. 

553 

554 :param actors: list of VTKActors 

555 :param name: labels the tab 

556 

557 Initialize the interactive rendering by calling the frames' 

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

559 renderer. 

560 

561 Requires installation of vtk including python wrapper. 

562 ''' 

563 if name is None: 

564 self._iplot += 1 

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

566 

567 try: 

568 f = VTKFrame(actors=actors) 

569 except ImportError as e: 

570 self.fail(e) 

571 

572 self._panel_parent.add_tab(name, f) 

573 return f 

574 

575 def tempdir(self): 

576 ''' 

577 Create a temporary directory and return its absolute path. 

578 

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

580 instance is deleted. 

581 ''' 

582 

583 if self._tempdir is None: 

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

585 

586 return self._tempdir 

587 

588 def set_live_update(self, live_update): 

589 ''' 

590 Enable/disable live updating. 

591 

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

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

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

595 item or pressing the call button. 

596 ''' 

597 

598 self._live_update = live_update 

599 if self._have_pre_process_hook: 

600 self._pre_process_hook_enabled = live_update 

601 if self._have_post_process_hook: 

602 self._post_process_hook_enabled = live_update 

603 

604 try: 

605 self.get_viewer().clean_update() 

606 except NoViewerSet: 

607 pass 

608 

609 def add_parameter(self, param): 

610 ''' 

611 Add an adjustable parameter to the snuffling. 

612 

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

614 :py:class:`Choice`. 

615 

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

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

618 ''' 

619 

620 self._parameters.append(param) 

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

622 

623 if self._panel is not None: 

624 self.delete_gui() 

625 self.setup_gui() 

626 

627 def add_trigger(self, name, method): 

628 ''' 

629 Add a button to the snuffling's panel. 

630 

631 :param name: string that labels the button 

632 :param method: method associated with the button 

633 ''' 

634 

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

636 

637 if self._panel is not None: 

638 self.delete_gui() 

639 self.setup_gui() 

640 

641 def get_parameters(self): 

642 ''' 

643 Get the snuffling's adjustable parameter definitions. 

644 

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

646 ''' 

647 

648 return self._parameters 

649 

650 def get_parameter(self, ident): 

651 ''' 

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

653 

654 :param ident: identifier of the parameter 

655 

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

657 ''' 

658 

659 for param in self._parameters: 

660 if param.ident == ident: 

661 return param 

662 return None 

663 

664 def set_parameter(self, ident, value): 

665 ''' 

666 Set one of the snuffling's adjustable parameters. 

667 

668 :param ident: identifier of the parameter 

669 :param value: new value of the parameter 

670 

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

672 ''' 

673 

674 self._set_parameter_value(ident, value) 

675 

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

677 if control: 

678 control.set_value(value) 

679 

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

681 ''' 

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

683 

684 :param ident: identifier of the parameter 

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

686 

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

688 ''' 

689 

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

691 if control: 

692 control.set_range(vmin, vmax) 

693 

694 def set_parameter_choices(self, ident, choices): 

695 ''' 

696 Update the choices of a Choice parameter. 

697 

698 :param ident: identifier of the parameter 

699 :param choices: list of strings 

700 ''' 

701 

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

703 if control: 

704 selected_choice = control.set_choices(choices) 

705 self._set_parameter_value(ident, selected_choice) 

706 

707 def _set_parameter_value(self, ident, value): 

708 setattr(self, ident, value) 

709 

710 def get_parameter_value(self, ident): 

711 ''' 

712 Get the current value of a parameter. 

713 

714 :param ident: identifier of the parameter 

715 ''' 

716 return getattr(self, ident) 

717 

718 def get_settings(self): 

719 ''' 

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

721 their values as the dictionaries values. 

722 ''' 

723 

724 params = self.get_parameters() 

725 settings = {} 

726 for param in params: 

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

728 

729 return settings 

730 

731 def set_settings(self, settings): 

732 params = self.get_parameters() 

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

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

735 if k in dparams: 

736 self._set_parameter_value(k, v) 

737 if k in self._param_controls: 

738 control = self._param_controls[k] 

739 control.set_value(v) 

740 

741 def get_viewer(self): 

742 ''' 

743 Get the parent viewer. 

744 

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

746 which is the main viewer widget. 

747 

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

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

750 ''' 

751 

752 if self._viewer is None: 

753 raise NoViewerSet() 

754 return self._viewer 

755 

756 def get_pile(self): 

757 ''' 

758 Get the pile. 

759 

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

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

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

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

764 mode. 

765 ''' 

766 

767 try: 

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

769 except NoViewerSet: 

770 if self._no_viewer_pile is None: 

771 self._no_viewer_pile = self.make_pile() 

772 

773 p = self._no_viewer_pile 

774 

775 return p 

776 

777 def get_active_event_and_stations( 

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

779 

780 ''' 

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

782 

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

784 query for available data 

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

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

787 

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

789 ''' 

790 

791 p = self.get_pile() 

792 v = self.get_viewer() 

793 

794 event = v.get_active_event() 

795 if event is None: 

796 self.fail( 

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

798 'it the "active event"') 

799 

800 stations = {} 

801 for traces in p.chopper( 

802 event.time+trange[0], 

803 event.time+trange[1], 

804 load_data=False, 

805 degap=False): 

806 

807 for tr in traces: 

808 try: 

809 for skey in v.station_keys(tr): 

810 if skey in stations: 

811 continue 

812 

813 station = v.get_station(skey) 

814 stations[skey] = station 

815 

816 except KeyError: 

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

818 % '.'.join(skey) 

819 

820 if missing == 'warn': 

821 logger.warning(s) 

822 elif missing == 'raise': 

823 raise MissingStationInformation(s) 

824 elif missing == 'ignore': 

825 pass 

826 else: 

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

828 

829 stations[skey] = None 

830 

831 return event, list(set( 

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

833 

834 def get_stations(self): 

835 ''' 

836 Get all stations known to the viewer. 

837 ''' 

838 

839 v = self.get_viewer() 

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

841 return stations 

842 

843 def get_markers(self): 

844 ''' 

845 Get all markers from the viewer. 

846 ''' 

847 

848 return self.get_viewer().get_markers() 

849 

850 def get_event_markers(self): 

851 ''' 

852 Get all event markers from the viewer. 

853 ''' 

854 

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

856 if isinstance(m, EventMarker)] 

857 

858 def get_selected_markers(self): 

859 ''' 

860 Get all selected markers from the viewer. 

861 ''' 

862 

863 return self.get_viewer().selected_markers() 

864 

865 def get_selected_event_markers(self): 

866 ''' 

867 Get all selected event markers from the viewer. 

868 ''' 

869 

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

871 if isinstance(m, EventMarker)] 

872 

873 def get_active_event_and_phase_markers(self): 

874 ''' 

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

876 ''' 

877 

878 viewer = self.get_viewer() 

879 markers = viewer.get_markers() 

880 event_marker = viewer.get_active_event_marker() 

881 if event_marker is None: 

882 self.fail( 

883 'No active event set. ' 

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

885 

886 event = event_marker.get_event() 

887 

888 selection = [] 

889 for m in markers: 

890 if isinstance(m, PhaseMarker): 

891 if m.get_event() is event: 

892 selection.append(m) 

893 

894 return ( 

895 event_marker, 

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

897 m.get_event() == event]) 

898 

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

900 ''' 

901 Get currently active trace selector from viewer. 

902 

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

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

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

906 to disable any restrictions. 

907 ''' 

908 

909 viewer = self.get_viewer() 

910 

911 def rtrue(tr): 

912 return True 

913 

914 if mode == 'inview': 

915 return viewer.trace_selector or rtrue 

916 elif mode == 'visible': 

917 return viewer.trace_filter or rtrue 

918 elif mode == 'all': 

919 return rtrue 

920 else: 

921 raise Exception('invalid mode argument') 

922 

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

924 mode='inview', main_bandpass=False, 

925 progress=None, responsive=False, 

926 *args, **kwargs): 

927 ''' 

928 Iterate over selected traces. 

929 

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

931 running snuffler. For each selected marker, 

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

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

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

935 *\\*args* and *\\*\\*kwargs*. 

936 

937 :param fallback: 

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

939 currently visible in the viewer. 

940 

941 :param marker_selector: 

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

943 

944 :param mode: 

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

946 shown in the viewer (excluding traces accessible through vertical 

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

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

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

950 restrictions. 

951 

952 :param main_bandpass: 

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

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

955 order Butterworth highpass and lowpass and the signal is always 

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

957 bandpass settings from the graphical interface are not respected 

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

959 artifacts. 

960 

961 :param progress: 

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

963 is used as the label for the progress bar. 

964 

965 :param responsive: 

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

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

968 to be aborted by the user. 

969 ''' 

970 

971 try: 

972 viewer = self.get_viewer() 

973 markers = [ 

974 m for m in viewer.selected_markers() 

975 if not isinstance(m, EventMarker)] 

976 

977 if marker_selector is not None: 

978 markers = [ 

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

980 

981 pile = self.get_pile() 

982 

983 def rtrue(tr): 

984 return True 

985 

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

987 trace_selector_viewer = self.get_viewer_trace_selector(mode) 

988 

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

990 

991 if main_bandpass: 

992 def apply_filters(traces): 

993 for tr in traces: 

994 if viewer.highpass is not None: 

995 tr.highpass(4, viewer.highpass) 

996 if viewer.lowpass is not None: 

997 tr.lowpass(4, viewer.lowpass) 

998 return traces 

999 else: 

1000 def apply_filters(traces): 

1001 return traces 

1002 

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

1004 

1005 time_last = [time.time()] 

1006 

1007 def update_progress(label, batch): 

1008 time_now = time.time() 

1009 if responsive: 

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

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

1012 # changes etc. 

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

1014 qw.qApp.processEvents() 

1015 else: 

1016 # redraw about once a second 

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

1018 viewer.repaint() 

1019 

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

1021 

1022 abort = pb.set_status( 

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

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

1025 

1026 return abort 

1027 

1028 if markers: 

1029 for imarker, marker in enumerate(markers): 

1030 try: 

1031 if progress: 

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

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

1034 

1035 pb.set_status(label, 0, responsive) 

1036 

1037 if not marker.nslc_ids: 

1038 trace_selector_marker = rtrue 

1039 else: 

1040 def trace_selector_marker(tr): 

1041 return marker.match_nslc(tr.nslc_id) 

1042 

1043 def trace_selector(tr): 

1044 return trace_selector_arg(tr) \ 

1045 and trace_selector_viewer(tr) \ 

1046 and trace_selector_marker(tr) 

1047 

1048 for batch in pile.chopper( 

1049 tmin=marker.tmin, 

1050 tmax=marker.tmax, 

1051 trace_selector=trace_selector, 

1052 style='batch', 

1053 *args, 

1054 **kwargs): 

1055 

1056 if progress: 

1057 abort = update_progress(label, batch) 

1058 if abort: 

1059 return 

1060 

1061 batch.traces = apply_filters(batch.traces) 

1062 if style_arg == 'batch': 

1063 yield batch 

1064 else: 

1065 yield batch.traces 

1066 

1067 finally: 

1068 if progress: 

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

1070 

1071 elif fallback: 

1072 def trace_selector(tr): 

1073 return trace_selector_arg(tr) \ 

1074 and trace_selector_viewer(tr) 

1075 

1076 tmin, tmax = viewer.get_time_range() 

1077 

1078 if not pile.is_empty(): 

1079 ptmin = pile.get_tmin() 

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

1081 if ptmin > tmin: 

1082 tmin = ptmin + tpad 

1083 ptmax = pile.get_tmax() 

1084 if ptmax < tmax: 

1085 tmax = ptmax - tpad 

1086 

1087 try: 

1088 if progress: 

1089 label = progress 

1090 pb.set_status(label, 0, responsive) 

1091 

1092 for batch in pile.chopper( 

1093 tmin=tmin, 

1094 tmax=tmax, 

1095 trace_selector=trace_selector, 

1096 style='batch', 

1097 *args, 

1098 **kwargs): 

1099 

1100 if progress: 

1101 abort = update_progress(label, batch) 

1102 

1103 if abort: 

1104 return 

1105 

1106 batch.traces = apply_filters(batch.traces) 

1107 

1108 if style_arg == 'batch': 

1109 yield batch 

1110 else: 

1111 yield batch.traces 

1112 

1113 finally: 

1114 if progress: 

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

1116 

1117 else: 

1118 raise NoTracesSelected() 

1119 

1120 except NoViewerSet: 

1121 pile = self.get_pile() 

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

1123 

1124 def get_selected_time_range(self, fallback=False): 

1125 ''' 

1126 Get the time range spanning all selected markers. 

1127 

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

1129 end of visible time range 

1130 ''' 

1131 

1132 viewer = self.get_viewer() 

1133 markers = viewer.selected_markers() 

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

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

1136 

1137 if mins and maxs: 

1138 tmin = min(mins) 

1139 tmax = max(maxs) 

1140 

1141 elif fallback: 

1142 tmin, tmax = viewer.get_time_range() 

1143 

1144 else: 

1145 raise NoTracesSelected() 

1146 

1147 return tmin, tmax 

1148 

1149 def panel_visibility_changed(self, bool): 

1150 ''' 

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

1152 

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

1154 when the panel is activated the first time. 

1155 ''' 

1156 

1157 pass 

1158 

1159 def make_pile(self): 

1160 ''' 

1161 Create a pile. 

1162 

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

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

1165 arguments. 

1166 ''' 

1167 

1168 cachedirname = config.config().cache_dir 

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

1170 return pile.make_pile( 

1171 sources, 

1172 cachedirname=cachedirname, 

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

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

1175 

1176 def make_panel(self, parent): 

1177 ''' 

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

1179 

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

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

1182 parameters). 

1183 ''' 

1184 

1185 params = self.get_parameters() 

1186 self._param_controls = {} 

1187 if params or self._force_panel: 

1188 sarea = MyScrollArea(parent.get_panel_parent_widget()) 

1189 sarea.setFrameStyle(qw.QFrame.NoFrame) 

1190 sarea.setSizePolicy(qw.QSizePolicy( 

1191 qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding)) 

1192 frame = MyFrame(sarea) 

1193 frame.widgetVisibilityChanged.connect( 

1194 self.panel_visibility_changed) 

1195 

1196 frame.setSizePolicy(qw.QSizePolicy( 

1197 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)) 

1198 frame.setFrameStyle(qw.QFrame.NoFrame) 

1199 sarea.setWidget(frame) 

1200 sarea.setWidgetResizable(True) 

1201 layout = qw.QGridLayout() 

1202 layout.setContentsMargins(0, 0, 0, 0) 

1203 layout.setSpacing(0) 

1204 frame.setLayout(layout) 

1205 

1206 parlayout = qw.QGridLayout() 

1207 

1208 irow = 0 

1209 ipar = 0 

1210 have_switches = False 

1211 have_params = False 

1212 for iparam, param in enumerate(params): 

1213 if isinstance(param, Param): 

1214 if param.minimum <= 0.0: 

1215 param_control = LinValControl( 

1216 high_is_none=param.high_is_none, 

1217 low_is_none=param.low_is_none) 

1218 else: 

1219 param_control = ValControl( 

1220 high_is_none=param.high_is_none, 

1221 low_is_none=param.low_is_none, 

1222 low_is_zero=param.low_is_zero) 

1223 

1224 param_control.setup( 

1225 param.name, 

1226 param.minimum, 

1227 param.maximum, 

1228 param.default, 

1229 iparam) 

1230 

1231 param_control.set_tracking(param.tracking) 

1232 param_control.set_type(param.type) 

1233 param_control.valchange.connect( 

1234 self.modified_snuffling_panel) 

1235 

1236 self._param_controls[param.ident] = param_control 

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

1238 parlayout.addWidget(w, ipar, iw) 

1239 

1240 ipar += 1 

1241 have_params = True 

1242 

1243 elif isinstance(param, Choice): 

1244 param_widget = ChoiceControl( 

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

1246 param_widget.choosen.connect( 

1247 self.choose_on_snuffling_panel) 

1248 

1249 self._param_controls[param.ident] = param_widget 

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

1251 ipar += 1 

1252 have_params = True 

1253 

1254 elif isinstance(param, Switch): 

1255 have_switches = True 

1256 

1257 if have_params: 

1258 parframe = qw.QFrame(sarea) 

1259 parframe.setSizePolicy(qw.QSizePolicy( 

1260 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)) 

1261 parframe.setLayout(parlayout) 

1262 layout.addWidget(parframe, irow, 0) 

1263 irow += 1 

1264 

1265 if have_switches: 

1266 swlayout = qw.QGridLayout() 

1267 isw = 0 

1268 for iparam, param in enumerate(params): 

1269 if isinstance(param, Switch): 

1270 param_widget = SwitchControl( 

1271 param.ident, param.default, param.name) 

1272 param_widget.sw_toggled.connect( 

1273 self.switch_on_snuffling_panel) 

1274 

1275 self._param_controls[param.ident] = param_widget 

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

1277 isw += 1 

1278 

1279 swframe = qw.QFrame(sarea) 

1280 swframe.setSizePolicy(qw.QSizePolicy( 

1281 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)) 

1282 swframe.setLayout(swlayout) 

1283 layout.addWidget(swframe, irow, 0) 

1284 irow += 1 

1285 

1286 butframe = qw.QFrame(sarea) 

1287 butframe.setSizePolicy(qw.QSizePolicy( 

1288 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)) 

1289 butlayout = qw.QHBoxLayout() 

1290 butframe.setLayout(butlayout) 

1291 

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

1293 if self._live_update: 

1294 live_update_checkbox.setCheckState(qc.Qt.Checked) 

1295 

1296 butlayout.addWidget(live_update_checkbox) 

1297 live_update_checkbox.toggled.connect( 

1298 self.live_update_toggled) 

1299 

1300 help_button = qw.QPushButton('Help') 

1301 butlayout.addWidget(help_button) 

1302 help_button.clicked.connect( 

1303 self.help_button_triggered) 

1304 

1305 clear_button = qw.QPushButton('Clear') 

1306 butlayout.addWidget(clear_button) 

1307 clear_button.clicked.connect( 

1308 self.clear_button_triggered) 

1309 

1310 call_button = qw.QPushButton('Run') 

1311 butlayout.addWidget(call_button) 

1312 call_button.clicked.connect( 

1313 self.call_button_triggered) 

1314 

1315 for name, method in self._triggers: 

1316 but = qw.QPushButton(name) 

1317 

1318 def call_and_update(method): 

1319 def f(): 

1320 try: 

1321 method() 

1322 except SnufflingError as e: 

1323 if not isinstance(e, SnufflingCallFailed): 

1324 # those have logged within error() 

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

1326 logger.error( 

1327 '%s: Snuffling action failed' % self._name) 

1328 

1329 self.get_viewer().update() 

1330 return f 

1331 

1332 but.clicked.connect( 

1333 call_and_update(method)) 

1334 

1335 butlayout.addWidget(but) 

1336 

1337 layout.addWidget(butframe, irow, 0) 

1338 

1339 irow += 1 

1340 spacer = qw.QSpacerItem( 

1341 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

1342 

1343 layout.addItem(spacer, irow, 0) 

1344 

1345 return sarea 

1346 

1347 else: 

1348 return None 

1349 

1350 def make_helpmenuitem(self, parent): 

1351 ''' 

1352 Create the help menu item for the snuffling. 

1353 ''' 

1354 

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

1356 

1357 item.triggered.connect( 

1358 self.help_button_triggered) 

1359 

1360 return item 

1361 

1362 def make_menuitem(self, parent): 

1363 ''' 

1364 Create the menu item for the snuffling. 

1365 

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

1367 menu entry is wanted. 

1368 ''' 

1369 

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

1371 item.setCheckable( 

1372 self._have_pre_process_hook or self._have_post_process_hook) 

1373 

1374 item.triggered.connect( 

1375 self.menuitem_triggered) 

1376 

1377 return item 

1378 

1379 def output_filename( 

1380 self, 

1381 caption='Save File', 

1382 dir='', 

1383 filter='', 

1384 selected_filter=None): 

1385 

1386 ''' 

1387 Query user for an output filename. 

1388 

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

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

1391 dialog. 

1392 ''' 

1393 

1394 if not dir and self._previous_output_filename: 

1395 dir = self._previous_output_filename 

1396 

1397 fn = getSaveFileName( 

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

1399 if not fn: 

1400 raise UserCancelled() 

1401 

1402 self._previous_output_filename = fn 

1403 return str(fn) 

1404 

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

1406 ''' 

1407 Query user for an input directory. 

1408 

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

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

1411 dialog. 

1412 ''' 

1413 

1414 if not dir and self._previous_input_directory: 

1415 dir = self._previous_input_directory 

1416 

1417 dn = qw.QFileDialog.getExistingDirectory( 

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

1419 

1420 if not dn: 

1421 raise UserCancelled() 

1422 

1423 self._previous_input_directory = dn 

1424 return str(dn) 

1425 

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

1427 selected_filter=None): 

1428 ''' 

1429 Query user for an input filename. 

1430 

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

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

1433 dialog. 

1434 ''' 

1435 

1436 if not dir and self._previous_input_filename: 

1437 dir = self._previous_input_filename 

1438 

1439 fn, _ = fnpatch(qw.QFileDialog.getOpenFileName( 

1440 self.get_viewer(), 

1441 caption, 

1442 dir, 

1443 filter)) # selected_filter) 

1444 

1445 if not fn: 

1446 raise UserCancelled() 

1447 

1448 self._previous_input_filename = fn 

1449 return str(fn) 

1450 

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

1452 ''' 

1453 Query user for a text input. 

1454 

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

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

1457 dialog. 

1458 ''' 

1459 

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

1461 

1462 if not ok: 

1463 raise UserCancelled() 

1464 

1465 return inp 

1466 

1467 def modified_snuffling_panel(self, value, iparam): 

1468 ''' 

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

1470 

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

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

1473 widget. 

1474 ''' 

1475 

1476 param = self.get_parameters()[iparam] 

1477 self._set_parameter_value(param.ident, value) 

1478 if self._live_update: 

1479 self.check_call() 

1480 self.get_viewer().update() 

1481 

1482 def switch_on_snuffling_panel(self, ident, state): 

1483 ''' 

1484 Called when the user has toggled a switchable parameter. 

1485 ''' 

1486 

1487 self._set_parameter_value(ident, state) 

1488 if self._live_update: 

1489 self.check_call() 

1490 self.get_viewer().update() 

1491 

1492 def choose_on_snuffling_panel(self, ident, state): 

1493 ''' 

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

1495 ''' 

1496 

1497 self._set_parameter_value(ident, state) 

1498 if self._live_update: 

1499 self.check_call() 

1500 self.get_viewer().update() 

1501 

1502 def menuitem_triggered(self, arg): 

1503 ''' 

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

1505 

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

1507 and triggers an update on the viewer widget. 

1508 ''' 

1509 

1510 self.check_call() 

1511 

1512 if self._have_pre_process_hook: 

1513 self._pre_process_hook_enabled = arg 

1514 

1515 if self._have_post_process_hook: 

1516 self._post_process_hook_enabled = arg 

1517 

1518 if self._have_pre_process_hook or self._have_post_process_hook: 

1519 self.get_viewer().clean_update() 

1520 else: 

1521 self.get_viewer().update() 

1522 

1523 def call_button_triggered(self): 

1524 ''' 

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

1526 

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

1528 and triggers an update on the viewer widget. 

1529 ''' 

1530 

1531 self.check_call() 

1532 self.get_viewer().update() 

1533 

1534 def clear_button_triggered(self): 

1535 ''' 

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

1537 

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

1539 viewer widget. 

1540 ''' 

1541 

1542 self.cleanup() 

1543 self.get_viewer().update() 

1544 

1545 def help_button_triggered(self): 

1546 ''' 

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

1548 given in the snufflings' __doc__ string. 

1549 ''' 

1550 

1551 if self.__doc__: 

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

1553 doc = qw.QLabel(self.__doc__) 

1554 else: 

1555 try: 

1556 import markdown 

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

1558 

1559 except ImportError: 

1560 logger.error( 

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

1562 'formatting.') 

1563 

1564 doc = qw.QLabel(self.__doc__) 

1565 else: 

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

1567 

1568 labels = [doc] 

1569 

1570 if self._filename: 

1571 from html import escape 

1572 

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

1574 

1575 doc_src = qw.QLabel( 

1576 '''<html><body> 

1577<hr /> 

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

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

1580<br /> 

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

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

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

1584 % ( 

1585 quote(self._filename), 

1586 escape(self._filename), 

1587 escape(code))) 

1588 

1589 labels.append(doc_src) 

1590 

1591 for h in labels: 

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

1593 h.setWordWrap(True) 

1594 

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

1596 

1597 def live_update_toggled(self, on): 

1598 ''' 

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

1600 ''' 

1601 

1602 self.set_live_update(on) 

1603 

1604 def add_traces(self, traces): 

1605 ''' 

1606 Add traces to the viewer. 

1607 

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

1609 

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

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

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

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

1614 traces are added. 

1615 

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

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

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

1619 method). 

1620 ''' 

1621 

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

1623 self._tickets.append(ticket) 

1624 return ticket 

1625 

1626 def add_trace(self, tr): 

1627 ''' 

1628 Add a trace to the viewer. 

1629 

1630 See :py:meth:`add_traces`. 

1631 ''' 

1632 

1633 self.add_traces([tr]) 

1634 

1635 def add_markers(self, markers): 

1636 ''' 

1637 Add some markers to the display. 

1638 

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

1640 adds these to the viewer. 

1641 ''' 

1642 

1643 self.get_viewer().add_markers(markers) 

1644 self._markers.extend(markers) 

1645 

1646 def add_marker(self, marker): 

1647 ''' 

1648 Add a marker to the display. 

1649 

1650 See :py:meth:`add_markers`. 

1651 ''' 

1652 

1653 self.add_markers([marker]) 

1654 

1655 def cleanup(self): 

1656 ''' 

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

1658 snuffling. 

1659 ''' 

1660 

1661 try: 

1662 viewer = self.get_viewer() 

1663 viewer.release_data(self._tickets) 

1664 viewer.remove_markers(self._markers) 

1665 

1666 except NoViewerSet: 

1667 pass 

1668 

1669 self._tickets = [] 

1670 self._markers = [] 

1671 

1672 def check_call(self): 

1673 

1674 if self._call_in_progress: 

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

1676 return 

1677 

1678 try: 

1679 self._call_in_progress = True 

1680 self.call() 

1681 return 0 

1682 

1683 except SnufflingError as e: 

1684 if not isinstance(e, SnufflingCallFailed): 

1685 # those have logged within error() 

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

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

1688 return 1 

1689 

1690 except Exception as e: 

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

1692 self._name, str(e)) 

1693 

1694 logger.exception(message) 

1695 self.show_message('error', message) 

1696 

1697 finally: 

1698 self._call_in_progress = False 

1699 

1700 def call(self): 

1701 ''' 

1702 Main work routine of the snuffling. 

1703 

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

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

1706 in subclass. The default implementation does nothing useful. 

1707 ''' 

1708 

1709 pass 

1710 

1711 def pre_process_hook(self, traces): 

1712 return traces 

1713 

1714 def post_process_hook(self, traces): 

1715 return traces 

1716 

1717 def get_tpad(self): 

1718 ''' 

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

1720 ''' 

1721 

1722 return 0.0 

1723 

1724 def pre_destroy(self): 

1725 ''' 

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

1727 

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

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

1730 the snuffling`s tempory directory, if needed. 

1731 ''' 

1732 

1733 self.cleanup() 

1734 if self._tempdir is not None: 

1735 import shutil 

1736 shutil.rmtree(self._tempdir) 

1737 

1738 

1739class SnufflingError(Exception): 

1740 pass 

1741 

1742 

1743class NoViewerSet(SnufflingError): 

1744 ''' 

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

1746 ''' 

1747 

1748 def __str__(self): 

1749 return 'No GUI available. ' \ 

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

1751 

1752 

1753class MissingStationInformation(SnufflingError): 

1754 ''' 

1755 Raised when station information is missing. 

1756 ''' 

1757 

1758 

1759class NoTracesSelected(SnufflingError): 

1760 ''' 

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

1762 and we cannot fallback to using the current view. 

1763 ''' 

1764 

1765 def __str__(self): 

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

1767 

1768 

1769class UserCancelled(SnufflingError): 

1770 ''' 

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

1772 ''' 

1773 

1774 def __str__(self): 

1775 return 'The user has cancelled a dialog.' 

1776 

1777 

1778class SnufflingCallFailed(SnufflingError): 

1779 ''' 

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

1781 :py:meth:`Snuffling.call`. 

1782 ''' 

1783 

1784 

1785class InvalidSnufflingFilename(Exception): 

1786 pass 

1787 

1788 

1789class SnufflingModule(object): 

1790 ''' 

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

1792 

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

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

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

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

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

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

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

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

1801 when needed. 

1802 ''' 

1803 

1804 mtimes = {} 

1805 

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

1807 self._path = path 

1808 self._name = name 

1809 self._mtime = None 

1810 self._module = None 

1811 self._snufflings = [] 

1812 self._handler = handler 

1813 

1814 def load_if_needed(self): 

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

1816 

1817 try: 

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

1819 except OSError as e: 

1820 if e.errno == 2: 

1821 logger.error(e) 

1822 raise BrokenSnufflingModule(filename) 

1823 

1824 if self._module is None: 

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

1826 try: 

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

1828 if self._name in sys.modules: 

1829 raise InvalidSnufflingFilename(self._name) 

1830 

1831 self._module = __import__(self._name) 

1832 del sys.modules[self._name] 

1833 

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

1835 snuffling._filename = filename 

1836 self.add_snuffling(snuffling) 

1837 

1838 except Exception: 

1839 logger.error(traceback.format_exc()) 

1840 raise BrokenSnufflingModule(filename) 

1841 

1842 finally: 

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

1844 

1845 elif self._mtime != mtime: 

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

1847 settings = self.remove_snufflings() 

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

1849 try: 

1850 

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

1852 

1853 reload(self._module) 

1854 del sys.modules[self._name] 

1855 

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

1857 snuffling._filename = filename 

1858 self.add_snuffling(snuffling, reloaded=True) 

1859 

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

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

1862 snuf.set_settings(sett) 

1863 

1864 except Exception: 

1865 logger.error(traceback.format_exc()) 

1866 raise BrokenSnufflingModule(filename) 

1867 

1868 finally: 

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

1870 

1871 self._mtime = mtime 

1872 

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

1874 snuffling._path = self._path 

1875 snuffling.setup() 

1876 self._snufflings.append(snuffling) 

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

1878 

1879 def remove_snufflings(self): 

1880 settings = [] 

1881 for snuffling in self._snufflings: 

1882 settings.append(snuffling.get_settings()) 

1883 self._handler.remove_snuffling(snuffling) 

1884 

1885 self._snufflings = [] 

1886 return settings 

1887 

1888 

1889class BrokenSnufflingModule(Exception): 

1890 pass 

1891 

1892 

1893class MyScrollArea(qw.QScrollArea): 

1894 

1895 def sizeHint(self): 

1896 s = qc.QSize() 

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

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

1899 return s 

1900 

1901 

1902class SwitchControl(qw.QCheckBox): 

1903 sw_toggled = qc.pyqtSignal(object, bool) 

1904 

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

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

1907 self.ident = ident 

1908 self.setChecked(default) 

1909 self.toggled.connect(self._sw_toggled) 

1910 

1911 def _sw_toggled(self, state): 

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

1913 

1914 def set_value(self, state): 

1915 self.blockSignals(True) 

1916 self.setChecked(state) 

1917 self.blockSignals(False) 

1918 

1919 

1920class ChoiceControl(qw.QFrame): 

1921 choosen = qc.pyqtSignal(object, object) 

1922 

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

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

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

1926 self.label.setMinimumWidth(120) 

1927 self.cbox = qw.QComboBox(self) 

1928 self.layout = qw.QHBoxLayout(self) 

1929 self.layout.addWidget(self.label) 

1930 self.layout.addWidget(self.cbox) 

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

1932 self.layout.setSpacing(0) 

1933 self.ident = ident 

1934 self.choices = choices 

1935 for ichoice, choice in enumerate(choices): 

1936 self.cbox.addItem(choice) 

1937 

1938 self.set_value(default) 

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

1940 

1941 def set_choices(self, choices): 

1942 icur = self.cbox.currentIndex() 

1943 if icur != -1: 

1944 selected_choice = choices[icur] 

1945 else: 

1946 selected_choice = None 

1947 

1948 self.choices = choices 

1949 self.cbox.clear() 

1950 for ichoice, choice in enumerate(choices): 

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

1952 

1953 if selected_choice is not None and selected_choice in choices: 

1954 self.set_value(selected_choice) 

1955 return selected_choice 

1956 else: 

1957 self.set_value(choices[0]) 

1958 return choices[0] 

1959 

1960 def emit_choosen(self, i): 

1961 self.choosen.emit( 

1962 self.ident, 

1963 self.choices[i]) 

1964 

1965 def set_value(self, v): 

1966 self.cbox.blockSignals(True) 

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

1968 if choice == v: 

1969 self.cbox.setCurrentIndex(i) 

1970 self.cbox.blockSignals(False)