1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

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

5from __future__ import absolute_import, print_function 

6 

7import sys 

8import os 

9import time 

10import calendar 

11import datetime 

12import re 

13import math 

14import logging 

15import operator 

16import copy 

17from itertools import groupby 

18 

19import numpy as num 

20import pyrocko.model 

21import pyrocko.pile 

22import pyrocko.shadow_pile 

23import pyrocko.trace 

24import pyrocko.util 

25import pyrocko.plot 

26import pyrocko.gui.snuffling 

27import pyrocko.gui.snufflings 

28import pyrocko.gui.marker_editor 

29 

30from pyrocko.util import hpfloat, gmtime_x, mystrftime 

31 

32from .marker import associate_phases_to_events, MarkerOneNSLCRequired 

33 

34from .util import (ValControl, LinValControl, Marker, EventMarker, 

35 PhaseMarker, make_QPolygonF, draw_label, Label, 

36 Progressbars) 

37 

38from .qt_compat import qc, qg, qw, qgl, qsvg, use_pyqt5 

39 

40import scipy.stats as sstats 

41import platform 

42 

43try: 

44 newstr = unicode 

45except NameError: 

46 newstr = str 

47 

48 

49def fnpatch(x): 

50 if use_pyqt5: 

51 return x 

52 else: 

53 return x, None 

54 

55 

56if sys.version_info[0] >= 3: 

57 qc.QString = str 

58 

59qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \ 

60 qw.QFileDialog.DontUseSheet 

61 

62if platform.mac_ver() != ('', ('', '', ''), ''): 

63 macosx = True 

64else: 

65 macosx = False 

66 

67logger = logging.getLogger('pyrocko.gui.pile_viewer') 

68 

69 

70def detrend(x, y): 

71 slope, offset, _, _, _ = sstats.linregress(x, y) 

72 y_detrended = y - slope * x - offset 

73 return y_detrended, slope, offset 

74 

75 

76def retrend(x, y_detrended, slope, offset): 

77 return x * slope + y_detrended + offset 

78 

79 

80class Global(object): 

81 appOnDemand = None 

82 

83 

84class NSLC(object): 

85 def __init__(self, n, s, l=None, c=None): # noqa 

86 self.network = n 

87 self.station = s 

88 self.location = l 

89 self.channel = c 

90 

91 

92class m_float(float): 

93 

94 def __str__(self): 

95 if abs(self) >= 10000.: 

96 return '%g km' % round(self/1000., 0) 

97 elif abs(self) >= 1000.: 

98 return '%g km' % round(self/1000., 1) 

99 else: 

100 return '%.5g m' % self 

101 

102 def __lt__(self, other): 

103 if other is None: 

104 return True 

105 return float(self) < float(other) 

106 

107 def __gt__(self, other): 

108 if other is None: 

109 return False 

110 return float(self) > float(other) 

111 

112 

113def m_float_or_none(x): 

114 if x is None: 

115 return None 

116 else: 

117 return m_float(x) 

118 

119 

120def make_chunks(items): 

121 ''' 

122 Split a list of integers into sublists of consecutive elements. 

123 ''' 

124 return [list(map(operator.itemgetter(1), g)) for k, g in groupby( 

125 enumerate(items), (lambda x: x[1]-x[0]))] 

126 

127 

128class deg_float(float): 

129 

130 def __str__(self): 

131 return '%4.0f' % self 

132 

133 

134def deg_float_or_none(x): 

135 if x is None: 

136 return None 

137 else: 

138 return deg_float(x) 

139 

140 

141class sector_int(int): 

142 

143 def __str__(self): 

144 return '[%i]' % self 

145 

146 

147def num_to_html(num): 

148 snum = '%g' % num 

149 m = re.match(r'(.+)[eE]([+-]?\d+)$', snum) 

150 if m: 

151 snum = m.group(1) + ' &times; 10<sup>%i</sup>' % int(m.group(2)) 

152 

153 return snum 

154 

155 

156gap_lap_tolerance = 5. 

157 

158 

159class Timer(object): 

160 def __init__(self): 

161 self._start = None 

162 self._stop = None 

163 

164 def start(self): 

165 self._start = os.times() 

166 

167 def stop(self): 

168 self._stop = os.times() 

169 

170 def get(self): 

171 a = self._start 

172 b = self._stop 

173 if a is not None and b is not None: 

174 return tuple([b[i] - a[i] for i in range(5)]) 

175 else: 

176 return tuple([0.] * 5) 

177 

178 def __sub__(self, other): 

179 a = self.get() 

180 b = other.get() 

181 return tuple([a[i] - b[i] for i in range(5)]) 

182 

183 

184class Integrator(pyrocko.shadow_pile.ShadowPile): 

185 

186 def process(self, iblock, tmin, tmax, traces): 

187 for trace in traces: 

188 trace.ydata = trace.ydata - trace.ydata.mean() 

189 trace.ydata = num.cumsum(trace.ydata) 

190 

191 return traces 

192 

193 

194class ObjectStyle(object): 

195 def __init__(self, frame_pen, fill_brush): 

196 self.frame_pen = frame_pen 

197 self.fill_brush = fill_brush 

198 

199 

200box_styles = [] 

201box_alpha = 100 

202for color in 'orange skyblue butter chameleon chocolate plum ' \ 

203 'scarletred'.split(): 

204 

205 box_styles.append(ObjectStyle( 

206 qg.QPen(qg.QColor(*pyrocko.plot.tango_colors[color+'3'])), 

207 qg.QBrush(qg.QColor( 

208 *(pyrocko.plot.tango_colors[color+'1'] + (box_alpha,)))), 

209 )) 

210 

211sday = 60*60*24. # \ 

212smonth = 60*60*24*30. # | only used as approx. intervals... 

213syear = 60*60*24*365. # / 

214 

215acceptable_tincs = num.array([ 

216 1, 2, 5, 10, 20, 30, 60, 60*5, 60*10, 60*20, 60*30, 60*60, 60*60*3, 

217 60*60*6, 60*60*12, sday, smonth, syear], dtype=float) 

218 

219 

220working_system_time_range = \ 

221 pyrocko.util.working_system_time_range() 

222 

223initial_time_range = [] 

224 

225try: 

226 initial_time_range.append( 

227 calendar.timegm((1950, 1, 1, 0, 0, 0))) 

228except Exception: 

229 initial_time_range.append(working_system_time_range[0]) 

230 

231try: 

232 initial_time_range.append( 

233 calendar.timegm((time.gmtime().tm_year + 11, 1, 1, 0, 0, 0))) 

234except Exception: 

235 initial_time_range.append(working_system_time_range[1]) 

236 

237 

238def is_working_time(t): 

239 return working_system_time_range[0] <= t and \ 

240 t <= working_system_time_range[1] 

241 

242 

243def fancy_time_ax_format(inc): 

244 l0_fmt_brief = '' 

245 l2_fmt = '' 

246 l2_trig = 0 

247 if inc < 0.000001: 

248 l0_fmt = '.%n' 

249 l0_center = False 

250 l1_fmt = '%H:%M:%S' 

251 l1_trig = 6 

252 l2_fmt = '%b %d, %Y' 

253 l2_trig = 3 

254 elif inc < 0.001: 

255 l0_fmt = '.%u' 

256 l0_center = False 

257 l1_fmt = '%H:%M:%S' 

258 l1_trig = 6 

259 l2_fmt = '%b %d, %Y' 

260 l2_trig = 3 

261 elif inc < 1: 

262 l0_fmt = '.%r' 

263 l0_center = False 

264 l1_fmt = '%H:%M:%S' 

265 l1_trig = 6 

266 l2_fmt = '%b %d, %Y' 

267 l2_trig = 3 

268 elif inc < 60: 

269 l0_fmt = '%H:%M:%S' 

270 l0_center = False 

271 l1_fmt = '%b %d, %Y' 

272 l1_trig = 3 

273 elif inc < 3600: 

274 l0_fmt = '%H:%M' 

275 l0_center = False 

276 l1_fmt = '%b %d, %Y' 

277 l1_trig = 3 

278 elif inc < sday: 

279 l0_fmt = '%H:%M' 

280 l0_center = False 

281 l1_fmt = '%b %d, %Y' 

282 l1_trig = 3 

283 elif inc < smonth: 

284 l0_fmt = '%a %d' 

285 l0_fmt_brief = '%d' 

286 l0_center = True 

287 l1_fmt = '%b, %Y' 

288 l1_trig = 2 

289 elif inc < syear: 

290 l0_fmt = '%b' 

291 l0_center = True 

292 l1_fmt = '%Y' 

293 l1_trig = 1 

294 else: 

295 l0_fmt = '%Y' 

296 l0_center = False 

297 l1_fmt = '' 

298 l1_trig = 0 

299 

300 return l0_fmt, l0_fmt_brief, l0_center, l1_fmt, l1_trig, l2_fmt, l2_trig 

301 

302 

303def day_start(timestamp): 

304 tt = time.gmtime(int(timestamp)) 

305 tts = tt[0:3] + (0, 0, 0) + tt[6:9] 

306 return calendar.timegm(tts) 

307 

308 

309def month_start(timestamp): 

310 tt = time.gmtime(int(timestamp)) 

311 tts = tt[0:2] + (1, 0, 0, 0) + tt[6:9] 

312 return calendar.timegm(tts) 

313 

314 

315def year_start(timestamp): 

316 tt = time.gmtime(int(timestamp)) 

317 tts = tt[0:1] + (1, 1, 0, 0, 0) + tt[6:9] 

318 return calendar.timegm(tts) 

319 

320 

321def time_nice_value(inc0): 

322 if inc0 < acceptable_tincs[0]: 

323 return pyrocko.plot.nice_value(inc0) 

324 elif inc0 > acceptable_tincs[-1]: 

325 return pyrocko.plot.nice_value(inc0/syear)*syear 

326 else: 

327 i = num.argmin(num.abs(acceptable_tincs-inc0)) 

328 return acceptable_tincs[i] 

329 

330 

331class TimeScaler(pyrocko.plot.AutoScaler): 

332 def __init__(self): 

333 pyrocko.plot.AutoScaler.__init__(self) 

334 self.mode = 'min-max' 

335 

336 def make_scale(self, data_range): 

337 assert self.mode in ('min-max', 'off'), \ 

338 'mode must be "min-max" or "off" for TimeScaler' 

339 

340 data_min = min(data_range) 

341 data_max = max(data_range) 

342 is_reverse = (data_range[0] > data_range[1]) 

343 

344 mi, ma = data_min, data_max 

345 nmi = mi 

346 if self.mode != 'off': 

347 nmi = mi - self.space*(ma-mi) 

348 

349 nma = ma 

350 if self.mode != 'off': 

351 nma = ma + self.space*(ma-mi) 

352 

353 mi, ma = nmi, nma 

354 

355 if mi == ma and self.mode != 'off': 

356 mi -= 1.0 

357 ma += 1.0 

358 

359 mi = max(working_system_time_range[0], mi) 

360 ma = min(working_system_time_range[1], ma) 

361 

362 # make nice tick increment 

363 if self.inc is not None: 

364 inc = self.inc 

365 else: 

366 if self.approx_ticks > 0.: 

367 inc = time_nice_value((ma-mi)/self.approx_ticks) 

368 else: 

369 inc = time_nice_value((ma-mi)*10.) 

370 

371 if inc == 0.0: 

372 inc = 1.0 

373 

374 if is_reverse: 

375 return ma, mi, -inc 

376 else: 

377 return mi, ma, inc 

378 

379 def make_ticks(self, data_range): 

380 mi, ma, inc = self.make_scale(data_range) 

381 

382 is_reverse = False 

383 if inc < 0: 

384 mi, ma, inc = ma, mi, -inc 

385 is_reverse = True 

386 

387 ticks = [] 

388 

389 if inc < sday: 

390 mi_day = day_start(max(mi, working_system_time_range[0]+sday*1.5)) 

391 if inc < 0.001: 

392 mi_day = hpfloat(mi_day) 

393 

394 base = mi_day+num.ceil((mi-mi_day)/inc)*inc 

395 if inc < 0.001: 

396 base = hpfloat(base) 

397 

398 base_day = mi_day 

399 i = 0 

400 while True: 

401 tick = base+i*inc 

402 if tick > ma: 

403 break 

404 

405 tick_day = day_start(tick) 

406 if tick_day > base_day: 

407 base_day = tick_day 

408 base = base_day 

409 i = 0 

410 else: 

411 ticks.append(tick) 

412 i += 1 

413 

414 elif inc < smonth: 

415 mi_day = day_start(max(mi, working_system_time_range[0]+sday*1.5)) 

416 dt_base = datetime.datetime(*time.gmtime(mi_day)[:6]) 

417 delta = datetime.timedelta(days=int(round(inc/sday))) 

418 if mi_day == mi: 

419 dt_base += delta 

420 i = 0 

421 while True: 

422 current = dt_base + i*delta 

423 tick = calendar.timegm(current.timetuple()) 

424 if tick > ma: 

425 break 

426 ticks.append(tick) 

427 i += 1 

428 

429 elif inc < syear: 

430 mi_month = month_start(max( 

431 mi, working_system_time_range[0]+smonth*1.5)) 

432 

433 y, m = time.gmtime(mi_month)[:2] 

434 while True: 

435 tick = calendar.timegm((y, m, 1, 0, 0, 0)) 

436 m += 1 

437 if m > 12: 

438 y, m = y+1, 1 

439 

440 if tick > ma: 

441 break 

442 

443 if tick >= mi: 

444 ticks.append(tick) 

445 

446 else: 

447 mi_year = year_start(max( 

448 mi, working_system_time_range[0]+syear*1.5)) 

449 

450 incy = int(round(inc/syear)) 

451 y = int(num.ceil(time.gmtime(mi_year)[0]/incy)*incy) 

452 

453 while True: 

454 tick = calendar.timegm((y, 1, 1, 0, 0, 0)) 

455 y += incy 

456 if tick > ma: 

457 break 

458 if tick >= mi: 

459 ticks.append(tick) 

460 

461 if is_reverse: 

462 ticks.reverse() 

463 

464 return ticks, inc 

465 

466 

467def need_l1_tick(tt, ms, l1_trig): 

468 return (0, 1, 1, 0, 0, 0)[l1_trig:] == tt[l1_trig:6] and ms == 0.0 

469 

470 

471def tick_to_labels(tick, inc): 

472 tt, ms = gmtime_x(tick) 

473 l0_fmt, l0_fmt_brief, l0_center, l1_fmt, l1_trig, l2_fmt, l2_trig = \ 

474 fancy_time_ax_format(inc) 

475 

476 l0 = mystrftime(l0_fmt, tt, ms) 

477 l0_brief = mystrftime(l0_fmt_brief, tt, ms) 

478 l1, l2 = None, None 

479 if need_l1_tick(tt, ms, l1_trig): 

480 l1 = mystrftime(l1_fmt, tt, ms) 

481 if need_l1_tick(tt, ms, l2_trig): 

482 l2 = mystrftime(l2_fmt, tt, ms) 

483 

484 return l0, l0_brief, l0_center, l1, l2 

485 

486 

487def l1_l2_tick(tick, inc): 

488 tt, ms = gmtime_x(tick) 

489 l0_fmt, l0_fmt_brief, l0_center, l1_fmt, l1_trig, l2_fmt, l2_trig = \ 

490 fancy_time_ax_format(inc) 

491 

492 l1 = mystrftime(l1_fmt, tt, ms) 

493 l2 = mystrftime(l2_fmt, tt, ms) 

494 return l1, l2 

495 

496 

497class TimeAx(TimeScaler): 

498 def __init__(self, *args): 

499 TimeScaler.__init__(self, *args) 

500 

501 def drawit(self, p, xprojection, yprojection): 

502 pen = qg.QPen(qg.QColor(*pyrocko.plot.tango_colors['aluminium5']), 1) 

503 p.setPen(pen) 

504 font = qg.QFont() 

505 font.setBold(True) 

506 p.setFont(font) 

507 fm = p.fontMetrics() 

508 ticklen = 10 

509 pad = 10 

510 tmin, tmax = xprojection.get_in_range() 

511 ticks, inc = self.make_ticks((tmin, tmax)) 

512 l1_hits = 0 

513 l2_hits = 0 

514 

515 vmin, vmax = yprojection(0), yprojection(ticklen) 

516 uumin, uumax = xprojection.get_out_range() 

517 first_tick_with_label = None 

518 for tick in ticks: 

519 umin = xprojection(tick) 

520 

521 umin_approx_next = xprojection(tick+inc) 

522 umax = xprojection(tick) 

523 

524 pinc_approx = umin_approx_next - umin 

525 

526 p.drawLine(qc.QPointF(umin, vmin), qc.QPointF(umax, vmax)) 

527 l0, l0_brief, l0_center, l1, l2 = tick_to_labels(tick, inc) 

528 

529 if tick == 0.0 and tmax - tmin < 3600*24: 

530 # hide year at epoch (we assume that synthetic data is shown) 

531 if l2: 

532 l2 = None 

533 elif l1: 

534 l1 = None 

535 

536 if l0_center: 

537 ushift = (umin_approx_next-umin)/2. 

538 else: 

539 ushift = 0. 

540 

541 for l0x in (l0, l0_brief, ''): 

542 label0 = l0x 

543 rect0 = fm.boundingRect(label0) 

544 if rect0.width() <= pinc_approx*0.9: 

545 break 

546 

547 if uumin+pad < umin-rect0.width()/2.+ushift and \ 

548 umin+rect0.width()/2.+ushift < uumax-pad: 

549 

550 if first_tick_with_label is None: 

551 first_tick_with_label = tick 

552 p.drawText(qc.QPointF( 

553 umin-rect0.width()/2.+ushift, 

554 vmin+rect0.height()+ticklen), label0) 

555 

556 if l1: 

557 label1 = l1 

558 rect1 = fm.boundingRect(label1) 

559 if uumin+pad < umin-rect1.width()/2. and \ 

560 umin+rect1.width()/2. < uumax-pad: 

561 

562 p.drawText(qc.QPointF( 

563 umin-rect1.width()/2., 

564 vmin+rect0.height()+rect1.height()+ticklen), 

565 label1) 

566 

567 l1_hits += 1 

568 

569 if l2: 

570 label2 = l2 

571 rect2 = fm.boundingRect(label2) 

572 if uumin+pad < umin-rect2.width()/2. and \ 

573 umin+rect2.width()/2. < uumax-pad: 

574 

575 p.drawText(qc.QPointF( 

576 umin-rect2.width()/2., 

577 vmin+rect0.height()+rect1.height()+rect2.height() + 

578 ticklen), label2) 

579 

580 l2_hits += 1 

581 

582 if first_tick_with_label is None: 

583 first_tick_with_label = tmin 

584 

585 l1, l2 = l1_l2_tick(first_tick_with_label, inc) 

586 

587 if -3600.*25 < first_tick_with_label <= 3600.*25 and \ 

588 tmax - tmin < 3600*24: 

589 

590 # hide year at epoch (we assume that synthetic data is shown) 

591 if l2: 

592 l2 = None 

593 elif l1: 

594 l1 = None 

595 

596 if l1_hits == 0 and l1: 

597 label1 = l1 

598 rect1 = fm.boundingRect(label1) 

599 p.drawText(qc.QPointF( 

600 uumin+pad, 

601 vmin+rect0.height()+rect1.height()+ticklen), 

602 label1) 

603 

604 l1_hits += 1 

605 

606 if l2_hits == 0 and l2: 

607 label2 = l2 

608 rect2 = fm.boundingRect(label2) 

609 p.drawText(qc.QPointF( 

610 uumin+pad, 

611 vmin+rect0.height()+rect1.height()+rect2.height()+ticklen), 

612 label2) 

613 

614 v = yprojection(0) 

615 p.drawLine(qc.QPointF(uumin, v), qc.QPointF(uumax, v)) 

616 

617 

618class Projection(object): 

619 def __init__(self): 

620 self.xr = 0., 1. 

621 self.ur = 0., 1. 

622 

623 def set_in_range(self, xmin, xmax): 

624 if xmax == xmin: 

625 xmax = xmin + 1. 

626 

627 self.xr = xmin, xmax 

628 

629 def get_in_range(self): 

630 return self.xr 

631 

632 def set_out_range(self, umin, umax): 

633 if umax == umin: 

634 umax = umin + 1. 

635 

636 self.ur = umin, umax 

637 

638 def get_out_range(self): 

639 return self.ur 

640 

641 def __call__(self, x): 

642 umin, umax = self.ur 

643 xmin, xmax = self.xr 

644 return umin + (x-xmin)*((umax-umin)/(xmax-xmin)) 

645 

646 def clipped(self, x): 

647 umin, umax = self.ur 

648 xmin, xmax = self.xr 

649 return min(umax, max(umin, umin + (x-xmin)*((umax-umin)/(xmax-xmin)))) 

650 

651 def rev(self, u): 

652 umin, umax = self.ur 

653 xmin, xmax = self.xr 

654 return xmin + (u-umin)*((xmax-xmin)/(umax-umin)) 

655 

656 def copy(self): 

657 return copy.copy(self) 

658 

659 

660def add_radiobuttongroup(menu, menudef, obj, target, default=None): 

661 group = qw.QActionGroup(menu) 

662 menuitems = [] 

663 for name, v in menudef: 

664 k = qw.QAction(name, menu) 

665 group.addAction(k) 

666 menu.addAction(k) 

667 k.setCheckable(True) 

668 group.triggered.connect(target) 

669 menuitems.append((k, v)) 

670 if default is not None: 

671 if name.lower().replace(' ', '_') == default: 

672 k.setChecked(True) 

673 

674 if default is None: 

675 menuitems[0][0].setChecked(True) 

676 return menuitems 

677 

678 

679def sort_actions(menu): 

680 actions = menu.actions() 

681 for action in actions: 

682 menu.removeAction(action) 

683 actions.sort(key=lambda x: newstr(x.text())) 

684 

685 help_action = [a for a in actions if a.text() == 'Snuffler Controls'] 

686 if help_action: 

687 actions.insert(0, actions.pop(actions.index(help_action[0]))) 

688 for action in actions: 

689 menu.addAction(action) 

690 

691 

692fkey_map = dict(zip( 

693 (qc.Qt.Key_F1, qc.Qt.Key_F2, qc.Qt.Key_F3, qc.Qt.Key_F4, qc.Qt.Key_F5, 

694 qc.Qt.Key_F6, qc.Qt.Key_F7, qc.Qt.Key_F8, qc.Qt.Key_F9, qc.Qt.Key_F10, 

695 qc.Qt.Key_F11, qc.Qt.Key_F12), 

696 range(12))) 

697 

698 

699class PileViewerMainException(Exception): 

700 pass 

701 

702 

703def MakePileViewerMainClass(base): 

704 

705 class PileViewerMain(base): 

706 

707 want_input = qc.pyqtSignal() 

708 about_to_close = qc.pyqtSignal() 

709 pile_has_changed_signal = qc.pyqtSignal() 

710 tracks_range_changed = qc.pyqtSignal(int, int, int) 

711 

712 begin_markers_add = qc.pyqtSignal(int, int) 

713 end_markers_add = qc.pyqtSignal() 

714 begin_markers_remove = qc.pyqtSignal(int, int) 

715 end_markers_remove = qc.pyqtSignal() 

716 

717 marker_selection_changed = qc.pyqtSignal(list) 

718 active_event_marker_changed = qc.pyqtSignal() 

719 

720 def __init__(self, pile, ntracks_shown_max, panel_parent, *args): 

721 if base == qgl.QGLWidget: 

722 from OpenGL import GL # noqa 

723 

724 base.__init__( 

725 self, qgl.QGLFormat(qgl.QGL.SampleBuffers), *args) 

726 else: 

727 base.__init__(self, *args) 

728 

729 self.pile = pile 

730 self.ax_height = 80 

731 self.panel_parent = panel_parent 

732 

733 self.click_tolerance = 5 

734 

735 self.ntracks_shown_max = ntracks_shown_max 

736 self.initial_ntracks_shown_max = ntracks_shown_max 

737 self.ntracks = 0 

738 self.show_all = True 

739 self.shown_tracks_range = None 

740 self.track_start = None 

741 self.track_trange = None 

742 

743 self.lowpass = None 

744 self.highpass = None 

745 self.gain = 1.0 

746 self.rotate = 0.0 

747 self.picking_down = None 

748 self.picking = None 

749 self.floating_marker = None 

750 self.markers = pyrocko.pile.Sorted([], 'tmin') 

751 self.markers_deltat_max = 0. 

752 self.n_selected_markers = 0 

753 self.all_marker_kinds = (0, 1, 2, 3, 4, 5) 

754 self.visible_marker_kinds = self.all_marker_kinds 

755 self.active_event_marker = None 

756 self.ignore_releases = 0 

757 self.message = None 

758 self.reloaded = False 

759 self.pile_has_changed = False 

760 self.config = pyrocko.config.config('snuffler') 

761 

762 self.tax = TimeAx() 

763 self.setBackgroundRole(qg.QPalette.Base) 

764 self.setAutoFillBackground(True) 

765 poli = qw.QSizePolicy( 

766 qw.QSizePolicy.Expanding, 

767 qw.QSizePolicy.Expanding) 

768 

769 self.setSizePolicy(poli) 

770 self.setMinimumSize(300, 200) 

771 self.setFocusPolicy(qc.Qt.ClickFocus) 

772 

773 self.menu = qw.QMenu(self) 

774 

775 mi = qw.QAction('Open waveform files...', self.menu) 

776 self.menu.addAction(mi) 

777 mi.triggered.connect(self.open_waveforms) 

778 

779 mi = qw.QAction('Open waveform directory...', self.menu) 

780 self.menu.addAction(mi) 

781 mi.triggered.connect(self.open_waveform_directory) 

782 

783 mi = qw.QAction('Open station files...', self.menu) 

784 self.menu.addAction(mi) 

785 mi.triggered.connect(self.open_stations) 

786 

787 mi = qw.QAction('Open StationXML files...', self.menu) 

788 self.menu.addAction(mi) 

789 mi.triggered.connect(self.open_stations_xml) 

790 

791 mi = qw.QAction('Save markers...', self.menu) 

792 self.menu.addAction(mi) 

793 mi.triggered.connect(self.write_markers) 

794 

795 mi = qw.QAction('Save selected markers...', self.menu) 

796 self.menu.addAction(mi) 

797 mi.triggered.connect(self.write_selected_markers) 

798 

799 mi = qw.QAction('Open marker file...', self.menu) 

800 self.menu.addAction(mi) 

801 mi.triggered.connect(self.read_markers) 

802 

803 mi = qw.QAction('Open event file...', self.menu) 

804 self.menu.addAction(mi) 

805 mi.triggered.connect(self.read_events) 

806 

807 self.menu.addSeparator() 

808 

809 menudef = [ 

810 ('Individual Scale', 

811 lambda tr: tr.nslc_id), 

812 ('Common Scale', 

813 lambda tr: None), 

814 ('Common Scale per Station', 

815 lambda tr: (tr.network, tr.station)), 

816 ('Common Scale per Station Location', 

817 lambda tr: (tr.network, tr.station, tr.location)), 

818 ('Common Scale per Component', 

819 lambda tr: (tr.channel)), 

820 ] 

821 

822 self.menuitems_scaling = add_radiobuttongroup( 

823 self.menu, menudef, self, self.scalingmode_change, 

824 default=self.config.trace_scale) 

825 

826 self.scaling_key = self.menuitems_scaling[0][1] 

827 self.scaling_hooks = {} 

828 self.scalingmode_change() 

829 

830 self.menu.addSeparator() 

831 

832 menudef = [ 

833 ('Scaling based on Minimum and Maximum', 'minmax'), 

834 ('Scaling based on Mean +- 2 x Std. Deviation', 2), 

835 ('Scaling based on Mean +- 4 x Std. Deviation', 4), 

836 ] 

837 

838 self.menuitems_scaling_base = add_radiobuttongroup( 

839 self.menu, menudef, self, self.scaling_base_change) 

840 

841 self.scaling_base = self.menuitems_scaling_base[0][1] 

842 

843 self.menu.addSeparator() 

844 

845 def sector_dist(sta): 

846 if sta.dist_m is None: 

847 return None, None 

848 else: 

849 return ( 

850 sector_int(round((sta.azimuth+15.)/30.)), 

851 m_float(sta.dist_m)) 

852 

853 menudef = [ 

854 ('Sort by Names', 

855 lambda tr: ()), 

856 ('Sort by Distance', 

857 lambda tr: self.station_attrib( 

858 tr, 

859 lambda sta: (m_float_or_none(sta.dist_m),), 

860 lambda tr: (None,))), 

861 ('Sort by Azimuth', 

862 lambda tr: self.station_attrib( 

863 tr, 

864 lambda sta: (deg_float_or_none(sta.azimuth),), 

865 lambda tr: (None,))), 

866 ('Sort by Distance in 12 Azimuthal Blocks', 

867 lambda tr: self.station_attrib( 

868 tr, 

869 sector_dist, 

870 lambda tr: (None, None))), 

871 ('Sort by Backazimuth', 

872 lambda tr: self.station_attrib( 

873 tr, 

874 lambda sta: (deg_float_or_none(sta.backazimuth),), 

875 lambda tr: (None,))), 

876 ] 

877 self.menuitems_ssorting = add_radiobuttongroup( 

878 self.menu, menudef, self, self.s_sortingmode_change) 

879 

880 self._ssort = lambda tr: () 

881 

882 self.menuitem_distances_3d = qw.QAction('3D distances', self.menu) 

883 self.menuitem_distances_3d.setCheckable(True) 

884 self.menuitem_distances_3d.setChecked(False) 

885 self.menuitem_distances_3d.toggled.connect( 

886 self.distances_3d_changed) 

887 

888 self.menu.addAction(self.menuitem_distances_3d) 

889 

890 self.menu.addSeparator() 

891 

892 menudef = [ 

893 ('Subsort by Network, Station, Location, Channel', 

894 (lambda tr: self.ssort(tr) + tr.nslc_id, # gathering 

895 lambda a: a, # sorting 

896 lambda tr: tr.location)), # coloring 

897 ('Subsort by Network, Station, Channel, Location', 

898 (lambda tr: self.ssort(tr) + ( 

899 tr.network, tr.station, tr.channel, tr.location), 

900 lambda a: a, 

901 lambda tr: tr.channel)), 

902 ('Subsort by Station, Network, Channel, Location', 

903 (lambda tr: self.ssort(tr) + ( 

904 tr.station, tr.network, tr.channel, tr.location), 

905 lambda a: a, 

906 lambda tr: tr.channel)), 

907 ('Subsort by Location, Network, Station, Channel', 

908 (lambda tr: self.ssort(tr) + ( 

909 tr.location, tr.network, tr.station, tr.channel), 

910 lambda a: a, 

911 lambda tr: tr.channel)), 

912 ('Subsort by Channel, Network, Station, Location', 

913 (lambda tr: self.ssort(tr) + ( 

914 tr.channel, tr.network, tr.station, tr.location), 

915 lambda a: a, 

916 lambda tr: (tr.network, tr.station, tr.location))), 

917 ('Subsort by Network, Station, Channel (Grouped by Location)', 

918 (lambda tr: self.ssort(tr) + ( 

919 tr.network, tr.station, tr.channel), 

920 lambda a: a, 

921 lambda tr: tr.location)), 

922 ('Subsort by Station, Network, Channel (Grouped by Location)', 

923 (lambda tr: self.ssort(tr) + ( 

924 tr.station, tr.network, tr.channel), 

925 lambda a: a, 

926 lambda tr: tr.location)), 

927 ] 

928 

929 self.menuitems_sorting = add_radiobuttongroup( 

930 self.menu, menudef, self, self.sortingmode_change) 

931 

932 self.menu.addSeparator() 

933 

934 self.menuitem_antialias = qw.QAction('Antialiasing', self.menu) 

935 self.menuitem_antialias.setCheckable(True) 

936 self.menu.addAction(self.menuitem_antialias) 

937 

938 self.menuitem_liberal_fetch = qw.QAction( 

939 'Liberal Fetch Optimization', self.menu) 

940 self.menuitem_liberal_fetch.setCheckable(True) 

941 self.menu.addAction(self.menuitem_liberal_fetch) 

942 

943 self.menuitem_cliptraces = qw.QAction('Clip Traces', self.menu) 

944 self.menuitem_cliptraces.setCheckable(True) 

945 self.menuitem_cliptraces.setChecked(self.config.clip_traces) 

946 self.menu.addAction(self.menuitem_cliptraces) 

947 

948 self.menuitem_showboxes = qw.QAction('Show Boxes', self.menu) 

949 self.menuitem_showboxes.setCheckable(True) 

950 self.menuitem_showboxes.setChecked( 

951 self.config.show_boxes) 

952 self.menu.addAction(self.menuitem_showboxes) 

953 

954 self.menuitem_colortraces = qw.QAction('Color Traces', self.menu) 

955 self.menuitem_colortraces.setCheckable(True) 

956 self.menuitem_colortraces.setChecked(False) 

957 self.menu.addAction(self.menuitem_colortraces) 

958 

959 self.menuitem_showscalerange = qw.QAction( 

960 'Show Scale Ranges', self.menu) 

961 self.menuitem_showscalerange.setCheckable(True) 

962 self.menuitem_showscalerange.setChecked( 

963 self.config.show_scale_ranges) 

964 self.menu.addAction(self.menuitem_showscalerange) 

965 

966 self.menuitem_showscaleaxis = qw.QAction( 

967 'Show Scale Axes', self.menu) 

968 self.menuitem_showscaleaxis.setCheckable(True) 

969 self.menuitem_showscaleaxis.setChecked( 

970 self.config.show_scale_axes) 

971 self.menu.addAction(self.menuitem_showscaleaxis) 

972 

973 self.menuitem_showzeroline = qw.QAction( 

974 'Show Zero Lines', self.menu) 

975 self.menuitem_showzeroline.setCheckable(True) 

976 self.menu.addAction(self.menuitem_showzeroline) 

977 

978 self.menuitem_fixscalerange = qw.QAction( 

979 'Fix Scale Ranges', self.menu) 

980 self.menuitem_fixscalerange.setCheckable(True) 

981 self.menu.addAction(self.menuitem_fixscalerange) 

982 

983 self.menuitem_allowdownsampling = qw.QAction( 

984 'Allow Downsampling', self.menu) 

985 self.menuitem_allowdownsampling.setCheckable(True) 

986 self.menuitem_allowdownsampling.setChecked(True) 

987 self.menu.addAction(self.menuitem_allowdownsampling) 

988 

989 self.menuitem_degap = qw.QAction('Allow Degapping', self.menu) 

990 self.menuitem_degap.setCheckable(True) 

991 self.menuitem_degap.setChecked(True) 

992 self.menu.addAction(self.menuitem_degap) 

993 

994 self.menuitem_demean = qw.QAction('Demean', self.menu) 

995 self.menuitem_demean.setCheckable(True) 

996 self.menuitem_demean.setChecked(self.config.demean) 

997 self.menu.addAction(self.menuitem_demean) 

998 

999 self.menuitem_fft_filtering = qw.QAction( 

1000 'FFT Filtering', self.menu) 

1001 self.menuitem_fft_filtering.setCheckable(True) 

1002 self.menuitem_fft_filtering.setChecked(False) 

1003 self.menu.addAction(self.menuitem_fft_filtering) 

1004 

1005 self.menuitem_lphp = qw.QAction( 

1006 'Bandpass is Lowpass + Highpass', self.menu) 

1007 self.menuitem_lphp.setCheckable(True) 

1008 self.menuitem_lphp.setChecked(True) 

1009 self.menu.addAction(self.menuitem_lphp) 

1010 

1011 self.menuitem_watch = qw.QAction('Watch Files', self.menu) 

1012 self.menuitem_watch.setCheckable(True) 

1013 self.menuitem_watch.setChecked(False) 

1014 self.menu.addAction(self.menuitem_watch) 

1015 

1016 self.visible_length_menu = qw.QMenu('Visible Length', self.menu) 

1017 

1018 menudef = [(x.key, x.value) for x in 

1019 self.config.visible_length_setting] 

1020 

1021 self.menuitems_visible_length = add_radiobuttongroup( 

1022 self.visible_length_menu, menudef, self, 

1023 self.visible_length_change) 

1024 

1025 self.visible_length = menudef[0][1] 

1026 self.menu.addMenu(self.visible_length_menu) 

1027 self.menu.addSeparator() 

1028 

1029 self.snufflings_menu = qw.QMenu('Run Snuffling', self.menu) 

1030 self.menu.addMenu(self.snufflings_menu) 

1031 

1032 self.toggle_panel_menu = qw.QMenu('Panels', self.menu) 

1033 self.menu.addMenu(self.toggle_panel_menu) 

1034 

1035 self.menuitem_reload = qw.QAction('Reload Snufflings', self.menu) 

1036 self.menu.addAction(self.menuitem_reload) 

1037 self.menuitem_reload.triggered.connect( 

1038 self.setup_snufflings) 

1039 

1040 self.menu.addSeparator() 

1041 

1042 # Disable ShadowPileTest 

1043 if False: 

1044 self.menuitem_test = qw.QAction('Test', self.menu) 

1045 self.menuitem_test.setCheckable(True) 

1046 self.menuitem_test.setChecked(False) 

1047 self.menu.addAction(self.menuitem_test) 

1048 self.menuitem_test.triggered.connect( 

1049 self.toggletest) 

1050 

1051 self.menuitem_print = qw.QAction('Print', self.menu) 

1052 self.menu.addAction(self.menuitem_print) 

1053 self.menuitem_print.triggered.connect( 

1054 self.printit) 

1055 

1056 self.menuitem_svg = qw.QAction('Save as SVG|PNG', self.menu) 

1057 self.menu.addAction(self.menuitem_svg) 

1058 self.menuitem_svg.triggered.connect( 

1059 self.savesvg) 

1060 

1061 self.snuffling_help_menu = qw.QMenu('Help', self.menu) 

1062 self.menu.addMenu(self.snuffling_help_menu) 

1063 self.menuitem_help = qw.QAction( 

1064 'Snuffler Controls', self.snuffling_help_menu) 

1065 self.snuffling_help_menu.addAction(self.menuitem_help) 

1066 self.menuitem_help.triggered.connect(self.help) 

1067 

1068 self.snuffling_help_menu.addSeparator() 

1069 

1070 self.menuitem_about = qw.QAction('About', self.menu) 

1071 self.menu.addAction(self.menuitem_about) 

1072 self.menuitem_about.triggered.connect(self.about) 

1073 

1074 self.menuitem_close = qw.QAction('Close', self.menu) 

1075 self.menu.addAction(self.menuitem_close) 

1076 self.menuitem_close.triggered.connect(self.myclose) 

1077 

1078 self.menu.addSeparator() 

1079 

1080 self.menu.triggered.connect(self.update) 

1081 

1082 self.time_projection = Projection() 

1083 self.set_time_range(self.pile.get_tmin(), self.pile.get_tmax()) 

1084 self.time_projection.set_out_range(0., self.width()) 

1085 

1086 self.gather = None 

1087 

1088 self.trace_filter = None 

1089 self.quick_filter = None 

1090 self.quick_filter_patterns = None, None 

1091 self.blacklist = [] 

1092 

1093 self.track_to_screen = Projection() 

1094 self.track_to_nslc_ids = {} 

1095 

1096 self.old_vec = None 

1097 self.old_processed_traces = None 

1098 

1099 self.timer = qc.QTimer(self) 

1100 self.timer.timeout.connect(self.periodical) 

1101 self.timer.setInterval(1000) 

1102 self.timer.start() 

1103 self.pile.add_listener(self) 

1104 self.trace_styles = {} 

1105 self.determine_box_styles() 

1106 self.setMouseTracking(True) 

1107 

1108 user_home_dir = os.path.expanduser('~') 

1109 self.snuffling_modules = {} 

1110 self.snuffling_paths = [os.path.join(user_home_dir, '.snufflings')] 

1111 self.default_snufflings = None 

1112 self.snufflings = [] 

1113 

1114 self.stations = {} 

1115 

1116 self.timer_draw = Timer() 

1117 self.timer_cutout = Timer() 

1118 self.time_spent_painting = 0.0 

1119 self.time_last_painted = time.time() 

1120 

1121 self.interactive_range_change_time = 0.0 

1122 self.interactive_range_change_delay_time = 10.0 

1123 self.follow_timer = None 

1124 

1125 self.sortingmode_change_time = 0.0 

1126 self.sortingmode_change_delay_time = None 

1127 

1128 self.old_data_ranges = {} 

1129 

1130 self.error_messages = {} 

1131 self.return_tag = None 

1132 self.wheel_pos = 60 

1133 

1134 self.setAcceptDrops(True) 

1135 self._paths_to_load = [] 

1136 

1137 self.tf_cache = {} 

1138 

1139 self.automatic_updates = True 

1140 

1141 self.closing = False 

1142 self.paint_timer = qc.QTimer(self) 

1143 self.paint_timer.timeout.connect(self.reset_updates) 

1144 self.paint_timer.setInterval(20) 

1145 self.paint_timer.start() 

1146 

1147 @qc.pyqtSlot() 

1148 def reset_updates(self): 

1149 if not self.updatesEnabled(): 

1150 self.setUpdatesEnabled(True) 

1151 

1152 def fail(self, reason): 

1153 box = qw.QMessageBox(self) 

1154 box.setText(reason) 

1155 box.exec_() 

1156 

1157 def set_trace_filter(self, filter_func): 

1158 self.trace_filter = filter_func 

1159 self.sortingmode_change() 

1160 

1161 def update_trace_filter(self): 

1162 if self.blacklist: 

1163 

1164 def blacklist_func(tr): 

1165 return not pyrocko.util.match_nslc( 

1166 self.blacklist, tr.nslc_id) 

1167 

1168 else: 

1169 blacklist_func = None 

1170 

1171 if self.quick_filter is None and blacklist_func is None: 

1172 self.set_trace_filter(None) 

1173 elif self.quick_filter is None: 

1174 self.set_trace_filter(blacklist_func) 

1175 elif blacklist_func is None: 

1176 self.set_trace_filter(self.quick_filter) 

1177 else: 

1178 self.set_trace_filter( 

1179 lambda tr: blacklist_func(tr) and self.quick_filter(tr)) 

1180 

1181 def set_quick_filter(self, filter_func): 

1182 self.quick_filter = filter_func 

1183 self.update_trace_filter() 

1184 

1185 def set_quick_filter_patterns(self, patterns, inputline=None): 

1186 if patterns is not None: 

1187 self.set_quick_filter( 

1188 lambda tr: pyrocko.util.match_nslc(patterns, tr.nslc_id)) 

1189 else: 

1190 self.set_quick_filter(None) 

1191 

1192 self.quick_filter_patterns = patterns, inputline 

1193 

1194 def get_quick_filter_patterns(self): 

1195 return self.quick_filter_patterns 

1196 

1197 def add_blacklist_pattern(self, pattern): 

1198 if pattern == 'empty': 

1199 keys = set(self.pile.nslc_ids) 

1200 trs = self.pile.all( 

1201 tmin=self.tmin, 

1202 tmax=self.tmax, 

1203 load_data=False, 

1204 degap=False) 

1205 

1206 for tr in trs: 

1207 if tr.nslc_id in keys: 

1208 keys.remove(tr.nslc_id) 

1209 

1210 for key in keys: 

1211 xpattern = '.'.join(key) 

1212 if xpattern not in self.blacklist: 

1213 self.blacklist.append(xpattern) 

1214 

1215 else: 

1216 if pattern in self.blacklist: 

1217 self.blacklist.remove(pattern) 

1218 

1219 self.blacklist.append(pattern) 

1220 

1221 logger.info('Blacklist is [ %s ]' % ', '.join(self.blacklist)) 

1222 self.update_trace_filter() 

1223 

1224 def remove_blacklist_pattern(self, pattern): 

1225 if pattern in self.blacklist: 

1226 self.blacklist.remove(pattern) 

1227 else: 

1228 raise PileViewerMainException( 

1229 'Pattern not found in blacklist.') 

1230 

1231 logger.info('Blacklist is [ %s ]' % ', '.join(self.blacklist)) 

1232 self.update_trace_filter() 

1233 

1234 def clear_blacklist(self): 

1235 self.blacklist = [] 

1236 self.update_trace_filter() 

1237 

1238 def ssort(self, tr): 

1239 return self._ssort(tr) 

1240 

1241 def station_key(self, x): 

1242 return x.network, x.station 

1243 

1244 def station_keys(self, x): 

1245 return [ 

1246 (x.network, x.station, x.location), 

1247 (x.network, x.station)] 

1248 

1249 def station_attrib(self, tr, getter, default_getter): 

1250 for sk in self.station_keys(tr): 

1251 if sk in self.stations: 

1252 station = self.stations[sk] 

1253 return getter(station) 

1254 

1255 return default_getter(tr) 

1256 

1257 def get_station(self, sk): 

1258 return self.stations[sk] 

1259 

1260 def has_station(self, station): 

1261 for sk in self.station_keys(station): 

1262 if sk in self.stations: 

1263 return True 

1264 

1265 return False 

1266 

1267 def station_latlon(self, tr, default_getter=lambda tr: (0., 0.)): 

1268 return self.station_attrib( 

1269 tr, lambda sta: (sta.lat, sta.lon), default_getter) 

1270 

1271 def set_stations(self, stations): 

1272 self.stations = {} 

1273 self.add_stations(stations) 

1274 

1275 def add_stations(self, stations): 

1276 for station in stations: 

1277 for sk in self.station_keys(station): 

1278 self.stations[sk] = station 

1279 

1280 ev = self.get_active_event() 

1281 if ev: 

1282 self.set_origin(ev) 

1283 

1284 def add_event(self, event): 

1285 marker = EventMarker(event) 

1286 self.add_marker(marker) 

1287 

1288 def add_events(self, events): 

1289 markers = [EventMarker(e) for e in events] 

1290 self.add_markers(markers) 

1291 

1292 def set_event_marker_as_origin(self, ignore=None): 

1293 selected = self.selected_markers() 

1294 if not selected: 

1295 self.fail('An event marker must be selected.') 

1296 return 

1297 

1298 m = selected[0] 

1299 if not isinstance(m, EventMarker): 

1300 self.fail('Selected marker is not an event.') 

1301 return 

1302 

1303 self.set_active_event_marker(m) 

1304 

1305 def deactivate_event_marker(self): 

1306 if self.active_event_marker: 

1307 self.active_event_marker.active = False 

1308 

1309 self.active_event_marker_changed.emit() 

1310 self.active_event_marker = None 

1311 

1312 def set_active_event_marker(self, event_marker): 

1313 if self.active_event_marker: 

1314 self.active_event_marker.active = False 

1315 

1316 self.active_event_marker = event_marker 

1317 event_marker.active = True 

1318 event = event_marker.get_event() 

1319 self.set_origin(event) 

1320 self.active_event_marker_changed.emit() 

1321 

1322 def set_active_event(self, event): 

1323 for marker in self.markers: 

1324 if isinstance(marker, EventMarker): 

1325 if marker.get_event() is event: 

1326 self.set_active_event_marker(marker) 

1327 

1328 def get_active_event_marker(self): 

1329 return self.active_event_marker 

1330 

1331 def get_active_event(self): 

1332 m = self.get_active_event_marker() 

1333 if m is not None: 

1334 return m.get_event() 

1335 else: 

1336 return None 

1337 

1338 def get_active_markers(self): 

1339 emarker = self.get_active_event_marker() 

1340 if emarker is None: 

1341 return None, [] 

1342 

1343 else: 

1344 ev = emarker.get_event() 

1345 pmarkers = [ 

1346 m for m in self.markers 

1347 if isinstance(m, PhaseMarker) and m.get_event() is ev] 

1348 

1349 return emarker, pmarkers 

1350 

1351 def set_origin(self, location): 

1352 for station in self.stations.values(): 

1353 station.set_event_relative_data( 

1354 location, 

1355 distance_3d=self.menuitem_distances_3d.isChecked()) 

1356 

1357 self.sortingmode_change() 

1358 

1359 def distances_3d_changed(self, ignore): 

1360 self.set_event_marker_as_origin(ignore) 

1361 

1362 def toggletest(self, checked): 

1363 if checked: 

1364 sp = Integrator() 

1365 

1366 self.add_shadow_pile(sp) 

1367 else: 

1368 self.remove_shadow_piles() 

1369 

1370 def add_shadow_pile(self, shadow_pile): 

1371 shadow_pile.set_basepile(self.pile) 

1372 shadow_pile.add_listener(self) 

1373 self.pile = shadow_pile 

1374 

1375 def remove_shadow_piles(self): 

1376 self.pile = self.pile.get_basepile() 

1377 

1378 def iter_snuffling_modules(self): 

1379 pjoin = os.path.join 

1380 for path in self.snuffling_paths: 

1381 

1382 if not os.path.isdir(path): 

1383 os.mkdir(path) 

1384 

1385 for entry in os.listdir(path): 

1386 directory = path 

1387 fn = entry 

1388 d = pjoin(path, entry) 

1389 if os.path.isdir(d): 

1390 directory = d 

1391 if os.path.isfile( 

1392 os.path.join(directory, 'snuffling.py')): 

1393 fn = 'snuffling.py' 

1394 

1395 if not fn.endswith('.py'): 

1396 continue 

1397 

1398 name = fn[:-3] 

1399 

1400 if (directory, name) not in self.snuffling_modules: 

1401 self.snuffling_modules[directory, name] = \ 

1402 pyrocko.gui.snuffling.SnufflingModule( 

1403 directory, name, self) 

1404 

1405 yield self.snuffling_modules[directory, name] 

1406 

1407 def setup_snufflings(self): 

1408 # user snufflings 

1409 for mod in self.iter_snuffling_modules(): 

1410 try: 

1411 mod.load_if_needed() 

1412 except pyrocko.gui.snuffling.BrokenSnufflingModule as e: 

1413 logger.warning('Snuffling module "%s" is broken' % e) 

1414 

1415 # load the default snufflings on first run 

1416 if self.default_snufflings is None: 

1417 self.default_snufflings = pyrocko.gui\ 

1418 .snufflings.__snufflings__() 

1419 for snuffling in self.default_snufflings: 

1420 self.add_snuffling(snuffling) 

1421 

1422 def set_panel_parent(self, panel_parent): 

1423 self.panel_parent = panel_parent 

1424 

1425 def get_panel_parent(self): 

1426 return self.panel_parent 

1427 

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

1429 logger.debug('Adding snuffling %s' % snuffling.get_name()) 

1430 snuffling.init_gui( 

1431 self, self.get_panel_parent(), self, reloaded=reloaded) 

1432 self.snufflings.append(snuffling) 

1433 self.update() 

1434 

1435 def remove_snuffling(self, snuffling): 

1436 snuffling.delete_gui() 

1437 self.update() 

1438 self.snufflings.remove(snuffling) 

1439 snuffling.pre_destroy() 

1440 

1441 def add_snuffling_menuitem(self, item): 

1442 self.snufflings_menu.addAction(item) 

1443 item.setParent(self.snufflings_menu) 

1444 sort_actions(self.snufflings_menu) 

1445 

1446 def remove_snuffling_menuitem(self, item): 

1447 self.snufflings_menu.removeAction(item) 

1448 

1449 def add_snuffling_help_menuitem(self, item): 

1450 self.snuffling_help_menu.addAction(item) 

1451 item.setParent(self.snuffling_help_menu) 

1452 sort_actions(self.snuffling_help_menu) 

1453 

1454 def remove_snuffling_help_menuitem(self, item): 

1455 self.snuffling_help_menu.removeAction(item) 

1456 

1457 def add_panel_toggler(self, item): 

1458 self.toggle_panel_menu.addAction(item) 

1459 item.setParent(self.toggle_panel_menu) 

1460 sort_actions(self.toggle_panel_menu) 

1461 

1462 def remove_panel_toggler(self, item): 

1463 self.toggle_panel_menu.removeAction(item) 

1464 

1465 def load(self, paths, regex=None, format='from_extension', 

1466 cache_dir=None, force_cache=False): 

1467 

1468 if cache_dir is None: 

1469 cache_dir = pyrocko.config.config().cache_dir 

1470 if isinstance(paths, str): 

1471 paths = [paths] 

1472 

1473 fns = pyrocko.util.select_files( 

1474 paths, selector=None, regex=regex, show_progress=False) 

1475 

1476 if not fns: 

1477 return 

1478 

1479 cache = pyrocko.pile.get_cache(cache_dir) 

1480 

1481 t = [time.time()] 

1482 

1483 def update_bar(label, value): 

1484 pbs = self.parent().get_progressbars() 

1485 if label.lower() == 'looking at files': 

1486 label = 'Looking at %i files' % len(fns) 

1487 else: 

1488 label = 'Scanning %i files' % len(fns) 

1489 

1490 return pbs.set_status(label, value) 

1491 

1492 def update_progress(label, i, n): 

1493 abort = False 

1494 

1495 qw.qApp.processEvents() 

1496 if n != 0: 

1497 perc = i*100/n 

1498 else: 

1499 perc = 100 

1500 abort |= update_bar(label, perc) 

1501 abort |= self.window().is_closing() 

1502 

1503 tnow = time.time() 

1504 if t[0] + 1. + self.time_spent_painting * 10. < tnow: 

1505 self.update() 

1506 t[0] = tnow 

1507 

1508 return abort 

1509 

1510 self.automatic_updates = False 

1511 

1512 self.pile.load_files( 

1513 sorted(fns), 

1514 filename_attributes=regex, 

1515 cache=cache, 

1516 fileformat=format, 

1517 show_progress=False, 

1518 update_progress=update_progress) 

1519 

1520 self.automatic_updates = True 

1521 self.update() 

1522 

1523 def load_queued(self): 

1524 if not self._paths_to_load: 

1525 return 

1526 paths = self._paths_to_load 

1527 self._paths_to_load = [] 

1528 self.load(paths) 

1529 

1530 def load_soon(self, paths): 

1531 self._paths_to_load.extend(paths) 

1532 qc.QTimer.singleShot(200, self.load_queued) 

1533 

1534 def open_waveforms(self): 

1535 caption = 'Select one or more files to open' 

1536 

1537 fns, _ = fnpatch(qw.QFileDialog.getOpenFileNames( 

1538 self, caption, options=qfiledialog_options)) 

1539 

1540 if fns: 

1541 self.load(list(str(fn) for fn in fns)) 

1542 

1543 def open_waveform_directory(self): 

1544 caption = 'Select directory to scan for waveform files' 

1545 

1546 dn = qw.QFileDialog.getExistingDirectory( 

1547 self, caption, options=qfiledialog_options) 

1548 

1549 if dn: 

1550 self.load([str(dn)]) 

1551 

1552 def open_stations(self, fns=None): 

1553 caption = 'Select one or more files to open' 

1554 

1555 if not fns: 

1556 fns, _ = fnpatch(qw.QFileDialog.getOpenFileNames( 

1557 self, caption, options=qfiledialog_options)) 

1558 

1559 stations = [pyrocko.model.load_stations(str(x)) for x in fns] 

1560 for stat in stations: 

1561 self.add_stations(stat) 

1562 

1563 def open_stations_xml(self, fns=None): 

1564 from pyrocko.io import stationxml 

1565 

1566 caption = 'Select one or more StationXML files to open' 

1567 

1568 if not fns: 

1569 fns, _ = fnpatch(qw.QFileDialog.getOpenFileNames( 

1570 self, caption, options=qfiledialog_options, 

1571 filter='StationXML *.xml (*.xml *.XML);;All files (*)')) 

1572 

1573 stations = [ 

1574 stationxml.load_xml(filename=str(x)).get_pyrocko_stations() 

1575 for x in fns] 

1576 

1577 for stat in stations: 

1578 self.add_stations(stat) 

1579 

1580 def add_traces(self, traces): 

1581 if traces: 

1582 mtf = pyrocko.pile.MemTracesFile(None, traces) 

1583 self.pile.add_file(mtf) 

1584 ticket = (self.pile, mtf) 

1585 return ticket 

1586 else: 

1587 return (None, None) 

1588 

1589 def release_data(self, tickets): 

1590 for ticket in tickets: 

1591 pile, mtf = ticket 

1592 if pile is not None: 

1593 pile.remove_file(mtf) 

1594 

1595 def periodical(self): 

1596 if self.menuitem_watch.isChecked(): 

1597 if self.pile.reload_modified(): 

1598 self.update() 

1599 

1600 def get_pile(self): 

1601 return self.pile 

1602 

1603 def pile_changed(self, what): 

1604 self.pile_has_changed = True 

1605 self.pile_has_changed_signal.emit() 

1606 if self.automatic_updates: 

1607 self.update() 

1608 

1609 def set_gathering(self, gather=None, order=None, color=None): 

1610 

1611 if gather is None: 

1612 def gather(tr): 

1613 return tr.nslc_id 

1614 

1615 if order is None: 

1616 def order(a): 

1617 return a 

1618 

1619 if color is None: 

1620 def color(tr): 

1621 return tr.location 

1622 

1623 self.gather = gather 

1624 keys = self.pile.gather_keys(gather, self.trace_filter) 

1625 self.color_gather = color 

1626 self.color_keys = self.pile.gather_keys(color) 

1627 previous_ntracks = self.ntracks 

1628 self.set_ntracks(len(keys)) 

1629 

1630 if self.shown_tracks_range is None or \ 

1631 previous_ntracks == 0 or \ 

1632 self.show_all: 

1633 

1634 low, high = 0, min(self.ntracks_shown_max, self.ntracks) 

1635 key_at_top = None 

1636 n = high-low 

1637 

1638 else: 

1639 low, high = self.shown_tracks_range 

1640 key_at_top = self.track_keys[low] 

1641 n = high-low 

1642 

1643 self.track_keys = sorted(keys, key=order) 

1644 

1645 if key_at_top is not None: 

1646 try: 

1647 ind = self.track_keys.index(key_at_top) 

1648 low = ind 

1649 high = low+n 

1650 except Exception: 

1651 pass 

1652 

1653 self.set_tracks_range((low, high)) 

1654 

1655 self.key_to_row = dict( 

1656 [(key, i) for (i, key) in enumerate(self.track_keys)]) 

1657 

1658 def inrange(x, r): 

1659 return r[0] <= x and x < r[1] 

1660 

1661 def trace_selector(trace): 

1662 gt = self.gather(trace) 

1663 return ( 

1664 gt in self.key_to_row and 

1665 inrange(self.key_to_row[gt], self.shown_tracks_range)) 

1666 

1667 if self.trace_filter is not None: 

1668 self.trace_selector = lambda x: \ 

1669 self.trace_filter(x) and trace_selector(x) 

1670 else: 

1671 self.trace_selector = trace_selector 

1672 

1673 if self.tmin == working_system_time_range[0] and \ 

1674 self.tmax == working_system_time_range[1] or \ 

1675 self.show_all: 

1676 

1677 tmin, tmax = self.pile.get_tmin(), self.pile.get_tmax() 

1678 if tmin is not None and tmax is not None: 

1679 tlen = (tmax - tmin) 

1680 tpad = tlen * 5./self.width() 

1681 self.set_time_range(tmin-tpad, tmax+tpad) 

1682 

1683 def set_time_range(self, tmin, tmax): 

1684 if tmin is None: 

1685 tmin = initial_time_range[0] 

1686 

1687 if tmax is None: 

1688 tmax = initial_time_range[1] 

1689 

1690 if tmin > tmax: 

1691 tmin, tmax = tmax, tmin 

1692 

1693 if tmin == tmax: 

1694 tmin -= 1. 

1695 tmax += 1. 

1696 

1697 tmin = max(working_system_time_range[0], tmin) 

1698 tmax = min(working_system_time_range[1], tmax) 

1699 

1700 min_deltat = self.content_deltat_range()[0] 

1701 if (tmax - tmin < min_deltat): 

1702 m = (tmin + tmax) / 2. 

1703 tmin = m - min_deltat/2. 

1704 tmax = m + min_deltat/2. 

1705 

1706 self.time_projection.set_in_range(tmin, tmax) 

1707 self.tmin, self.tmax = tmin, tmax 

1708 

1709 def get_time_range(self): 

1710 return self.tmin, self.tmax 

1711 

1712 def ypart(self, y): 

1713 if y < self.ax_height: 

1714 return -1 

1715 elif y > self.height()-self.ax_height: 

1716 return 1 

1717 else: 

1718 return 0 

1719 

1720 def time_fractional_digits(self): 

1721 min_deltat = self.content_deltat_range()[0] 

1722 return min(9, max(1, int(-math.floor(math.log10(min_deltat)))+2)) 

1723 

1724 def write_markers(self, fn=None): 

1725 caption = "Choose a file name to write markers" 

1726 if not fn: 

1727 fn, _ = fnpatch(qw.QFileDialog.getSaveFileName( 

1728 self, caption, options=qfiledialog_options)) 

1729 if fn: 

1730 Marker.save_markers( 

1731 self.markers, fn, 

1732 fdigits=self.time_fractional_digits()) 

1733 

1734 def write_selected_markers(self, fn=None): 

1735 caption = "Choose a file name to write selected markers" 

1736 if not fn: 

1737 fn, _ = fnpatch(qw.QFileDialog.getSaveFileName( 

1738 self, caption, options=qfiledialog_options)) 

1739 if fn: 

1740 Marker.save_markers( 

1741 self.iter_selected_markers(), fn, 

1742 fdigits=self.time_fractional_digits()) 

1743 

1744 def read_events(self, fn=None): 

1745 ''' 

1746 Open QFileDialog to open, read and add 

1747 :py:class:`pyrocko.model.Event` instances and their marker 

1748 representation to the pile viewer. 

1749 ''' 

1750 caption = "Selet one or more files to open" 

1751 if not fn: 

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

1753 self, caption, options=qfiledialog_options)) 

1754 if fn: 

1755 self.add_events(pyrocko.model.load_events(fn)) 

1756 

1757 self.associate_phases_to_events() 

1758 

1759 def read_markers(self, fn=None): 

1760 ''' 

1761 Open QFileDialog to open, read and add markers to the pile viewer. 

1762 ''' 

1763 caption = "Selet one or more files to open" 

1764 if not fn: 

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

1766 self, caption, options=qfiledialog_options)) 

1767 if fn: 

1768 self.add_markers(Marker.load_markers(fn)) 

1769 

1770 self.associate_phases_to_events() 

1771 

1772 def associate_phases_to_events(self): 

1773 associate_phases_to_events(self.markers) 

1774 

1775 def add_marker(self, marker): 

1776 # need index to inform QAbstactTableModel about upcoming change, 

1777 # but have to restore current state in order to not cause problems 

1778 self.markers.insert(marker) 

1779 i = self.markers.remove(marker) 

1780 

1781 self.begin_markers_add.emit(i, i) 

1782 self.markers.insert(marker) 

1783 self.end_markers_add.emit() 

1784 self.markers_deltat_max = max( 

1785 self.markers_deltat_max, marker.tmax - marker.tmin) 

1786 

1787 def add_markers(self, markers): 

1788 if not self.markers: 

1789 self.begin_markers_add.emit(0, len(markers) - 1) 

1790 self.markers.insert_many(markers) 

1791 self.end_markers_add.emit() 

1792 self.update_markers_deltat_max() 

1793 else: 

1794 for marker in markers: 

1795 self.add_marker(marker) 

1796 

1797 def update_markers_deltat_max(self): 

1798 if self.markers: 

1799 self.markers_deltat_max = max( 

1800 marker.tmax - marker.tmin for marker in self.markers) 

1801 

1802 def remove_marker(self, marker): 

1803 ''' 

1804 Remove a ``marker`` from the :py:class:`PileViewer`. 

1805 

1806 :param marker: :py:class:`Marker` (or subclass) instance 

1807 ''' 

1808 

1809 if marker is self.active_event_marker: 

1810 self.deactivate_event_marker() 

1811 

1812 try: 

1813 i = self.markers.index(marker) 

1814 self.begin_markers_remove.emit(i, i) 

1815 self.markers.remove_at(i) 

1816 self.end_markers_remove.emit() 

1817 except ValueError: 

1818 pass 

1819 

1820 def remove_markers(self, markers): 

1821 ''' 

1822 Remove a list of ``markers`` from the :py:class:`PileViewer`. 

1823 

1824 :param markers: list of :py:class:`Marker` (or subclass) 

1825 instances 

1826 ''' 

1827 

1828 if markers is self.markers: 

1829 markers = list(markers) 

1830 

1831 for marker in markers: 

1832 self.remove_marker(marker) 

1833 

1834 self.update_markers_deltat_max() 

1835 

1836 def remove_selected_markers(self): 

1837 def delete_segment(istart, iend): 

1838 self.begin_markers_remove.emit(istart, iend-1) 

1839 for _ in range(iend - istart): 

1840 self.markers.remove_at(istart) 

1841 

1842 self.end_markers_remove.emit() 

1843 

1844 istart = None 

1845 ipos = 0 

1846 markers = self.markers 

1847 nmarkers = len(self.markers) 

1848 while ipos < nmarkers: 

1849 marker = markers[ipos] 

1850 if marker.is_selected(): 

1851 if marker is self.active_event_marker: 

1852 self.deactivate_event_marker() 

1853 

1854 if istart is None: 

1855 istart = ipos 

1856 else: 

1857 if istart is not None: 

1858 delete_segment(istart, ipos) 

1859 nmarkers -= ipos - istart 

1860 ipos = istart - 1 

1861 istart = None 

1862 

1863 ipos += 1 

1864 

1865 if istart is not None: 

1866 delete_segment(istart, ipos) 

1867 

1868 self.update_markers_deltat_max() 

1869 

1870 def selected_markers(self): 

1871 return [marker for marker in self.markers if marker.is_selected()] 

1872 

1873 def iter_selected_markers(self): 

1874 for marker in self.markers: 

1875 if marker.is_selected(): 

1876 yield marker 

1877 

1878 def get_markers(self): 

1879 return self.markers 

1880 

1881 def mousePressEvent(self, mouse_ev): 

1882 self.show_all = False 

1883 point = self.mapFromGlobal(mouse_ev.globalPos()) 

1884 

1885 if mouse_ev.button() == qc.Qt.LeftButton: 

1886 marker = self.marker_under_cursor(point.x(), point.y()) 

1887 if self.picking: 

1888 if self.picking_down is None: 

1889 self.picking_down = ( 

1890 self.time_projection.rev(mouse_ev.x()), 

1891 mouse_ev.y()) 

1892 

1893 elif marker is not None: 

1894 if not (mouse_ev.modifiers() & qc.Qt.ShiftModifier): 

1895 self.deselect_all() 

1896 marker.selected = True 

1897 self.emit_selected_markers() 

1898 self.update() 

1899 else: 

1900 self.track_start = mouse_ev.x(), mouse_ev.y() 

1901 self.track_trange = self.tmin, self.tmax 

1902 

1903 if mouse_ev.button() == qc.Qt.RightButton: 

1904 self.menu.exec_(qg.QCursor.pos()) 

1905 self.update_status() 

1906 

1907 def mouseReleaseEvent(self, mouse_ev): 

1908 if self.ignore_releases: 

1909 self.ignore_releases -= 1 

1910 return 

1911 

1912 if self.picking: 

1913 self.stop_picking(mouse_ev.x(), mouse_ev.y()) 

1914 self.emit_selected_markers() 

1915 

1916 if self.track_start: 

1917 self.update() 

1918 

1919 self.track_start = None 

1920 self.track_trange = None 

1921 self.update_status() 

1922 

1923 def mouseDoubleClickEvent(self, mouse_ev): 

1924 self.show_all = False 

1925 self.start_picking(None) 

1926 self.ignore_releases = 1 

1927 

1928 def mouseMoveEvent(self, mouse_ev): 

1929 self.setUpdatesEnabled(False) 

1930 point = self.mapFromGlobal(mouse_ev.globalPos()) 

1931 

1932 if self.picking: 

1933 self.update_picking(point.x(), point.y()) 

1934 

1935 elif self.track_start is not None: 

1936 x0, y0 = self.track_start 

1937 dx = (point.x() - x0)/float(self.width()) 

1938 dy = (point.y() - y0)/float(self.height()) 

1939 if self.ypart(y0) == 1: 

1940 dy = 0 

1941 

1942 tmin0, tmax0 = self.track_trange 

1943 

1944 scale = math.exp(-dy*5.) 

1945 dtr = scale*(tmax0-tmin0) - (tmax0-tmin0) 

1946 frac = x0/float(self.width()) 

1947 dt = dx*(tmax0-tmin0)*scale 

1948 

1949 self.interrupt_following() 

1950 self.set_time_range( 

1951 tmin0 - dt - dtr*frac, 

1952 tmax0 - dt + dtr*(1.-frac)) 

1953 

1954 self.update() 

1955 else: 

1956 self.hoovering(point.x(), point.y()) 

1957 

1958 self.update_status() 

1959 

1960 def nslc_ids_under_cursor(self, x, y): 

1961 ftrack = self.track_to_screen.rev(y) 

1962 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

1963 return nslc_ids 

1964 

1965 def marker_under_cursor(self, x, y): 

1966 mouset = self.time_projection.rev(x) 

1967 deltat = (self.tmax-self.tmin)*self.click_tolerance/self.width() 

1968 relevant_nslc_ids = None 

1969 for marker in self.markers: 

1970 if marker.kind not in self.visible_marker_kinds: 

1971 continue 

1972 

1973 if (abs(mouset-marker.tmin) < deltat or 

1974 abs(mouset-marker.tmax) < deltat): 

1975 

1976 if relevant_nslc_ids is None: 

1977 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

1978 

1979 marker_nslc_ids = marker.get_nslc_ids() 

1980 if not marker_nslc_ids: 

1981 return marker 

1982 

1983 for nslc_id in marker_nslc_ids: 

1984 if nslc_id in relevant_nslc_ids: 

1985 return marker 

1986 

1987 def hoovering(self, x, y): 

1988 mouset = self.time_projection.rev(x) 

1989 deltat = (self.tmax-self.tmin)*self.click_tolerance/self.width() 

1990 needupdate = False 

1991 haveone = False 

1992 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

1993 for marker in self.markers: 

1994 if marker.kind not in self.visible_marker_kinds: 

1995 continue 

1996 

1997 state = abs(mouset-marker.tmin) < deltat or \ 

1998 abs(mouset-marker.tmax) < deltat and not haveone 

1999 

2000 if state: 

2001 xstate = False 

2002 

2003 marker_nslc_ids = marker.get_nslc_ids() 

2004 if not marker_nslc_ids: 

2005 xstate = True 

2006 

2007 for nslc in relevant_nslc_ids: 

2008 if marker.match_nslc(nslc): 

2009 xstate = True 

2010 

2011 state = xstate 

2012 

2013 if state: 

2014 haveone = True 

2015 oldstate = marker.is_alerted() 

2016 if oldstate != state: 

2017 needupdate = True 

2018 marker.set_alerted(state) 

2019 if state: 

2020 self.message = marker.hoover_message() 

2021 

2022 if not haveone: 

2023 self.message = None 

2024 

2025 if needupdate: 

2026 self.update() 

2027 

2028 def event(self, event): 

2029 if event.type() == qc.QEvent.KeyPress: 

2030 self.keyPressEvent(event) 

2031 return True 

2032 else: 

2033 return base.event(self, event) 

2034 

2035 def keyPressEvent(self, key_event): 

2036 self.show_all = False 

2037 dt = self.tmax - self.tmin 

2038 tmid = (self.tmin + self.tmax) / 2. 

2039 

2040 try: 

2041 keytext = str(key_event.text()) 

2042 except UnicodeEncodeError: 

2043 return 

2044 

2045 if keytext == '?': 

2046 self.help() 

2047 

2048 elif keytext == ' ': 

2049 self.interrupt_following() 

2050 self.set_time_range(self.tmin+dt, self.tmax+dt) 

2051 

2052 elif key_event.key() == qc.Qt.Key_Up: 

2053 for m in self.selected_markers(): 

2054 if isinstance(m, PhaseMarker): 

2055 if key_event.modifiers() & qc.Qt.ShiftModifier: 

2056 p = 0 

2057 else: 

2058 p = 1 if m.get_polarity() != 1 else None 

2059 m.set_polarity(p) 

2060 

2061 elif key_event.key() == qc.Qt.Key_Down: 

2062 for m in self.selected_markers(): 

2063 if isinstance(m, PhaseMarker): 

2064 if key_event.modifiers() & qc.Qt.ShiftModifier: 

2065 p = 0 

2066 else: 

2067 p = -1 if m.get_polarity() != -1 else None 

2068 m.set_polarity(p) 

2069 

2070 elif keytext == 'b': 

2071 dt = self.tmax - self.tmin 

2072 self.interrupt_following() 

2073 self.set_time_range(self.tmin-dt, self.tmax-dt) 

2074 

2075 elif key_event.key() in (qc.Qt.Key_Tab, qc.Qt.Key_Backtab): 

2076 self.interrupt_following() 

2077 

2078 tgo = None 

2079 

2080 class TraceDummy(object): 

2081 def __init__(self, marker): 

2082 self._marker = marker 

2083 

2084 @property 

2085 def nslc_id(self): 

2086 return self._marker.one_nslc() 

2087 

2088 def marker_to_itrack(marker): 

2089 try: 

2090 return self.key_to_row.get( 

2091 self.gather(TraceDummy(marker)), -1) 

2092 

2093 except MarkerOneNSLCRequired: 

2094 return -1 

2095 

2096 emarker, pmarkers = self.get_active_markers() 

2097 pmarkers = [ 

2098 m for m in pmarkers if m.kind in self.visible_marker_kinds] 

2099 pmarkers.sort(key=lambda m: ( 

2100 marker_to_itrack(m), (m.tmin + m.tmax) / 2.0)) 

2101 

2102 if key_event.key() == qc.Qt.Key_Backtab: 

2103 pmarkers.reverse() 

2104 

2105 smarkers = self.selected_markers() 

2106 iselected = [] 

2107 for sm in smarkers: 

2108 try: 

2109 iselected.append(pmarkers.index(sm)) 

2110 except ValueError: 

2111 pass 

2112 

2113 if iselected: 

2114 icurrent = max(iselected) + 1 

2115 else: 

2116 icurrent = 0 

2117 

2118 if icurrent < len(pmarkers): 

2119 self.deselect_all() 

2120 cmarker = pmarkers[icurrent] 

2121 cmarker.selected = True 

2122 tgo = cmarker.tmin 

2123 if not self.tmin < tgo < self.tmax: 

2124 self.set_time_range(tgo-dt/2., tgo+dt/2.) 

2125 

2126 itrack = marker_to_itrack(cmarker) 

2127 if itrack != -1: 

2128 if itrack < self.shown_tracks_range[0]: 

2129 self.scroll_tracks( 

2130 - (self.shown_tracks_range[0] - itrack)) 

2131 elif self.shown_tracks_range[1] <= itrack: 

2132 self.scroll_tracks( 

2133 itrack - self.shown_tracks_range[1]+1) 

2134 

2135 if itrack not in self.track_to_nslc_ids: 

2136 self.go_to_selection() 

2137 

2138 elif keytext in ('p', 'n', 'P', 'N'): 

2139 smarkers = self.selected_markers() 

2140 tgo = None 

2141 dir = str(keytext) 

2142 if smarkers: 

2143 tmid = smarkers[0].tmin 

2144 for smarker in smarkers: 

2145 if dir == 'n': 

2146 tmid = max(smarker.tmin, tmid) 

2147 else: 

2148 tmid = min(smarker.tmin, tmid) 

2149 

2150 tgo = tmid 

2151 

2152 if dir.lower() == 'n': 

2153 for marker in sorted( 

2154 self.markers, 

2155 key=operator.attrgetter('tmin')): 

2156 

2157 t = marker.tmin 

2158 if t > tmid and \ 

2159 marker.kind in self.visible_marker_kinds and \ 

2160 (dir == 'n' or 

2161 isinstance(marker, EventMarker)): 

2162 

2163 self.deselect_all() 

2164 marker.selected = True 

2165 tgo = t 

2166 break 

2167 else: 

2168 for marker in sorted( 

2169 self.markers, 

2170 key=operator.attrgetter('tmin'), 

2171 reverse=True): 

2172 

2173 t = marker.tmin 

2174 if t < tmid and \ 

2175 marker.kind in self.visible_marker_kinds and \ 

2176 (dir == 'p' or 

2177 isinstance(marker, EventMarker)): 

2178 self.deselect_all() 

2179 marker.selected = True 

2180 tgo = t 

2181 break 

2182 

2183 if tgo is not None: 

2184 self.interrupt_following() 

2185 self.set_time_range(tgo-dt/2., tgo+dt/2.) 

2186 

2187 elif keytext == 'q' or keytext == 'x': 

2188 self.myclose(keytext) 

2189 

2190 elif keytext == 'r': 

2191 if self.pile.reload_modified(): 

2192 self.reloaded = True 

2193 

2194 elif keytext == 'R': 

2195 self.setup_snufflings() 

2196 

2197 elif key_event.key() == qc.Qt.Key_Backspace: 

2198 self.remove_selected_markers() 

2199 

2200 elif keytext == 'a': 

2201 for marker in self.markers: 

2202 if ((self.tmin <= marker.tmin <= self.tmax or 

2203 self.tmin <= marker.tmax <= self.tmax) and 

2204 marker.kind in self.visible_marker_kinds): 

2205 marker.selected = True 

2206 else: 

2207 marker.selected = False 

2208 

2209 elif keytext == 'A': 

2210 for marker in self.markers: 

2211 if marker.kind in self.visible_marker_kinds: 

2212 marker.selected = True 

2213 

2214 elif keytext == 'd': 

2215 self.deselect_all() 

2216 

2217 elif keytext == 'E': 

2218 self.deactivate_event_marker() 

2219 

2220 elif keytext == 'e': 

2221 markers = self.selected_markers() 

2222 event_markers_in_spe = [ 

2223 marker for marker in markers 

2224 if not isinstance(marker, PhaseMarker)] 

2225 

2226 phase_markers = [ 

2227 marker for marker in markers 

2228 if isinstance(marker, PhaseMarker)] 

2229 

2230 if len(event_markers_in_spe) == 1: 

2231 event_marker = event_markers_in_spe[0] 

2232 if not isinstance(event_marker, EventMarker): 

2233 nslcs = list(event_marker.nslc_ids) 

2234 lat, lon = 0.0, 0.0 

2235 old = self.get_active_event() 

2236 if len(nslcs) == 1: 

2237 lat, lon = self.station_latlon(NSLC(*nslcs[0])) 

2238 elif old is not None: 

2239 lat, lon = old.lat, old.lon 

2240 

2241 event_marker.convert_to_event_marker(lat, lon) 

2242 

2243 self.set_active_event_marker(event_marker) 

2244 event = event_marker.get_event() 

2245 for marker in phase_markers: 

2246 marker.set_event(event) 

2247 

2248 else: 

2249 for marker in event_markers_in_spe: 

2250 marker.convert_to_event_marker() 

2251 

2252 elif keytext in ('0', '1', '2', '3', '4', '5'): 

2253 for marker in self.selected_markers(): 

2254 marker.set_kind(int(keytext)) 

2255 self.emit_selected_markers() 

2256 

2257 elif key_event.key() in fkey_map: 

2258 self.handle_fkeys(key_event.key()) 

2259 

2260 elif key_event.key() == qc.Qt.Key_Escape: 

2261 if self.picking: 

2262 self.stop_picking(0, 0, abort=True) 

2263 

2264 elif key_event.key() == qc.Qt.Key_PageDown: 

2265 self.scroll_tracks( 

2266 self.shown_tracks_range[1]-self.shown_tracks_range[0]) 

2267 

2268 elif key_event.key() == qc.Qt.Key_PageUp: 

2269 self.scroll_tracks( 

2270 self.shown_tracks_range[0]-self.shown_tracks_range[1]) 

2271 

2272 elif keytext == '+': 

2273 self.zoom_tracks(0., 1.) 

2274 

2275 elif keytext == '-': 

2276 self.zoom_tracks(0., -1.) 

2277 

2278 elif keytext == '=': 

2279 ntracks_shown = self.shown_tracks_range[1] - \ 

2280 self.shown_tracks_range[0] 

2281 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2282 self.zoom_tracks(0., dtracks) 

2283 

2284 elif keytext == ':': 

2285 self.want_input.emit() 

2286 

2287 elif keytext == 'f': 

2288 if self.window().windowState() & qc.Qt.WindowFullScreen or \ 

2289 self.window().windowState() & qc.Qt.WindowMaximized: 

2290 

2291 self.window().showNormal() 

2292 else: 

2293 if macosx: 

2294 self.window().showMaximized() 

2295 else: 

2296 self.window().showFullScreen() 

2297 

2298 elif keytext == 'g': 

2299 self.go_to_selection() 

2300 

2301 elif keytext == 'G': 

2302 self.go_to_selection(tight=True) 

2303 

2304 elif keytext == 'm': 

2305 self.toggle_marker_editor() 

2306 

2307 elif keytext == 'c': 

2308 self.toggle_main_controls() 

2309 

2310 elif key_event.key() in (qc.Qt.Key_Left, qc.Qt.Key_Right): 

2311 dir = 1 

2312 amount = 1 

2313 if key_event.key() == qc.Qt.Key_Left: 

2314 dir = -1 

2315 if key_event.modifiers() & qc.Qt.ShiftModifier: 

2316 amount = 10 

2317 self.nudge_selected_markers(dir*amount) 

2318 

2319 if keytext != '' and keytext in 'degaApPnN': 

2320 self.emit_selected_markers() 

2321 

2322 self.update() 

2323 self.update_status() 

2324 

2325 def handle_fkeys(self, key): 

2326 self.set_phase_kind( 

2327 self.selected_markers(), 

2328 fkey_map[key] + 1) 

2329 self.emit_selected_markers() 

2330 

2331 def emit_selected_markers(self): 

2332 ibounds = [] 

2333 last_selected = False 

2334 for imarker, marker in enumerate(self.markers): 

2335 this_selected = marker.is_selected() 

2336 if this_selected != last_selected: 

2337 ibounds.append(imarker) 

2338 

2339 last_selected = this_selected 

2340 

2341 if last_selected: 

2342 ibounds.append(len(self.markers)) 

2343 

2344 chunks = list(zip(ibounds[::2], ibounds[1::2])) 

2345 self.n_selected_markers = sum( 

2346 chunk[1] - chunk[0] for chunk in chunks) 

2347 self.marker_selection_changed.emit(chunks) 

2348 

2349 def toggle_marker_editor(self): 

2350 self.panel_parent.toggle_marker_editor() 

2351 

2352 def toggle_main_controls(self): 

2353 self.panel_parent.toggle_main_controls() 

2354 

2355 def nudge_selected_markers(self, npixels): 

2356 a, b = self.time_projection.ur 

2357 c, d = self.time_projection.xr 

2358 for marker in self.selected_markers(): 

2359 if not isinstance(marker, EventMarker): 

2360 marker.tmin += npixels * (d-c)/b 

2361 marker.tmax += npixels * (d-c)/b 

2362 

2363 def about(self): 

2364 fn = pyrocko.util.data_file('snuffler.png') 

2365 with open(pyrocko.util.data_file('snuffler_about.html')) as f: 

2366 txt = f.read() 

2367 label = qw.QLabel(txt % {'logo': fn}) 

2368 label.setAlignment(qc.Qt.AlignVCenter | qc.Qt.AlignHCenter) 

2369 self.show_doc('About', [label], target='tab') 

2370 

2371 def help(self): 

2372 class MyScrollArea(qw.QScrollArea): 

2373 

2374 def sizeHint(self): 

2375 s = qc.QSize() 

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

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

2378 return s 

2379 

2380 with open(pyrocko.util.data_file( 

2381 'snuffler_help.html')) as f: 

2382 hcheat = qw.QLabel(f.read()) 

2383 

2384 with open(pyrocko.util.data_file( 

2385 'snuffler_help_epilog.html')) as f: 

2386 hepilog = qw.QLabel(f.read()) 

2387 

2388 for h in [hcheat, hepilog]: 

2389 h.setAlignment(qc.Qt.AlignTop | qc.Qt.AlignHCenter) 

2390 h.setWordWrap(True) 

2391 

2392 self.show_doc('Help', [hcheat, hepilog], target='panel') 

2393 

2394 def show_doc(self, name, labels, target='panel'): 

2395 scroller = qw.QScrollArea() 

2396 frame = qw.QFrame(scroller) 

2397 frame.setLineWidth(0) 

2398 layout = qw.QVBoxLayout() 

2399 layout.setContentsMargins(0, 0, 0, 0) 

2400 layout.setSpacing(0) 

2401 frame.setLayout(layout) 

2402 scroller.setWidget(frame) 

2403 scroller.setWidgetResizable(True) 

2404 frame.setBackgroundRole(qg.QPalette.Base) 

2405 for h in labels: 

2406 h.setParent(frame) 

2407 h.setMargin(3) 

2408 h.setTextInteractionFlags( 

2409 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2410 h.setBackgroundRole(qg.QPalette.Base) 

2411 layout.addWidget(h) 

2412 h.linkActivated.connect( 

2413 self.open_link) 

2414 

2415 if self.panel_parent is not None: 

2416 if target == 'panel': 

2417 self.panel_parent.add_panel( 

2418 name, scroller, True, volatile=False) 

2419 else: 

2420 self.panel_parent.add_tab(name, scroller) 

2421 

2422 def open_link(self, link): 

2423 qg.QDesktopServices.openUrl(qc.QUrl(link)) 

2424 

2425 def wheelEvent(self, wheel_event): 

2426 if use_pyqt5: 

2427 self.wheel_pos += wheel_event.angleDelta().y() 

2428 else: 

2429 self.wheel_pos += wheel_event.delta() 

2430 

2431 n = self.wheel_pos // 120 

2432 self.wheel_pos = self.wheel_pos % 120 

2433 if n == 0: 

2434 return 

2435 

2436 amount = max( 

2437 1., 

2438 abs(self.shown_tracks_range[0]-self.shown_tracks_range[1])/5.) 

2439 wdelta = amount * n 

2440 

2441 trmin, trmax = self.track_to_screen.get_in_range() 

2442 anchor = (self.track_to_screen.rev(wheel_event.y())-trmin) \ 

2443 / (trmax-trmin) 

2444 

2445 if wheel_event.modifiers() & qc.Qt.ControlModifier: 

2446 self.zoom_tracks(anchor, wdelta) 

2447 else: 

2448 self.scroll_tracks(-wdelta) 

2449 

2450 def dragEnterEvent(self, event): 

2451 if event.mimeData().hasUrls(): 

2452 if any(url.toLocalFile() for url in event.mimeData().urls()): 

2453 event.setDropAction(qc.Qt.LinkAction) 

2454 event.accept() 

2455 

2456 def dropEvent(self, event): 

2457 if event.mimeData().hasUrls(): 

2458 paths = list( 

2459 str(url.toLocalFile()) for url in event.mimeData().urls()) 

2460 event.acceptProposedAction() 

2461 self.load(paths) 

2462 

2463 def get_phase_name(self, kind): 

2464 return self.config.get_phase_name(kind) 

2465 

2466 def set_phase_kind(self, markers, kind): 

2467 phasename = self.get_phase_name(kind) 

2468 

2469 for marker in markers: 

2470 if isinstance(marker, PhaseMarker): 

2471 if kind == 10: 

2472 marker.convert_to_marker() 

2473 else: 

2474 marker.set_phasename(phasename) 

2475 marker.set_event(self.get_active_event()) 

2476 

2477 elif isinstance(marker, EventMarker): 

2478 pass 

2479 

2480 else: 

2481 if kind != 10: 

2482 event = self.get_active_event() 

2483 marker.convert_to_phase_marker( 

2484 event, phasename, None, False) 

2485 

2486 def set_ntracks(self, ntracks): 

2487 if self.ntracks != ntracks: 

2488 self.ntracks = ntracks 

2489 if self.shown_tracks_range is not None: 

2490 l, h = self.shown_tracks_range 

2491 else: 

2492 l, h = 0, self.ntracks 

2493 

2494 self.tracks_range_changed.emit(self.ntracks, l, h) 

2495 

2496 def set_tracks_range(self, range, start=None): 

2497 

2498 low, high = range 

2499 low = min(self.ntracks-1, low) 

2500 high = min(self.ntracks, high) 

2501 low = max(0, low) 

2502 high = max(1, high) 

2503 

2504 if start is None: 

2505 start = float(low) 

2506 

2507 if self.shown_tracks_range != (low, high): 

2508 self.shown_tracks_range = low, high 

2509 self.shown_tracks_start = start 

2510 

2511 self.tracks_range_changed.emit(self.ntracks, low, high) 

2512 

2513 def scroll_tracks(self, shift): 

2514 shown = self.shown_tracks_range 

2515 shiftmin = -shown[0] 

2516 shiftmax = self.ntracks-shown[1] 

2517 shift = max(shiftmin, shift) 

2518 shift = min(shiftmax, shift) 

2519 shown = shown[0] + shift, shown[1] + shift 

2520 

2521 self.set_tracks_range((int(shown[0]), int(shown[1]))) 

2522 

2523 self.update() 

2524 

2525 def zoom_tracks(self, anchor, delta): 

2526 ntracks_shown = self.shown_tracks_range[1] \ 

2527 - self.shown_tracks_range[0] 

2528 

2529 if (ntracks_shown == 1 and delta <= 0) or \ 

2530 (ntracks_shown == self.ntracks and delta >= 0): 

2531 return 

2532 

2533 ntracks_shown += int(round(delta)) 

2534 ntracks_shown = min(max(1, ntracks_shown), self.ntracks) 

2535 

2536 u = self.shown_tracks_start 

2537 nu = max(0., u-anchor*delta) 

2538 nv = nu + ntracks_shown 

2539 if nv > self.ntracks: 

2540 nu -= nv - self.ntracks 

2541 nv -= nv - self.ntracks 

2542 

2543 self.set_tracks_range((int(round(nu)), int(round(nv))), nu) 

2544 

2545 self.ntracks_shown_max = self.shown_tracks_range[1] \ 

2546 - self.shown_tracks_range[0] 

2547 

2548 self.update() 

2549 

2550 def content_time_range(self): 

2551 pile = self.get_pile() 

2552 tmin, tmax = pile.get_tmin(), pile.get_tmax() 

2553 if tmin is None: 

2554 tmin = initial_time_range[0] 

2555 if tmax is None: 

2556 tmax = initial_time_range[1] 

2557 

2558 return tmin, tmax 

2559 

2560 def content_deltat_range(self): 

2561 pile = self.get_pile() 

2562 

2563 deltatmin, deltatmax = pile.get_deltatmin(), pile.get_deltatmax() 

2564 

2565 if deltatmin is None: 

2566 deltatmin = 0.001 

2567 

2568 if deltatmax is None: 

2569 deltatmax = 1000.0 

2570 

2571 return deltatmin, deltatmax 

2572 

2573 def make_good_looking_time_range(self, tmin, tmax, tight=False): 

2574 if tmax < tmin: 

2575 tmin, tmax = tmax, tmin 

2576 

2577 deltatmin = self.content_deltat_range()[0] 

2578 dt = deltatmin * self.visible_length * 0.95 

2579 

2580 if dt == 0.0: 

2581 dt = 1.0 

2582 

2583 if tight: 

2584 if tmax != tmin: 

2585 dtm = tmax - tmin 

2586 tmin -= dtm*0.1 

2587 tmax += dtm*0.1 

2588 return tmin, tmax 

2589 else: 

2590 tcenter = (tmin + tmax) / 2. 

2591 tmin = tcenter - 0.5*dt 

2592 tmax = tcenter + 0.5*dt 

2593 return tmin, tmax 

2594 

2595 if tmax-tmin < dt: 

2596 vmin, vmax = self.get_time_range() 

2597 dt = min(vmax - vmin, dt) 

2598 

2599 tcenter = (tmin+tmax)/2. 

2600 etmin, etmax = tmin, tmax 

2601 tmin = min(etmin, tcenter - 0.5*dt) 

2602 tmax = max(etmax, tcenter + 0.5*dt) 

2603 dtm = tmax-tmin 

2604 if etmin == tmin: 

2605 tmin -= dtm*0.1 

2606 if etmax == tmax: 

2607 tmax += dtm*0.1 

2608 

2609 else: 

2610 dtm = tmax-tmin 

2611 tmin -= dtm*0.1 

2612 tmax += dtm*0.1 

2613 

2614 return tmin, tmax 

2615 

2616 def go_to_selection(self, tight=False): 

2617 markers = self.selected_markers() 

2618 if markers: 

2619 tmax, tmin = self.content_time_range() 

2620 for marker in markers: 

2621 tmin = min(tmin, marker.tmin) 

2622 tmax = max(tmax, marker.tmax) 

2623 

2624 else: 

2625 if tight: 

2626 vmin, vmax = self.get_time_range() 

2627 tmin = tmax = (vmin + vmax) / 2. 

2628 else: 

2629 tmin, tmax = self.content_time_range() 

2630 

2631 tmin, tmax = self.make_good_looking_time_range( 

2632 tmin, tmax, tight=tight) 

2633 

2634 self.interrupt_following() 

2635 self.set_time_range(tmin, tmax) 

2636 self.update() 

2637 

2638 def go_to_time(self, t, tlen=None): 

2639 tmax = t 

2640 if tlen is not None: 

2641 tmax = t+tlen 

2642 tmin, tmax = self.make_good_looking_time_range(t, tmax) 

2643 self.interrupt_following() 

2644 self.set_time_range(tmin, tmax) 

2645 self.update() 

2646 

2647 def go_to_event_by_name(self, name): 

2648 for marker in self.markers: 

2649 if isinstance(marker, EventMarker): 

2650 event = marker.get_event() 

2651 if event.name and event.name.lower() == name.lower(): 

2652 tmin, tmax = self.make_good_looking_time_range( 

2653 event.time, event.time) 

2654 

2655 self.interrupt_following() 

2656 self.set_time_range(tmin, tmax) 

2657 

2658 def printit(self): 

2659 from .qt_compat import qprint 

2660 printer = qprint.QPrinter() 

2661 printer.setOrientation(qprint.QPrinter.Landscape) 

2662 

2663 dialog = qprint.QPrintDialog(printer, self) 

2664 dialog.setWindowTitle('Print') 

2665 

2666 if dialog.exec_() != qw.QDialog.Accepted: 

2667 return 

2668 

2669 painter = qg.QPainter() 

2670 painter.begin(printer) 

2671 page = printer.pageRect() 

2672 self.drawit( 

2673 painter, printmode=False, w=page.width(), h=page.height()) 

2674 

2675 painter.end() 

2676 

2677 def savesvg(self, fn=None): 

2678 

2679 if not fn: 

2680 fn, _ = fnpatch(qw.QFileDialog.getSaveFileName( 

2681 self, 

2682 'Save as SVG|PNG', 

2683 os.path.expanduser(os.path.join('~', 'untitled.svg')), 

2684 'SVG|PNG (*.svg *.png)', 

2685 options=qfiledialog_options)) 

2686 

2687 if fn == '': 

2688 return 

2689 

2690 if str(fn).endswith('.svg'): 

2691 w, h = 842, 595 

2692 margin = 0.025 

2693 m = max(w, h)*margin 

2694 

2695 generator = qsvg.QSvgGenerator() 

2696 generator.setFileName(fn) 

2697 generator.setSize(qc.QSize(w, h)) 

2698 generator.setViewBox(qc.QRectF(-m, -m, w+2*m, h+2*m)) 

2699 

2700 painter = qg.QPainter() 

2701 painter.begin(generator) 

2702 self.drawit(painter, printmode=False, w=w, h=h) 

2703 painter.end() 

2704 

2705 elif str(fn).endswith('.png'): 

2706 if use_pyqt5: 

2707 pixmap = self.grab() 

2708 else: 

2709 pixmap = qg.QPixmap().grabWidget(self) 

2710 

2711 pixmap.save(fn) 

2712 

2713 else: 

2714 logger.warning('unsupported file type') 

2715 

2716 def paintEvent(self, paint_ev): 

2717 ''' 

2718 Called by QT whenever widget needs to be painted. 

2719 ''' 

2720 painter = qg.QPainter(self) 

2721 

2722 if self.menuitem_antialias.isChecked(): 

2723 painter.setRenderHint(qg.QPainter.Antialiasing) 

2724 

2725 self.drawit(painter) 

2726 

2727 logger.debug( 

2728 'Time spent drawing: %.3f %.3f %.3f %.3f %.3f' % 

2729 (self.timer_draw - self.timer_cutout)) 

2730 

2731 logger.debug( 

2732 'Time spent processing: %.3f %.3f %.3f %.3f %.3f' % 

2733 self.timer_cutout.get()) 

2734 

2735 self.time_spent_painting = self.timer_draw.get()[-1] 

2736 self.time_last_painted = time.time() 

2737 

2738 def determine_box_styles(self): 

2739 

2740 traces = list(self.pile.iter_traces()) 

2741 traces.sort(key=operator.attrgetter('full_id')) 

2742 istyle = 0 

2743 trace_styles = {} 

2744 for itr, tr in enumerate(traces): 

2745 if itr > 0: 

2746 other = traces[itr-1] 

2747 if not ( 

2748 other.nslc_id == tr.nslc_id 

2749 and other.deltat == tr.deltat 

2750 and abs(other.tmax - tr.tmin) 

2751 < gap_lap_tolerance*tr.deltat): 

2752 

2753 istyle += 1 

2754 

2755 trace_styles[tr.full_id, tr.deltat] = istyle 

2756 

2757 self.trace_styles = trace_styles 

2758 

2759 def draw_trace_boxes(self, p, time_projection, track_projections): 

2760 

2761 for v_projection in track_projections.values(): 

2762 v_projection.set_in_range(0., 1.) 

2763 

2764 def selector(x): 

2765 return x.overlaps(*time_projection.get_in_range()) 

2766 

2767 if self.trace_filter is not None: 

2768 def tselector(x): 

2769 return selector(x) and self.trace_filter(x) 

2770 

2771 else: 

2772 tselector = selector 

2773 

2774 traces = list(self.pile.iter_traces( 

2775 group_selector=selector, trace_selector=tselector)) 

2776 

2777 traces.sort(key=operator.attrgetter('full_id')) 

2778 

2779 def drawbox(itrack, istyle, traces): 

2780 v_projection = track_projections[itrack] 

2781 dvmin = v_projection(0.) 

2782 dvmax = v_projection(1.) 

2783 dtmin = time_projection.clipped(traces[0].tmin) 

2784 dtmax = time_projection.clipped(traces[-1].tmax) 

2785 

2786 style = box_styles[istyle % len(box_styles)] 

2787 rect = qc.QRectF(dtmin, dvmin, float(dtmax-dtmin), dvmax-dvmin) 

2788 p.fillRect(rect, style.fill_brush) 

2789 p.setPen(style.frame_pen) 

2790 p.drawRect(rect) 

2791 

2792 traces_by_style = {} 

2793 for itr, tr in enumerate(traces): 

2794 gt = self.gather(tr) 

2795 if gt not in self.key_to_row: 

2796 continue 

2797 

2798 itrack = self.key_to_row[gt] 

2799 if itrack not in track_projections: 

2800 continue 

2801 

2802 istyle = self.trace_styles.get((tr.full_id, tr.deltat), 0) 

2803 

2804 if len(traces) < 500: 

2805 drawbox(itrack, istyle, [tr]) 

2806 else: 

2807 if (itrack, istyle) not in traces_by_style: 

2808 traces_by_style[itrack, istyle] = [] 

2809 traces_by_style[itrack, istyle].append(tr) 

2810 

2811 for (itrack, istyle), traces in traces_by_style.items(): 

2812 drawbox(itrack, istyle, traces) 

2813 

2814 def draw_visible_markers( 

2815 self, p, vcenter_projection, primary_pen): 

2816 

2817 try: 

2818 markers = self.markers.with_key_in_limited( 

2819 self.tmin - self.markers_deltat_max, self.tmax, 2000) 

2820 

2821 except pyrocko.pile.TooMany: 

2822 tmin = self.markers[0].tmin 

2823 tmax = self.markers[-1].tmax 

2824 umin_view, umax_view = self.time_projection.get_out_range() 

2825 umin = max(umin_view, self.time_projection(tmin)) 

2826 umax = min(umax_view, self.time_projection(tmax)) 

2827 v0, _ = vcenter_projection.get_out_range() 

2828 label_bg = qg.QBrush(qg.QColor(255, 255, 255)) 

2829 

2830 p.save() 

2831 

2832 pen = qg.QPen(primary_pen) 

2833 pen.setWidth(2) 

2834 pen.setStyle(qc.Qt.DotLine) 

2835 # pat = [5., 3.] 

2836 # pen.setDashPattern(pat) 

2837 p.setPen(pen) 

2838 

2839 if self.n_selected_markers == len(self.markers): 

2840 s_selected = ' (all selected)' 

2841 elif self.n_selected_markers > 0: 

2842 s_selected = ' (%i selected)' % self.n_selected_markers 

2843 else: 

2844 s_selected = '' 

2845 

2846 draw_label( 

2847 p, umin+10., v0-10., 

2848 '%i Markers' % len(self.markers) + s_selected, 

2849 label_bg, 'LB') 

2850 

2851 line = qc.QLineF(umin, v0, umax, v0) 

2852 p.drawLine(line) 

2853 p.restore() 

2854 

2855 return 

2856 

2857 for marker in markers: 

2858 if marker.tmin < self.tmax and self.tmin < marker.tmax \ 

2859 and marker.kind in self.visible_marker_kinds: 

2860 

2861 marker.draw( 

2862 p, self.time_projection, vcenter_projection, 

2863 with_label=True) 

2864 

2865 def drawit(self, p, printmode=False, w=None, h=None): 

2866 ''' 

2867 This performs the actual drawing. 

2868 ''' 

2869 

2870 self.timer_draw.start() 

2871 

2872 if self.gather is None: 

2873 self.set_gathering() 

2874 

2875 if self.pile_has_changed: 

2876 

2877 if not self.sortingmode_change_delayed(): 

2878 self.sortingmode_change() 

2879 

2880 if self.menuitem_showboxes.isChecked(): 

2881 self.determine_box_styles() 

2882 

2883 self.pile_has_changed = False 

2884 

2885 if h is None: 

2886 h = float(self.height()) 

2887 if w is None: 

2888 w = float(self.width()) 

2889 

2890 if printmode: 

2891 primary_color = (0, 0, 0) 

2892 else: 

2893 primary_color = pyrocko.plot.tango_colors['aluminium5'] 

2894 

2895 primary_pen = qg.QPen(qg.QColor(*primary_color)) 

2896 

2897 ax_h = float(self.ax_height) 

2898 

2899 vbottom_ax_projection = Projection() 

2900 vtop_ax_projection = Projection() 

2901 vcenter_projection = Projection() 

2902 

2903 self.time_projection.set_out_range(0., w) 

2904 vbottom_ax_projection.set_out_range(h-ax_h, h) 

2905 vtop_ax_projection.set_out_range(0., ax_h) 

2906 vcenter_projection.set_out_range(ax_h, h-ax_h) 

2907 vcenter_projection.set_in_range(0., 1.) 

2908 self.track_to_screen.set_out_range(ax_h, h-ax_h) 

2909 

2910 self.track_to_screen.set_in_range(*self.shown_tracks_range) 

2911 track_projections = {} 

2912 for i in range(*self.shown_tracks_range): 

2913 proj = Projection() 

2914 proj.set_out_range( 

2915 self.track_to_screen(i+0.05), 

2916 self.track_to_screen(i+1.-0.05)) 

2917 

2918 track_projections[i] = proj 

2919 

2920 if self.tmin < self.tmax: 

2921 self.time_projection.set_in_range(self.tmin, self.tmax) 

2922 vbottom_ax_projection.set_in_range(0, ax_h) 

2923 

2924 self.tax.drawit(p, self.time_projection, vbottom_ax_projection) 

2925 

2926 yscaler = pyrocko.plot.AutoScaler() 

2927 if not printmode and self.menuitem_showboxes.isChecked(): 

2928 self.draw_trace_boxes( 

2929 p, self.time_projection, track_projections) 

2930 

2931 if self.floating_marker: 

2932 self.floating_marker.draw( 

2933 p, self.time_projection, vcenter_projection) 

2934 

2935 self.draw_visible_markers( 

2936 p, vcenter_projection, primary_pen) 

2937 

2938 p.setPen(primary_pen) 

2939 

2940 font = qg.QFont() 

2941 font.setBold(True) 

2942 

2943 axannotfont = qg.QFont() 

2944 axannotfont.setBold(True) 

2945 axannotfont.setPointSize(8) 

2946 

2947 p.setFont(font) 

2948 label_bg = qg.QBrush(qg.QColor(255, 255, 255, 100)) 

2949 

2950 processed_traces = self.prepare_cutout2( 

2951 self.tmin, self.tmax, 

2952 trace_selector=self.trace_selector, 

2953 degap=self.menuitem_degap.isChecked(), 

2954 demean=self.menuitem_demean.isChecked()) 

2955 

2956 color_lookup = dict( 

2957 [(k, i) for (i, k) in enumerate(self.color_keys)]) 

2958 

2959 self.track_to_nslc_ids = {} 

2960 nticks = 0 

2961 annot_labels = [] 

2962 if processed_traces: 

2963 show_scales = self.menuitem_showscalerange.isChecked() \ 

2964 or self.menuitem_showscaleaxis.isChecked() 

2965 

2966 fm = qg.QFontMetrics(axannotfont, p.device()) 

2967 trackheight = self.track_to_screen(1.-0.05) \ 

2968 - self.track_to_screen(0.05) 

2969 

2970 nlinesavail = trackheight/float(fm.lineSpacing()) 

2971 if self.menuitem_showscaleaxis.isChecked(): 

2972 nticks = max(3, min(nlinesavail * 0.5, 15)) 

2973 else: 

2974 nticks = 15 

2975 

2976 yscaler = pyrocko.plot.AutoScaler( 

2977 no_exp_interval=(-3, 2), approx_ticks=nticks, 

2978 snap=show_scales 

2979 and not self.menuitem_showscaleaxis.isChecked()) 

2980 

2981 data_ranges = pyrocko.trace.minmax( 

2982 processed_traces, 

2983 key=self.scaling_key, 

2984 mode=self.scaling_base) 

2985 

2986 if not self.menuitem_fixscalerange.isChecked(): 

2987 self.old_data_ranges = data_ranges 

2988 else: 

2989 data_ranges.update(self.old_data_ranges) 

2990 

2991 self.apply_scaling_hooks(data_ranges) 

2992 

2993 trace_to_itrack = {} 

2994 track_scaling_keys = {} 

2995 track_scaling_colors = {} 

2996 for trace in processed_traces: 

2997 gt = self.gather(trace) 

2998 if gt not in self.key_to_row: 

2999 continue 

3000 

3001 itrack = self.key_to_row[gt] 

3002 if itrack not in track_projections: 

3003 continue 

3004 

3005 trace_to_itrack[trace] = itrack 

3006 

3007 if itrack not in self.track_to_nslc_ids: 

3008 self.track_to_nslc_ids[itrack] = set() 

3009 

3010 self.track_to_nslc_ids[itrack].add(trace.nslc_id) 

3011 

3012 if itrack not in track_scaling_keys: 

3013 track_scaling_keys[itrack] = set() 

3014 

3015 scaling_key = self.scaling_key(trace) 

3016 track_scaling_keys[itrack].add(scaling_key) 

3017 

3018 color = pyrocko.plot.color( 

3019 color_lookup[self.color_gather(trace)]) 

3020 

3021 k = itrack, scaling_key 

3022 if k not in track_scaling_colors \ 

3023 and self.menuitem_colortraces.isChecked(): 

3024 track_scaling_colors[k] = color 

3025 else: 

3026 track_scaling_colors[k] = primary_color 

3027 

3028 # y axes, zero lines 

3029 

3030 trace_projections = {} 

3031 for itrack in list(track_projections.keys()): 

3032 if itrack not in track_scaling_keys: 

3033 continue 

3034 uoff = 0 

3035 for scaling_key in track_scaling_keys[itrack]: 

3036 data_range = data_ranges[scaling_key] 

3037 dymin, dymax = data_range 

3038 ymin, ymax, yinc = yscaler.make_scale( 

3039 (dymin/self.gain, dymax/self.gain)) 

3040 iexp = yscaler.make_exp(yinc) 

3041 factor = 10**iexp 

3042 trace_projection = track_projections[itrack].copy() 

3043 trace_projection.set_in_range(ymax, ymin) 

3044 trace_projections[itrack, scaling_key] = \ 

3045 trace_projection 

3046 umin, umax = self.time_projection.get_out_range() 

3047 vmin, vmax = trace_projection.get_out_range() 

3048 umax_zeroline = umax 

3049 uoffnext = uoff 

3050 

3051 if show_scales: 

3052 pen = qg.QPen(primary_pen) 

3053 k = itrack, scaling_key 

3054 if k in track_scaling_colors: 

3055 c = qg.QColor(*track_scaling_colors[ 

3056 itrack, scaling_key]) 

3057 

3058 pen.setColor(c) 

3059 

3060 p.setPen(pen) 

3061 if nlinesavail > 3: 

3062 if self.menuitem_showscaleaxis.isChecked(): 

3063 ymin_annot = math.ceil(ymin/yinc)*yinc 

3064 ny_annot = int( 

3065 math.floor(ymax/yinc) 

3066 - math.ceil(ymin/yinc)) + 1 

3067 

3068 for iy_annot in range(ny_annot): 

3069 y = ymin_annot + iy_annot*yinc 

3070 v = trace_projection(y) 

3071 line = qc.QLineF( 

3072 umax-10-uoff, v, umax-uoff, v) 

3073 

3074 p.drawLine(line) 

3075 if iy_annot == ny_annot - 1 \ 

3076 and iexp != 0: 

3077 

3078 sexp = ' &times; ' \ 

3079 '10<sup>%i</sup>' % iexp 

3080 else: 

3081 sexp = '' 

3082 

3083 snum = num_to_html(y/factor) 

3084 lab = Label( 

3085 p, 

3086 umax-20-uoff, 

3087 v, '%s%s' % (snum, sexp), 

3088 label_bg=None, 

3089 anchor='MR', 

3090 font=axannotfont, 

3091 color=c) 

3092 

3093 uoffnext = max( 

3094 lab.rect.width()+30., 

3095 uoffnext) 

3096 

3097 annot_labels.append(lab) 

3098 if y == 0.: 

3099 umax_zeroline = \ 

3100 umax - 20 \ 

3101 - lab.rect.width() - 10 \ 

3102 - uoff 

3103 else: 

3104 if not self.menuitem_showboxes\ 

3105 .isChecked(): 

3106 qpoints = make_QPolygonF( 

3107 [umax-20-uoff, 

3108 umax-10-uoff, 

3109 umax-10-uoff, 

3110 umax-20-uoff], 

3111 [vmax, vmax, vmin, vmin]) 

3112 p.drawPolyline(qpoints) 

3113 

3114 snum = num_to_html(ymin) 

3115 labmin = Label( 

3116 p, umax-15-uoff, vmax, snum, 

3117 label_bg=None, 

3118 anchor='BR', 

3119 font=axannotfont, 

3120 color=c) 

3121 

3122 annot_labels.append(labmin) 

3123 snum = num_to_html(ymax) 

3124 labmax = Label( 

3125 p, umax-15-uoff, vmin, snum, 

3126 label_bg=None, 

3127 anchor='TR', 

3128 font=axannotfont, 

3129 color=c) 

3130 

3131 annot_labels.append(labmax) 

3132 

3133 for lab in (labmin, labmax): 

3134 uoffnext = max( 

3135 lab.rect.width()+10., uoffnext) 

3136 

3137 if self.menuitem_showzeroline.isChecked(): 

3138 v = trace_projection(0.) 

3139 if vmin <= v <= vmax: 

3140 line = qc.QLineF(umin, v, umax_zeroline, v) 

3141 p.drawLine(line) 

3142 

3143 uoff = uoffnext 

3144 

3145 p.setFont(font) 

3146 p.setPen(primary_pen) 

3147 for trace in processed_traces: 

3148 if trace not in trace_to_itrack: 

3149 continue 

3150 

3151 itrack = trace_to_itrack[trace] 

3152 scaling_key = self.scaling_key(trace) 

3153 trace_projection = trace_projections[ 

3154 itrack, scaling_key] 

3155 

3156 vdata = trace_projection(trace.get_ydata()) 

3157 

3158 udata_min = float(self.time_projection(trace.tmin)) 

3159 udata_max = float(self.time_projection( 

3160 trace.tmin+trace.deltat*(vdata.size-1))) 

3161 udata = num.linspace(udata_min, udata_max, vdata.size) 

3162 

3163 qpoints = make_QPolygonF(udata, vdata) 

3164 

3165 umin, umax = self.time_projection.get_out_range() 

3166 vmin, vmax = trace_projection.get_out_range() 

3167 

3168 trackrect = qc.QRectF(umin, vmin, umax-umin, vmax-vmin) 

3169 

3170 if self.menuitem_cliptraces.isChecked(): 

3171 p.setClipRect(trackrect) 

3172 

3173 if self.menuitem_colortraces.isChecked(): 

3174 color = pyrocko.plot.color( 

3175 color_lookup[self.color_gather(trace)]) 

3176 pen = qg.QPen(qg.QColor(*color), 1) 

3177 p.setPen(pen) 

3178 

3179 p.drawPolyline(qpoints) 

3180 

3181 if self.floating_marker: 

3182 self.floating_marker.draw_trace( 

3183 self, p, trace, 

3184 self.time_projection, trace_projection, 1.0) 

3185 

3186 for marker in self.markers.with_key_in( 

3187 self.tmin - self.markers_deltat_max, 

3188 self.tmax): 

3189 

3190 if marker.tmin < self.tmax \ 

3191 and self.tmin < marker.tmax \ 

3192 and marker.kind \ 

3193 in self.visible_marker_kinds: 

3194 marker.draw_trace( 

3195 self, p, trace, self.time_projection, 

3196 trace_projection, 1.0) 

3197 

3198 p.setPen(primary_pen) 

3199 

3200 if self.menuitem_cliptraces.isChecked(): 

3201 p.setClipRect(0, 0, w, h) 

3202 

3203 p.setPen(primary_pen) 

3204 

3205 while font.pointSize() > 2: 

3206 fm = qg.QFontMetrics(font, p.device()) 

3207 trackheight = self.track_to_screen(1.-0.05) \ 

3208 - self.track_to_screen(0.05) 

3209 nlinesavail = trackheight/float(fm.lineSpacing()) 

3210 if nlinesavail > 1: 

3211 break 

3212 

3213 font.setPointSize(font.pointSize()-1) 

3214 

3215 p.setFont(font) 

3216 for key in self.track_keys: 

3217 itrack = self.key_to_row[key] 

3218 if itrack in track_projections: 

3219 plabel = ' '.join( 

3220 [str(x) for x in key if x is not None]) 

3221 lx = 10 

3222 ly = self.track_to_screen(itrack+0.5) 

3223 draw_label(p, lx, ly, plabel, label_bg, 'ML') 

3224 

3225 for lab in annot_labels: 

3226 lab.draw() 

3227 

3228 self.timer_draw.stop() 

3229 

3230 def see_data_params(self): 

3231 

3232 min_deltat = self.content_deltat_range()[0] 

3233 

3234 # determine padding and downampling requirements 

3235 if self.lowpass is not None: 

3236 deltat_target = 1./self.lowpass * 0.25 

3237 ndecimate = min( 

3238 50, 

3239 max(1, int(round(deltat_target / min_deltat)))) 

3240 tpad = 1./self.lowpass * 2. 

3241 else: 

3242 ndecimate = 1 

3243 tpad = min_deltat*5. 

3244 

3245 if self.highpass is not None: 

3246 tpad = max(1./self.highpass * 2., tpad) 

3247 

3248 nsee_points_per_trace = 5000*10 

3249 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3250 

3251 return ndecimate, tpad, tsee 

3252 

3253 def clean_update(self): 

3254 self.old_processed_traces = None 

3255 self.update() 

3256 

3257 def get_adequate_tpad(self): 

3258 tpad = 0. 

3259 for f in [self.highpass, self.lowpass]: 

3260 if f is not None: 

3261 tpad = max(tpad, 1.0/f) 

3262 

3263 for snuffling in self.snufflings: 

3264 if snuffling._post_process_hook_enabled \ 

3265 or snuffling._pre_process_hook_enabled: 

3266 

3267 tpad = max(tpad, snuffling.get_tpad()) 

3268 

3269 return tpad 

3270 

3271 def prepare_cutout2( 

3272 self, tmin, tmax, trace_selector=None, degap=True, 

3273 demean=True, nmax=6000): 

3274 

3275 if self.pile.is_empty(): 

3276 return [] 

3277 

3278 nmax = self.visible_length 

3279 

3280 self.timer_cutout.start() 

3281 

3282 tsee = tmax-tmin 

3283 min_deltat_wo_decimate = tsee/nmax 

3284 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3285 

3286 min_deltat_allow = min_deltat_wo_decimate 

3287 if self.lowpass is not None: 

3288 target_deltat_lp = 0.25/self.lowpass 

3289 if target_deltat_lp > min_deltat_wo_decimate: 

3290 min_deltat_allow = min_deltat_w_decimate 

3291 

3292 min_deltat_allow = math.exp( 

3293 int(math.floor(math.log(min_deltat_allow)))) 

3294 

3295 tmin_ = tmin 

3296 tmax_ = tmax 

3297 

3298 # fetch more than needed? 

3299 if self.menuitem_liberal_fetch.isChecked(): 

3300 tlen = pyrocko.trace.nextpow2((tmax-tmin)*1.5) 

3301 tmin = math.floor(tmin/tlen) * tlen 

3302 tmax = math.ceil(tmax/tlen) * tlen 

3303 

3304 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3305 lphp = self.menuitem_lphp.isChecked() 

3306 ads = self.menuitem_allowdownsampling.isChecked() 

3307 

3308 tpad = self.get_adequate_tpad() 

3309 tpad = max(tpad, tsee) 

3310 

3311 # state vector to decide if cached traces can be used 

3312 vec = ( 

3313 tmin, tmax, tpad, trace_selector, degap, demean, self.lowpass, 

3314 self.highpass, fft_filtering, lphp, 

3315 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3316 ads, self.pile.get_update_count()) 

3317 

3318 if (self.old_vec 

3319 and self.old_vec[0] <= vec[0] 

3320 and vec[1] <= self.old_vec[1] 

3321 and vec[2:] == self.old_vec[2:] 

3322 and not (self.reloaded or self.menuitem_watch.isChecked()) 

3323 and self.old_processed_traces is not None): 

3324 

3325 logger.debug('Using cached traces') 

3326 processed_traces = self.old_processed_traces 

3327 

3328 else: 

3329 self.old_vec = vec 

3330 

3331 processed_traces = [] 

3332 

3333 if self.pile.deltatmax >= min_deltat_allow: 

3334 

3335 def group_selector(gr): 

3336 return gr.deltatmax >= min_deltat_allow 

3337 

3338 if trace_selector is not None: 

3339 def trace_selectorx(tr): 

3340 return tr.deltat >= min_deltat_allow \ 

3341 and trace_selector(tr) 

3342 else: 

3343 def trace_selectorx(tr): 

3344 return tr.deltat >= min_deltat_allow 

3345 

3346 for traces in self.pile.chopper( 

3347 tmin=tmin, tmax=tmax, tpad=tpad, 

3348 want_incomplete=True, 

3349 degap=degap, 

3350 maxgap=gap_lap_tolerance, 

3351 maxlap=gap_lap_tolerance, 

3352 keep_current_files_open=True, 

3353 group_selector=group_selector, 

3354 trace_selector=trace_selectorx, 

3355 accessor_id=id(self), 

3356 snap=(math.floor, math.ceil), 

3357 include_last=True): 

3358 

3359 if demean: 

3360 for tr in traces: 

3361 if (tr.meta and tr.meta.get('tabu', False)): 

3362 continue 

3363 y = tr.get_ydata() 

3364 tr.set_ydata(y - num.mean(y)) 

3365 

3366 traces = self.pre_process_hooks(traces) 

3367 

3368 for trace in traces: 

3369 

3370 if not (trace.meta 

3371 and trace.meta.get('tabu', False)): 

3372 

3373 if fft_filtering: 

3374 but = pyrocko.trace.ButterworthResponse 

3375 multres = pyrocko.trace.MultiplyResponse 

3376 if self.lowpass is not None \ 

3377 or self.highpass is not None: 

3378 

3379 it = num.arange( 

3380 trace.data_len(), dtype=float) 

3381 detr_data, m, b = detrend( 

3382 it, trace.get_ydata()) 

3383 

3384 trace.set_ydata(detr_data) 

3385 

3386 freqs, fdata = trace.spectrum( 

3387 pad_to_pow2=True, tfade=None) 

3388 

3389 nfreqs = fdata.size 

3390 

3391 key = (trace.deltat, nfreqs) 

3392 

3393 if key not in self.tf_cache: 

3394 resps = [] 

3395 if self.lowpass is not None: 

3396 resps.append(but( 

3397 order=4, 

3398 corner=self.lowpass, 

3399 type='low')) 

3400 

3401 if self.highpass is not None: 

3402 resps.append(but( 

3403 order=4, 

3404 corner=self.highpass, 

3405 type='high')) 

3406 

3407 resp = multres(resps) 

3408 self.tf_cache[key] = \ 

3409 resp.evaluate(freqs) 

3410 

3411 filtered_data = num.fft.irfft( 

3412 fdata*self.tf_cache[key] 

3413 )[:trace.data_len()] 

3414 

3415 retrended_data = retrend( 

3416 it, filtered_data, m, b) 

3417 

3418 trace.set_ydata(retrended_data) 

3419 

3420 else: 

3421 

3422 if ads and self.lowpass is not None: 

3423 while trace.deltat \ 

3424 < min_deltat_wo_decimate: 

3425 

3426 trace.downsample(2, demean=False) 

3427 

3428 fmax = 0.5/trace.deltat 

3429 if not lphp and ( 

3430 self.lowpass is not None 

3431 and self.highpass is not None 

3432 and self.lowpass < fmax 

3433 and self.highpass < fmax 

3434 and self.highpass < self.lowpass): 

3435 

3436 trace.bandpass( 

3437 2, self.highpass, self.lowpass) 

3438 else: 

3439 if self.lowpass is not None: 

3440 if self.lowpass < 0.5/trace.deltat: 

3441 trace.lowpass( 

3442 4, self.lowpass, 

3443 demean=False) 

3444 

3445 if self.highpass is not None: 

3446 if self.lowpass is None \ 

3447 or self.highpass \ 

3448 < self.lowpass: 

3449 

3450 if self.highpass < \ 

3451 0.5/trace.deltat: 

3452 trace.highpass( 

3453 4, self.highpass, 

3454 demean=False) 

3455 

3456 processed_traces.append(trace) 

3457 

3458 if self.rotate != 0.0: 

3459 phi = self.rotate/180.*math.pi 

3460 cphi = math.cos(phi) 

3461 sphi = math.sin(phi) 

3462 for a in processed_traces: 

3463 for b in processed_traces: 

3464 if (a.network == b.network 

3465 and a.station == b.station 

3466 and a.location == b.location 

3467 and ((a.channel.lower().endswith('n') 

3468 and b.channel.lower().endswith('e')) 

3469 or (a.channel.endswith('1') 

3470 and b.channel.endswith('2'))) 

3471 and abs(a.deltat-b.deltat) < a.deltat*0.001 

3472 and abs(a.tmin-b.tmin) < a.deltat*0.01 and 

3473 len(a.get_ydata()) == len(b.get_ydata())): 

3474 

3475 aydata = a.get_ydata()*cphi+b.get_ydata()*sphi 

3476 bydata = -a.get_ydata()*sphi+b.get_ydata()*cphi 

3477 a.set_ydata(aydata) 

3478 b.set_ydata(bydata) 

3479 

3480 processed_traces = self.post_process_hooks(processed_traces) 

3481 

3482 self.old_processed_traces = processed_traces 

3483 

3484 chopped_traces = [] 

3485 for trace in processed_traces: 

3486 try: 

3487 ctrace = trace.chop( 

3488 tmin_-trace.deltat*4., tmax_+trace.deltat*4., 

3489 inplace=False) 

3490 

3491 except pyrocko.trace.NoData: 

3492 continue 

3493 

3494 if ctrace.data_len() < 2: 

3495 continue 

3496 

3497 chopped_traces.append(ctrace) 

3498 

3499 self.timer_cutout.stop() 

3500 return chopped_traces 

3501 

3502 def pre_process_hooks(self, traces): 

3503 for snuffling in self.snufflings: 

3504 if snuffling._pre_process_hook_enabled: 

3505 traces = snuffling.pre_process_hook(traces) 

3506 

3507 return traces 

3508 

3509 def post_process_hooks(self, traces): 

3510 for snuffling in self.snufflings: 

3511 if snuffling._post_process_hook_enabled: 

3512 traces = snuffling.post_process_hook(traces) 

3513 

3514 return traces 

3515 

3516 def visible_length_change(self, ignore=None): 

3517 for menuitem, vlen in self.menuitems_visible_length: 

3518 if menuitem.isChecked(): 

3519 self.visible_length = vlen 

3520 

3521 def scaling_base_change(self, ignore=None): 

3522 for menuitem, scaling_base in self.menuitems_scaling_base: 

3523 if menuitem.isChecked(): 

3524 self.scaling_base = scaling_base 

3525 

3526 def scalingmode_change(self, ignore=None): 

3527 for menuitem, scaling_key in self.menuitems_scaling: 

3528 if menuitem.isChecked(): 

3529 self.scaling_key = scaling_key 

3530 

3531 def apply_scaling_hooks(self, data_ranges): 

3532 for k in sorted(self.scaling_hooks.keys()): 

3533 hook = self.scaling_hooks[k] 

3534 hook(data_ranges) 

3535 

3536 def set_scaling_hook(self, k, hook): 

3537 self.scaling_hooks[k] = hook 

3538 

3539 def remove_scaling_hook(self, k): 

3540 del self.scaling_hooks[k] 

3541 

3542 def remove_scaling_hooks(self): 

3543 self.scaling_hooks = {} 

3544 

3545 def s_sortingmode_change(self, ignore=None): 

3546 for menuitem, valfunc in self.menuitems_ssorting: 

3547 if menuitem.isChecked(): 

3548 self._ssort = valfunc 

3549 

3550 self.sortingmode_change() 

3551 

3552 def sortingmode_change(self, ignore=None): 

3553 for menuitem, (gather, order, color) in self.menuitems_sorting: 

3554 if menuitem.isChecked(): 

3555 self.set_gathering(gather, order, color) 

3556 

3557 self.sortingmode_change_time = time.time() 

3558 

3559 def lowpass_change(self, value, ignore=None): 

3560 self.lowpass = value 

3561 self.passband_check() 

3562 self.tf_cache = {} 

3563 self.update() 

3564 

3565 def highpass_change(self, value, ignore=None): 

3566 self.highpass = value 

3567 self.passband_check() 

3568 self.tf_cache = {} 

3569 self.update() 

3570 

3571 def passband_check(self): 

3572 if self.highpass and self.lowpass \ 

3573 and self.highpass >= self.lowpass: 

3574 

3575 self.message = 'Corner frequency of highpass larger than ' \ 

3576 'corner frequency of lowpass! I will now ' \ 

3577 'deactivate the highpass.' 

3578 

3579 self.update_status() 

3580 else: 

3581 oldmess = self.message 

3582 self.message = None 

3583 if oldmess is not None: 

3584 self.update_status() 

3585 

3586 def gain_change(self, value, ignore): 

3587 self.gain = value 

3588 self.update() 

3589 

3590 def rot_change(self, value, ignore): 

3591 self.rotate = value 

3592 self.update() 

3593 

3594 def set_selected_markers(self, markers): 

3595 ''' 

3596 Set a list of markers selected 

3597 

3598 :param markers: list of markers 

3599 ''' 

3600 self.deselect_all() 

3601 for m in markers: 

3602 m.selected = True 

3603 

3604 self.update() 

3605 

3606 def deselect_all(self): 

3607 for marker in self.markers: 

3608 marker.selected = False 

3609 

3610 def animate_picking(self): 

3611 point = self.mapFromGlobal(qg.QCursor.pos()) 

3612 self.update_picking(point.x(), point.y(), doshift=True) 

3613 

3614 def get_nslc_ids_for_track(self, ftrack): 

3615 itrack = int(ftrack) 

3616 return self.track_to_nslc_ids.get(itrack, []) 

3617 

3618 def stop_picking(self, x, y, abort=False): 

3619 if self.picking: 

3620 self.update_picking(x, y, doshift=False) 

3621 self.picking = None 

3622 self.picking_down = None 

3623 self.picking_timer.stop() 

3624 self.picking_timer = None 

3625 if not abort: 

3626 self.add_marker(self.floating_marker) 

3627 self.floating_marker.selected = True 

3628 self.emit_selected_markers() 

3629 

3630 self.floating_marker = None 

3631 

3632 def start_picking(self, ignore): 

3633 

3634 if not self.picking: 

3635 self.deselect_all() 

3636 self.picking = qw.QRubberBand(qw.QRubberBand.Rectangle) 

3637 point = self.mapFromGlobal(qg.QCursor.pos()) 

3638 

3639 gpoint = self.mapToGlobal(qc.QPoint(point.x(), 0)) 

3640 self.picking.setGeometry( 

3641 gpoint.x(), gpoint.y(), 1, self.height()) 

3642 t = self.time_projection.rev(point.x()) 

3643 

3644 ftrack = self.track_to_screen.rev(point.y()) 

3645 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

3646 self.floating_marker = Marker(nslc_ids, t, t) 

3647 self.floating_marker.selected = True 

3648 

3649 self.picking_timer = qc.QTimer() 

3650 self.picking_timer.timeout.connect( 

3651 self.animate_picking) 

3652 

3653 self.picking_timer.setInterval(50) 

3654 self.picking_timer.start() 

3655 

3656 def update_picking(self, x, y, doshift=False): 

3657 if self.picking: 

3658 mouset = self.time_projection.rev(x) 

3659 dt = 0.0 

3660 if mouset < self.tmin or mouset > self.tmax: 

3661 if mouset < self.tmin: 

3662 dt = -(self.tmin - mouset) 

3663 else: 

3664 dt = mouset - self.tmax 

3665 ddt = self.tmax-self.tmin 

3666 dt = max(dt, -ddt/10.) 

3667 dt = min(dt, ddt/10.) 

3668 

3669 x0 = x 

3670 if self.picking_down is not None: 

3671 x0 = self.time_projection(self.picking_down[0]) 

3672 

3673 w = abs(x-x0) 

3674 x0 = min(x0, x) 

3675 

3676 tmin, tmax = ( 

3677 self.time_projection.rev(x0), 

3678 self.time_projection.rev(x0+w)) 

3679 

3680 tmin, tmax = ( 

3681 max(working_system_time_range[0], tmin), 

3682 min(working_system_time_range[1], tmax)) 

3683 

3684 p1 = self.mapToGlobal(qc.QPoint(x0, 0)) 

3685 

3686 self.picking.setGeometry( 

3687 p1.x(), p1.y(), max(w, 1), self.height()) 

3688 

3689 ftrack = self.track_to_screen.rev(y) 

3690 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

3691 self.floating_marker.set(nslc_ids, tmin, tmax) 

3692 

3693 if dt != 0.0 and doshift: 

3694 self.interrupt_following() 

3695 self.set_time_range(self.tmin+dt, self.tmax+dt) 

3696 

3697 self.update() 

3698 

3699 def update_status(self): 

3700 

3701 if self.message is None: 

3702 point = self.mapFromGlobal(qg.QCursor.pos()) 

3703 

3704 mouse_t = self.time_projection.rev(point.x()) 

3705 if not is_working_time(mouse_t): 

3706 return 

3707 

3708 if self.floating_marker: 

3709 tmi, tma = ( 

3710 self.floating_marker.tmin, 

3711 self.floating_marker.tmax) 

3712 

3713 tt, ms = gmtime_x(tmi) 

3714 

3715 if tmi == tma: 

3716 message = mystrftime( 

3717 fmt='Pick: %Y-%m-%d %H:%M:%S .%r', 

3718 tt=tt, milliseconds=ms) 

3719 else: 

3720 srange = '%g s' % (tma-tmi) 

3721 message = mystrftime( 

3722 fmt='Start: %Y-%m-%d %H:%M:%S .%r Length: '+srange, 

3723 tt=tt, milliseconds=ms) 

3724 else: 

3725 tt, ms = gmtime_x(mouse_t) 

3726 

3727 message = mystrftime(fmt=None, tt=tt, milliseconds=ms) 

3728 else: 

3729 message = self.message 

3730 

3731 sb = self.window().statusBar() 

3732 sb.clearMessage() 

3733 sb.showMessage(message) 

3734 

3735 def set_sortingmode_change_delay_time(self, dt): 

3736 self.sortingmode_change_delay_time = dt 

3737 

3738 def sortingmode_change_delayed(self): 

3739 now = time.time() 

3740 return ( 

3741 self.sortingmode_change_delay_time is not None 

3742 and now - self.sortingmode_change_time 

3743 < self.sortingmode_change_delay_time) 

3744 

3745 def set_visible_marker_kinds(self, kinds): 

3746 self.deselect_all() 

3747 self.visible_marker_kinds = tuple(kinds) 

3748 self.emit_selected_markers() 

3749 

3750 def following(self): 

3751 return self.follow_timer is not None \ 

3752 and not self.following_interrupted() 

3753 

3754 def interrupt_following(self): 

3755 self.interactive_range_change_time = time.time() 

3756 

3757 def following_interrupted(self, now=None): 

3758 if now is None: 

3759 now = time.time() 

3760 return now - self.interactive_range_change_time \ 

3761 < self.interactive_range_change_delay_time 

3762 

3763 def follow(self, tlen, interval=50, lapse=None, tmax_start=None): 

3764 if tmax_start is None: 

3765 tmax_start = time.time() 

3766 self.show_all = False 

3767 self.follow_time = tlen 

3768 self.follow_timer = qc.QTimer(self) 

3769 self.follow_timer.timeout.connect( 

3770 self.follow_update) 

3771 self.follow_timer.setInterval(interval) 

3772 self.follow_timer.start() 

3773 self.follow_started = time.time() 

3774 self.follow_lapse = lapse 

3775 self.follow_tshift = self.follow_started - tmax_start 

3776 self.interactive_range_change_time = 0.0 

3777 

3778 def unfollow(self): 

3779 if self.follow_timer is not None: 

3780 self.follow_timer.stop() 

3781 self.follow_timer = None 

3782 self.interactive_range_change_time = 0.0 

3783 

3784 def follow_update(self): 

3785 rnow = time.time() 

3786 if self.follow_lapse is None: 

3787 now = rnow 

3788 else: 

3789 now = self.follow_started + (rnow - self.follow_started) \ 

3790 * self.follow_lapse 

3791 

3792 if self.following_interrupted(rnow): 

3793 return 

3794 self.set_time_range( 

3795 now-self.follow_time-self.follow_tshift, 

3796 now-self.follow_tshift) 

3797 

3798 self.update() 

3799 

3800 def myclose(self, return_tag=''): 

3801 self.return_tag = return_tag 

3802 self.window().close() 

3803 

3804 def cleanup(self): 

3805 self.about_to_close.emit() 

3806 self.timer.stop() 

3807 if self.follow_timer is not None: 

3808 self.follow_timer.stop() 

3809 

3810 for snuffling in list(self.snufflings): 

3811 self.remove_snuffling(snuffling) 

3812 

3813 def set_error_message(self, key, value): 

3814 if value is None: 

3815 if key in self.error_messages: 

3816 del self.error_messages[key] 

3817 else: 

3818 self.error_messages[key] = value 

3819 

3820 def inputline_changed(self, text): 

3821 pass 

3822 

3823 def inputline_finished(self, text): 

3824 line = str(text) 

3825 

3826 toks = line.split() 

3827 clearit, hideit, error = False, True, None 

3828 if len(toks) >= 1: 

3829 command = toks[0].lower() 

3830 

3831 try: 

3832 quick_filter_commands = { 

3833 'n': '%s.*.*.*', 

3834 's': '*.%s.*.*', 

3835 'l': '*.*.%s.*', 

3836 'c': '*.*.*.%s'} 

3837 

3838 if command in quick_filter_commands: 

3839 if len(toks) >= 2: 

3840 patterns = [ 

3841 quick_filter_commands[toks[0]] % pat 

3842 for pat in toks[1:]] 

3843 self.set_quick_filter_patterns(patterns, line) 

3844 else: 

3845 self.set_quick_filter_patterns(None) 

3846 

3847 self.update() 

3848 

3849 elif command in ('hide', 'unhide'): 

3850 if len(toks) >= 2: 

3851 patterns = [] 

3852 if len(toks) == 2: 

3853 patterns = [toks[1]] 

3854 elif len(toks) >= 3: 

3855 x = { 

3856 'n': '%s.*.*.*', 

3857 's': '*.%s.*.*', 

3858 'l': '*.*.%s.*', 

3859 'c': '*.*.*.%s'} 

3860 

3861 if toks[1] in x: 

3862 patterns.extend( 

3863 x[toks[1]] % tok for tok in toks[2:]) 

3864 

3865 for pattern in patterns: 

3866 if command == 'hide': 

3867 self.add_blacklist_pattern(pattern) 

3868 else: 

3869 self.remove_blacklist_pattern(pattern) 

3870 

3871 elif command == 'unhide' and len(toks) == 1: 

3872 self.clear_blacklist() 

3873 

3874 clearit = True 

3875 

3876 self.update() 

3877 

3878 elif command == 'markers': 

3879 if len(toks) == 2: 

3880 if toks[1] == 'all': 

3881 kinds = self.all_marker_kinds 

3882 else: 

3883 kinds = [] 

3884 for x in toks[1]: 

3885 try: 

3886 kinds.append(int(x)) 

3887 except Exception: 

3888 pass 

3889 

3890 self.set_visible_marker_kinds(kinds) 

3891 

3892 elif len(toks) == 1: 

3893 self.set_visible_marker_kinds(()) 

3894 

3895 self.update() 

3896 

3897 elif command == 'scaling': 

3898 if len(toks) == 2: 

3899 hideit = False 

3900 error = 'wrong number of arguments' 

3901 

3902 if len(toks) >= 3: 

3903 vmin, vmax = [ 

3904 pyrocko.model.float_or_none(x) 

3905 for x in toks[-2:]] 

3906 

3907 def upd(d, k, vmin, vmax): 

3908 if k in d: 

3909 if vmin is not None: 

3910 d[k] = vmin, d[k][1] 

3911 if vmax is not None: 

3912 d[k] = d[k][0], vmax 

3913 

3914 if len(toks) == 1: 

3915 self.remove_scaling_hooks() 

3916 

3917 elif len(toks) == 3: 

3918 def hook(data_ranges): 

3919 for k in data_ranges: 

3920 upd(data_ranges, k, vmin, vmax) 

3921 

3922 self.set_scaling_hook('_', hook) 

3923 

3924 elif len(toks) == 4: 

3925 pattern = toks[1] 

3926 

3927 def hook(data_ranges): 

3928 for k in pyrocko.util.match_nslcs( 

3929 pattern, list(data_ranges.keys())): 

3930 

3931 upd(data_ranges, k, vmin, vmax) 

3932 

3933 self.set_scaling_hook(pattern, hook) 

3934 

3935 elif command == 'goto': 

3936 toks2 = line.split(None, 1) 

3937 if len(toks2) == 2: 

3938 arg = toks2[1] 

3939 m = re.match( 

3940 r'^\d\d\d\d(-\d\d(-\d\d( \d\d(:\d\d' 

3941 r'(:\d\d(\.\d+)?)?)?)?)?)?$', arg) 

3942 if m: 

3943 tlen = None 

3944 if not m.group(1): 

3945 tlen = 12*32*24*60*60 

3946 elif not m.group(2): 

3947 tlen = 32*24*60*60 

3948 elif not m.group(3): 

3949 tlen = 24*60*60 

3950 elif not m.group(4): 

3951 tlen = 60*60 

3952 elif not m.group(5): 

3953 tlen = 60 

3954 

3955 supl = '1970-01-01 00:00:00' 

3956 if len(supl) > len(arg): 

3957 arg = arg + supl[-(len(supl)-len(arg)):] 

3958 t = pyrocko.util.str_to_time(arg) 

3959 self.go_to_time(t, tlen=tlen) 

3960 

3961 elif re.match(r'^\d\d:\d\d(:\d\d(\.\d+)?)?$', arg): 

3962 supl = '00:00:00' 

3963 if len(supl) > len(arg): 

3964 arg = arg + supl[-(len(supl)-len(arg)):] 

3965 tmin, tmax = self.get_time_range() 

3966 sdate = pyrocko.util.time_to_str( 

3967 tmin/2.+tmax/2., format='%Y-%m-%d') 

3968 t = pyrocko.util.str_to_time(sdate + ' ' + arg) 

3969 self.go_to_time(t) 

3970 

3971 else: 

3972 self.go_to_event_by_name(arg) 

3973 

3974 else: 

3975 raise PileViewerMainException( 

3976 'No such command: %s' % command) 

3977 

3978 except PileViewerMainException as e: 

3979 error = str(e) 

3980 hideit = False 

3981 

3982 return clearit, hideit, error 

3983 

3984 return PileViewerMain 

3985 

3986 

3987PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

3988GLPileViewerMain = MakePileViewerMainClass(qgl.QGLWidget) 

3989 

3990 

3991class LineEditWithAbort(qw.QLineEdit): 

3992 

3993 aborted = qc.pyqtSignal() 

3994 history_down = qc.pyqtSignal() 

3995 history_up = qc.pyqtSignal() 

3996 

3997 def keyPressEvent(self, key_event): 

3998 if key_event.key() == qc.Qt.Key_Escape: 

3999 self.aborted.emit() 

4000 elif key_event.key() == qc.Qt.Key_Down: 

4001 self.history_down.emit() 

4002 elif key_event.key() == qc.Qt.Key_Up: 

4003 self.history_up.emit() 

4004 else: 

4005 return qw.QLineEdit.keyPressEvent(self, key_event) 

4006 

4007 

4008class PileViewer(qw.QFrame): 

4009 ''' 

4010 PileViewerMain + Controls + Inputline 

4011 ''' 

4012 

4013 def __init__( 

4014 self, pile, 

4015 ntracks_shown_max=20, 

4016 marker_editor_sortable=True, 

4017 use_opengl=False, 

4018 panel_parent=None, 

4019 *args): 

4020 

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

4022 

4023 if use_opengl: 

4024 self.viewer = GLPileViewerMain( 

4025 pile, 

4026 ntracks_shown_max=ntracks_shown_max, 

4027 panel_parent=panel_parent) 

4028 else: 

4029 self.viewer = PileViewerMain( 

4030 pile, 

4031 ntracks_shown_max=ntracks_shown_max, 

4032 panel_parent=panel_parent) 

4033 

4034 self.marker_editor_sortable = marker_editor_sortable 

4035 

4036 layout = qw.QGridLayout() 

4037 self.setLayout(layout) 

4038 layout.setContentsMargins(0, 0, 0, 0) 

4039 layout.setSpacing(0) 

4040 

4041 self.setFrameShape(qw.QFrame.StyledPanel) 

4042 self.setFrameShadow(qw.QFrame.Sunken) 

4043 

4044 self.input_area = qw.QFrame(self) 

4045 ia_layout = qw.QGridLayout() 

4046 ia_layout.setContentsMargins(11, 11, 11, 11) 

4047 self.input_area.setLayout(ia_layout) 

4048 

4049 self.inputline = LineEditWithAbort(self.input_area) 

4050 self.inputline.returnPressed.connect( 

4051 self.inputline_returnpressed) 

4052 self.inputline.editingFinished.connect( 

4053 self.inputline_finished) 

4054 self.inputline.aborted.connect( 

4055 self.inputline_aborted) 

4056 

4057 self.inputline.history_down.connect( 

4058 lambda: self.step_through_history(1)) 

4059 self.inputline.history_up.connect( 

4060 lambda: self.step_through_history(-1)) 

4061 

4062 self.inputline.textEdited.connect( 

4063 self.inputline_changed) 

4064 

4065 self.inputline.setFocusPolicy(qc.Qt.ClickFocus) 

4066 self.input_area.hide() 

4067 self.history = None 

4068 

4069 self.inputline_error_str = None 

4070 

4071 self.inputline_error = qw.QLabel() 

4072 self.inputline_error.hide() 

4073 

4074 ia_layout.addWidget(self.inputline, 0, 0) 

4075 ia_layout.addWidget(self.inputline_error, 1, 0) 

4076 layout.addWidget(self.input_area, 0, 0, 1, 2) 

4077 layout.addWidget(self.viewer, 1, 0) 

4078 

4079 pb = Progressbars(self) 

4080 layout.addWidget(pb, 2, 0, 1, 2) 

4081 self.progressbars = pb 

4082 

4083 scrollbar = qw.QScrollBar(qc.Qt.Vertical) 

4084 self.scrollbar = scrollbar 

4085 layout.addWidget(scrollbar, 1, 1) 

4086 self.scrollbar.valueChanged.connect( 

4087 self.scrollbar_changed) 

4088 

4089 self.block_scrollbar_changes = False 

4090 

4091 self.viewer.want_input.connect( 

4092 self.inputline_show) 

4093 self.viewer.tracks_range_changed.connect( 

4094 self.tracks_range_changed) 

4095 self.viewer.pile_has_changed_signal.connect( 

4096 self.adjust_controls) 

4097 self.viewer.about_to_close.connect( 

4098 self.save_inputline_history) 

4099 

4100 def cleanup(self): 

4101 self.viewer.cleanup() 

4102 

4103 def get_progressbars(self): 

4104 return self.progressbars 

4105 

4106 def inputline_show(self): 

4107 if not self.history: 

4108 self.load_inputline_history() 

4109 

4110 self.input_area.show() 

4111 self.inputline.setFocus(qc.Qt.OtherFocusReason) 

4112 self.inputline.selectAll() 

4113 

4114 def inputline_set_error(self, string): 

4115 self.inputline_error_str = string 

4116 self.inputline.setPalette(pyrocko.gui.util.get_err_palette()) 

4117 self.inputline.selectAll() 

4118 self.inputline_error.setText(string) 

4119 self.input_area.show() 

4120 self.inputline_error.show() 

4121 

4122 def inputline_clear_error(self): 

4123 if self.inputline_error_str: 

4124 self.inputline.setPalette(qw.QApplication.palette()) 

4125 self.inputline_error_str = None 

4126 self.inputline_error.clear() 

4127 self.inputline_error.hide() 

4128 

4129 def inputline_changed(self, line): 

4130 self.viewer.inputline_changed(str(line)) 

4131 self.inputline_clear_error() 

4132 

4133 def inputline_returnpressed(self): 

4134 line = str(self.inputline.text()) 

4135 clearit, hideit, error = self.viewer.inputline_finished(line) 

4136 

4137 if error: 

4138 self.inputline_set_error(error) 

4139 

4140 line = line.strip() 

4141 

4142 if line != '' and not error: 

4143 if not (len(self.history) >= 1 and line == self.history[-1]): 

4144 self.history.append(line) 

4145 

4146 if clearit: 

4147 

4148 self.inputline.blockSignals(True) 

4149 qpat, qinp = self.viewer.get_quick_filter_patterns() 

4150 if qpat is None: 

4151 self.inputline.clear() 

4152 else: 

4153 self.inputline.setText(qinp) 

4154 self.inputline.blockSignals(False) 

4155 

4156 if hideit and not error: 

4157 self.viewer.setFocus(qc.Qt.OtherFocusReason) 

4158 self.input_area.hide() 

4159 

4160 self.hist_ind = len(self.history) 

4161 

4162 def inputline_aborted(self): 

4163 ''' 

4164 Hide the input line. 

4165 ''' 

4166 self.viewer.setFocus(qc.Qt.OtherFocusReason) 

4167 self.hist_ind = len(self.history) 

4168 self.input_area.hide() 

4169 

4170 def save_inputline_history(self): 

4171 ''' 

4172 Save input line history to "$HOME/.pyrocko/.snuffler_history.pf" 

4173 ''' 

4174 if not self.history: 

4175 return 

4176 

4177 conf = pyrocko.config 

4178 fn_hist = conf.expand(conf.make_conf_path_tmpl('.snuffler_history')) 

4179 with open(fn_hist, 'w') as f: 

4180 i = min(100, len(self.history)) 

4181 for c in self.history[-i:]: 

4182 f.write('%s\n' % c) 

4183 

4184 def load_inputline_history(self): 

4185 ''' 

4186 Load input line history from "$HOME/.pyrocko/.snuffler_history.pf" 

4187 ''' 

4188 conf = pyrocko.config 

4189 fn_hist = conf.expand(conf.make_conf_path_tmpl('.snuffler_history')) 

4190 if not os.path.exists(fn_hist): 

4191 with open(fn_hist, 'w+') as f: 

4192 f.write('\n') 

4193 

4194 with open(fn_hist, 'r') as f: 

4195 self.history = [line.strip() for line in f.readlines()] 

4196 

4197 self.hist_ind = len(self.history) 

4198 

4199 def step_through_history(self, ud=1): 

4200 ''' 

4201 Step through input line history and set the input line text. 

4202 ''' 

4203 n = len(self.history) 

4204 self.hist_ind += ud 

4205 self.hist_ind %= (n + 1) 

4206 if len(self.history) != 0 and self.hist_ind != n: 

4207 self.inputline.setText(self.history[self.hist_ind]) 

4208 else: 

4209 self.inputline.setText('') 

4210 

4211 def inputline_finished(self): 

4212 pass 

4213 

4214 def tracks_range_changed(self, ntracks, ilo, ihi): 

4215 if self.block_scrollbar_changes: 

4216 return 

4217 

4218 self.scrollbar.blockSignals(True) 

4219 self.scrollbar.setPageStep(ihi-ilo) 

4220 vmax = max(0, ntracks-(ihi-ilo)) 

4221 self.scrollbar.setRange(0, vmax) 

4222 self.scrollbar.setValue(ilo) 

4223 self.scrollbar.setHidden(vmax == 0) 

4224 self.scrollbar.blockSignals(False) 

4225 

4226 def scrollbar_changed(self, value): 

4227 self.block_scrollbar_changes = True 

4228 ilo = value 

4229 ihi = ilo + self.scrollbar.pageStep() 

4230 self.viewer.set_tracks_range((ilo, ihi)) 

4231 self.block_scrollbar_changes = False 

4232 self.update_contents() 

4233 

4234 def controls(self): 

4235 frame = qw.QFrame(self) 

4236 layout = qw.QGridLayout() 

4237 frame.setLayout(layout) 

4238 

4239 minfreq = 0.001 

4240 maxfreq = 1000.0 

4241 self.lowpass_control = ValControl(high_is_none=True) 

4242 self.lowpass_control.setup( 

4243 'Lowpass [Hz]:', minfreq, maxfreq, maxfreq, 0) 

4244 self.highpass_control = ValControl(low_is_none=True) 

4245 self.highpass_control.setup( 

4246 'Highpass [Hz]:', minfreq, maxfreq, minfreq, 1) 

4247 self.gain_control = ValControl() 

4248 self.gain_control.setup('Gain:', 0.001, 1000., 1., 2) 

4249 self.rot_control = LinValControl() 

4250 self.rot_control.setup('Rotate [deg]:', -180., 180., 0., 3) 

4251 

4252 self.lowpass_control.valchange.connect( 

4253 self.viewer.lowpass_change) 

4254 self.highpass_control.valchange.connect( 

4255 self.viewer.highpass_change) 

4256 self.gain_control.valchange.connect( 

4257 self.viewer.gain_change) 

4258 self.rot_control.valchange.connect( 

4259 self.viewer.rot_change) 

4260 

4261 for icontrol, control in enumerate(( 

4262 self.highpass_control, 

4263 self.lowpass_control, 

4264 self.gain_control, 

4265 self.rot_control)): 

4266 

4267 for iwidget, widget in enumerate(control.widgets()): 

4268 layout.addWidget(widget, icontrol, iwidget) 

4269 

4270 spacer = qw.QSpacerItem( 

4271 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

4272 layout.addItem(spacer, 4, 0, 1, 3) 

4273 

4274 self.adjust_controls() 

4275 return frame 

4276 

4277 def marker_editor(self): 

4278 editor = pyrocko.gui.marker_editor.MarkerEditor( 

4279 self, sortable=self.marker_editor_sortable) 

4280 

4281 editor.set_viewer(self.get_view()) 

4282 editor.get_marker_model().dataChanged.connect( 

4283 self.update_contents) 

4284 return editor 

4285 

4286 def adjust_controls(self): 

4287 dtmin, dtmax = self.viewer.content_deltat_range() 

4288 maxfreq = 0.5/dtmin 

4289 minfreq = (0.5/dtmax)*0.001 

4290 self.lowpass_control.set_range(minfreq, maxfreq) 

4291 self.highpass_control.set_range(minfreq, maxfreq) 

4292 

4293 def setup_snufflings(self): 

4294 self.viewer.setup_snufflings() 

4295 

4296 def get_view(self): 

4297 return self.viewer 

4298 

4299 def update_contents(self): 

4300 self.viewer.update() 

4301 

4302 def get_pile(self): 

4303 return self.viewer.get_pile()