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 

17import enum 

18from itertools import groupby 

19 

20import numpy as num 

21import pyrocko.model 

22import pyrocko.pile 

23import pyrocko.trace 

24import pyrocko.response 

25import pyrocko.util 

26import pyrocko.plot 

27import pyrocko.gui.snuffling 

28import pyrocko.gui.snufflings 

29import pyrocko.gui.marker_editor 

30 

31from pyrocko.util import hpfloat, gmtime_x, mystrftime 

32 

33from .marker import associate_phases_to_events, MarkerOneNSLCRequired 

34 

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

36 PhaseMarker, make_QPolygonF, draw_label, Label, 

37 Progressbars, ColorbarControl) 

38 

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

40 

41from .pile_viewer_waterfall import TraceWaterfall 

42 

43import scipy.stats as sstats 

44import platform 

45 

46MIN_LABEL_SIZE_PT = 6 

47 

48try: 

49 newstr = unicode 

50except NameError: 

51 newstr = str 

52 

53 

54def fnpatch(x): 

55 if use_pyqt5: 

56 return x 

57 else: 

58 return x, None 

59 

60 

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

62 qc.QString = str 

63 

64qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \ 

65 qw.QFileDialog.DontUseSheet 

66 

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

68 macosx = True 

69else: 

70 macosx = False 

71 

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

73 

74 

75def detrend(x, y): 

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

77 y_detrended = y - slope * x - offset 

78 return y_detrended, slope, offset 

79 

80 

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

82 return x * slope + y_detrended + offset 

83 

84 

85class Global(object): 

86 appOnDemand = None 

87 

88 

89class NSLC(object): 

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

91 self.network = n 

92 self.station = s 

93 self.location = l 

94 self.channel = c 

95 

96 

97class m_float(float): 

98 

99 def __str__(self): 

100 if abs(self) >= 10000.: 

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

102 elif abs(self) >= 1000.: 

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

104 else: 

105 return '%.5g m' % self 

106 

107 def __lt__(self, other): 

108 if other is None: 

109 return True 

110 return float(self) < float(other) 

111 

112 def __gt__(self, other): 

113 if other is None: 

114 return False 

115 return float(self) > float(other) 

116 

117 

118def m_float_or_none(x): 

119 if x is None: 

120 return None 

121 else: 

122 return m_float(x) 

123 

124 

125def make_chunks(items): 

126 ''' 

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

128 ''' 

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

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

131 

132 

133class deg_float(float): 

134 

135 def __str__(self): 

136 return '%4.0f' % self 

137 

138 

139def deg_float_or_none(x): 

140 if x is None: 

141 return None 

142 else: 

143 return deg_float(x) 

144 

145 

146class sector_int(int): 

147 

148 def __str__(self): 

149 return '[%i]' % self 

150 

151 

152def num_to_html(num): 

153 snum = '%g' % num 

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

155 if m: 

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

157 

158 return snum 

159 

160 

161gap_lap_tolerance = 5. 

162 

163 

164class ViewMode(enum.Enum): 

165 Wiggle = 1 

166 Waterfall = 2 

167 

168 

169class Timer(object): 

170 def __init__(self): 

171 self._start = None 

172 self._stop = None 

173 

174 def start(self): 

175 self._start = os.times() 

176 

177 def stop(self): 

178 self._stop = os.times() 

179 

180 def get(self): 

181 a = self._start 

182 b = self._stop 

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

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

185 else: 

186 return tuple([0.] * 5) 

187 

188 def __sub__(self, other): 

189 a = self.get() 

190 b = other.get() 

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

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 

211box_styles_coverage = [ 

212 ObjectStyle( 

213 qg.QPen( 

214 qg.QColor(*pyrocko.plot.tango_colors['aluminium3']), 

215 1, qc.Qt.DashLine), 

216 qg.QBrush(qg.QColor( 

217 *(pyrocko.plot.tango_colors['aluminium1'] + (50,)))), 

218 ), 

219 ObjectStyle( 

220 qg.QPen(qg.QColor(*pyrocko.plot.tango_colors['aluminium4'])), 

221 qg.QBrush(qg.QColor( 

222 *(pyrocko.plot.tango_colors['aluminium2'] + (50,)))), 

223 ), 

224 ObjectStyle( 

225 qg.QPen(qg.QColor(*pyrocko.plot.tango_colors['plum3'])), 

226 qg.QBrush(qg.QColor( 

227 *(pyrocko.plot.tango_colors['plum1'] + (50,)))), 

228 )] 

229 

230sday = 60*60*24. # \ 

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

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

233 

234acceptable_tincs = num.array([ 

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

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

237 

238 

239working_system_time_range = \ 

240 pyrocko.util.working_system_time_range() 

241 

242initial_time_range = [] 

243 

244try: 

245 initial_time_range.append( 

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

247except Exception: 

248 initial_time_range.append(working_system_time_range[0]) 

249 

250try: 

251 initial_time_range.append( 

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

253except Exception: 

254 initial_time_range.append(working_system_time_range[1]) 

255 

256 

257def is_working_time(t): 

258 return working_system_time_range[0] <= t and \ 

259 t <= working_system_time_range[1] 

260 

261 

262def fancy_time_ax_format(inc): 

263 l0_fmt_brief = '' 

264 l2_fmt = '' 

265 l2_trig = 0 

266 if inc < 0.000001: 

267 l0_fmt = '.%n' 

268 l0_center = False 

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

270 l1_trig = 6 

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

272 l2_trig = 3 

273 elif inc < 0.001: 

274 l0_fmt = '.%u' 

275 l0_center = False 

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

277 l1_trig = 6 

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

279 l2_trig = 3 

280 elif inc < 1: 

281 l0_fmt = '.%r' 

282 l0_center = False 

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

284 l1_trig = 6 

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

286 l2_trig = 3 

287 elif inc < 60: 

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

289 l0_center = False 

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

291 l1_trig = 3 

292 elif inc < 3600: 

293 l0_fmt = '%H:%M' 

294 l0_center = False 

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

296 l1_trig = 3 

297 elif inc < sday: 

298 l0_fmt = '%H:%M' 

299 l0_center = False 

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

301 l1_trig = 3 

302 elif inc < smonth: 

303 l0_fmt = '%a %d' 

304 l0_fmt_brief = '%d' 

305 l0_center = True 

306 l1_fmt = '%b, %Y' 

307 l1_trig = 2 

308 elif inc < syear: 

309 l0_fmt = '%b' 

310 l0_center = True 

311 l1_fmt = '%Y' 

312 l1_trig = 1 

313 else: 

314 l0_fmt = '%Y' 

315 l0_center = False 

316 l1_fmt = '' 

317 l1_trig = 0 

318 

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

320 

321 

322def day_start(timestamp): 

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

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

325 return calendar.timegm(tts) 

326 

327 

328def month_start(timestamp): 

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

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

331 return calendar.timegm(tts) 

332 

333 

334def year_start(timestamp): 

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

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

337 return calendar.timegm(tts) 

338 

339 

340def time_nice_value(inc0): 

341 if inc0 < acceptable_tincs[0]: 

342 return pyrocko.plot.nice_value(inc0) 

343 elif inc0 > acceptable_tincs[-1]: 

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

345 else: 

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

347 return acceptable_tincs[i] 

348 

349 

350class TimeScaler(pyrocko.plot.AutoScaler): 

351 def __init__(self): 

352 pyrocko.plot.AutoScaler.__init__(self) 

353 self.mode = 'min-max' 

354 

355 def make_scale(self, data_range): 

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

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

358 

359 data_min = min(data_range) 

360 data_max = max(data_range) 

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

362 

363 mi, ma = data_min, data_max 

364 nmi = mi 

365 if self.mode != 'off': 

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

367 

368 nma = ma 

369 if self.mode != 'off': 

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

371 

372 mi, ma = nmi, nma 

373 

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

375 mi -= 1.0 

376 ma += 1.0 

377 

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

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

380 

381 # make nice tick increment 

382 if self.inc is not None: 

383 inc = self.inc 

384 else: 

385 if self.approx_ticks > 0.: 

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

387 else: 

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

389 

390 if inc == 0.0: 

391 inc = 1.0 

392 

393 if is_reverse: 

394 return ma, mi, -inc 

395 else: 

396 return mi, ma, inc 

397 

398 def make_ticks(self, data_range): 

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

400 

401 is_reverse = False 

402 if inc < 0: 

403 mi, ma, inc = ma, mi, -inc 

404 is_reverse = True 

405 

406 ticks = [] 

407 

408 if inc < sday: 

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

410 if inc < 0.001: 

411 mi_day = hpfloat(mi_day) 

412 

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

414 if inc < 0.001: 

415 base = hpfloat(base) 

416 

417 base_day = mi_day 

418 i = 0 

419 while True: 

420 tick = base+i*inc 

421 if tick > ma: 

422 break 

423 

424 tick_day = day_start(tick) 

425 if tick_day > base_day: 

426 base_day = tick_day 

427 base = base_day 

428 i = 0 

429 else: 

430 ticks.append(tick) 

431 i += 1 

432 

433 elif inc < smonth: 

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

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

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

437 if mi_day == mi: 

438 dt_base += delta 

439 i = 0 

440 while True: 

441 current = dt_base + i*delta 

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

443 if tick > ma: 

444 break 

445 ticks.append(tick) 

446 i += 1 

447 

448 elif inc < syear: 

449 mi_month = month_start(max( 

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

451 

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

453 while True: 

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

455 m += 1 

456 if m > 12: 

457 y, m = y+1, 1 

458 

459 if tick > ma: 

460 break 

461 

462 if tick >= mi: 

463 ticks.append(tick) 

464 

465 else: 

466 mi_year = year_start(max( 

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

468 

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

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

471 

472 while True: 

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

474 y += incy 

475 if tick > ma: 

476 break 

477 if tick >= mi: 

478 ticks.append(tick) 

479 

480 if is_reverse: 

481 ticks.reverse() 

482 

483 return ticks, inc 

484 

485 

486def need_l1_tick(tt, ms, l1_trig): 

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

488 

489 

490def tick_to_labels(tick, inc): 

491 tt, ms = gmtime_x(tick) 

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

493 fancy_time_ax_format(inc) 

494 

495 l0 = mystrftime(l0_fmt, tt, ms) 

496 l0_brief = mystrftime(l0_fmt_brief, tt, ms) 

497 l1, l2 = None, None 

498 if need_l1_tick(tt, ms, l1_trig): 

499 l1 = mystrftime(l1_fmt, tt, ms) 

500 if need_l1_tick(tt, ms, l2_trig): 

501 l2 = mystrftime(l2_fmt, tt, ms) 

502 

503 return l0, l0_brief, l0_center, l1, l2 

504 

505 

506def l1_l2_tick(tick, inc): 

507 tt, ms = gmtime_x(tick) 

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

509 fancy_time_ax_format(inc) 

510 

511 l1 = mystrftime(l1_fmt, tt, ms) 

512 l2 = mystrftime(l2_fmt, tt, ms) 

513 return l1, l2 

514 

515 

516class TimeAx(TimeScaler): 

517 def __init__(self, *args): 

518 TimeScaler.__init__(self, *args) 

519 

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

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

522 p.setPen(pen) 

523 font = qg.QFont() 

524 font.setBold(True) 

525 p.setFont(font) 

526 fm = p.fontMetrics() 

527 ticklen = 10 

528 pad = 10 

529 tmin, tmax = xprojection.get_in_range() 

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

531 l1_hits = 0 

532 l2_hits = 0 

533 

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

535 uumin, uumax = xprojection.get_out_range() 

536 first_tick_with_label = None 

537 for tick in ticks: 

538 umin = xprojection(tick) 

539 

540 umin_approx_next = xprojection(tick+inc) 

541 umax = xprojection(tick) 

542 

543 pinc_approx = umin_approx_next - umin 

544 

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

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

547 

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

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

550 if l2: 

551 l2 = None 

552 elif l1: 

553 l1 = None 

554 

555 if l0_center: 

556 ushift = (umin_approx_next-umin)/2. 

557 else: 

558 ushift = 0. 

559 

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

561 label0 = l0x 

562 rect0 = fm.boundingRect(label0) 

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

564 break 

565 

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

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

568 

569 if first_tick_with_label is None: 

570 first_tick_with_label = tick 

571 p.drawText(qc.QPointF( 

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

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

574 

575 if l1: 

576 label1 = l1 

577 rect1 = fm.boundingRect(label1) 

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

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

580 

581 p.drawText(qc.QPointF( 

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

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

584 label1) 

585 

586 l1_hits += 1 

587 

588 if l2: 

589 label2 = l2 

590 rect2 = fm.boundingRect(label2) 

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

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

593 

594 p.drawText(qc.QPointF( 

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

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

597 ticklen), label2) 

598 

599 l2_hits += 1 

600 

601 if first_tick_with_label is None: 

602 first_tick_with_label = tmin 

603 

604 l1, l2 = l1_l2_tick(first_tick_with_label, inc) 

605 

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

607 tmax - tmin < 3600*24: 

608 

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

610 if l2: 

611 l2 = None 

612 elif l1: 

613 l1 = None 

614 

615 if l1_hits == 0 and l1: 

616 label1 = l1 

617 rect1 = fm.boundingRect(label1) 

618 p.drawText(qc.QPointF( 

619 uumin+pad, 

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

621 label1) 

622 

623 l1_hits += 1 

624 

625 if l2_hits == 0 and l2: 

626 label2 = l2 

627 rect2 = fm.boundingRect(label2) 

628 p.drawText(qc.QPointF( 

629 uumin+pad, 

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

631 label2) 

632 

633 v = yprojection(0) 

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

635 

636 

637class Projection(object): 

638 def __init__(self): 

639 self.xr = 0., 1. 

640 self.ur = 0., 1. 

641 

642 def set_in_range(self, xmin, xmax): 

643 if xmax == xmin: 

644 xmax = xmin + 1. 

645 

646 self.xr = xmin, xmax 

647 

648 def get_in_range(self): 

649 return self.xr 

650 

651 def set_out_range(self, umin, umax): 

652 if umax == umin: 

653 umax = umin + 1. 

654 

655 self.ur = umin, umax 

656 

657 def get_out_range(self): 

658 return self.ur 

659 

660 def __call__(self, x): 

661 umin, umax = self.ur 

662 xmin, xmax = self.xr 

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

664 

665 def clipped(self, x): 

666 umin, umax = self.ur 

667 xmin, xmax = self.xr 

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

669 

670 def rev(self, u): 

671 umin, umax = self.ur 

672 xmin, xmax = self.xr 

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

674 

675 def copy(self): 

676 return copy.copy(self) 

677 

678 

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

680 group = qw.QActionGroup(menu) 

681 group.setExclusive(True) 

682 menuitems = [] 

683 

684 for name, value, *shortcut in menudef: 

685 action = menu.addAction(name) 

686 action.setCheckable(True) 

687 action.setActionGroup(group) 

688 if shortcut: 

689 action.setShortcut(shortcut[0]) 

690 

691 menuitems.append((action, value)) 

692 if default is not None and ( 

693 name.lower().replace(' ', '_') == default or 

694 value == default): 

695 action.setChecked(True) 

696 

697 group.triggered.connect(target) 

698 

699 if default is None: 

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

701 

702 return menuitems 

703 

704 

705def sort_actions(menu): 

706 actions = [act for act in menu.actions() if not act.menu()] 

707 for action in actions: 

708 menu.removeAction(action) 

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

710 

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

712 if help_action: 

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

714 for action in actions: 

715 menu.addAction(action) 

716 

717 

718fkey_map = dict(zip( 

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

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

721 qc.Qt.Key_F11, qc.Qt.Key_F12), 

722 range(12))) 

723 

724 

725class PileViewerMainException(Exception): 

726 pass 

727 

728 

729class PileViewerMenuBar(qw.QMenuBar): 

730 ... 

731 

732 

733class PileViewerMenu(qw.QMenu): 

734 ... 

735 

736 

737def MakePileViewerMainClass(base): 

738 

739 class PileViewerMain(base): 

740 

741 want_input = qc.pyqtSignal() 

742 about_to_close = qc.pyqtSignal() 

743 pile_has_changed_signal = qc.pyqtSignal() 

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

745 

746 begin_markers_add = qc.pyqtSignal(int, int) 

747 end_markers_add = qc.pyqtSignal() 

748 begin_markers_remove = qc.pyqtSignal(int, int) 

749 end_markers_remove = qc.pyqtSignal() 

750 

751 marker_selection_changed = qc.pyqtSignal(list) 

752 active_event_marker_changed = qc.pyqtSignal() 

753 

754 def __init__(self, pile, ntracks_shown_max, panel_parent, *args, 

755 menu=None): 

756 if base == qgl.QGLWidget: 

757 from OpenGL import GL # noqa 

758 

759 base.__init__( 

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

761 else: 

762 base.__init__(self, *args) 

763 

764 self.pile = pile 

765 self.ax_height = 80 

766 self.panel_parent = panel_parent 

767 

768 self.click_tolerance = 5 

769 

770 self.ntracks_shown_max = ntracks_shown_max 

771 self.initial_ntracks_shown_max = ntracks_shown_max 

772 self.ntracks = 0 

773 self.show_all = True 

774 self.shown_tracks_range = None 

775 self.track_start = None 

776 self.track_trange = None 

777 

778 self.lowpass = None 

779 self.highpass = None 

780 self.gain = 1.0 

781 self.rotate = 0.0 

782 self.picking_down = None 

783 self.picking = None 

784 self.floating_marker = None 

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

786 self.markers_deltat_max = 0. 

787 self.n_selected_markers = 0 

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

789 self.visible_marker_kinds = self.all_marker_kinds 

790 self.active_event_marker = None 

791 self.ignore_releases = 0 

792 self.message = None 

793 self.reloaded = False 

794 self.pile_has_changed = False 

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

796 

797 self.tax = TimeAx() 

798 self.setBackgroundRole(qg.QPalette.Base) 

799 self.setAutoFillBackground(True) 

800 poli = qw.QSizePolicy( 

801 qw.QSizePolicy.Expanding, 

802 qw.QSizePolicy.Expanding) 

803 

804 self.setSizePolicy(poli) 

805 self.setMinimumSize(300, 200) 

806 self.setFocusPolicy(qc.Qt.ClickFocus) 

807 

808 self.menu = menu or PileViewerMenu(self) 

809 

810 file_menu = self.menu.addMenu('&File') 

811 view_menu = self.menu.addMenu('&View') 

812 options_menu = self.menu.addMenu('&Options') 

813 scale_menu = self.menu.addMenu('&Scaling') 

814 sort_menu = self.menu.addMenu('Sor&ting') 

815 self.toggle_panel_menu = self.menu.addMenu('Sn&ufflings') 

816 

817 help_menu = self.menu.addMenu('&Help') 

818 

819 self.snufflings_menu = self.toggle_panel_menu.addMenu( 

820 'Run Snuffling') 

821 self.toggle_panel_menu.addSeparator() 

822 self.snuffling_help = help_menu.addMenu('Snuffling Help') 

823 help_menu.addSeparator() 

824 

825 file_menu.addAction( 

826 qg.QIcon.fromTheme('document-open'), 

827 'Open waveform files...', 

828 self.open_waveforms, 

829 qg.QKeySequence.Open) 

830 

831 file_menu.addAction( 

832 qg.QIcon.fromTheme('document-open'), 

833 'Open waveform directory...', 

834 self.open_waveform_directory) 

835 

836 file_menu.addAction( 

837 'Open station files...', 

838 self.open_stations) 

839 

840 file_menu.addAction( 

841 'Open StationXML files...', 

842 self.open_stations_xml) 

843 

844 file_menu.addAction( 

845 'Open event file...', 

846 self.read_events) 

847 

848 file_menu.addSeparator() 

849 file_menu.addAction( 

850 'Open marker file...', 

851 self.read_markers) 

852 

853 file_menu.addAction( 

854 qg.QIcon.fromTheme('document-save'), 

855 'Save markers...', 

856 self.write_markers, 

857 qg.QKeySequence.Save) 

858 

859 file_menu.addAction( 

860 qg.QIcon.fromTheme('document-save-as'), 

861 'Save selected markers...', 

862 self.write_selected_markers, 

863 qg.QKeySequence.SaveAs) 

864 

865 file_menu.addSeparator() 

866 file_menu.addAction( 

867 qg.QIcon.fromTheme('document-print'), 

868 'Print', 

869 self.printit, 

870 qg.QKeySequence.Print) 

871 

872 file_menu.addAction( 

873 qg.QIcon.fromTheme('insert-image'), 

874 'Save as SVG or PNG', 

875 self.savesvg, 

876 qg.QKeySequence(qc.Qt.CTRL + qc.Qt.Key_E)) 

877 

878 file_menu.addSeparator() 

879 close = file_menu.addAction( 

880 qg.QIcon.fromTheme('window-close'), 

881 'Close', 

882 self.myclose) 

883 close.setShortcuts( 

884 (qg.QKeySequence(qc.Qt.Key_Q), 

885 qg.QKeySequence(qc.Qt.Key_X))) 

886 

887 # Scale Menu 

888 menudef = [ 

889 ('Individual Scale', 

890 lambda tr: tr.nslc_id, 

891 qg.QKeySequence(qc.Qt.Key_S, qc.Qt.Key_I)), 

892 ('Common Scale', 

893 lambda tr: None, 

894 qg.QKeySequence(qc.Qt.Key_S, qc.Qt.Key_C)), 

895 ('Common Scale per Station', 

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

897 qg.QKeySequence(qc.Qt.Key_S, qc.Qt.Key_S)), 

898 ('Common Scale per Station Location', 

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

900 ('Common Scale per Component', 

901 lambda tr: (tr.channel)), 

902 ] 

903 

904 self.menuitems_scaling = add_radiobuttongroup( 

905 scale_menu, menudef, self.scalingmode_change, 

906 default=self.config.trace_scale) 

907 scale_menu.addSeparator() 

908 

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

910 self.scaling_hooks = {} 

911 self.scalingmode_change() 

912 

913 menudef = [ 

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

915 ('Scaling based on Mean ± 2x Std. Deviation', 2), 

916 ('Scaling based on Mean ± 4x Std. Deviation', 4), 

917 ] 

918 

919 self.menuitems_scaling_base = add_radiobuttongroup( 

920 scale_menu, menudef, self.scaling_base_change) 

921 

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

923 scale_menu.addSeparator() 

924 

925 self.menuitem_fixscalerange = scale_menu.addAction( 

926 'Fix Scale Ranges') 

927 self.menuitem_fixscalerange.setCheckable(True) 

928 

929 # Sort Menu 

930 def sector_dist(sta): 

931 if sta.dist_m is None: 

932 return None, None 

933 else: 

934 return ( 

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

936 m_float(sta.dist_m)) 

937 

938 menudef = [ 

939 ('Sort by Names', 

940 lambda tr: (), 

941 qg.QKeySequence(qc.Qt.Key_N)), 

942 ('Sort by Distance', 

943 lambda tr: self.station_attrib( 

944 tr, 

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

946 lambda tr: (None,)), 

947 qg.QKeySequence(qc.Qt.Key_D)), 

948 ('Sort by Azimuth', 

949 lambda tr: self.station_attrib( 

950 tr, 

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

952 lambda tr: (None,))), 

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

954 lambda tr: self.station_attrib( 

955 tr, 

956 sector_dist, 

957 lambda tr: (None, None))), 

958 ('Sort by Backazimuth', 

959 lambda tr: self.station_attrib( 

960 tr, 

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

962 lambda tr: (None,))), 

963 ] 

964 self.menuitems_ssorting = add_radiobuttongroup( 

965 sort_menu, menudef, self.s_sortingmode_change) 

966 sort_menu.addSeparator() 

967 

968 self._ssort = lambda tr: () 

969 

970 self.menu.addSeparator() 

971 

972 menudef = [ 

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

974 ((0, 1, 2, 3), # gathering 

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

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

977 ((0, 1, 3, 2), 

978 lambda tr: tr.channel)), 

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

980 ((1, 0, 3, 2), 

981 lambda tr: tr.channel)), 

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

983 ((2, 0, 1, 3), 

984 lambda tr: tr.channel)), 

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

986 ((3, 0, 1, 2), 

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

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

989 ((0, 1, 3), 

990 lambda tr: tr.location)), 

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

992 ((1, 0, 3), 

993 lambda tr: tr.location)), 

994 ] 

995 

996 self.menuitems_sorting = add_radiobuttongroup( 

997 sort_menu, menudef, self.sortingmode_change) 

998 

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

1000 self.config.visible_length_setting] 

1001 

1002 # View menu 

1003 self.menuitems_visible_length = add_radiobuttongroup( 

1004 view_menu, menudef, 

1005 self.visible_length_change) 

1006 view_menu.addSeparator() 

1007 

1008 view_modes = [ 

1009 ('Wiggle Plot', ViewMode.Wiggle), 

1010 ('Waterfall', ViewMode.Waterfall) 

1011 ] 

1012 

1013 self.menuitems_viewmode = add_radiobuttongroup( 

1014 view_menu, view_modes, 

1015 self.viewmode_change, default=ViewMode.Wiggle) 

1016 view_menu.addSeparator() 

1017 

1018 self.menuitem_cliptraces = view_menu.addAction( 

1019 'Clip Traces') 

1020 self.menuitem_cliptraces.setCheckable(True) 

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

1022 

1023 self.menuitem_showboxes = view_menu.addAction( 

1024 'Show Boxes') 

1025 self.menuitem_showboxes.setCheckable(True) 

1026 self.menuitem_showboxes.setChecked( 

1027 self.config.show_boxes) 

1028 

1029 self.menuitem_colortraces = view_menu.addAction( 

1030 'Color Traces') 

1031 self.menuitem_colortraces.setCheckable(True) 

1032 self.menuitem_antialias = view_menu.addAction( 

1033 'Antialiasing') 

1034 self.menuitem_antialias.setCheckable(True) 

1035 

1036 view_menu.addSeparator() 

1037 self.menuitem_showscalerange = view_menu.addAction( 

1038 'Show Scale Ranges') 

1039 self.menuitem_showscalerange.setCheckable(True) 

1040 self.menuitem_showscalerange.setChecked( 

1041 self.config.show_scale_ranges) 

1042 

1043 self.menuitem_showscaleaxis = view_menu.addAction( 

1044 'Show Scale Axes') 

1045 self.menuitem_showscaleaxis.setCheckable(True) 

1046 self.menuitem_showscaleaxis.setChecked( 

1047 self.config.show_scale_axes) 

1048 

1049 self.menuitem_showzeroline = view_menu.addAction( 

1050 'Show Zero Lines') 

1051 self.menuitem_showzeroline.setCheckable(True) 

1052 

1053 view_menu.addSeparator() 

1054 view_menu.addAction( 

1055 qg.QIcon.fromTheme('view-fullscreen'), 

1056 'Fullscreen', 

1057 self.toggle_fullscreen, 

1058 qg.QKeySequence(qc.Qt.Key_F11)) 

1059 

1060 # Options Menu 

1061 self.menuitem_demean = options_menu.addAction('Demean') 

1062 self.menuitem_demean.setCheckable(True) 

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

1064 self.menuitem_demean.setShortcut( 

1065 qg.QKeySequence(qc.Qt.Key_Underscore)) 

1066 

1067 self.menuitem_distances_3d = options_menu.addAction( 

1068 '3D distances', 

1069 self.distances_3d_changed) 

1070 self.menuitem_distances_3d.setCheckable(True) 

1071 

1072 self.menuitem_allowdownsampling = options_menu.addAction( 

1073 'Allow Downsampling') 

1074 self.menuitem_allowdownsampling.setCheckable(True) 

1075 self.menuitem_allowdownsampling.setChecked(True) 

1076 

1077 self.menuitem_degap = options_menu.addAction( 

1078 'Allow Degapping') 

1079 self.menuitem_degap.setCheckable(True) 

1080 self.menuitem_degap.setChecked(True) 

1081 

1082 options_menu.addSeparator() 

1083 

1084 self.menuitem_fft_filtering = options_menu.addAction( 

1085 'FFT Filtering') 

1086 self.menuitem_fft_filtering.setCheckable(True) 

1087 

1088 self.menuitem_lphp = options_menu.addAction( 

1089 'Bandpass is Low- + Highpass') 

1090 self.menuitem_lphp.setCheckable(True) 

1091 self.menuitem_lphp.setChecked(True) 

1092 

1093 options_menu.addSeparator() 

1094 self.menuitem_watch = options_menu.addAction( 

1095 'Watch Files') 

1096 self.menuitem_watch.setCheckable(True) 

1097 

1098 self.menuitem_liberal_fetch = options_menu.addAction( 

1099 'Liberal Fetch Optimization') 

1100 self.menuitem_liberal_fetch.setCheckable(True) 

1101 

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

1103 

1104 self.snufflings_menu.addAction( 

1105 'Reload Snufflings', 

1106 self.setup_snufflings) 

1107 

1108 # Disable ShadowPileTest 

1109 if False: 

1110 test_action = self.menu.addAction( 

1111 'Test', 

1112 self.toggletest) 

1113 test_action.setCheckable(True) 

1114 

1115 help_menu.addAction( 

1116 qg.QIcon.fromTheme('preferences-desktop-keyboard'), 

1117 'Snuffler Controls', 

1118 self.help, 

1119 qg.QKeySequence(qc.Qt.Key_Question)) 

1120 

1121 help_menu.addAction( 

1122 'About', 

1123 self.about) 

1124 

1125 self.time_projection = Projection() 

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

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

1128 

1129 self.gather = None 

1130 

1131 self.trace_filter = None 

1132 self.quick_filter = None 

1133 self.quick_filter_patterns = None, None 

1134 self.blacklist = [] 

1135 

1136 self.track_to_screen = Projection() 

1137 self.track_to_nslc_ids = {} 

1138 

1139 self.cached_vec = None 

1140 self.cached_processed_traces = None 

1141 self.cached_chopped_traces = {} 

1142 

1143 self.timer = qc.QTimer(self) 

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

1145 self.timer.setInterval(1000) 

1146 self.timer.start() 

1147 self.pile.add_listener(self) 

1148 self.trace_styles = {} 

1149 if self.get_squirrel() is None: 

1150 self.determine_box_styles() 

1151 

1152 self.setMouseTracking(True) 

1153 

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

1155 self.snuffling_modules = {} 

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

1157 self.default_snufflings = None 

1158 self.snufflings = [] 

1159 

1160 self.stations = {} 

1161 

1162 self.timer_draw = Timer() 

1163 self.timer_cutout = Timer() 

1164 self.time_spent_painting = 0.0 

1165 self.time_last_painted = time.time() 

1166 

1167 self.interactive_range_change_time = 0.0 

1168 self.interactive_range_change_delay_time = 10.0 

1169 self.follow_timer = None 

1170 

1171 self.sortingmode_change_time = 0.0 

1172 self.sortingmode_change_delay_time = None 

1173 

1174 self.old_data_ranges = {} 

1175 

1176 self.error_messages = {} 

1177 self.return_tag = None 

1178 self.wheel_pos = 60 

1179 

1180 self.setAcceptDrops(True) 

1181 self._paths_to_load = [] 

1182 

1183 self.tf_cache = {} 

1184 

1185 self.waterfall = TraceWaterfall() 

1186 self.waterfall_cmap = 'viridis' 

1187 self.waterfall_clip_min = 0. 

1188 self.waterfall_clip_max = 1. 

1189 self.waterfall_show_absolute = False 

1190 self.waterfall_integrate = False 

1191 self.view_mode = ViewMode.Wiggle 

1192 

1193 self.automatic_updates = True 

1194 

1195 self.closing = False 

1196 self.paint_timer = qc.QTimer(self) 

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

1198 self.paint_timer.setInterval(20) 

1199 self.paint_timer.start() 

1200 

1201 @qc.pyqtSlot() 

1202 def reset_updates(self): 

1203 if not self.updatesEnabled(): 

1204 self.setUpdatesEnabled(True) 

1205 

1206 def fail(self, reason): 

1207 box = qw.QMessageBox(self) 

1208 box.setText(reason) 

1209 box.exec_() 

1210 

1211 def set_trace_filter(self, filter_func): 

1212 self.trace_filter = filter_func 

1213 self.sortingmode_change() 

1214 

1215 def update_trace_filter(self): 

1216 if self.blacklist: 

1217 

1218 def blacklist_func(tr): 

1219 return not pyrocko.util.match_nslc( 

1220 self.blacklist, tr.nslc_id) 

1221 

1222 else: 

1223 blacklist_func = None 

1224 

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

1226 self.set_trace_filter(None) 

1227 elif self.quick_filter is None: 

1228 self.set_trace_filter(blacklist_func) 

1229 elif blacklist_func is None: 

1230 self.set_trace_filter(self.quick_filter) 

1231 else: 

1232 self.set_trace_filter( 

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

1234 

1235 def set_quick_filter(self, filter_func): 

1236 self.quick_filter = filter_func 

1237 self.update_trace_filter() 

1238 

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

1240 if patterns is not None: 

1241 self.set_quick_filter( 

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

1243 else: 

1244 self.set_quick_filter(None) 

1245 

1246 self.quick_filter_patterns = patterns, inputline 

1247 

1248 def get_quick_filter_patterns(self): 

1249 return self.quick_filter_patterns 

1250 

1251 def add_blacklist_pattern(self, pattern): 

1252 if pattern == 'empty': 

1253 keys = set(self.pile.nslc_ids) 

1254 trs = self.pile.all( 

1255 tmin=self.tmin, 

1256 tmax=self.tmax, 

1257 load_data=False, 

1258 degap=False) 

1259 

1260 for tr in trs: 

1261 if tr.nslc_id in keys: 

1262 keys.remove(tr.nslc_id) 

1263 

1264 for key in keys: 

1265 xpattern = '.'.join(key) 

1266 if xpattern not in self.blacklist: 

1267 self.blacklist.append(xpattern) 

1268 

1269 else: 

1270 if pattern in self.blacklist: 

1271 self.blacklist.remove(pattern) 

1272 

1273 self.blacklist.append(pattern) 

1274 

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

1276 self.update_trace_filter() 

1277 

1278 def remove_blacklist_pattern(self, pattern): 

1279 if pattern in self.blacklist: 

1280 self.blacklist.remove(pattern) 

1281 else: 

1282 raise PileViewerMainException( 

1283 'Pattern not found in blacklist.') 

1284 

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

1286 self.update_trace_filter() 

1287 

1288 def clear_blacklist(self): 

1289 self.blacklist = [] 

1290 self.update_trace_filter() 

1291 

1292 def ssort(self, tr): 

1293 return self._ssort(tr) 

1294 

1295 def station_key(self, x): 

1296 return x.network, x.station 

1297 

1298 def station_keys(self, x): 

1299 return [ 

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

1301 (x.network, x.station)] 

1302 

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

1304 for sk in self.station_keys(tr): 

1305 if sk in self.stations: 

1306 station = self.stations[sk] 

1307 return getter(station) 

1308 

1309 return default_getter(tr) 

1310 

1311 def get_station(self, sk): 

1312 return self.stations[sk] 

1313 

1314 def has_station(self, station): 

1315 for sk in self.station_keys(station): 

1316 if sk in self.stations: 

1317 return True 

1318 

1319 return False 

1320 

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

1322 return self.station_attrib( 

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

1324 

1325 def set_stations(self, stations): 

1326 self.stations = {} 

1327 self.add_stations(stations) 

1328 

1329 def add_stations(self, stations): 

1330 for station in stations: 

1331 for sk in self.station_keys(station): 

1332 self.stations[sk] = station 

1333 

1334 ev = self.get_active_event() 

1335 if ev: 

1336 self.set_origin(ev) 

1337 

1338 def add_event(self, event): 

1339 marker = EventMarker(event) 

1340 self.add_marker(marker) 

1341 

1342 def add_events(self, events): 

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

1344 self.add_markers(markers) 

1345 

1346 def set_event_marker_as_origin(self, ignore=None): 

1347 selected = self.selected_markers() 

1348 if not selected: 

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

1350 return 

1351 

1352 m = selected[0] 

1353 if not isinstance(m, EventMarker): 

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

1355 return 

1356 

1357 self.set_active_event_marker(m) 

1358 

1359 def deactivate_event_marker(self): 

1360 if self.active_event_marker: 

1361 self.active_event_marker.active = False 

1362 

1363 self.active_event_marker_changed.emit() 

1364 self.active_event_marker = None 

1365 

1366 def set_active_event_marker(self, event_marker): 

1367 if self.active_event_marker: 

1368 self.active_event_marker.active = False 

1369 

1370 self.active_event_marker = event_marker 

1371 event_marker.active = True 

1372 event = event_marker.get_event() 

1373 self.set_origin(event) 

1374 self.active_event_marker_changed.emit() 

1375 

1376 def set_active_event(self, event): 

1377 for marker in self.markers: 

1378 if isinstance(marker, EventMarker): 

1379 if marker.get_event() is event: 

1380 self.set_active_event_marker(marker) 

1381 

1382 def get_active_event_marker(self): 

1383 return self.active_event_marker 

1384 

1385 def get_active_event(self): 

1386 m = self.get_active_event_marker() 

1387 if m is not None: 

1388 return m.get_event() 

1389 else: 

1390 return None 

1391 

1392 def get_active_markers(self): 

1393 emarker = self.get_active_event_marker() 

1394 if emarker is None: 

1395 return None, [] 

1396 

1397 else: 

1398 ev = emarker.get_event() 

1399 pmarkers = [ 

1400 m for m in self.markers 

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

1402 

1403 return emarker, pmarkers 

1404 

1405 def set_origin(self, location): 

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

1407 station.set_event_relative_data( 

1408 location, 

1409 distance_3d=self.menuitem_distances_3d.isChecked()) 

1410 

1411 self.sortingmode_change() 

1412 

1413 def distances_3d_changed(self): 

1414 ignore = self.menuitem_distances_3d.isChecked() 

1415 self.set_event_marker_as_origin(ignore) 

1416 

1417 def iter_snuffling_modules(self): 

1418 pjoin = os.path.join 

1419 for path in self.snuffling_paths: 

1420 

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

1422 os.mkdir(path) 

1423 

1424 for entry in os.listdir(path): 

1425 directory = path 

1426 fn = entry 

1427 d = pjoin(path, entry) 

1428 if os.path.isdir(d): 

1429 directory = d 

1430 if os.path.isfile( 

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

1432 fn = 'snuffling.py' 

1433 

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

1435 continue 

1436 

1437 name = fn[:-3] 

1438 

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

1440 self.snuffling_modules[directory, name] = \ 

1441 pyrocko.gui.snuffling.SnufflingModule( 

1442 directory, name, self) 

1443 

1444 yield self.snuffling_modules[directory, name] 

1445 

1446 def setup_snufflings(self): 

1447 # user snufflings 

1448 for mod in self.iter_snuffling_modules(): 

1449 try: 

1450 mod.load_if_needed() 

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

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

1453 

1454 # load the default snufflings on first run 

1455 if self.default_snufflings is None: 

1456 self.default_snufflings = pyrocko.gui\ 

1457 .snufflings.__snufflings__() 

1458 for snuffling in self.default_snufflings: 

1459 self.add_snuffling(snuffling) 

1460 

1461 def set_panel_parent(self, panel_parent): 

1462 self.panel_parent = panel_parent 

1463 

1464 def get_panel_parent(self): 

1465 return self.panel_parent 

1466 

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

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

1469 snuffling.init_gui( 

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

1471 self.snufflings.append(snuffling) 

1472 self.update() 

1473 

1474 def remove_snuffling(self, snuffling): 

1475 snuffling.delete_gui() 

1476 self.update() 

1477 self.snufflings.remove(snuffling) 

1478 snuffling.pre_destroy() 

1479 

1480 def add_snuffling_menuitem(self, item): 

1481 self.snufflings_menu.addAction(item) 

1482 item.setParent(self.snufflings_menu) 

1483 sort_actions(self.snufflings_menu) 

1484 

1485 def remove_snuffling_menuitem(self, item): 

1486 self.snufflings_menu.removeAction(item) 

1487 

1488 def add_snuffling_help_menuitem(self, item): 

1489 self.snuffling_help.addAction(item) 

1490 item.setParent(self.snuffling_help) 

1491 sort_actions(self.snuffling_help) 

1492 

1493 def remove_snuffling_help_menuitem(self, item): 

1494 self.snuffling_help.removeAction(item) 

1495 

1496 def add_panel_toggler(self, item): 

1497 self.toggle_panel_menu.addAction(item) 

1498 item.setParent(self.toggle_panel_menu) 

1499 sort_actions(self.toggle_panel_menu) 

1500 

1501 def remove_panel_toggler(self, item): 

1502 self.toggle_panel_menu.removeAction(item) 

1503 

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

1505 cache_dir=None, force_cache=False): 

1506 

1507 if cache_dir is None: 

1508 cache_dir = pyrocko.config.config().cache_dir 

1509 if isinstance(paths, str): 

1510 paths = [paths] 

1511 

1512 fns = pyrocko.util.select_files( 

1513 paths, selector=None, include=regex, show_progress=False) 

1514 

1515 if not fns: 

1516 return 

1517 

1518 cache = pyrocko.pile.get_cache(cache_dir) 

1519 

1520 t = [time.time()] 

1521 

1522 def update_bar(label, value): 

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

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

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

1526 else: 

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

1528 

1529 return pbs.set_status(label, value) 

1530 

1531 def update_progress(label, i, n): 

1532 abort = False 

1533 

1534 qw.qApp.processEvents() 

1535 if n != 0: 

1536 perc = i*100/n 

1537 else: 

1538 perc = 100 

1539 abort |= update_bar(label, perc) 

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

1541 

1542 tnow = time.time() 

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

1544 self.update() 

1545 t[0] = tnow 

1546 

1547 return abort 

1548 

1549 self.automatic_updates = False 

1550 

1551 self.pile.load_files( 

1552 sorted(fns), 

1553 filename_attributes=regex, 

1554 cache=cache, 

1555 fileformat=format, 

1556 show_progress=False, 

1557 update_progress=update_progress) 

1558 

1559 self.automatic_updates = True 

1560 self.update() 

1561 

1562 def load_queued(self): 

1563 if not self._paths_to_load: 

1564 return 

1565 paths = self._paths_to_load 

1566 self._paths_to_load = [] 

1567 self.load(paths) 

1568 

1569 def load_soon(self, paths): 

1570 self._paths_to_load.extend(paths) 

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

1572 

1573 def open_waveforms(self): 

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

1575 

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

1577 self, caption, options=qfiledialog_options)) 

1578 

1579 if fns: 

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

1581 

1582 def open_waveform_directory(self): 

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

1584 

1585 dn = qw.QFileDialog.getExistingDirectory( 

1586 self, caption, options=qfiledialog_options) 

1587 

1588 if dn: 

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

1590 

1591 def open_stations(self, fns=None): 

1592 caption = 'Select one or more Pyrocko station files to open' 

1593 

1594 if not fns: 

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

1596 self, caption, options=qfiledialog_options)) 

1597 

1598 try: 

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

1600 for stat in stations: 

1601 self.add_stations(stat) 

1602 

1603 except Exception as e: 

1604 self.fail('Failed to read station file: %s' % str(e)) 

1605 

1606 def open_stations_xml(self, fns=None): 

1607 from pyrocko.io import stationxml 

1608 

1609 caption = 'Select one or more StationXML files' 

1610 if not fns: 

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

1612 self, caption, options=qfiledialog_options, 

1613 filter='StationXML (*.xml *.XML *.stationxml *.stationXML)' 

1614 ';;All files (*)')) 

1615 

1616 try: 

1617 stations = [ 

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

1619 for x in fns] 

1620 

1621 for stat in stations: 

1622 self.add_stations(stat) 

1623 

1624 except Exception as e: 

1625 self.fail('Failed to read StationXML file: %s' % str(e)) 

1626 

1627 def add_traces(self, traces): 

1628 if traces: 

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

1630 self.pile.add_file(mtf) 

1631 ticket = (self.pile, mtf) 

1632 return ticket 

1633 else: 

1634 return (None, None) 

1635 

1636 def release_data(self, tickets): 

1637 for ticket in tickets: 

1638 pile, mtf = ticket 

1639 if pile is not None: 

1640 pile.remove_file(mtf) 

1641 

1642 def periodical(self): 

1643 if self.menuitem_watch.isChecked(): 

1644 if self.pile.reload_modified(): 

1645 self.update() 

1646 

1647 def get_pile(self): 

1648 return self.pile 

1649 

1650 def pile_changed(self, what): 

1651 self.pile_has_changed = True 

1652 self.pile_has_changed_signal.emit() 

1653 if self.automatic_updates: 

1654 self.update() 

1655 

1656 def set_gathering(self, gather=None, color=None): 

1657 

1658 if gather is None: 

1659 def gather_func(tr): 

1660 return tr.nslc_id 

1661 

1662 gather = (0, 1, 2, 3) 

1663 

1664 else: 

1665 def gather_func(tr): 

1666 return ( 

1667 self.ssort(tr) + tuple(tr.nslc_id[i] for i in gather)) 

1668 

1669 if color is None: 

1670 def color(tr): 

1671 return tr.location 

1672 

1673 self.gather = gather_func 

1674 keys = self.pile.gather_keys(gather_func, self.trace_filter) 

1675 

1676 self.color_gather = color 

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

1678 previous_ntracks = self.ntracks 

1679 self.set_ntracks(len(keys)) 

1680 

1681 if self.shown_tracks_range is None or \ 

1682 previous_ntracks == 0 or \ 

1683 self.show_all: 

1684 

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

1686 key_at_top = None 

1687 n = high-low 

1688 

1689 else: 

1690 low, high = self.shown_tracks_range 

1691 key_at_top = self.track_keys[low] 

1692 n = high-low 

1693 

1694 self.track_keys = sorted(keys) 

1695 

1696 track_patterns = [] 

1697 for k in self.track_keys: 

1698 pat = ['*', '*', '*', '*'] 

1699 for i, j in enumerate(gather): 

1700 pat[j] = k[-len(gather)+i] 

1701 

1702 track_patterns.append(pat) 

1703 

1704 self.track_patterns = track_patterns 

1705 

1706 if key_at_top is not None: 

1707 try: 

1708 ind = self.track_keys.index(key_at_top) 

1709 low = ind 

1710 high = low+n 

1711 except Exception: 

1712 pass 

1713 

1714 self.set_tracks_range((low, high)) 

1715 

1716 self.key_to_row = dict( 

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

1718 

1719 def inrange(x, r): 

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

1721 

1722 def trace_selector(trace): 

1723 gt = self.gather(trace) 

1724 return ( 

1725 gt in self.key_to_row and 

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

1727 

1728 if self.trace_filter is not None: 

1729 self.trace_selector = lambda x: \ 

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

1731 else: 

1732 self.trace_selector = trace_selector 

1733 

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

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

1736 self.show_all: 

1737 

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

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

1740 tlen = (tmax - tmin) 

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

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

1743 

1744 def set_time_range(self, tmin, tmax): 

1745 if tmin is None: 

1746 tmin = initial_time_range[0] 

1747 

1748 if tmax is None: 

1749 tmax = initial_time_range[1] 

1750 

1751 if tmin > tmax: 

1752 tmin, tmax = tmax, tmin 

1753 

1754 if tmin == tmax: 

1755 tmin -= 1. 

1756 tmax += 1. 

1757 

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

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

1760 

1761 min_deltat = self.content_deltat_range()[0] 

1762 if (tmax - tmin < min_deltat): 

1763 m = (tmin + tmax) / 2. 

1764 tmin = m - min_deltat/2. 

1765 tmax = m + min_deltat/2. 

1766 

1767 self.time_projection.set_in_range(tmin, tmax) 

1768 self.tmin, self.tmax = tmin, tmax 

1769 

1770 def get_time_range(self): 

1771 return self.tmin, self.tmax 

1772 

1773 def ypart(self, y): 

1774 if y < self.ax_height: 

1775 return -1 

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

1777 return 1 

1778 else: 

1779 return 0 

1780 

1781 def time_fractional_digits(self): 

1782 min_deltat = self.content_deltat_range()[0] 

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

1784 

1785 def write_markers(self, fn=None): 

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

1787 if not fn: 

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

1789 self, caption, options=qfiledialog_options)) 

1790 if fn: 

1791 try: 

1792 Marker.save_markers( 

1793 self.markers, fn, 

1794 fdigits=self.time_fractional_digits()) 

1795 

1796 except Exception as e: 

1797 self.fail('Failed to write marker file: %s' % str(e)) 

1798 

1799 def write_selected_markers(self, fn=None): 

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

1801 if not fn: 

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

1803 self, caption, options=qfiledialog_options)) 

1804 if fn: 

1805 try: 

1806 Marker.save_markers( 

1807 self.iter_selected_markers(), 

1808 fn, 

1809 fdigits=self.time_fractional_digits()) 

1810 

1811 except Exception as e: 

1812 self.fail('Failed to write marker file: %s' % str(e)) 

1813 

1814 def read_events(self, fn=None): 

1815 ''' 

1816 Open QFileDialog to open, read and add 

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

1818 representation to the pile viewer. 

1819 ''' 

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

1821 if not fn: 

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

1823 self, caption, options=qfiledialog_options)) 

1824 if fn: 

1825 try: 

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

1827 self.associate_phases_to_events() 

1828 

1829 except Exception as e: 

1830 self.fail('Failed to read event file: %s' % str(e)) 

1831 

1832 def read_markers(self, fn=None): 

1833 ''' 

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

1835 ''' 

1836 caption = "Selet one or more marker files to open" 

1837 if not fn: 

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

1839 self, caption, options=qfiledialog_options)) 

1840 if fn: 

1841 try: 

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

1843 self.associate_phases_to_events() 

1844 

1845 except Exception as e: 

1846 self.fail('Failed to read marker file: %s' % str(e)) 

1847 

1848 def associate_phases_to_events(self): 

1849 associate_phases_to_events(self.markers) 

1850 

1851 def add_marker(self, marker): 

1852 # need index to inform QAbstactTableModel about upcoming change, 

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

1854 self.markers.insert(marker) 

1855 i = self.markers.remove(marker) 

1856 

1857 self.begin_markers_add.emit(i, i) 

1858 self.markers.insert(marker) 

1859 self.end_markers_add.emit() 

1860 self.markers_deltat_max = max( 

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

1862 

1863 def add_markers(self, markers): 

1864 if not self.markers: 

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

1866 self.markers.insert_many(markers) 

1867 self.end_markers_add.emit() 

1868 self.update_markers_deltat_max() 

1869 else: 

1870 for marker in markers: 

1871 self.add_marker(marker) 

1872 

1873 def update_markers_deltat_max(self): 

1874 if self.markers: 

1875 self.markers_deltat_max = max( 

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

1877 

1878 def remove_marker(self, marker): 

1879 ''' 

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

1881 

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

1883 ''' 

1884 

1885 if marker is self.active_event_marker: 

1886 self.deactivate_event_marker() 

1887 

1888 try: 

1889 i = self.markers.index(marker) 

1890 self.begin_markers_remove.emit(i, i) 

1891 self.markers.remove_at(i) 

1892 self.end_markers_remove.emit() 

1893 except ValueError: 

1894 pass 

1895 

1896 def remove_markers(self, markers): 

1897 ''' 

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

1899 

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

1901 instances 

1902 ''' 

1903 

1904 if markers is self.markers: 

1905 markers = list(markers) 

1906 

1907 for marker in markers: 

1908 self.remove_marker(marker) 

1909 

1910 self.update_markers_deltat_max() 

1911 

1912 def remove_selected_markers(self): 

1913 def delete_segment(istart, iend): 

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

1915 for _ in range(iend - istart): 

1916 self.markers.remove_at(istart) 

1917 

1918 self.end_markers_remove.emit() 

1919 

1920 istart = None 

1921 ipos = 0 

1922 markers = self.markers 

1923 nmarkers = len(self.markers) 

1924 while ipos < nmarkers: 

1925 marker = markers[ipos] 

1926 if marker.is_selected(): 

1927 if marker is self.active_event_marker: 

1928 self.deactivate_event_marker() 

1929 

1930 if istart is None: 

1931 istart = ipos 

1932 else: 

1933 if istart is not None: 

1934 delete_segment(istart, ipos) 

1935 nmarkers -= ipos - istart 

1936 ipos = istart - 1 

1937 istart = None 

1938 

1939 ipos += 1 

1940 

1941 if istart is not None: 

1942 delete_segment(istart, ipos) 

1943 

1944 self.update_markers_deltat_max() 

1945 

1946 def selected_markers(self): 

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

1948 

1949 def iter_selected_markers(self): 

1950 for marker in self.markers: 

1951 if marker.is_selected(): 

1952 yield marker 

1953 

1954 def get_markers(self): 

1955 return self.markers 

1956 

1957 def mousePressEvent(self, mouse_ev): 

1958 self.show_all = False 

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

1960 

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

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

1963 if self.picking: 

1964 if self.picking_down is None: 

1965 self.picking_down = ( 

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

1967 mouse_ev.y()) 

1968 

1969 elif marker is not None: 

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

1971 self.deselect_all() 

1972 marker.selected = True 

1973 self.emit_selected_markers() 

1974 self.update() 

1975 else: 

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

1977 self.track_trange = self.tmin, self.tmax 

1978 

1979 if mouse_ev.button() == qc.Qt.RightButton \ 

1980 and isinstance(self.menu, qw.QMenu): 

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

1982 self.update_status() 

1983 

1984 def mouseReleaseEvent(self, mouse_ev): 

1985 if self.ignore_releases: 

1986 self.ignore_releases -= 1 

1987 return 

1988 

1989 if self.picking: 

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

1991 self.emit_selected_markers() 

1992 

1993 if self.track_start: 

1994 self.update() 

1995 

1996 self.track_start = None 

1997 self.track_trange = None 

1998 self.update_status() 

1999 

2000 def mouseDoubleClickEvent(self, mouse_ev): 

2001 self.show_all = False 

2002 self.start_picking(None) 

2003 self.ignore_releases = 1 

2004 

2005 def mouseMoveEvent(self, mouse_ev): 

2006 self.setUpdatesEnabled(False) 

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

2008 

2009 if self.picking: 

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

2011 

2012 elif self.track_start is not None: 

2013 x0, y0 = self.track_start 

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

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

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

2017 dy = 0 

2018 

2019 tmin0, tmax0 = self.track_trange 

2020 

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

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

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

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

2025 

2026 self.interrupt_following() 

2027 self.set_time_range( 

2028 tmin0 - dt - dtr*frac, 

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

2030 

2031 self.update() 

2032 else: 

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

2034 

2035 self.update_status() 

2036 

2037 def nslc_ids_under_cursor(self, x, y): 

2038 ftrack = self.track_to_screen.rev(y) 

2039 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2040 return nslc_ids 

2041 

2042 def marker_under_cursor(self, x, y): 

2043 mouset = self.time_projection.rev(x) 

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

2045 relevant_nslc_ids = None 

2046 for marker in self.markers: 

2047 if marker.kind not in self.visible_marker_kinds: 

2048 continue 

2049 

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

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

2052 

2053 if relevant_nslc_ids is None: 

2054 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2055 

2056 marker_nslc_ids = marker.get_nslc_ids() 

2057 if not marker_nslc_ids: 

2058 return marker 

2059 

2060 for nslc_id in marker_nslc_ids: 

2061 if nslc_id in relevant_nslc_ids: 

2062 return marker 

2063 

2064 def hoovering(self, x, y): 

2065 mouset = self.time_projection.rev(x) 

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

2067 needupdate = False 

2068 haveone = False 

2069 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2070 for marker in self.markers: 

2071 if marker.kind not in self.visible_marker_kinds: 

2072 continue 

2073 

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

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

2076 

2077 if state: 

2078 xstate = False 

2079 

2080 marker_nslc_ids = marker.get_nslc_ids() 

2081 if not marker_nslc_ids: 

2082 xstate = True 

2083 

2084 for nslc in relevant_nslc_ids: 

2085 if marker.match_nslc(nslc): 

2086 xstate = True 

2087 

2088 state = xstate 

2089 

2090 if state: 

2091 haveone = True 

2092 oldstate = marker.is_alerted() 

2093 if oldstate != state: 

2094 needupdate = True 

2095 marker.set_alerted(state) 

2096 if state: 

2097 self.message = marker.hoover_message() 

2098 

2099 if not haveone: 

2100 self.message = None 

2101 

2102 if needupdate: 

2103 self.update() 

2104 

2105 def event(self, event): 

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

2107 self.keyPressEvent(event) 

2108 return True 

2109 else: 

2110 return base.event(self, event) 

2111 

2112 def keyPressEvent(self, key_event): 

2113 self.show_all = False 

2114 dt = self.tmax - self.tmin 

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

2116 

2117 key = key_event.key() 

2118 try: 

2119 keytext = str(key_event.text()) 

2120 except UnicodeEncodeError: 

2121 return 

2122 

2123 if key == qc.Qt.Key_Space: 

2124 self.interrupt_following() 

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

2126 

2127 elif key == qc.Qt.Key_Up: 

2128 for m in self.selected_markers(): 

2129 if isinstance(m, PhaseMarker): 

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

2131 p = 0 

2132 else: 

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

2134 m.set_polarity(p) 

2135 

2136 elif key == qc.Qt.Key_Down: 

2137 for m in self.selected_markers(): 

2138 if isinstance(m, PhaseMarker): 

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

2140 p = 0 

2141 else: 

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

2143 m.set_polarity(p) 

2144 

2145 elif key == qc.Qt.Key_B: 

2146 dt = self.tmax - self.tmin 

2147 self.interrupt_following() 

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

2149 

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

2151 self.interrupt_following() 

2152 

2153 tgo = None 

2154 

2155 class TraceDummy(object): 

2156 def __init__(self, marker): 

2157 self._marker = marker 

2158 

2159 @property 

2160 def nslc_id(self): 

2161 return self._marker.one_nslc() 

2162 

2163 def marker_to_itrack(marker): 

2164 try: 

2165 return self.key_to_row.get( 

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

2167 

2168 except MarkerOneNSLCRequired: 

2169 return -1 

2170 

2171 emarker, pmarkers = self.get_active_markers() 

2172 pmarkers = [ 

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

2174 pmarkers.sort(key=lambda m: ( 

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

2176 

2177 if key == qc.Qt.Key_Backtab: 

2178 pmarkers.reverse() 

2179 

2180 smarkers = self.selected_markers() 

2181 iselected = [] 

2182 for sm in smarkers: 

2183 try: 

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

2185 except ValueError: 

2186 pass 

2187 

2188 if iselected: 

2189 icurrent = max(iselected) + 1 

2190 else: 

2191 icurrent = 0 

2192 

2193 if icurrent < len(pmarkers): 

2194 self.deselect_all() 

2195 cmarker = pmarkers[icurrent] 

2196 cmarker.selected = True 

2197 tgo = cmarker.tmin 

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

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

2200 

2201 itrack = marker_to_itrack(cmarker) 

2202 if itrack != -1: 

2203 if itrack < self.shown_tracks_range[0]: 

2204 self.scroll_tracks( 

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

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

2207 self.scroll_tracks( 

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

2209 

2210 if itrack not in self.track_to_nslc_ids: 

2211 self.go_to_selection() 

2212 

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

2214 smarkers = self.selected_markers() 

2215 tgo = None 

2216 dir = str(keytext) 

2217 if smarkers: 

2218 tmid = smarkers[0].tmin 

2219 for smarker in smarkers: 

2220 if dir == 'n': 

2221 tmid = max(smarker.tmin, tmid) 

2222 else: 

2223 tmid = min(smarker.tmin, tmid) 

2224 

2225 tgo = tmid 

2226 

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

2228 for marker in sorted( 

2229 self.markers, 

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

2231 

2232 t = marker.tmin 

2233 if t > tmid and \ 

2234 marker.kind in self.visible_marker_kinds and \ 

2235 (dir == 'n' or 

2236 isinstance(marker, EventMarker)): 

2237 

2238 self.deselect_all() 

2239 marker.selected = True 

2240 tgo = t 

2241 break 

2242 else: 

2243 for marker in sorted( 

2244 self.markers, 

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

2246 reverse=True): 

2247 

2248 t = marker.tmin 

2249 if t < tmid and \ 

2250 marker.kind in self.visible_marker_kinds and \ 

2251 (dir == 'p' or 

2252 isinstance(marker, EventMarker)): 

2253 self.deselect_all() 

2254 marker.selected = True 

2255 tgo = t 

2256 break 

2257 

2258 if tgo is not None: 

2259 self.interrupt_following() 

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

2261 

2262 elif keytext == 'r': 

2263 if self.pile.reload_modified(): 

2264 self.reloaded = True 

2265 

2266 elif keytext == 'R': 

2267 self.setup_snufflings() 

2268 

2269 elif key == qc.Qt.Key_Backspace: 

2270 self.remove_selected_markers() 

2271 

2272 elif keytext == 'a': 

2273 for marker in self.markers: 

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

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

2276 marker.kind in self.visible_marker_kinds): 

2277 marker.selected = True 

2278 else: 

2279 marker.selected = False 

2280 

2281 elif keytext == 'A': 

2282 for marker in self.markers: 

2283 if marker.kind in self.visible_marker_kinds: 

2284 marker.selected = True 

2285 

2286 elif keytext == 'd': 

2287 self.deselect_all() 

2288 

2289 elif keytext == 'E': 

2290 self.deactivate_event_marker() 

2291 

2292 elif keytext == 'e': 

2293 markers = self.selected_markers() 

2294 event_markers_in_spe = [ 

2295 marker for marker in markers 

2296 if not isinstance(marker, PhaseMarker)] 

2297 

2298 phase_markers = [ 

2299 marker for marker in markers 

2300 if isinstance(marker, PhaseMarker)] 

2301 

2302 if len(event_markers_in_spe) == 1: 

2303 event_marker = event_markers_in_spe[0] 

2304 if not isinstance(event_marker, EventMarker): 

2305 nslcs = list(event_marker.nslc_ids) 

2306 lat, lon = 0.0, 0.0 

2307 old = self.get_active_event() 

2308 if len(nslcs) == 1: 

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

2310 elif old is not None: 

2311 lat, lon = old.lat, old.lon 

2312 

2313 event_marker.convert_to_event_marker(lat, lon) 

2314 

2315 self.set_active_event_marker(event_marker) 

2316 event = event_marker.get_event() 

2317 for marker in phase_markers: 

2318 marker.set_event(event) 

2319 

2320 else: 

2321 for marker in event_markers_in_spe: 

2322 marker.convert_to_event_marker() 

2323 

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

2325 for marker in self.selected_markers(): 

2326 marker.set_kind(int(keytext)) 

2327 self.emit_selected_markers() 

2328 

2329 elif key in fkey_map: 

2330 self.handle_fkeys(key) 

2331 

2332 elif key == qc.Qt.Key_Escape: 

2333 if self.picking: 

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

2335 

2336 elif key == qc.Qt.Key_PageDown: 

2337 self.scroll_tracks( 

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

2339 

2340 elif key == qc.Qt.Key_PageUp: 

2341 self.scroll_tracks( 

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

2343 

2344 elif key == qc.Qt.Key_Plus: 

2345 self.zoom_tracks(0., 1.) 

2346 

2347 elif key == qc.Qt.Key_Minus: 

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

2349 

2350 elif key == qc.Qt.Key_Equal: 

2351 ntracks_shown = self.shown_tracks_range[1] - \ 

2352 self.shown_tracks_range[0] 

2353 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2354 self.zoom_tracks(0., dtracks) 

2355 

2356 elif key == qc.Qt.Key_Colon: 

2357 self.want_input.emit() 

2358 

2359 elif keytext == 'g': 

2360 self.go_to_selection() 

2361 

2362 elif keytext == 'G': 

2363 self.go_to_selection(tight=True) 

2364 

2365 elif keytext == 'm': 

2366 self.toggle_marker_editor() 

2367 

2368 elif keytext == 'c': 

2369 self.toggle_main_controls() 

2370 

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

2372 dir = 1 

2373 amount = 1 

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

2375 dir = -1 

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

2377 amount = 10 

2378 self.nudge_selected_markers(dir*amount) 

2379 else: 

2380 super().keyPressEvent(key_event) 

2381 

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

2383 self.emit_selected_markers() 

2384 

2385 self.update() 

2386 self.update_status() 

2387 

2388 def handle_fkeys(self, key): 

2389 self.set_phase_kind( 

2390 self.selected_markers(), 

2391 fkey_map[key] + 1) 

2392 self.emit_selected_markers() 

2393 

2394 def emit_selected_markers(self): 

2395 ibounds = [] 

2396 last_selected = False 

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

2398 this_selected = marker.is_selected() 

2399 if this_selected != last_selected: 

2400 ibounds.append(imarker) 

2401 

2402 last_selected = this_selected 

2403 

2404 if last_selected: 

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

2406 

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

2408 self.n_selected_markers = sum( 

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

2410 self.marker_selection_changed.emit(chunks) 

2411 

2412 def toggle_marker_editor(self): 

2413 self.panel_parent.toggle_marker_editor() 

2414 

2415 def toggle_main_controls(self): 

2416 self.panel_parent.toggle_main_controls() 

2417 

2418 def nudge_selected_markers(self, npixels): 

2419 a, b = self.time_projection.ur 

2420 c, d = self.time_projection.xr 

2421 for marker in self.selected_markers(): 

2422 if not isinstance(marker, EventMarker): 

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

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

2425 

2426 def toggle_fullscreen(self): 

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

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

2429 self.window().showNormal() 

2430 else: 

2431 if macosx: 

2432 self.window().showMaximized() 

2433 else: 

2434 self.window().showFullScreen() 

2435 

2436 def about(self): 

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

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

2439 txt = f.read() 

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

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

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

2443 

2444 def help(self): 

2445 class MyScrollArea(qw.QScrollArea): 

2446 

2447 def sizeHint(self): 

2448 s = qc.QSize() 

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

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

2451 return s 

2452 

2453 with open(pyrocko.util.data_file( 

2454 'snuffler_help.html')) as f: 

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

2456 

2457 with open(pyrocko.util.data_file( 

2458 'snuffler_help_epilog.html')) as f: 

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

2460 

2461 for h in [hcheat, hepilog]: 

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

2463 h.setWordWrap(True) 

2464 

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

2466 

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

2468 scroller = qw.QScrollArea() 

2469 frame = qw.QFrame(scroller) 

2470 frame.setLineWidth(0) 

2471 layout = qw.QVBoxLayout() 

2472 layout.setContentsMargins(0, 0, 0, 0) 

2473 layout.setSpacing(0) 

2474 frame.setLayout(layout) 

2475 scroller.setWidget(frame) 

2476 scroller.setWidgetResizable(True) 

2477 frame.setBackgroundRole(qg.QPalette.Base) 

2478 for h in labels: 

2479 h.setParent(frame) 

2480 h.setMargin(3) 

2481 h.setTextInteractionFlags( 

2482 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2483 h.setBackgroundRole(qg.QPalette.Base) 

2484 layout.addWidget(h) 

2485 h.linkActivated.connect( 

2486 self.open_link) 

2487 

2488 if self.panel_parent is not None: 

2489 if target == 'panel': 

2490 self.panel_parent.add_panel( 

2491 name, scroller, True, volatile=False) 

2492 else: 

2493 self.panel_parent.add_tab(name, scroller) 

2494 

2495 def open_link(self, link): 

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

2497 

2498 def wheelEvent(self, wheel_event): 

2499 if use_pyqt5: 

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

2501 else: 

2502 self.wheel_pos += wheel_event.delta() 

2503 

2504 n = self.wheel_pos // 120 

2505 self.wheel_pos = self.wheel_pos % 120 

2506 if n == 0: 

2507 return 

2508 

2509 amount = max( 

2510 1., 

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

2512 wdelta = amount * n 

2513 

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

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

2516 / (trmax-trmin) 

2517 

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

2519 self.zoom_tracks(anchor, wdelta) 

2520 else: 

2521 self.scroll_tracks(-wdelta) 

2522 

2523 def dragEnterEvent(self, event): 

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

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

2526 event.setDropAction(qc.Qt.LinkAction) 

2527 event.accept() 

2528 

2529 def dropEvent(self, event): 

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

2531 paths = list( 

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

2533 event.acceptProposedAction() 

2534 self.load(paths) 

2535 

2536 def get_phase_name(self, kind): 

2537 return self.config.get_phase_name(kind) 

2538 

2539 def set_phase_kind(self, markers, kind): 

2540 phasename = self.get_phase_name(kind) 

2541 

2542 for marker in markers: 

2543 if isinstance(marker, PhaseMarker): 

2544 if kind == 10: 

2545 marker.convert_to_marker() 

2546 else: 

2547 marker.set_phasename(phasename) 

2548 marker.set_event(self.get_active_event()) 

2549 

2550 elif isinstance(marker, EventMarker): 

2551 pass 

2552 

2553 else: 

2554 if kind != 10: 

2555 event = self.get_active_event() 

2556 marker.convert_to_phase_marker( 

2557 event, phasename, None, False) 

2558 

2559 def set_ntracks(self, ntracks): 

2560 if self.ntracks != ntracks: 

2561 self.ntracks = ntracks 

2562 if self.shown_tracks_range is not None: 

2563 l, h = self.shown_tracks_range 

2564 else: 

2565 l, h = 0, self.ntracks 

2566 

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

2568 

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

2570 

2571 low, high = range 

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

2573 high = min(self.ntracks, high) 

2574 low = max(0, low) 

2575 high = max(1, high) 

2576 

2577 if start is None: 

2578 start = float(low) 

2579 

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

2581 self.shown_tracks_range = low, high 

2582 self.shown_tracks_start = start 

2583 

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

2585 

2586 def scroll_tracks(self, shift): 

2587 shown = self.shown_tracks_range 

2588 shiftmin = -shown[0] 

2589 shiftmax = self.ntracks-shown[1] 

2590 shift = max(shiftmin, shift) 

2591 shift = min(shiftmax, shift) 

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

2593 

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

2595 

2596 self.update() 

2597 

2598 def zoom_tracks(self, anchor, delta): 

2599 ntracks_shown = self.shown_tracks_range[1] \ 

2600 - self.shown_tracks_range[0] 

2601 

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

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

2604 return 

2605 

2606 ntracks_shown += int(round(delta)) 

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

2608 

2609 u = self.shown_tracks_start 

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

2611 nv = nu + ntracks_shown 

2612 if nv > self.ntracks: 

2613 nu -= nv - self.ntracks 

2614 nv -= nv - self.ntracks 

2615 

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

2617 

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

2619 - self.shown_tracks_range[0] 

2620 

2621 self.update() 

2622 

2623 def content_time_range(self): 

2624 pile = self.get_pile() 

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

2626 if tmin is None: 

2627 tmin = initial_time_range[0] 

2628 if tmax is None: 

2629 tmax = initial_time_range[1] 

2630 

2631 return tmin, tmax 

2632 

2633 def content_deltat_range(self): 

2634 pile = self.get_pile() 

2635 

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

2637 

2638 if deltatmin is None: 

2639 deltatmin = 0.001 

2640 

2641 if deltatmax is None: 

2642 deltatmax = 1000.0 

2643 

2644 return deltatmin, deltatmax 

2645 

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

2647 if tmax < tmin: 

2648 tmin, tmax = tmax, tmin 

2649 

2650 deltatmin = self.content_deltat_range()[0] 

2651 dt = deltatmin * self.visible_length * 0.95 

2652 

2653 if dt == 0.0: 

2654 dt = 1.0 

2655 

2656 if tight: 

2657 if tmax != tmin: 

2658 dtm = tmax - tmin 

2659 tmin -= dtm*0.1 

2660 tmax += dtm*0.1 

2661 return tmin, tmax 

2662 else: 

2663 tcenter = (tmin + tmax) / 2. 

2664 tmin = tcenter - 0.5*dt 

2665 tmax = tcenter + 0.5*dt 

2666 return tmin, tmax 

2667 

2668 if tmax-tmin < dt: 

2669 vmin, vmax = self.get_time_range() 

2670 dt = min(vmax - vmin, dt) 

2671 

2672 tcenter = (tmin+tmax)/2. 

2673 etmin, etmax = tmin, tmax 

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

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

2676 dtm = tmax-tmin 

2677 if etmin == tmin: 

2678 tmin -= dtm*0.1 

2679 if etmax == tmax: 

2680 tmax += dtm*0.1 

2681 

2682 else: 

2683 dtm = tmax-tmin 

2684 tmin -= dtm*0.1 

2685 tmax += dtm*0.1 

2686 

2687 return tmin, tmax 

2688 

2689 def go_to_selection(self, tight=False): 

2690 markers = self.selected_markers() 

2691 if markers: 

2692 tmax, tmin = self.content_time_range() 

2693 for marker in markers: 

2694 tmin = min(tmin, marker.tmin) 

2695 tmax = max(tmax, marker.tmax) 

2696 

2697 else: 

2698 if tight: 

2699 vmin, vmax = self.get_time_range() 

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

2701 else: 

2702 tmin, tmax = self.content_time_range() 

2703 

2704 tmin, tmax = self.make_good_looking_time_range( 

2705 tmin, tmax, tight=tight) 

2706 

2707 self.interrupt_following() 

2708 self.set_time_range(tmin, tmax) 

2709 self.update() 

2710 

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

2712 tmax = t 

2713 if tlen is not None: 

2714 tmax = t+tlen 

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

2716 self.interrupt_following() 

2717 self.set_time_range(tmin, tmax) 

2718 self.update() 

2719 

2720 def go_to_event_by_name(self, name): 

2721 for marker in self.markers: 

2722 if isinstance(marker, EventMarker): 

2723 event = marker.get_event() 

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

2725 tmin, tmax = self.make_good_looking_time_range( 

2726 event.time, event.time) 

2727 

2728 self.interrupt_following() 

2729 self.set_time_range(tmin, tmax) 

2730 

2731 def printit(self): 

2732 from .qt_compat import qprint 

2733 printer = qprint.QPrinter() 

2734 printer.setOrientation(qprint.QPrinter.Landscape) 

2735 

2736 dialog = qprint.QPrintDialog(printer, self) 

2737 dialog.setWindowTitle('Print') 

2738 

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

2740 return 

2741 

2742 painter = qg.QPainter() 

2743 painter.begin(printer) 

2744 page = printer.pageRect() 

2745 self.drawit( 

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

2747 

2748 painter.end() 

2749 

2750 def savesvg(self, fn=None): 

2751 

2752 if not fn: 

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

2754 self, 

2755 'Save as SVG|PNG', 

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

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

2758 options=qfiledialog_options)) 

2759 

2760 if fn == '': 

2761 return 

2762 

2763 fn = str(fn) 

2764 

2765 if fn.lower().endswith('.svg'): 

2766 try: 

2767 w, h = 842, 595 

2768 margin = 0.025 

2769 m = max(w, h)*margin 

2770 

2771 generator = qsvg.QSvgGenerator() 

2772 generator.setFileName(fn) 

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

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

2775 

2776 painter = qg.QPainter() 

2777 painter.begin(generator) 

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

2779 painter.end() 

2780 

2781 except Exception as e: 

2782 self.fail('Failed to write SVG file: %s' % str(e)) 

2783 

2784 elif fn.lower().endswith('.png'): 

2785 if use_pyqt5: 

2786 pixmap = self.grab() 

2787 else: 

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

2789 

2790 try: 

2791 pixmap.save(fn) 

2792 

2793 except Exception as e: 

2794 self.fail('Failed to write PNG file: %s' % str(e)) 

2795 

2796 else: 

2797 self.fail( 

2798 'Unsupported file type: filename must end with ".svg" or ' 

2799 '".png".') 

2800 

2801 def paintEvent(self, paint_ev): 

2802 ''' 

2803 Called by QT whenever widget needs to be painted. 

2804 ''' 

2805 painter = qg.QPainter(self) 

2806 

2807 if self.menuitem_antialias.isChecked(): 

2808 painter.setRenderHint(qg.QPainter.Antialiasing) 

2809 

2810 self.drawit(painter) 

2811 

2812 logger.debug( 

2813 'Time spent drawing: ' 

2814 ' user:%.3f sys:%.3f children_user:%.3f' 

2815 ' childred_sys:%.3f elapsed:%.3f' % 

2816 (self.timer_draw - self.timer_cutout)) 

2817 

2818 logger.debug( 

2819 'Time spent processing:' 

2820 ' user:%.3f sys:%.3f children_user:%.3f' 

2821 ' childred_sys:%.3f elapsed:%.3f' % 

2822 self.timer_cutout.get()) 

2823 

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

2825 self.time_last_painted = time.time() 

2826 

2827 def determine_box_styles(self): 

2828 

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

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

2831 istyle = 0 

2832 trace_styles = {} 

2833 for itr, tr in enumerate(traces): 

2834 if itr > 0: 

2835 other = traces[itr-1] 

2836 if not ( 

2837 other.nslc_id == tr.nslc_id 

2838 and other.deltat == tr.deltat 

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

2840 < gap_lap_tolerance*tr.deltat): 

2841 

2842 istyle += 1 

2843 

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

2845 

2846 self.trace_styles = trace_styles 

2847 

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

2849 

2850 for v_projection in track_projections.values(): 

2851 v_projection.set_in_range(0., 1.) 

2852 

2853 def selector(x): 

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

2855 

2856 if self.trace_filter is not None: 

2857 def tselector(x): 

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

2859 

2860 else: 

2861 tselector = selector 

2862 

2863 traces = list(self.pile.iter_traces( 

2864 group_selector=selector, trace_selector=tselector)) 

2865 

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

2867 

2868 def drawbox(itrack, istyle, traces): 

2869 v_projection = track_projections[itrack] 

2870 dvmin = v_projection(0.) 

2871 dvmax = v_projection(1.) 

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

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

2874 

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

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

2877 p.fillRect(rect, style.fill_brush) 

2878 p.setPen(style.frame_pen) 

2879 p.drawRect(rect) 

2880 

2881 traces_by_style = {} 

2882 for itr, tr in enumerate(traces): 

2883 gt = self.gather(tr) 

2884 if gt not in self.key_to_row: 

2885 continue 

2886 

2887 itrack = self.key_to_row[gt] 

2888 if itrack not in track_projections: 

2889 continue 

2890 

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

2892 

2893 if len(traces) < 500: 

2894 drawbox(itrack, istyle, [tr]) 

2895 else: 

2896 if (itrack, istyle) not in traces_by_style: 

2897 traces_by_style[itrack, istyle] = [] 

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

2899 

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

2901 drawbox(itrack, istyle, traces) 

2902 

2903 def draw_visible_markers( 

2904 self, p, vcenter_projection, primary_pen): 

2905 

2906 try: 

2907 markers = self.markers.with_key_in_limited( 

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

2909 

2910 except pyrocko.pile.TooMany: 

2911 tmin = self.markers[0].tmin 

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

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

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

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

2916 v0, _ = vcenter_projection.get_out_range() 

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

2918 

2919 p.save() 

2920 

2921 pen = qg.QPen(primary_pen) 

2922 pen.setWidth(2) 

2923 pen.setStyle(qc.Qt.DotLine) 

2924 # pat = [5., 3.] 

2925 # pen.setDashPattern(pat) 

2926 p.setPen(pen) 

2927 

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

2929 s_selected = ' (all selected)' 

2930 elif self.n_selected_markers > 0: 

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

2932 else: 

2933 s_selected = '' 

2934 

2935 draw_label( 

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

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

2938 label_bg, 'LB') 

2939 

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

2941 p.drawLine(line) 

2942 p.restore() 

2943 

2944 return 

2945 

2946 for marker in markers: 

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

2948 and marker.kind in self.visible_marker_kinds: 

2949 

2950 marker.draw( 

2951 p, self.time_projection, vcenter_projection, 

2952 with_label=True) 

2953 

2954 def get_squirrel(self): 

2955 try: 

2956 return self.pile._squirrel 

2957 except AttributeError: 

2958 return None 

2959 

2960 def draw_coverage(self, p, time_projection, track_projections): 

2961 sq = self.get_squirrel() 

2962 if sq is None: 

2963 return 

2964 

2965 def drawbox(itrack, tmin, tmax, style): 

2966 v_projection = track_projections[itrack] 

2967 dvmin = v_projection(0.) 

2968 dvmax = v_projection(1.) 

2969 dtmin = time_projection.clipped(tmin) 

2970 dtmax = time_projection.clipped(tmax) 

2971 

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

2973 p.fillRect(rect, style.fill_brush) 

2974 p.setPen(style.frame_pen) 

2975 p.drawRect(rect) 

2976 

2977 pattern_list = [] 

2978 pattern_to_itrack = {} 

2979 for key in self.track_keys: 

2980 itrack = self.key_to_row[key] 

2981 if itrack not in track_projections: 

2982 continue 

2983 

2984 pattern = self.track_patterns[itrack] 

2985 pattern_to_itrack[tuple(pattern)] = itrack 

2986 pattern_list.append(pattern) 

2987 

2988 vmin, vmax = self.get_time_range() 

2989 for entry in sq.get_coverage( 

2990 'waveform', vmin, vmax, pattern_list, limit=500): 

2991 pattern, codes, deltat, tmin, tmax, cover_data = entry 

2992 itrack = pattern_to_itrack[tuple(pattern)] 

2993 

2994 if cover_data is None: 

2995 drawbox(itrack, tmin, tmax, box_styles_coverage[0]) 

2996 else: 

2997 t = None 

2998 pcount = 0 

2999 for tb, count in cover_data: 

3000 if t is not None and tb > t: 

3001 if pcount > 0: 

3002 drawbox( 

3003 itrack, t, tb, 

3004 box_styles_coverage[ 

3005 min(len(box_styles_coverage)-1, 

3006 pcount)]) 

3007 

3008 t = tb 

3009 pcount = count 

3010 

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

3012 ''' 

3013 This performs the actual drawing. 

3014 ''' 

3015 

3016 self.timer_draw.start() 

3017 show_boxes = self.menuitem_showboxes.isChecked() 

3018 sq = self.get_squirrel() 

3019 

3020 if self.gather is None: 

3021 self.set_gathering() 

3022 

3023 if self.pile_has_changed: 

3024 

3025 if not self.sortingmode_change_delayed(): 

3026 self.sortingmode_change() 

3027 

3028 if show_boxes and sq is None: 

3029 self.determine_box_styles() 

3030 

3031 self.pile_has_changed = False 

3032 

3033 if h is None: 

3034 h = float(self.height()) 

3035 if w is None: 

3036 w = float(self.width()) 

3037 

3038 if printmode: 

3039 primary_color = (0, 0, 0) 

3040 else: 

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

3042 

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

3044 

3045 ax_h = float(self.ax_height) 

3046 

3047 vbottom_ax_projection = Projection() 

3048 vtop_ax_projection = Projection() 

3049 vcenter_projection = Projection() 

3050 

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

3052 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3053 vtop_ax_projection.set_out_range(0., ax_h) 

3054 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3055 vcenter_projection.set_in_range(0., 1.) 

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

3057 

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

3059 track_projections = {} 

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

3061 proj = Projection() 

3062 proj.set_out_range( 

3063 self.track_to_screen(i+0.05), 

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

3065 

3066 track_projections[i] = proj 

3067 

3068 if self.tmin > self.tmax: 

3069 return 

3070 

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

3072 vbottom_ax_projection.set_in_range(0, ax_h) 

3073 

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

3075 

3076 yscaler = pyrocko.plot.AutoScaler() 

3077 

3078 p.setPen(primary_pen) 

3079 

3080 font = qg.QFont() 

3081 font.setBold(True) 

3082 

3083 axannotfont = qg.QFont() 

3084 axannotfont.setBold(True) 

3085 axannotfont.setPointSize(8) 

3086 

3087 processed_traces = self.prepare_cutout2( 

3088 self.tmin, self.tmax, 

3089 trace_selector=self.trace_selector, 

3090 degap=self.menuitem_degap.isChecked(), 

3091 demean=self.menuitem_demean.isChecked()) 

3092 

3093 if not printmode and show_boxes: 

3094 if (self.view_mode is ViewMode.Wiggle) \ 

3095 or (self.view_mode is ViewMode.Waterfall 

3096 and not processed_traces): 

3097 

3098 if sq is None: 

3099 self.draw_trace_boxes( 

3100 p, self.time_projection, track_projections) 

3101 

3102 else: 

3103 self.draw_coverage( 

3104 p, self.time_projection, track_projections) 

3105 

3106 p.setFont(font) 

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

3108 

3109 color_lookup = dict( 

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

3111 

3112 self.track_to_nslc_ids = {} 

3113 nticks = 0 

3114 annot_labels = [] 

3115 

3116 if self.view_mode is ViewMode.Waterfall and processed_traces: 

3117 waterfall = self.waterfall 

3118 waterfall.set_time_range(self.tmin, self.tmax) 

3119 waterfall.set_traces(processed_traces) 

3120 waterfall.set_cmap(self.waterfall_cmap) 

3121 waterfall.set_integrate(self.waterfall_integrate) 

3122 waterfall.set_clip( 

3123 self.waterfall_clip_min, self.waterfall_clip_max) 

3124 waterfall.show_absolute_values( 

3125 self.waterfall_show_absolute) 

3126 

3127 rect = qc.QRectF( 

3128 0, self.ax_height, 

3129 self.width(), self.height() - self.ax_height*2 

3130 ) 

3131 waterfall.draw_waterfall(p, rect=rect) 

3132 

3133 elif self.view_mode is ViewMode.Wiggle and processed_traces: 

3134 show_scales = self.menuitem_showscalerange.isChecked() \ 

3135 or self.menuitem_showscaleaxis.isChecked() 

3136 

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

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

3139 - self.track_to_screen(0.05) 

3140 

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

3142 

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

3144 if self.menuitem_showscaleaxis.isChecked() \ 

3145 else 15 

3146 

3147 yscaler = pyrocko.plot.AutoScaler( 

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

3149 snap=show_scales 

3150 and not self.menuitem_showscaleaxis.isChecked()) 

3151 

3152 data_ranges = pyrocko.trace.minmax( 

3153 processed_traces, 

3154 key=self.scaling_key, 

3155 mode=self.scaling_base) 

3156 

3157 if not self.menuitem_fixscalerange.isChecked(): 

3158 self.old_data_ranges = data_ranges 

3159 else: 

3160 data_ranges.update(self.old_data_ranges) 

3161 

3162 self.apply_scaling_hooks(data_ranges) 

3163 

3164 trace_to_itrack = {} 

3165 track_scaling_keys = {} 

3166 track_scaling_colors = {} 

3167 for trace in processed_traces: 

3168 gt = self.gather(trace) 

3169 if gt not in self.key_to_row: 

3170 continue 

3171 

3172 itrack = self.key_to_row[gt] 

3173 if itrack not in track_projections: 

3174 continue 

3175 

3176 trace_to_itrack[trace] = itrack 

3177 

3178 if itrack not in self.track_to_nslc_ids: 

3179 self.track_to_nslc_ids[itrack] = set() 

3180 

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

3182 

3183 if itrack not in track_scaling_keys: 

3184 track_scaling_keys[itrack] = set() 

3185 

3186 scaling_key = self.scaling_key(trace) 

3187 track_scaling_keys[itrack].add(scaling_key) 

3188 

3189 color = pyrocko.plot.color( 

3190 color_lookup[self.color_gather(trace)]) 

3191 

3192 k = itrack, scaling_key 

3193 if k not in track_scaling_colors \ 

3194 and self.menuitem_colortraces.isChecked(): 

3195 track_scaling_colors[k] = color 

3196 else: 

3197 track_scaling_colors[k] = primary_color 

3198 

3199 # y axes, zero lines 

3200 trace_projections = {} 

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

3202 if itrack not in track_scaling_keys: 

3203 continue 

3204 uoff = 0 

3205 for scaling_key in track_scaling_keys[itrack]: 

3206 data_range = data_ranges[scaling_key] 

3207 dymin, dymax = data_range 

3208 ymin, ymax, yinc = yscaler.make_scale( 

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

3210 iexp = yscaler.make_exp(yinc) 

3211 factor = 10**iexp 

3212 trace_projection = track_projections[itrack].copy() 

3213 trace_projection.set_in_range(ymax, ymin) 

3214 trace_projections[itrack, scaling_key] = \ 

3215 trace_projection 

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

3217 vmin, vmax = trace_projection.get_out_range() 

3218 umax_zeroline = umax 

3219 uoffnext = uoff 

3220 

3221 if show_scales: 

3222 pen = qg.QPen(primary_pen) 

3223 k = itrack, scaling_key 

3224 if k in track_scaling_colors: 

3225 c = qg.QColor(*track_scaling_colors[ 

3226 itrack, scaling_key]) 

3227 

3228 pen.setColor(c) 

3229 

3230 p.setPen(pen) 

3231 if nlinesavail > 3: 

3232 if self.menuitem_showscaleaxis.isChecked(): 

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

3234 ny_annot = int( 

3235 math.floor(ymax/yinc) 

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

3237 

3238 for iy_annot in range(ny_annot): 

3239 y = ymin_annot + iy_annot*yinc 

3240 v = trace_projection(y) 

3241 line = qc.QLineF( 

3242 umax-10-uoff, v, umax-uoff, v) 

3243 

3244 p.drawLine(line) 

3245 if iy_annot == ny_annot - 1 \ 

3246 and iexp != 0: 

3247 sexp = ' &times; ' \ 

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

3249 else: 

3250 sexp = '' 

3251 

3252 snum = num_to_html(y/factor) 

3253 lab = Label( 

3254 p, 

3255 umax-20-uoff, 

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

3257 label_bg=None, 

3258 anchor='MR', 

3259 font=axannotfont, 

3260 color=c) 

3261 

3262 uoffnext = max( 

3263 lab.rect.width()+30., uoffnext) 

3264 

3265 annot_labels.append(lab) 

3266 if y == 0.: 

3267 umax_zeroline = \ 

3268 umax - 20 \ 

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

3270 - uoff 

3271 else: 

3272 if not show_boxes: 

3273 qpoints = make_QPolygonF( 

3274 [umax-20-uoff, 

3275 umax-10-uoff, 

3276 umax-10-uoff, 

3277 umax-20-uoff], 

3278 [vmax, vmax, vmin, vmin]) 

3279 p.drawPolyline(qpoints) 

3280 

3281 snum = num_to_html(ymin) 

3282 labmin = Label( 

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

3284 label_bg=None, 

3285 anchor='BR', 

3286 font=axannotfont, 

3287 color=c) 

3288 

3289 annot_labels.append(labmin) 

3290 snum = num_to_html(ymax) 

3291 labmax = Label( 

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

3293 label_bg=None, 

3294 anchor='TR', 

3295 font=axannotfont, 

3296 color=c) 

3297 

3298 annot_labels.append(labmax) 

3299 

3300 for lab in (labmin, labmax): 

3301 uoffnext = max( 

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

3303 

3304 if self.menuitem_showzeroline.isChecked(): 

3305 v = trace_projection(0.) 

3306 if vmin <= v <= vmax: 

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

3308 p.drawLine(line) 

3309 

3310 uoff = uoffnext 

3311 

3312 p.setFont(font) 

3313 p.setPen(primary_pen) 

3314 for trace in processed_traces: 

3315 if self.view_mode is not ViewMode.Wiggle: 

3316 break 

3317 

3318 if trace not in trace_to_itrack: 

3319 continue 

3320 

3321 itrack = trace_to_itrack[trace] 

3322 scaling_key = self.scaling_key(trace) 

3323 trace_projection = trace_projections[ 

3324 itrack, scaling_key] 

3325 

3326 vdata = trace_projection(trace.get_ydata()) 

3327 

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

3329 udata_max = float(self.time_projection( 

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

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

3332 

3333 qpoints = make_QPolygonF(udata, vdata) 

3334 

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

3336 vmin, vmax = trace_projection.get_out_range() 

3337 

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

3339 

3340 if self.menuitem_cliptraces.isChecked(): 

3341 p.setClipRect(trackrect) 

3342 

3343 if self.menuitem_colortraces.isChecked(): 

3344 color = pyrocko.plot.color( 

3345 color_lookup[self.color_gather(trace)]) 

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

3347 p.setPen(pen) 

3348 

3349 p.drawPolyline(qpoints) 

3350 

3351 if self.floating_marker: 

3352 self.floating_marker.draw_trace( 

3353 self, p, trace, 

3354 self.time_projection, trace_projection, 1.0) 

3355 

3356 for marker in self.markers.with_key_in( 

3357 self.tmin - self.markers_deltat_max, 

3358 self.tmax): 

3359 

3360 if marker.tmin < self.tmax \ 

3361 and self.tmin < marker.tmax \ 

3362 and marker.kind \ 

3363 in self.visible_marker_kinds: 

3364 marker.draw_trace( 

3365 self, p, trace, self.time_projection, 

3366 trace_projection, 1.0) 

3367 

3368 p.setPen(primary_pen) 

3369 

3370 if self.menuitem_cliptraces.isChecked(): 

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

3372 

3373 if self.floating_marker: 

3374 self.floating_marker.draw( 

3375 p, self.time_projection, vcenter_projection) 

3376 

3377 self.draw_visible_markers( 

3378 p, vcenter_projection, primary_pen) 

3379 

3380 p.setPen(primary_pen) 

3381 while font.pointSize() > 2: 

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

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

3384 - self.track_to_screen(0.05) 

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

3386 if nlinesavail > 1: 

3387 break 

3388 

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

3390 

3391 p.setFont(font) 

3392 mouse_pos = self.mapFromGlobal(qg.QCursor.pos()) 

3393 

3394 for key in self.track_keys: 

3395 itrack = self.key_to_row[key] 

3396 if itrack in track_projections: 

3397 plabel = ' '.join( 

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

3399 lx = 10 

3400 ly = self.track_to_screen(itrack+0.5) 

3401 

3402 if p.font().pointSize() >= MIN_LABEL_SIZE_PT: 

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

3404 continue 

3405 

3406 contains_cursor = \ 

3407 self.track_to_screen(itrack) \ 

3408 < mouse_pos.y() \ 

3409 < self.track_to_screen(itrack+1) 

3410 

3411 if not contains_cursor: 

3412 continue 

3413 

3414 font_large = p.font() 

3415 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3416 p.setFont(font_large) 

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

3418 p.setFont(font) 

3419 

3420 for lab in annot_labels: 

3421 lab.draw() 

3422 

3423 self.timer_draw.stop() 

3424 

3425 def see_data_params(self): 

3426 

3427 min_deltat = self.content_deltat_range()[0] 

3428 

3429 # determine padding and downampling requirements 

3430 if self.lowpass is not None: 

3431 deltat_target = 1./self.lowpass * 0.25 

3432 ndecimate = min( 

3433 50, 

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

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

3436 else: 

3437 ndecimate = 1 

3438 tpad = min_deltat*5. 

3439 

3440 if self.highpass is not None: 

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

3442 

3443 nsee_points_per_trace = 5000*10 

3444 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3445 

3446 return ndecimate, tpad, tsee 

3447 

3448 def clean_update(self): 

3449 self.cached_processed_traces = None 

3450 self.cached_chopped_traces = {} 

3451 self.update() 

3452 

3453 def get_adequate_tpad(self): 

3454 tpad = 0. 

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

3456 if f is not None: 

3457 tpad = max(tpad, 1.0/f) 

3458 

3459 for snuffling in self.snufflings: 

3460 if snuffling._post_process_hook_enabled \ 

3461 or snuffling._pre_process_hook_enabled: 

3462 

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

3464 

3465 return tpad 

3466 

3467 def prepare_cutout2( 

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

3469 demean=True, nmax=6000): 

3470 

3471 if self.pile.is_empty(): 

3472 return [] 

3473 

3474 nmax = self.visible_length 

3475 

3476 self.timer_cutout.start() 

3477 

3478 tsee = tmax-tmin 

3479 min_deltat_wo_decimate = tsee/nmax 

3480 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3481 

3482 min_deltat_allow = min_deltat_wo_decimate 

3483 if self.lowpass is not None: 

3484 target_deltat_lp = 0.25/self.lowpass 

3485 if target_deltat_lp > min_deltat_wo_decimate: 

3486 min_deltat_allow = min_deltat_w_decimate 

3487 

3488 min_deltat_allow = math.exp( 

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

3490 

3491 tmin_ = tmin 

3492 tmax_ = tmax 

3493 

3494 # fetch more than needed? 

3495 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3499 

3500 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3501 lphp = self.menuitem_lphp.isChecked() 

3502 ads = self.menuitem_allowdownsampling.isChecked() 

3503 

3504 tpad = self.get_adequate_tpad() 

3505 tpad = max(tpad, tsee) 

3506 

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

3508 vec = ( 

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

3510 self.highpass, fft_filtering, lphp, 

3511 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3512 ads, self.pile.get_update_count()) 

3513 

3514 dtmin = 0. if not self.cached_vec else tmin_ - self.cached_vec[0] 

3515 dtmax = 0. if not self.cached_vec else tmax_ - self.cached_vec[1] 

3516 

3517 if (self.cached_vec 

3518 and self.cached_vec[0] <= vec[0] 

3519 and vec[1] <= self.cached_vec[1] 

3520 and vec[2:] == self.cached_vec[2:] 

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

3522 and self.cached_processed_traces is not None): 

3523 

3524 logger.debug('Using cached traces') 

3525 processed_traces = self.cached_processed_traces 

3526 

3527 else: 

3528 processed_traces = [] 

3529 if self.pile.deltatmax >= min_deltat_allow: 

3530 

3531 def group_selector(gr): 

3532 return gr.deltatmax >= min_deltat_allow 

3533 

3534 if trace_selector is not None: 

3535 def trace_selectorx(tr): 

3536 return tr.deltat >= min_deltat_allow \ 

3537 and trace_selector(tr) 

3538 else: 

3539 def trace_selectorx(tr): 

3540 return tr.deltat >= min_deltat_allow 

3541 

3542 for traces in self.pile.chopper( 

3543 tmin=tmin, tmax=tmax, tpad=tpad, 

3544 want_incomplete=True, 

3545 degap=degap, 

3546 maxgap=gap_lap_tolerance, 

3547 maxlap=gap_lap_tolerance, 

3548 keep_current_files_open=True, 

3549 group_selector=group_selector, 

3550 trace_selector=trace_selectorx, 

3551 accessor_id=id(self), 

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

3553 include_last=True): 

3554 

3555 if demean: 

3556 for tr in traces: 

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

3558 continue 

3559 y = tr.get_ydata() 

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

3561 

3562 traces = self.pre_process_hooks(traces) 

3563 

3564 for trace in traces: 

3565 

3566 if not (trace.meta 

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

3568 

3569 if fft_filtering: 

3570 but = pyrocko.response.ButterworthResponse 

3571 multres = pyrocko.response.MultiplyResponse 

3572 if self.lowpass is not None \ 

3573 or self.highpass is not None: 

3574 

3575 it = num.arange( 

3576 trace.data_len(), dtype=float) 

3577 detr_data, m, b = detrend( 

3578 it, trace.get_ydata()) 

3579 

3580 trace.set_ydata(detr_data) 

3581 

3582 freqs, fdata = trace.spectrum( 

3583 pad_to_pow2=True, tfade=None) 

3584 

3585 nfreqs = fdata.size 

3586 

3587 key = (trace.deltat, nfreqs) 

3588 

3589 if key not in self.tf_cache: 

3590 resps = [] 

3591 if self.lowpass is not None: 

3592 resps.append(but( 

3593 order=4, 

3594 corner=self.lowpass, 

3595 type='low')) 

3596 

3597 if self.highpass is not None: 

3598 resps.append(but( 

3599 order=4, 

3600 corner=self.highpass, 

3601 type='high')) 

3602 

3603 resp = multres(resps) 

3604 self.tf_cache[key] = \ 

3605 resp.evaluate(freqs) 

3606 

3607 filtered_data = num.fft.irfft( 

3608 fdata*self.tf_cache[key] 

3609 )[:trace.data_len()] 

3610 

3611 retrended_data = retrend( 

3612 it, filtered_data, m, b) 

3613 

3614 trace.set_ydata(retrended_data) 

3615 

3616 else: 

3617 

3618 if ads and self.lowpass is not None: 

3619 while trace.deltat \ 

3620 < min_deltat_wo_decimate: 

3621 

3622 trace.downsample(2, demean=False) 

3623 

3624 fmax = 0.5/trace.deltat 

3625 if not lphp and ( 

3626 self.lowpass is not None 

3627 and self.highpass is not None 

3628 and self.lowpass < fmax 

3629 and self.highpass < fmax 

3630 and self.highpass < self.lowpass): 

3631 

3632 trace.bandpass( 

3633 2, self.highpass, self.lowpass) 

3634 else: 

3635 if self.lowpass is not None: 

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

3637 trace.lowpass( 

3638 4, self.lowpass, 

3639 demean=False) 

3640 

3641 if self.highpass is not None: 

3642 if self.lowpass is None \ 

3643 or self.highpass \ 

3644 < self.lowpass: 

3645 

3646 if self.highpass < \ 

3647 0.5/trace.deltat: 

3648 trace.highpass( 

3649 4, self.highpass, 

3650 demean=False) 

3651 

3652 processed_traces.append(trace) 

3653 

3654 if self.rotate != 0.0: 

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

3656 cphi = math.cos(phi) 

3657 sphi = math.sin(phi) 

3658 for a in processed_traces: 

3659 for b in processed_traces: 

3660 if (a.network == b.network 

3661 and a.station == b.station 

3662 and a.location == b.location 

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

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

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

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

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

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

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

3670 

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

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

3673 a.set_ydata(aydata) 

3674 b.set_ydata(bydata) 

3675 

3676 processed_traces = self.post_process_hooks(processed_traces) 

3677 

3678 self.cached_processed_traces = processed_traces 

3679 self.cached_vec = vec 

3680 

3681 chopped_traces = [] 

3682 for trace in processed_traces: 

3683 chop_tmin = tmin_ - trace.deltat*4 

3684 chop_tmax = tmax_ + trace.deltat*4 

3685 trace_hash = trace.hash(unsafe=True) 

3686 

3687 # Use cache if tmin and tmax have not changed 

3688 if dtmin == 0. and dtmax == 0. \ 

3689 and trace_hash in self.cached_chopped_traces: 

3690 ctrace = self.cached_chopped_traces[trace_hash] 

3691 

3692 else: 

3693 try: 

3694 ctrace = trace.chop( 

3695 chop_tmin, chop_tmax, 

3696 inplace=False) 

3697 

3698 except pyrocko.trace.NoData: 

3699 continue 

3700 

3701 if ctrace.data_len() < 2: 

3702 continue 

3703 

3704 self.cached_chopped_traces[trace_hash] = ctrace 

3705 chopped_traces.append(ctrace) 

3706 

3707 self.timer_cutout.stop() 

3708 return chopped_traces 

3709 

3710 def pre_process_hooks(self, traces): 

3711 for snuffling in self.snufflings: 

3712 if snuffling._pre_process_hook_enabled: 

3713 traces = snuffling.pre_process_hook(traces) 

3714 

3715 return traces 

3716 

3717 def post_process_hooks(self, traces): 

3718 for snuffling in self.snufflings: 

3719 if snuffling._post_process_hook_enabled: 

3720 traces = snuffling.post_process_hook(traces) 

3721 

3722 return traces 

3723 

3724 def visible_length_change(self, ignore=None): 

3725 for menuitem, vlen in self.menuitems_visible_length: 

3726 if menuitem.isChecked(): 

3727 self.visible_length = vlen 

3728 

3729 def scaling_base_change(self, ignore=None): 

3730 for menuitem, scaling_base in self.menuitems_scaling_base: 

3731 if menuitem.isChecked(): 

3732 self.scaling_base = scaling_base 

3733 

3734 def scalingmode_change(self, ignore=None): 

3735 for menuitem, scaling_key in self.menuitems_scaling: 

3736 if menuitem.isChecked(): 

3737 self.scaling_key = scaling_key 

3738 self.update() 

3739 

3740 def apply_scaling_hooks(self, data_ranges): 

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

3742 hook = self.scaling_hooks[k] 

3743 hook(data_ranges) 

3744 

3745 def viewmode_change(self, ignore=True): 

3746 for item, mode in self.menuitems_viewmode: 

3747 if item.isChecked(): 

3748 self.view_mode = mode 

3749 break 

3750 else: 

3751 raise AttributeError('unknown view mode') 

3752 

3753 items_waterfall_disabled = ( 

3754 self.menuitem_showscaleaxis, 

3755 self.menuitem_showscalerange, 

3756 self.menuitem_showzeroline, 

3757 self.menuitem_colortraces, 

3758 self.menuitem_cliptraces, 

3759 *(itm[0] for itm in self.menuitems_visible_length) 

3760 ) 

3761 

3762 if self.view_mode is ViewMode.Waterfall: 

3763 self.parent().show_colorbar_ctrl(True) 

3764 self.parent().show_gain_ctrl(False) 

3765 

3766 for item in items_waterfall_disabled: 

3767 item.setDisabled(True) 

3768 

3769 self.visible_length = 180. 

3770 else: 

3771 self.parent().show_colorbar_ctrl(False) 

3772 self.parent().show_gain_ctrl(True) 

3773 

3774 for item in items_waterfall_disabled: 

3775 item.setDisabled(False) 

3776 

3777 self.visible_length_change() 

3778 self.update() 

3779 

3780 def set_scaling_hook(self, k, hook): 

3781 self.scaling_hooks[k] = hook 

3782 

3783 def remove_scaling_hook(self, k): 

3784 del self.scaling_hooks[k] 

3785 

3786 def remove_scaling_hooks(self): 

3787 self.scaling_hooks = {} 

3788 

3789 def s_sortingmode_change(self, ignore=None): 

3790 for menuitem, valfunc in self.menuitems_ssorting: 

3791 if menuitem.isChecked(): 

3792 self._ssort = valfunc 

3793 

3794 self.sortingmode_change() 

3795 

3796 def sortingmode_change(self, ignore=None): 

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

3798 if menuitem.isChecked(): 

3799 self.set_gathering(gather, color) 

3800 

3801 self.sortingmode_change_time = time.time() 

3802 

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

3804 self.lowpass = value 

3805 self.passband_check() 

3806 self.tf_cache = {} 

3807 self.update() 

3808 

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

3810 self.highpass = value 

3811 self.passband_check() 

3812 self.tf_cache = {} 

3813 self.update() 

3814 

3815 def passband_check(self): 

3816 if self.highpass and self.lowpass \ 

3817 and self.highpass >= self.lowpass: 

3818 

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

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

3821 'deactivate the highpass.' 

3822 

3823 self.update_status() 

3824 else: 

3825 oldmess = self.message 

3826 self.message = None 

3827 if oldmess is not None: 

3828 self.update_status() 

3829 

3830 def gain_change(self, value, ignore): 

3831 self.gain = value 

3832 self.update() 

3833 

3834 def rot_change(self, value, ignore): 

3835 self.rotate = value 

3836 self.update() 

3837 

3838 def waterfall_cmap_change(self, cmap): 

3839 self.waterfall_cmap = cmap 

3840 self.update() 

3841 

3842 def waterfall_clip_change(self, clip_min, clip_max): 

3843 self.waterfall_clip_min = clip_min 

3844 self.waterfall_clip_max = clip_max 

3845 self.update() 

3846 

3847 def waterfall_show_absolute_change(self, toggle): 

3848 self.waterfall_show_absolute = toggle 

3849 self.update() 

3850 

3851 def waterfall_set_integrate(self, toggle): 

3852 self.waterfall_integrate = toggle 

3853 self.update() 

3854 

3855 def set_selected_markers(self, markers): 

3856 ''' 

3857 Set a list of markers selected 

3858 

3859 :param markers: list of markers 

3860 ''' 

3861 self.deselect_all() 

3862 for m in markers: 

3863 m.selected = True 

3864 

3865 self.update() 

3866 

3867 def deselect_all(self): 

3868 for marker in self.markers: 

3869 marker.selected = False 

3870 

3871 def animate_picking(self): 

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

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

3874 

3875 def get_nslc_ids_for_track(self, ftrack): 

3876 itrack = int(ftrack) 

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

3878 

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

3880 if self.picking: 

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

3882 self.picking = None 

3883 self.picking_down = None 

3884 self.picking_timer.stop() 

3885 self.picking_timer = None 

3886 if not abort: 

3887 self.add_marker(self.floating_marker) 

3888 self.floating_marker.selected = True 

3889 self.emit_selected_markers() 

3890 

3891 self.floating_marker = None 

3892 

3893 def start_picking(self, ignore): 

3894 

3895 if not self.picking: 

3896 self.deselect_all() 

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

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

3899 

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

3901 self.picking.setGeometry( 

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

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

3904 

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

3906 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3908 self.floating_marker.selected = True 

3909 

3910 self.picking_timer = qc.QTimer() 

3911 self.picking_timer.timeout.connect( 

3912 self.animate_picking) 

3913 

3914 self.picking_timer.setInterval(50) 

3915 self.picking_timer.start() 

3916 

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

3918 if self.picking: 

3919 mouset = self.time_projection.rev(x) 

3920 dt = 0.0 

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

3922 if mouset < self.tmin: 

3923 dt = -(self.tmin - mouset) 

3924 else: 

3925 dt = mouset - self.tmax 

3926 ddt = self.tmax-self.tmin 

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

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

3929 

3930 x0 = x 

3931 if self.picking_down is not None: 

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

3933 

3934 w = abs(x-x0) 

3935 x0 = min(x0, x) 

3936 

3937 tmin, tmax = ( 

3938 self.time_projection.rev(x0), 

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

3940 

3941 tmin, tmax = ( 

3942 max(working_system_time_range[0], tmin), 

3943 min(working_system_time_range[1], tmax)) 

3944 

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

3946 

3947 self.picking.setGeometry( 

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

3949 

3950 ftrack = self.track_to_screen.rev(y) 

3951 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3953 

3954 if dt != 0.0 and doshift: 

3955 self.interrupt_following() 

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

3957 

3958 self.update() 

3959 

3960 def update_status(self): 

3961 

3962 if self.message is None: 

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

3964 

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

3966 if not is_working_time(mouse_t): 

3967 return 

3968 

3969 if self.floating_marker: 

3970 tmi, tma = ( 

3971 self.floating_marker.tmin, 

3972 self.floating_marker.tmax) 

3973 

3974 tt, ms = gmtime_x(tmi) 

3975 

3976 if tmi == tma: 

3977 message = mystrftime( 

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

3979 tt=tt, milliseconds=ms) 

3980 else: 

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

3982 message = mystrftime( 

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

3984 tt=tt, milliseconds=ms) 

3985 else: 

3986 tt, ms = gmtime_x(mouse_t) 

3987 

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

3989 else: 

3990 message = self.message 

3991 

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

3993 sb.clearMessage() 

3994 sb.showMessage(message) 

3995 

3996 def set_sortingmode_change_delay_time(self, dt): 

3997 self.sortingmode_change_delay_time = dt 

3998 

3999 def sortingmode_change_delayed(self): 

4000 now = time.time() 

4001 return ( 

4002 self.sortingmode_change_delay_time is not None 

4003 and now - self.sortingmode_change_time 

4004 < self.sortingmode_change_delay_time) 

4005 

4006 def set_visible_marker_kinds(self, kinds): 

4007 self.deselect_all() 

4008 self.visible_marker_kinds = tuple(kinds) 

4009 self.emit_selected_markers() 

4010 

4011 def following(self): 

4012 return self.follow_timer is not None \ 

4013 and not self.following_interrupted() 

4014 

4015 def interrupt_following(self): 

4016 self.interactive_range_change_time = time.time() 

4017 

4018 def following_interrupted(self, now=None): 

4019 if now is None: 

4020 now = time.time() 

4021 return now - self.interactive_range_change_time \ 

4022 < self.interactive_range_change_delay_time 

4023 

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

4025 if tmax_start is None: 

4026 tmax_start = time.time() 

4027 self.show_all = False 

4028 self.follow_time = tlen 

4029 self.follow_timer = qc.QTimer(self) 

4030 self.follow_timer.timeout.connect( 

4031 self.follow_update) 

4032 self.follow_timer.setInterval(interval) 

4033 self.follow_timer.start() 

4034 self.follow_started = time.time() 

4035 self.follow_lapse = lapse 

4036 self.follow_tshift = self.follow_started - tmax_start 

4037 self.interactive_range_change_time = 0.0 

4038 

4039 def unfollow(self): 

4040 if self.follow_timer is not None: 

4041 self.follow_timer.stop() 

4042 self.follow_timer = None 

4043 self.interactive_range_change_time = 0.0 

4044 

4045 def follow_update(self): 

4046 rnow = time.time() 

4047 if self.follow_lapse is None: 

4048 now = rnow 

4049 else: 

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

4051 * self.follow_lapse 

4052 

4053 if self.following_interrupted(rnow): 

4054 return 

4055 self.set_time_range( 

4056 now-self.follow_time-self.follow_tshift, 

4057 now-self.follow_tshift) 

4058 

4059 self.update() 

4060 

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

4062 self.return_tag = return_tag 

4063 self.window().close() 

4064 

4065 def cleanup(self): 

4066 self.about_to_close.emit() 

4067 self.timer.stop() 

4068 if self.follow_timer is not None: 

4069 self.follow_timer.stop() 

4070 

4071 for snuffling in list(self.snufflings): 

4072 self.remove_snuffling(snuffling) 

4073 

4074 def set_error_message(self, key, value): 

4075 if value is None: 

4076 if key in self.error_messages: 

4077 del self.error_messages[key] 

4078 else: 

4079 self.error_messages[key] = value 

4080 

4081 def inputline_changed(self, text): 

4082 pass 

4083 

4084 def inputline_finished(self, text): 

4085 line = str(text) 

4086 

4087 toks = line.split() 

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

4089 if len(toks) >= 1: 

4090 command = toks[0].lower() 

4091 

4092 try: 

4093 quick_filter_commands = { 

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

4095 's': '*.%s.*.*', 

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

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

4098 

4099 if command in quick_filter_commands: 

4100 if len(toks) >= 2: 

4101 patterns = [ 

4102 quick_filter_commands[toks[0]] % pat 

4103 for pat in toks[1:]] 

4104 self.set_quick_filter_patterns(patterns, line) 

4105 else: 

4106 self.set_quick_filter_patterns(None) 

4107 

4108 self.update() 

4109 

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

4111 if len(toks) >= 2: 

4112 patterns = [] 

4113 if len(toks) == 2: 

4114 patterns = [toks[1]] 

4115 elif len(toks) >= 3: 

4116 x = { 

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

4118 's': '*.%s.*.*', 

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

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

4121 

4122 if toks[1] in x: 

4123 patterns.extend( 

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

4125 

4126 for pattern in patterns: 

4127 if command == 'hide': 

4128 self.add_blacklist_pattern(pattern) 

4129 else: 

4130 self.remove_blacklist_pattern(pattern) 

4131 

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

4133 self.clear_blacklist() 

4134 

4135 clearit = True 

4136 

4137 self.update() 

4138 

4139 elif command == 'markers': 

4140 if len(toks) == 2: 

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

4142 kinds = self.all_marker_kinds 

4143 else: 

4144 kinds = [] 

4145 for x in toks[1]: 

4146 try: 

4147 kinds.append(int(x)) 

4148 except Exception: 

4149 pass 

4150 

4151 self.set_visible_marker_kinds(kinds) 

4152 

4153 elif len(toks) == 1: 

4154 self.set_visible_marker_kinds(()) 

4155 

4156 self.update() 

4157 

4158 elif command == 'scaling': 

4159 if len(toks) == 2: 

4160 hideit = False 

4161 error = 'wrong number of arguments' 

4162 

4163 if len(toks) >= 3: 

4164 vmin, vmax = [ 

4165 pyrocko.model.float_or_none(x) 

4166 for x in toks[-2:]] 

4167 

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

4169 if k in d: 

4170 if vmin is not None: 

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

4172 if vmax is not None: 

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

4174 

4175 if len(toks) == 1: 

4176 self.remove_scaling_hooks() 

4177 

4178 elif len(toks) == 3: 

4179 def hook(data_ranges): 

4180 for k in data_ranges: 

4181 upd(data_ranges, k, vmin, vmax) 

4182 

4183 self.set_scaling_hook('_', hook) 

4184 

4185 elif len(toks) == 4: 

4186 pattern = toks[1] 

4187 

4188 def hook(data_ranges): 

4189 for k in pyrocko.util.match_nslcs( 

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

4191 

4192 upd(data_ranges, k, vmin, vmax) 

4193 

4194 self.set_scaling_hook(pattern, hook) 

4195 

4196 elif command == 'goto': 

4197 toks2 = line.split(None, 1) 

4198 if len(toks2) == 2: 

4199 arg = toks2[1] 

4200 m = re.match( 

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

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

4203 if m: 

4204 tlen = None 

4205 if not m.group(1): 

4206 tlen = 12*32*24*60*60 

4207 elif not m.group(2): 

4208 tlen = 32*24*60*60 

4209 elif not m.group(3): 

4210 tlen = 24*60*60 

4211 elif not m.group(4): 

4212 tlen = 60*60 

4213 elif not m.group(5): 

4214 tlen = 60 

4215 

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

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

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

4219 t = pyrocko.util.str_to_time(arg) 

4220 self.go_to_time(t, tlen=tlen) 

4221 

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

4223 supl = '00:00:00' 

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

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

4226 tmin, tmax = self.get_time_range() 

4227 sdate = pyrocko.util.time_to_str( 

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

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

4230 self.go_to_time(t) 

4231 

4232 else: 

4233 self.go_to_event_by_name(arg) 

4234 

4235 else: 

4236 raise PileViewerMainException( 

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

4238 

4239 except PileViewerMainException as e: 

4240 error = str(e) 

4241 hideit = False 

4242 

4243 return clearit, hideit, error 

4244 

4245 return PileViewerMain 

4246 

4247 

4248PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4249GLPileViewerMain = MakePileViewerMainClass(qgl.QGLWidget) 

4250 

4251 

4252class LineEditWithAbort(qw.QLineEdit): 

4253 

4254 aborted = qc.pyqtSignal() 

4255 history_down = qc.pyqtSignal() 

4256 history_up = qc.pyqtSignal() 

4257 

4258 def keyPressEvent(self, key_event): 

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

4260 self.aborted.emit() 

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

4262 self.history_down.emit() 

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

4264 self.history_up.emit() 

4265 else: 

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

4267 

4268 

4269class PileViewer(qw.QFrame): 

4270 ''' 

4271 PileViewerMain + Controls + Inputline 

4272 ''' 

4273 

4274 def __init__( 

4275 self, pile, 

4276 ntracks_shown_max=20, 

4277 marker_editor_sortable=True, 

4278 use_opengl=False, 

4279 panel_parent=None, 

4280 *args): 

4281 

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

4283 

4284 layout = qw.QGridLayout() 

4285 layout.setContentsMargins(0, 0, 0, 0) 

4286 layout.setSpacing(0) 

4287 

4288 self.menu = PileViewerMenuBar(self) 

4289 

4290 if use_opengl: 

4291 self.viewer = GLPileViewerMain( 

4292 pile, 

4293 ntracks_shown_max=ntracks_shown_max, 

4294 panel_parent=panel_parent, 

4295 menu=self.menu) 

4296 else: 

4297 self.viewer = PileViewerMain( 

4298 pile, 

4299 ntracks_shown_max=ntracks_shown_max, 

4300 panel_parent=panel_parent, 

4301 menu=self.menu) 

4302 

4303 self.marker_editor_sortable = marker_editor_sortable 

4304 

4305 self.setFrameShape(qw.QFrame.StyledPanel) 

4306 self.setFrameShadow(qw.QFrame.Sunken) 

4307 

4308 self.input_area = qw.QFrame(self) 

4309 ia_layout = qw.QGridLayout() 

4310 ia_layout.setContentsMargins(11, 11, 11, 11) 

4311 self.input_area.setLayout(ia_layout) 

4312 

4313 self.inputline = LineEditWithAbort(self.input_area) 

4314 self.inputline.returnPressed.connect( 

4315 self.inputline_returnpressed) 

4316 self.inputline.editingFinished.connect( 

4317 self.inputline_finished) 

4318 self.inputline.aborted.connect( 

4319 self.inputline_aborted) 

4320 

4321 self.inputline.history_down.connect( 

4322 lambda: self.step_through_history(1)) 

4323 self.inputline.history_up.connect( 

4324 lambda: self.step_through_history(-1)) 

4325 

4326 self.inputline.textEdited.connect( 

4327 self.inputline_changed) 

4328 

4329 self.inputline.setPlaceholderText( 

4330 u'Quick commands: e.g. \'c HH?\' to select channels. ' 

4331 u'Use ↑ or ↓ to navigate.') 

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

4333 self.input_area.hide() 

4334 self.history = None 

4335 

4336 self.inputline_error_str = None 

4337 

4338 self.inputline_error = qw.QLabel() 

4339 self.inputline_error.hide() 

4340 

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

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

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

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

4345 

4346 pb = Progressbars(self) 

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

4348 self.progressbars = pb 

4349 

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

4351 self.scrollbar = scrollbar 

4352 layout.addWidget(scrollbar, 1, 1) 

4353 self.scrollbar.valueChanged.connect( 

4354 self.scrollbar_changed) 

4355 

4356 self.block_scrollbar_changes = False 

4357 

4358 self.viewer.want_input.connect( 

4359 self.inputline_show) 

4360 self.viewer.tracks_range_changed.connect( 

4361 self.tracks_range_changed) 

4362 self.viewer.pile_has_changed_signal.connect( 

4363 self.adjust_controls) 

4364 self.viewer.about_to_close.connect( 

4365 self.save_inputline_history) 

4366 

4367 self.setLayout(layout) 

4368 

4369 def cleanup(self): 

4370 self.viewer.cleanup() 

4371 

4372 def get_progressbars(self): 

4373 return self.progressbars 

4374 

4375 def inputline_show(self): 

4376 if not self.history: 

4377 self.load_inputline_history() 

4378 

4379 self.input_area.show() 

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

4381 self.inputline.selectAll() 

4382 

4383 def inputline_set_error(self, string): 

4384 self.inputline_error_str = string 

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

4386 self.inputline.selectAll() 

4387 self.inputline_error.setText(string) 

4388 self.input_area.show() 

4389 self.inputline_error.show() 

4390 

4391 def inputline_clear_error(self): 

4392 if self.inputline_error_str: 

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

4394 self.inputline_error_str = None 

4395 self.inputline_error.clear() 

4396 self.inputline_error.hide() 

4397 

4398 def inputline_changed(self, line): 

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

4400 self.inputline_clear_error() 

4401 

4402 def inputline_returnpressed(self): 

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

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

4405 

4406 if error: 

4407 self.inputline_set_error(error) 

4408 

4409 line = line.strip() 

4410 

4411 if line != '' and not error: 

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

4413 self.history.append(line) 

4414 

4415 if clearit: 

4416 

4417 self.inputline.blockSignals(True) 

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

4419 if qpat is None: 

4420 self.inputline.clear() 

4421 else: 

4422 self.inputline.setText(qinp) 

4423 self.inputline.blockSignals(False) 

4424 

4425 if hideit and not error: 

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

4427 self.input_area.hide() 

4428 

4429 self.hist_ind = len(self.history) 

4430 

4431 def inputline_aborted(self): 

4432 ''' 

4433 Hide the input line. 

4434 ''' 

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

4436 self.hist_ind = len(self.history) 

4437 self.input_area.hide() 

4438 

4439 def save_inputline_history(self): 

4440 ''' 

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

4442 ''' 

4443 if not self.history: 

4444 return 

4445 

4446 conf = pyrocko.config 

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

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

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

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

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

4452 

4453 def load_inputline_history(self): 

4454 ''' 

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

4456 ''' 

4457 conf = pyrocko.config 

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

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

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

4461 f.write('\n') 

4462 

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

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

4465 

4466 self.hist_ind = len(self.history) 

4467 

4468 def step_through_history(self, ud=1): 

4469 ''' 

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

4471 ''' 

4472 n = len(self.history) 

4473 self.hist_ind += ud 

4474 self.hist_ind %= (n + 1) 

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

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

4477 else: 

4478 self.inputline.setText('') 

4479 

4480 def inputline_finished(self): 

4481 pass 

4482 

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

4484 if self.block_scrollbar_changes: 

4485 return 

4486 

4487 self.scrollbar.blockSignals(True) 

4488 self.scrollbar.setPageStep(ihi-ilo) 

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

4490 self.scrollbar.setRange(0, vmax) 

4491 self.scrollbar.setValue(ilo) 

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

4493 self.scrollbar.blockSignals(False) 

4494 

4495 def scrollbar_changed(self, value): 

4496 self.block_scrollbar_changes = True 

4497 ilo = value 

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

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

4500 self.block_scrollbar_changes = False 

4501 self.update_contents() 

4502 

4503 def controls(self): 

4504 frame = qw.QFrame(self) 

4505 layout = qw.QGridLayout() 

4506 frame.setLayout(layout) 

4507 

4508 minfreq = 0.001 

4509 maxfreq = 1000.0 

4510 self.lowpass_control = ValControl(high_is_none=True) 

4511 self.lowpass_control.setup( 

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

4513 self.highpass_control = ValControl(low_is_none=True) 

4514 self.highpass_control.setup( 

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

4516 self.gain_control = ValControl() 

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

4518 self.rot_control = LinValControl() 

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

4520 self.colorbar_control = ColorbarControl(self) 

4521 

4522 self.lowpass_control.valchange.connect( 

4523 self.viewer.lowpass_change) 

4524 self.highpass_control.valchange.connect( 

4525 self.viewer.highpass_change) 

4526 self.gain_control.valchange.connect( 

4527 self.viewer.gain_change) 

4528 self.rot_control.valchange.connect( 

4529 self.viewer.rot_change) 

4530 self.colorbar_control.cmap_changed.connect( 

4531 self.viewer.waterfall_cmap_change 

4532 ) 

4533 self.colorbar_control.clip_changed.connect( 

4534 self.viewer.waterfall_clip_change 

4535 ) 

4536 self.colorbar_control.show_absolute_toggled.connect( 

4537 self.viewer.waterfall_show_absolute_change 

4538 ) 

4539 self.colorbar_control.show_integrate_toggled.connect( 

4540 self.viewer.waterfall_set_integrate 

4541 ) 

4542 

4543 for icontrol, control in enumerate(( 

4544 self.highpass_control, 

4545 self.lowpass_control, 

4546 self.gain_control, 

4547 self.rot_control, 

4548 self.colorbar_control)): 

4549 

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

4551 layout.addWidget(widget, icontrol, iwidget) 

4552 

4553 spacer = qw.QSpacerItem( 

4554 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4556 

4557 self.adjust_controls() 

4558 self.viewer.viewmode_change(ViewMode.Wiggle) 

4559 return frame 

4560 

4561 def marker_editor(self): 

4562 editor = pyrocko.gui.marker_editor.MarkerEditor( 

4563 self, sortable=self.marker_editor_sortable) 

4564 

4565 editor.set_viewer(self.get_view()) 

4566 editor.get_marker_model().dataChanged.connect( 

4567 self.update_contents) 

4568 return editor 

4569 

4570 def adjust_controls(self): 

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

4572 maxfreq = 0.5/dtmin 

4573 minfreq = (0.5/dtmax)*0.001 

4574 self.lowpass_control.set_range(minfreq, maxfreq) 

4575 self.highpass_control.set_range(minfreq, maxfreq) 

4576 

4577 def setup_snufflings(self): 

4578 self.viewer.setup_snufflings() 

4579 

4580 def get_view(self): 

4581 return self.viewer 

4582 

4583 def update_contents(self): 

4584 self.viewer.update() 

4585 

4586 def get_pile(self): 

4587 return self.viewer.get_pile() 

4588 

4589 def show_colorbar_ctrl(self, show): 

4590 for w in self.colorbar_control.widgets(): 

4591 w.setVisible(show) 

4592 

4593 def show_gain_ctrl(self, show): 

4594 for w in self.gain_control.widgets(): 

4595 w.setVisible(show)