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 os 

8import time 

9import calendar 

10import datetime 

11import re 

12import math 

13import logging 

14import operator 

15import copy 

16import enum 

17from itertools import groupby 

18 

19import numpy as num 

20import pyrocko.model 

21import pyrocko.pile 

22import pyrocko.trace 

23import pyrocko.response 

24import pyrocko.util 

25import pyrocko.plot 

26import pyrocko.plot.colors 

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

40 

41from .pile_viewer_waterfall import TraceWaterfall 

42 

43import scipy.stats as sstats 

44import platform 

45 

46MIN_LABEL_SIZE_PT = 6 

47 

48 

49qc.QString = str 

50 

51qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \ 

52 qw.QFileDialog.DontUseSheet 

53 

54is_macos = platform.uname()[0] == 'Darwin' 

55 

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

57 

58 

59def detrend(x, y): 

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

61 y_detrended = y - slope * x - offset 

62 return y_detrended, slope, offset 

63 

64 

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

66 return x * slope + y_detrended + offset 

67 

68 

69class Global(object): 

70 appOnDemand = None 

71 

72 

73class NSLC(object): 

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

75 self.network = n 

76 self.station = s 

77 self.location = l 

78 self.channel = c 

79 

80 

81class m_float(float): 

82 

83 def __str__(self): 

84 if abs(self) >= 10000.: 

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

86 elif abs(self) >= 1000.: 

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

88 else: 

89 return '%.5g m' % self 

90 

91 def __lt__(self, other): 

92 if other is None: 

93 return True 

94 return float(self) < float(other) 

95 

96 def __gt__(self, other): 

97 if other is None: 

98 return False 

99 return float(self) > float(other) 

100 

101 

102def m_float_or_none(x): 

103 if x is None: 

104 return None 

105 else: 

106 return m_float(x) 

107 

108 

109def make_chunks(items): 

110 ''' 

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

112 ''' 

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

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

115 

116 

117class deg_float(float): 

118 

119 def __str__(self): 

120 return '%4.0f' % self 

121 

122 def __lt__(self, other): 

123 if other is None: 

124 return True 

125 return float(self) < float(other) 

126 

127 def __gt__(self, other): 

128 if other is None: 

129 return False 

130 return float(self) > float(other) 

131 

132 

133def deg_float_or_none(x): 

134 if x is None: 

135 return None 

136 else: 

137 return deg_float(x) 

138 

139 

140class sector_int(int): 

141 

142 def __str__(self): 

143 return '[%i]' % self 

144 

145 def __lt__(self, other): 

146 if other is None: 

147 return True 

148 return int(self) < int(other) 

149 

150 def __gt__(self, other): 

151 if other is None: 

152 return False 

153 return int(self) > int(other) 

154 

155 

156def num_to_html(num): 

157 snum = '%g' % num 

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

159 if m: 

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

161 

162 return snum 

163 

164 

165gap_lap_tolerance = 5. 

166 

167 

168class ViewMode(enum.Enum): 

169 Wiggle = 1 

170 Waterfall = 2 

171 

172 

173class Timer(object): 

174 def __init__(self): 

175 self._start = None 

176 self._stop = None 

177 

178 def start(self): 

179 self._start = os.times() 

180 

181 def stop(self): 

182 self._stop = os.times() 

183 

184 def get(self): 

185 a = self._start 

186 b = self._stop 

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

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

189 else: 

190 return tuple([0.] * 5) 

191 

192 def __sub__(self, other): 

193 a = self.get() 

194 b = other.get() 

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

196 

197 

198class ObjectStyle(object): 

199 def __init__(self, frame_pen, fill_brush): 

200 self.frame_pen = frame_pen 

201 self.fill_brush = fill_brush 

202 

203 

204box_styles = [] 

205box_alpha = 100 

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

207 'scarletred'.split(): 

208 

209 box_styles.append(ObjectStyle( 

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

211 qg.QBrush(qg.QColor( 

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

213 )) 

214 

215box_styles_coverage = {} 

216 

217box_styles_coverage['waveform'] = [ 

218 ObjectStyle( 

219 qg.QPen( 

220 qg.QColor(*pyrocko.plot.colors.g_nat_colors['nat_green1']), 

221 1, qc.Qt.DashLine), 

222 qg.QBrush(qg.QColor( 

223 *(pyrocko.plot.colors.g_nat_colors['nat_green1'] + (50,)))), 

224 ), 

225 ObjectStyle( 

226 qg.QPen(qg.QColor(*pyrocko.plot.colors.g_nat_colors['nat_brown'])), 

227 qg.QBrush(qg.QColor( 

228 *(pyrocko.plot.colors.g_nat_colors['nat_brown'] + (50,)))), 

229 ), 

230 ObjectStyle( 

231 qg.QPen(qg.QColor(*pyrocko.plot.colors.g_nat_colors['nat_yellow'])), 

232 qg.QBrush(qg.QColor( 

233 *(pyrocko.plot.colors.g_nat_colors['nat_yellow'] + (50,)))), 

234 )] 

235 

236box_styles_coverage['waveform_promise'] = [ 

237 ObjectStyle( 

238 qg.QPen( 

239 qg.QColor(*pyrocko.plot.tango_colors['skyblue3']), 

240 1, qc.Qt.DashLine), 

241 qg.QBrush(qg.QColor( 

242 *(pyrocko.plot.tango_colors['skyblue1'] + (50,)))), 

243 ), 

244 ObjectStyle( 

245 qg.QPen(qg.QColor(*pyrocko.plot.tango_colors['skyblue3'])), 

246 qg.QBrush(qg.QColor( 

247 *(pyrocko.plot.tango_colors['skyblue1'] + (50,)))), 

248 ), 

249 ObjectStyle( 

250 qg.QPen(qg.QColor(*pyrocko.plot.tango_colors['skyblue3'])), 

251 qg.QBrush(qg.QColor( 

252 *(pyrocko.plot.tango_colors['skyblue2'] + (50,)))), 

253 )] 

254 

255sday = 60*60*24. # \ 

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

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

258 

259acceptable_tincs = num.array([ 

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

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

262 

263 

264working_system_time_range = \ 

265 pyrocko.util.working_system_time_range() 

266 

267initial_time_range = [] 

268 

269try: 

270 initial_time_range.append( 

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

272except Exception: 

273 initial_time_range.append(working_system_time_range[0]) 

274 

275try: 

276 initial_time_range.append( 

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

278except Exception: 

279 initial_time_range.append(working_system_time_range[1]) 

280 

281 

282def is_working_time(t): 

283 return working_system_time_range[0] <= t and \ 

284 t <= working_system_time_range[1] 

285 

286 

287def fancy_time_ax_format(inc): 

288 l0_fmt_brief = '' 

289 l2_fmt = '' 

290 l2_trig = 0 

291 if inc < 0.000001: 

292 l0_fmt = '.%n' 

293 l0_center = False 

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

295 l1_trig = 6 

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

297 l2_trig = 3 

298 elif inc < 0.001: 

299 l0_fmt = '.%u' 

300 l0_center = False 

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

302 l1_trig = 6 

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

304 l2_trig = 3 

305 elif inc < 1: 

306 l0_fmt = '.%r' 

307 l0_center = False 

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

309 l1_trig = 6 

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

311 l2_trig = 3 

312 elif inc < 60: 

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

314 l0_center = False 

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

316 l1_trig = 3 

317 elif inc < 3600: 

318 l0_fmt = '%H:%M' 

319 l0_center = False 

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

321 l1_trig = 3 

322 elif inc < sday: 

323 l0_fmt = '%H:%M' 

324 l0_center = False 

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

326 l1_trig = 3 

327 elif inc < smonth: 

328 l0_fmt = '%a %d' 

329 l0_fmt_brief = '%d' 

330 l0_center = True 

331 l1_fmt = '%b, %Y' 

332 l1_trig = 2 

333 elif inc < syear: 

334 l0_fmt = '%b' 

335 l0_center = True 

336 l1_fmt = '%Y' 

337 l1_trig = 1 

338 else: 

339 l0_fmt = '%Y' 

340 l0_center = False 

341 l1_fmt = '' 

342 l1_trig = 0 

343 

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

345 

346 

347def day_start(timestamp): 

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

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

350 return calendar.timegm(tts) 

351 

352 

353def month_start(timestamp): 

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

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

356 return calendar.timegm(tts) 

357 

358 

359def year_start(timestamp): 

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

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

362 return calendar.timegm(tts) 

363 

364 

365def time_nice_value(inc0): 

366 if inc0 < acceptable_tincs[0]: 

367 return pyrocko.plot.nice_value(inc0) 

368 elif inc0 > acceptable_tincs[-1]: 

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

370 else: 

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

372 return acceptable_tincs[i] 

373 

374 

375class TimeScaler(pyrocko.plot.AutoScaler): 

376 def __init__(self): 

377 pyrocko.plot.AutoScaler.__init__(self) 

378 self.mode = 'min-max' 

379 

380 def make_scale(self, data_range): 

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

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

383 

384 data_min = min(data_range) 

385 data_max = max(data_range) 

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

387 

388 mi, ma = data_min, data_max 

389 nmi = mi 

390 if self.mode != 'off': 

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

392 

393 nma = ma 

394 if self.mode != 'off': 

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

396 

397 mi, ma = nmi, nma 

398 

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

400 mi -= 1.0 

401 ma += 1.0 

402 

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

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

405 

406 # make nice tick increment 

407 if self.inc is not None: 

408 inc = self.inc 

409 else: 

410 if self.approx_ticks > 0.: 

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

412 else: 

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

414 

415 if inc == 0.0: 

416 inc = 1.0 

417 

418 if is_reverse: 

419 return ma, mi, -inc 

420 else: 

421 return mi, ma, inc 

422 

423 def make_ticks(self, data_range): 

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

425 

426 is_reverse = False 

427 if inc < 0: 

428 mi, ma, inc = ma, mi, -inc 

429 is_reverse = True 

430 

431 ticks = [] 

432 

433 if inc < sday: 

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

435 if inc < 0.001: 

436 mi_day = hpfloat(mi_day) 

437 

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

439 if inc < 0.001: 

440 base = hpfloat(base) 

441 

442 base_day = mi_day 

443 i = 0 

444 while True: 

445 tick = base+i*inc 

446 if tick > ma: 

447 break 

448 

449 tick_day = day_start(tick) 

450 if tick_day > base_day: 

451 base_day = tick_day 

452 base = base_day 

453 i = 0 

454 else: 

455 ticks.append(tick) 

456 i += 1 

457 

458 elif inc < smonth: 

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

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

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

462 if mi_day == mi: 

463 dt_base += delta 

464 i = 0 

465 while True: 

466 current = dt_base + i*delta 

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

468 if tick > ma: 

469 break 

470 ticks.append(tick) 

471 i += 1 

472 

473 elif inc < syear: 

474 mi_month = month_start(max( 

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

476 

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

478 while True: 

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

480 m += 1 

481 if m > 12: 

482 y, m = y+1, 1 

483 

484 if tick > ma: 

485 break 

486 

487 if tick >= mi: 

488 ticks.append(tick) 

489 

490 else: 

491 mi_year = year_start(max( 

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

493 

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

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

496 

497 while True: 

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

499 y += incy 

500 if tick > ma: 

501 break 

502 if tick >= mi: 

503 ticks.append(tick) 

504 

505 if is_reverse: 

506 ticks.reverse() 

507 

508 return ticks, inc 

509 

510 

511def need_l1_tick(tt, ms, l1_trig): 

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

513 

514 

515def tick_to_labels(tick, inc): 

516 tt, ms = gmtime_x(tick) 

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

518 fancy_time_ax_format(inc) 

519 

520 l0 = mystrftime(l0_fmt, tt, ms) 

521 l0_brief = mystrftime(l0_fmt_brief, tt, ms) 

522 l1, l2 = None, None 

523 if need_l1_tick(tt, ms, l1_trig): 

524 l1 = mystrftime(l1_fmt, tt, ms) 

525 if need_l1_tick(tt, ms, l2_trig): 

526 l2 = mystrftime(l2_fmt, tt, ms) 

527 

528 return l0, l0_brief, l0_center, l1, l2 

529 

530 

531def l1_l2_tick(tick, inc): 

532 tt, ms = gmtime_x(tick) 

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

534 fancy_time_ax_format(inc) 

535 

536 l1 = mystrftime(l1_fmt, tt, ms) 

537 l2 = mystrftime(l2_fmt, tt, ms) 

538 return l1, l2 

539 

540 

541class TimeAx(TimeScaler): 

542 def __init__(self, *args): 

543 TimeScaler.__init__(self, *args) 

544 

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

546 pen = qg.QPen( 

547 qg.QColor(*pyrocko.plot.colors.g_nat_colors['nat_gray']), 1) 

548 p.setPen(pen) 

549 font = qg.QFont() 

550 font.setBold(True) 

551 p.setFont(font) 

552 fm = p.fontMetrics() 

553 ticklen = 10 

554 pad = 10 

555 tmin, tmax = xprojection.get_in_range() 

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

557 l1_hits = 0 

558 l2_hits = 0 

559 

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

561 uumin, uumax = xprojection.get_out_range() 

562 first_tick_with_label = None 

563 

564 data = [] 

565 for tick in ticks: 

566 umin = xprojection(tick) 

567 

568 umin_approx_next = xprojection(tick+inc) 

569 umax = xprojection(tick) 

570 

571 pinc_approx = umin_approx_next - umin 

572 

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

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

575 

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

577 # hide year at epoch 

578 # (we assume that synthetic data is shown) 

579 if l2: 

580 l2 = None 

581 elif l1: 

582 l1 = None 

583 

584 if l0_center: 

585 ushift = (umin_approx_next-umin)/2. 

586 else: 

587 ushift = 0. 

588 

589 abbr_level = 0 

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

591 label0 = l0x 

592 rect0 = fm.boundingRect(label0) 

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

594 break 

595 

596 abbr_level += 1 

597 

598 data.append(( 

599 l0, l0_brief, l0_center, l1, l2, tick, ushift, umin, 

600 pinc_approx)) 

601 

602 for (l0, l0_brief, l0_center, l1, l2, tick, ushift, umin, 

603 pinc_approx) in data: 

604 

605 label0 = (l0, l0_brief, '')[abbr_level] 

606 rect0 = fm.boundingRect(label0) 

607 

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

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

610 

611 if first_tick_with_label is None: 

612 first_tick_with_label = tick 

613 p.drawText(qc.QPointF( 

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

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

616 

617 if l1: 

618 label1 = l1 

619 rect1 = fm.boundingRect(label1) 

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

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

622 

623 p.drawText(qc.QPointF( 

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

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

626 label1) 

627 

628 l1_hits += 1 

629 

630 if l2: 

631 label2 = l2 

632 rect2 = fm.boundingRect(label2) 

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

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

635 

636 p.drawText(qc.QPointF( 

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

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

639 ticklen), label2) 

640 

641 l2_hits += 1 

642 

643 if first_tick_with_label is None: 

644 first_tick_with_label = tmin 

645 

646 l1, l2 = l1_l2_tick(first_tick_with_label, inc) 

647 

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

649 tmax - tmin < 3600*24: 

650 

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

652 if l2: 

653 l2 = None 

654 elif l1: 

655 l1 = None 

656 

657 if l1_hits == 0 and l1: 

658 label1 = l1 

659 rect1 = fm.boundingRect(label1) 

660 p.drawText(qc.QPointF( 

661 uumin+pad, 

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

663 label1) 

664 

665 l1_hits += 1 

666 

667 if l2_hits == 0 and l2: 

668 label2 = l2 

669 rect2 = fm.boundingRect(label2) 

670 p.drawText(qc.QPointF( 

671 uumin+pad, 

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

673 label2) 

674 

675 v = yprojection(0) 

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

677 

678 

679class Projection(object): 

680 def __init__(self): 

681 self.xr = 0., 1. 

682 self.ur = 0., 1. 

683 

684 def set_in_range(self, xmin, xmax): 

685 if xmax == xmin: 

686 xmax = xmin + 1. 

687 

688 self.xr = xmin, xmax 

689 

690 def get_in_range(self): 

691 return self.xr 

692 

693 def set_out_range(self, umin, umax): 

694 if umax == umin: 

695 umax = umin + 1. 

696 

697 self.ur = umin, umax 

698 

699 def get_out_range(self): 

700 return self.ur 

701 

702 def __call__(self, x): 

703 umin, umax = self.ur 

704 xmin, xmax = self.xr 

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

706 

707 def clipped(self, x, umax_pad): 

708 umin, umax = self.ur 

709 xmin, xmax = self.xr 

710 return min( 

711 umax-umax_pad, 

712 max(umin, umin + (x-xmin)*((umax-umin)/(xmax-xmin)))) 

713 

714 def rev(self, u): 

715 umin, umax = self.ur 

716 xmin, xmax = self.xr 

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

718 

719 def copy(self): 

720 return copy.copy(self) 

721 

722 

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

724 group = qw.QActionGroup(menu) 

725 group.setExclusive(True) 

726 menuitems = [] 

727 

728 for name, value, *shortcut in menudef: 

729 action = menu.addAction(name) 

730 action.setCheckable(True) 

731 action.setActionGroup(group) 

732 if shortcut: 

733 action.setShortcut(shortcut[0]) 

734 

735 menuitems.append((action, value)) 

736 if default is not None and ( 

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

738 value == default): 

739 action.setChecked(True) 

740 

741 group.triggered.connect(target) 

742 

743 if default is None: 

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

745 

746 return menuitems 

747 

748 

749def sort_actions(menu): 

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

751 for action in actions: 

752 menu.removeAction(action) 

753 actions.sort(key=lambda x: str(x.text())) 

754 

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

756 if help_action: 

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

758 for action in actions: 

759 menu.addAction(action) 

760 

761 

762fkey_map = dict(zip( 

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

764 qc.Qt.Key_F6, qc.Qt.Key_F7, qc.Qt.Key_F8, qc.Qt.Key_F9, qc.Qt.Key_F10), 

765 range(10))) 

766 

767 

768class PileViewerMainException(Exception): 

769 pass 

770 

771 

772class PileViewerMenuBar(qw.QMenuBar): 

773 ... 

774 

775 

776class PileViewerMenu(qw.QMenu): 

777 ... 

778 

779 

780def MakePileViewerMainClass(base): 

781 

782 class PileViewerMain(base): 

783 

784 want_input = qc.pyqtSignal() 

785 about_to_close = qc.pyqtSignal() 

786 pile_has_changed_signal = qc.pyqtSignal() 

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

788 

789 begin_markers_add = qc.pyqtSignal(int, int) 

790 end_markers_add = qc.pyqtSignal() 

791 begin_markers_remove = qc.pyqtSignal(int, int) 

792 end_markers_remove = qc.pyqtSignal() 

793 

794 marker_selection_changed = qc.pyqtSignal(list) 

795 active_event_marker_changed = qc.pyqtSignal() 

796 

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

798 menu=None): 

799 base.__init__(self, *args) 

800 

801 self.pile = pile 

802 self.ax_height = 80 

803 self.panel_parent = panel_parent 

804 

805 self.click_tolerance = 5 

806 

807 self.ntracks_shown_max = ntracks_shown_max 

808 self.initial_ntracks_shown_max = ntracks_shown_max 

809 self.ntracks = 0 

810 self.show_all = True 

811 self.shown_tracks_range = None 

812 self.track_start = None 

813 self.track_trange = None 

814 

815 self.lowpass = None 

816 self.highpass = None 

817 self.gain = 1.0 

818 self.rotate = 0.0 

819 self.picking_down = None 

820 self.picking = None 

821 self.floating_marker = None 

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

823 self.markers_deltat_max = 0. 

824 self.n_selected_markers = 0 

825 self.all_marker_kinds = (0, 1, 2, 3, 4, 5, 6, 7) 

826 self.visible_marker_kinds = self.all_marker_kinds 

827 self.active_event_marker = None 

828 self.ignore_releases = 0 

829 self.message = None 

830 self.reloaded = False 

831 self.pile_has_changed = False 

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

833 

834 self.tax = TimeAx() 

835 self.setBackgroundRole(qg.QPalette.Base) 

836 self.setAutoFillBackground(True) 

837 poli = qw.QSizePolicy( 

838 qw.QSizePolicy.Expanding, 

839 qw.QSizePolicy.Expanding) 

840 

841 self.setSizePolicy(poli) 

842 self.setMinimumSize(300, 200) 

843 self.setFocusPolicy(qc.Qt.ClickFocus) 

844 

845 self.menu = menu or PileViewerMenu(self) 

846 

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

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

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

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

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

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

853 

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

855 

856 self.snufflings_menu = self.toggle_panel_menu.addMenu( 

857 'Run Snuffling') 

858 self.toggle_panel_menu.addSeparator() 

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

860 help_menu.addSeparator() 

861 

862 file_menu.addAction( 

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

864 'Open waveform files...', 

865 self.open_waveforms, 

866 qg.QKeySequence.Open) 

867 

868 file_menu.addAction( 

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

870 'Open waveform directory...', 

871 self.open_waveform_directory) 

872 

873 file_menu.addAction( 

874 'Open station files...', 

875 self.open_stations) 

876 

877 file_menu.addAction( 

878 'Open StationXML files...', 

879 self.open_stations_xml) 

880 

881 file_menu.addAction( 

882 'Open event file...', 

883 self.read_events) 

884 

885 file_menu.addSeparator() 

886 file_menu.addAction( 

887 'Open marker file...', 

888 self.read_markers) 

889 

890 file_menu.addAction( 

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

892 'Save markers...', 

893 self.write_markers, 

894 qg.QKeySequence.Save) 

895 

896 file_menu.addAction( 

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

898 'Save selected markers...', 

899 self.write_selected_markers, 

900 qg.QKeySequence.SaveAs) 

901 

902 file_menu.addSeparator() 

903 file_menu.addAction( 

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

905 'Print', 

906 self.printit, 

907 qg.QKeySequence.Print) 

908 

909 file_menu.addAction( 

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

911 'Save as SVG or PNG', 

912 self.savesvg, 

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

914 

915 file_menu.addSeparator() 

916 close = file_menu.addAction( 

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

918 'Close', 

919 self.myclose) 

920 close.setShortcuts( 

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

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

923 

924 # Scale Menu 

925 menudef = [ 

926 ('Individual Scale', 

927 lambda tr: tr.nslc_id, 

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

929 ('Common Scale', 

930 lambda tr: None, 

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

932 ('Common Scale per Station', 

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

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

935 ('Common Scale per Station Location', 

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

937 ('Common Scale per Component', 

938 lambda tr: (tr.channel)), 

939 ] 

940 

941 self.menuitems_scaling = add_radiobuttongroup( 

942 scale_menu, menudef, self.scalingmode_change, 

943 default=self.config.trace_scale) 

944 scale_menu.addSeparator() 

945 

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

947 self.scaling_hooks = {} 

948 self.scalingmode_change() 

949 

950 menudef = [ 

951 ('Scaling based on Minimum and Maximum', 

952 ('minmax', 'minmax')), 

953 ('Scaling based on Minimum and Maximum (Robust)', 

954 ('minmax', 'robust')), 

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

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

957 ] 

958 

959 self.menuitems_scaling_base = add_radiobuttongroup( 

960 scale_menu, menudef, self.scaling_base_change) 

961 

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

963 scale_menu.addSeparator() 

964 

965 self.menuitem_fixscalerange = scale_menu.addAction( 

966 'Fix Scale Ranges') 

967 self.menuitem_fixscalerange.setCheckable(True) 

968 

969 # Sort Menu 

970 def sector_dist(sta): 

971 if sta.dist_m is None: 

972 return None, None 

973 else: 

974 return ( 

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

976 m_float(sta.dist_m)) 

977 

978 menudef = [ 

979 ('Sort by Names', 

980 lambda tr: (), 

981 qg.QKeySequence(qc.Qt.Key_S, qc.Qt.Key_N)), 

982 ('Sort by Distance', 

983 lambda tr: self.station_attrib( 

984 tr, 

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

986 lambda tr: (None,)), 

987 qg.QKeySequence(qc.Qt.Key_S, qc.Qt.Key_D)), 

988 ('Sort by Azimuth', 

989 lambda tr: self.station_attrib( 

990 tr, 

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

992 lambda tr: (None,))), 

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

994 lambda tr: self.station_attrib( 

995 tr, 

996 sector_dist, 

997 lambda tr: (None, None))), 

998 ('Sort by Backazimuth', 

999 lambda tr: self.station_attrib( 

1000 tr, 

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

1002 lambda tr: (None,))), 

1003 ] 

1004 self.menuitems_ssorting = add_radiobuttongroup( 

1005 sort_menu, menudef, self.s_sortingmode_change) 

1006 sort_menu.addSeparator() 

1007 

1008 self._ssort = lambda tr: () 

1009 

1010 self.menu.addSeparator() 

1011 

1012 menudef = [ 

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

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

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

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

1017 ((0, 1, 3, 2), 

1018 lambda tr: tr.channel)), 

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

1020 ((1, 0, 3, 2), 

1021 lambda tr: tr.channel)), 

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

1023 ((2, 0, 1, 3), 

1024 lambda tr: tr.channel)), 

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

1026 ((3, 0, 1, 2), 

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

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

1029 ((0, 1, 3), 

1030 lambda tr: tr.location)), 

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

1032 ((1, 0, 3), 

1033 lambda tr: tr.location)), 

1034 ] 

1035 

1036 self.menuitems_sorting = add_radiobuttongroup( 

1037 sort_menu, menudef, self.sortingmode_change) 

1038 

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

1040 self.config.visible_length_setting] 

1041 

1042 # View menu 

1043 self.menuitems_visible_length = add_radiobuttongroup( 

1044 view_menu, menudef, 

1045 self.visible_length_change) 

1046 view_menu.addSeparator() 

1047 

1048 view_modes = [ 

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

1050 ('Waterfall', ViewMode.Waterfall) 

1051 ] 

1052 

1053 self.menuitems_viewmode = add_radiobuttongroup( 

1054 view_menu, view_modes, 

1055 self.viewmode_change, default=ViewMode.Wiggle) 

1056 view_menu.addSeparator() 

1057 

1058 self.menuitem_cliptraces = view_menu.addAction( 

1059 'Clip Traces') 

1060 self.menuitem_cliptraces.setCheckable(True) 

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

1062 

1063 self.menuitem_showboxes = view_menu.addAction( 

1064 'Show Boxes') 

1065 self.menuitem_showboxes.setCheckable(True) 

1066 self.menuitem_showboxes.setChecked( 

1067 self.config.show_boxes) 

1068 

1069 self.menuitem_colortraces = view_menu.addAction( 

1070 'Color Traces') 

1071 self.menuitem_colortraces.setCheckable(True) 

1072 self.menuitem_antialias = view_menu.addAction( 

1073 'Antialiasing') 

1074 self.menuitem_antialias.setCheckable(True) 

1075 

1076 view_menu.addSeparator() 

1077 self.menuitem_showscalerange = view_menu.addAction( 

1078 'Show Scale Ranges') 

1079 self.menuitem_showscalerange.setCheckable(True) 

1080 self.menuitem_showscalerange.setChecked( 

1081 self.config.show_scale_ranges) 

1082 

1083 self.menuitem_showscaleaxis = view_menu.addAction( 

1084 'Show Scale Axes') 

1085 self.menuitem_showscaleaxis.setCheckable(True) 

1086 self.menuitem_showscaleaxis.setChecked( 

1087 self.config.show_scale_axes) 

1088 

1089 self.menuitem_showzeroline = view_menu.addAction( 

1090 'Show Zero Lines') 

1091 self.menuitem_showzeroline.setCheckable(True) 

1092 

1093 view_menu.addSeparator() 

1094 view_menu.addAction( 

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

1096 'Fullscreen', 

1097 self.toggle_fullscreen, 

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

1099 

1100 # Options Menu 

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

1102 self.menuitem_demean.setCheckable(True) 

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

1104 self.menuitem_demean.setShortcut( 

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

1106 

1107 self.menuitem_distances_3d = options_menu.addAction( 

1108 '3D distances', 

1109 self.distances_3d_changed) 

1110 self.menuitem_distances_3d.setCheckable(True) 

1111 

1112 self.menuitem_allowdownsampling = options_menu.addAction( 

1113 'Allow Downsampling') 

1114 self.menuitem_allowdownsampling.setCheckable(True) 

1115 self.menuitem_allowdownsampling.setChecked(True) 

1116 

1117 self.menuitem_degap = options_menu.addAction( 

1118 'Allow Degapping') 

1119 self.menuitem_degap.setCheckable(True) 

1120 self.menuitem_degap.setChecked(True) 

1121 

1122 options_menu.addSeparator() 

1123 

1124 self.menuitem_fft_filtering = options_menu.addAction( 

1125 'FFT Filtering') 

1126 self.menuitem_fft_filtering.setCheckable(True) 

1127 

1128 self.menuitem_lphp = options_menu.addAction( 

1129 'Bandpass is Low- + Highpass') 

1130 self.menuitem_lphp.setCheckable(True) 

1131 self.menuitem_lphp.setChecked(True) 

1132 

1133 options_menu.addSeparator() 

1134 self.menuitem_watch = options_menu.addAction( 

1135 'Watch Files') 

1136 self.menuitem_watch.setCheckable(True) 

1137 

1138 self.menuitem_liberal_fetch = options_menu.addAction( 

1139 'Liberal Fetch Optimization') 

1140 self.menuitem_liberal_fetch.setCheckable(True) 

1141 

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

1143 

1144 self.snufflings_menu.addAction( 

1145 'Reload Snufflings', 

1146 self.setup_snufflings) 

1147 

1148 # Disable ShadowPileTest 

1149 if False: 

1150 test_action = self.menu.addAction( 

1151 'Test', 

1152 self.toggletest) 

1153 test_action.setCheckable(True) 

1154 

1155 help_menu.addAction( 

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

1157 'Snuffler Controls', 

1158 self.help, 

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

1160 

1161 help_menu.addAction( 

1162 'About', 

1163 self.about) 

1164 

1165 self.time_projection = Projection() 

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

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

1168 

1169 self.gather = None 

1170 

1171 self.trace_filter = None 

1172 self.quick_filter = None 

1173 self.quick_filter_patterns = None, None 

1174 self.blacklist = [] 

1175 

1176 self.track_to_screen = Projection() 

1177 self.track_to_nslc_ids = {} 

1178 

1179 self.cached_vec = None 

1180 self.cached_processed_traces = None 

1181 

1182 self.timer = qc.QTimer(self) 

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

1184 self.timer.setInterval(1000) 

1185 self.timer.start() 

1186 self.pile.add_listener(self) 

1187 self.trace_styles = {} 

1188 if self.get_squirrel() is None: 

1189 self.determine_box_styles() 

1190 

1191 self.setMouseTracking(True) 

1192 

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

1194 self.snuffling_modules = {} 

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

1196 self.default_snufflings = None 

1197 self.snufflings = [] 

1198 

1199 self.stations = {} 

1200 

1201 self.timer_draw = Timer() 

1202 self.timer_cutout = Timer() 

1203 self.time_spent_painting = 0.0 

1204 self.time_last_painted = time.time() 

1205 

1206 self.interactive_range_change_time = 0.0 

1207 self.interactive_range_change_delay_time = 10.0 

1208 self.follow_timer = None 

1209 

1210 self.sortingmode_change_time = 0.0 

1211 self.sortingmode_change_delay_time = None 

1212 

1213 self.old_data_ranges = {} 

1214 

1215 self.error_messages = {} 

1216 self.return_tag = None 

1217 self.wheel_pos = 60 

1218 

1219 self.setAcceptDrops(True) 

1220 self._paths_to_load = [] 

1221 

1222 self.tf_cache = {} 

1223 

1224 self.waterfall = TraceWaterfall() 

1225 self.waterfall_cmap = 'viridis' 

1226 self.waterfall_clip_min = 0. 

1227 self.waterfall_clip_max = 1. 

1228 self.waterfall_show_absolute = False 

1229 self.waterfall_integrate = False 

1230 self.view_mode = ViewMode.Wiggle 

1231 

1232 self.automatic_updates = True 

1233 

1234 self.closing = False 

1235 self.in_paint_event = False 

1236 

1237 def fail(self, reason): 

1238 box = qw.QMessageBox(self) 

1239 box.setText(reason) 

1240 box.exec_() 

1241 

1242 def set_trace_filter(self, filter_func): 

1243 self.trace_filter = filter_func 

1244 self.sortingmode_change() 

1245 

1246 def update_trace_filter(self): 

1247 if self.blacklist: 

1248 

1249 def blacklist_func(tr): 

1250 return not pyrocko.util.match_nslc( 

1251 self.blacklist, tr.nslc_id) 

1252 

1253 else: 

1254 blacklist_func = None 

1255 

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

1257 self.set_trace_filter(None) 

1258 elif self.quick_filter is None: 

1259 self.set_trace_filter(blacklist_func) 

1260 elif blacklist_func is None: 

1261 self.set_trace_filter(self.quick_filter) 

1262 else: 

1263 self.set_trace_filter( 

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

1265 

1266 def set_quick_filter(self, filter_func): 

1267 self.quick_filter = filter_func 

1268 self.update_trace_filter() 

1269 

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

1271 if patterns is not None: 

1272 self.set_quick_filter( 

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

1274 else: 

1275 self.set_quick_filter(None) 

1276 

1277 self.quick_filter_patterns = patterns, inputline 

1278 

1279 def get_quick_filter_patterns(self): 

1280 return self.quick_filter_patterns 

1281 

1282 def add_blacklist_pattern(self, pattern): 

1283 if pattern == 'empty': 

1284 keys = set(self.pile.nslc_ids) 

1285 trs = self.pile.all( 

1286 tmin=self.tmin, 

1287 tmax=self.tmax, 

1288 load_data=False, 

1289 degap=False) 

1290 

1291 for tr in trs: 

1292 if tr.nslc_id in keys: 

1293 keys.remove(tr.nslc_id) 

1294 

1295 for key in keys: 

1296 xpattern = '.'.join(key) 

1297 if xpattern not in self.blacklist: 

1298 self.blacklist.append(xpattern) 

1299 

1300 else: 

1301 if pattern in self.blacklist: 

1302 self.blacklist.remove(pattern) 

1303 

1304 self.blacklist.append(pattern) 

1305 

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

1307 self.update_trace_filter() 

1308 

1309 def remove_blacklist_pattern(self, pattern): 

1310 if pattern in self.blacklist: 

1311 self.blacklist.remove(pattern) 

1312 else: 

1313 raise PileViewerMainException( 

1314 'Pattern not found in blacklist.') 

1315 

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

1317 self.update_trace_filter() 

1318 

1319 def clear_blacklist(self): 

1320 self.blacklist = [] 

1321 self.update_trace_filter() 

1322 

1323 def ssort(self, tr): 

1324 return self._ssort(tr) 

1325 

1326 def station_key(self, x): 

1327 return x.network, x.station 

1328 

1329 def station_keys(self, x): 

1330 return [ 

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

1332 (x.network, x.station)] 

1333 

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

1335 for sk in self.station_keys(tr): 

1336 if sk in self.stations: 

1337 station = self.stations[sk] 

1338 return getter(station) 

1339 

1340 return default_getter(tr) 

1341 

1342 def get_station(self, sk): 

1343 return self.stations[sk] 

1344 

1345 def has_station(self, station): 

1346 for sk in self.station_keys(station): 

1347 if sk in self.stations: 

1348 return True 

1349 

1350 return False 

1351 

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

1353 return self.station_attrib( 

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

1355 

1356 def set_stations(self, stations): 

1357 self.stations = {} 

1358 self.add_stations(stations) 

1359 

1360 def add_stations(self, stations): 

1361 for station in stations: 

1362 for sk in self.station_keys(station): 

1363 self.stations[sk] = station 

1364 

1365 ev = self.get_active_event() 

1366 if ev: 

1367 self.set_origin(ev) 

1368 

1369 def add_event(self, event): 

1370 marker = EventMarker(event) 

1371 self.add_marker(marker) 

1372 

1373 def add_events(self, events): 

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

1375 self.add_markers(markers) 

1376 

1377 def set_event_marker_as_origin(self, ignore=None): 

1378 selected = self.selected_markers() 

1379 if not selected: 

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

1381 return 

1382 

1383 m = selected[0] 

1384 if not isinstance(m, EventMarker): 

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

1386 return 

1387 

1388 self.set_active_event_marker(m) 

1389 

1390 def deactivate_event_marker(self): 

1391 if self.active_event_marker: 

1392 self.active_event_marker.active = False 

1393 

1394 self.active_event_marker_changed.emit() 

1395 self.active_event_marker = None 

1396 

1397 def set_active_event_marker(self, event_marker): 

1398 if self.active_event_marker: 

1399 self.active_event_marker.active = False 

1400 

1401 self.active_event_marker = event_marker 

1402 event_marker.active = True 

1403 event = event_marker.get_event() 

1404 self.set_origin(event) 

1405 self.active_event_marker_changed.emit() 

1406 

1407 def set_active_event(self, event): 

1408 for marker in self.markers: 

1409 if isinstance(marker, EventMarker): 

1410 if marker.get_event() is event: 

1411 self.set_active_event_marker(marker) 

1412 

1413 def get_active_event_marker(self): 

1414 return self.active_event_marker 

1415 

1416 def get_active_event(self): 

1417 m = self.get_active_event_marker() 

1418 if m is not None: 

1419 return m.get_event() 

1420 else: 

1421 return None 

1422 

1423 def get_active_markers(self): 

1424 emarker = self.get_active_event_marker() 

1425 if emarker is None: 

1426 return None, [] 

1427 

1428 else: 

1429 ev = emarker.get_event() 

1430 pmarkers = [ 

1431 m for m in self.markers 

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

1433 

1434 return emarker, pmarkers 

1435 

1436 def set_origin(self, location): 

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

1438 station.set_event_relative_data( 

1439 location, 

1440 distance_3d=self.menuitem_distances_3d.isChecked()) 

1441 

1442 self.sortingmode_change() 

1443 

1444 def distances_3d_changed(self): 

1445 ignore = self.menuitem_distances_3d.isChecked() 

1446 self.set_event_marker_as_origin(ignore) 

1447 

1448 def iter_snuffling_modules(self): 

1449 pjoin = os.path.join 

1450 for path in self.snuffling_paths: 

1451 

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

1453 os.mkdir(path) 

1454 

1455 for entry in os.listdir(path): 

1456 directory = path 

1457 fn = entry 

1458 d = pjoin(path, entry) 

1459 if os.path.isdir(d): 

1460 directory = d 

1461 if os.path.isfile( 

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

1463 fn = 'snuffling.py' 

1464 

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

1466 continue 

1467 

1468 name = fn[:-3] 

1469 

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

1471 self.snuffling_modules[directory, name] = \ 

1472 pyrocko.gui.snuffling.SnufflingModule( 

1473 directory, name, self) 

1474 

1475 yield self.snuffling_modules[directory, name] 

1476 

1477 def setup_snufflings(self): 

1478 # user snufflings 

1479 for mod in self.iter_snuffling_modules(): 

1480 try: 

1481 mod.load_if_needed() 

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

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

1484 

1485 # load the default snufflings on first run 

1486 if self.default_snufflings is None: 

1487 self.default_snufflings = pyrocko.gui\ 

1488 .snufflings.__snufflings__() 

1489 for snuffling in self.default_snufflings: 

1490 self.add_snuffling(snuffling) 

1491 

1492 def set_panel_parent(self, panel_parent): 

1493 self.panel_parent = panel_parent 

1494 

1495 def get_panel_parent(self): 

1496 return self.panel_parent 

1497 

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

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

1500 snuffling.init_gui( 

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

1502 self.snufflings.append(snuffling) 

1503 self.update() 

1504 

1505 def remove_snuffling(self, snuffling): 

1506 snuffling.delete_gui() 

1507 self.update() 

1508 self.snufflings.remove(snuffling) 

1509 snuffling.pre_destroy() 

1510 

1511 def add_snuffling_menuitem(self, item): 

1512 self.snufflings_menu.addAction(item) 

1513 item.setParent(self.snufflings_menu) 

1514 sort_actions(self.snufflings_menu) 

1515 

1516 def remove_snuffling_menuitem(self, item): 

1517 self.snufflings_menu.removeAction(item) 

1518 

1519 def add_snuffling_help_menuitem(self, item): 

1520 self.snuffling_help.addAction(item) 

1521 item.setParent(self.snuffling_help) 

1522 sort_actions(self.snuffling_help) 

1523 

1524 def remove_snuffling_help_menuitem(self, item): 

1525 self.snuffling_help.removeAction(item) 

1526 

1527 def add_panel_toggler(self, item): 

1528 self.toggle_panel_menu.addAction(item) 

1529 item.setParent(self.toggle_panel_menu) 

1530 sort_actions(self.toggle_panel_menu) 

1531 

1532 def remove_panel_toggler(self, item): 

1533 self.toggle_panel_menu.removeAction(item) 

1534 

1535 def load(self, paths, regex=None, format='detect', 

1536 cache_dir=None, force_cache=False): 

1537 

1538 if cache_dir is None: 

1539 cache_dir = pyrocko.config.config().cache_dir 

1540 if isinstance(paths, str): 

1541 paths = [paths] 

1542 

1543 fns = pyrocko.util.select_files( 

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

1545 

1546 if not fns: 

1547 return 

1548 

1549 cache = pyrocko.pile.get_cache(cache_dir) 

1550 

1551 t = [time.time()] 

1552 

1553 def update_bar(label, value): 

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

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

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

1557 else: 

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

1559 

1560 return pbs.set_status(label, value) 

1561 

1562 def update_progress(label, i, n): 

1563 abort = False 

1564 

1565 qw.qApp.processEvents() 

1566 if n != 0: 

1567 perc = i*100/n 

1568 else: 

1569 perc = 100 

1570 abort |= update_bar(label, perc) 

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

1572 

1573 tnow = time.time() 

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

1575 self.update() 

1576 t[0] = tnow 

1577 

1578 return abort 

1579 

1580 self.automatic_updates = False 

1581 

1582 self.pile.load_files( 

1583 sorted(fns), 

1584 filename_attributes=regex, 

1585 cache=cache, 

1586 fileformat=format, 

1587 show_progress=False, 

1588 update_progress=update_progress) 

1589 

1590 self.automatic_updates = True 

1591 self.update() 

1592 

1593 def load_queued(self): 

1594 if not self._paths_to_load: 

1595 return 

1596 paths = self._paths_to_load 

1597 self._paths_to_load = [] 

1598 self.load(paths) 

1599 

1600 def load_soon(self, paths): 

1601 self._paths_to_load.extend(paths) 

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

1603 

1604 def open_waveforms(self): 

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

1606 

1607 fns, _ = qw.QFileDialog.getOpenFileNames( 

1608 self, caption, options=qfiledialog_options) 

1609 

1610 if fns: 

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

1612 

1613 def open_waveform_directory(self): 

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

1615 

1616 dn = qw.QFileDialog.getExistingDirectory( 

1617 self, caption, options=qfiledialog_options) 

1618 

1619 if dn: 

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

1621 

1622 def open_stations(self, fns=None): 

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

1624 

1625 if not fns: 

1626 fns, _ = qw.QFileDialog.getOpenFileNames( 

1627 self, caption, options=qfiledialog_options) 

1628 

1629 try: 

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

1631 for stat in stations: 

1632 self.add_stations(stat) 

1633 

1634 except Exception as e: 

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

1636 

1637 def open_stations_xml(self, fns=None): 

1638 from pyrocko.io import stationxml 

1639 

1640 caption = 'Select one or more StationXML files' 

1641 if not fns: 

1642 fns, _ = qw.QFileDialog.getOpenFileNames( 

1643 self, caption, options=qfiledialog_options, 

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

1645 ';;All files (*)') 

1646 

1647 try: 

1648 stations = [ 

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

1650 for x in fns] 

1651 

1652 for stat in stations: 

1653 self.add_stations(stat) 

1654 

1655 except Exception as e: 

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

1657 

1658 def add_traces(self, traces): 

1659 if traces: 

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

1661 self.pile.add_file(mtf) 

1662 ticket = (self.pile, mtf) 

1663 return ticket 

1664 else: 

1665 return (None, None) 

1666 

1667 def release_data(self, tickets): 

1668 for ticket in tickets: 

1669 pile, mtf = ticket 

1670 if pile is not None: 

1671 pile.remove_file(mtf) 

1672 

1673 def periodical(self): 

1674 if self.menuitem_watch.isChecked(): 

1675 if self.pile.reload_modified(): 

1676 self.update() 

1677 

1678 def get_pile(self): 

1679 return self.pile 

1680 

1681 def pile_changed(self, what): 

1682 self.pile_has_changed = True 

1683 self.pile_has_changed_signal.emit() 

1684 if self.automatic_updates: 

1685 self.update() 

1686 

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

1688 

1689 if gather is None: 

1690 def gather_func(tr): 

1691 return tr.nslc_id 

1692 

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

1694 

1695 else: 

1696 def gather_func(tr): 

1697 return ( 

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

1699 

1700 if color is None: 

1701 def color(tr): 

1702 return tr.location 

1703 

1704 self.gather = gather_func 

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

1706 

1707 self.color_gather = color 

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

1709 previous_ntracks = self.ntracks 

1710 self.set_ntracks(len(keys)) 

1711 

1712 if self.shown_tracks_range is None or \ 

1713 previous_ntracks == 0 or \ 

1714 self.show_all: 

1715 

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

1717 key_at_top = None 

1718 n = high-low 

1719 

1720 else: 

1721 low, high = self.shown_tracks_range 

1722 key_at_top = self.track_keys[low] 

1723 n = high-low 

1724 

1725 self.track_keys = sorted(keys) 

1726 

1727 track_patterns = [] 

1728 for k in self.track_keys: 

1729 pat = ['*', '*', '*', '*'] 

1730 for i, j in enumerate(gather): 

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

1732 

1733 track_patterns.append(pat) 

1734 

1735 self.track_patterns = track_patterns 

1736 

1737 if key_at_top is not None: 

1738 try: 

1739 ind = self.track_keys.index(key_at_top) 

1740 low = ind 

1741 high = low+n 

1742 except Exception: 

1743 pass 

1744 

1745 self.set_tracks_range((low, high)) 

1746 

1747 self.key_to_row = dict( 

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

1749 

1750 def inrange(x, r): 

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

1752 

1753 def trace_selector(trace): 

1754 gt = self.gather(trace) 

1755 return ( 

1756 gt in self.key_to_row and 

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

1758 

1759 self.trace_selector = lambda x: \ 

1760 (self.trace_filter is None or self.trace_filter(x)) \ 

1761 and trace_selector(x) 

1762 

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

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

1765 self.show_all: 

1766 

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

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

1769 tlen = (tmax - tmin) 

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

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

1772 

1773 def set_time_range(self, tmin, tmax): 

1774 if tmin is None: 

1775 tmin = initial_time_range[0] 

1776 

1777 if tmax is None: 

1778 tmax = initial_time_range[1] 

1779 

1780 if tmin > tmax: 

1781 tmin, tmax = tmax, tmin 

1782 

1783 if tmin == tmax: 

1784 tmin -= 1. 

1785 tmax += 1. 

1786 

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

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

1789 

1790 min_deltat = self.content_deltat_range()[0] 

1791 if (tmax - tmin < min_deltat): 

1792 m = (tmin + tmax) / 2. 

1793 tmin = m - min_deltat/2. 

1794 tmax = m + min_deltat/2. 

1795 

1796 self.time_projection.set_in_range(tmin, tmax) 

1797 self.tmin, self.tmax = tmin, tmax 

1798 

1799 def get_time_range(self): 

1800 return self.tmin, self.tmax 

1801 

1802 def ypart(self, y): 

1803 if y < self.ax_height: 

1804 return -1 

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

1806 return 1 

1807 else: 

1808 return 0 

1809 

1810 def time_fractional_digits(self): 

1811 min_deltat = self.content_deltat_range()[0] 

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

1813 

1814 def write_markers(self, fn=None): 

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

1816 if not fn: 

1817 fn, _ = qw.QFileDialog.getSaveFileName( 

1818 self, caption, options=qfiledialog_options) 

1819 if fn: 

1820 try: 

1821 Marker.save_markers( 

1822 self.markers, fn, 

1823 fdigits=self.time_fractional_digits()) 

1824 

1825 except Exception as e: 

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

1827 

1828 def write_selected_markers(self, fn=None): 

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

1830 if not fn: 

1831 fn, _ = qw.QFileDialog.getSaveFileName( 

1832 self, caption, options=qfiledialog_options) 

1833 if fn: 

1834 try: 

1835 Marker.save_markers( 

1836 self.iter_selected_markers(), 

1837 fn, 

1838 fdigits=self.time_fractional_digits()) 

1839 

1840 except Exception as e: 

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

1842 

1843 def read_events(self, fn=None): 

1844 ''' 

1845 Open QFileDialog to open, read and add 

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

1847 representation to the pile viewer. 

1848 ''' 

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

1850 if not fn: 

1851 fn, _ = qw.QFileDialog.getOpenFileName( 

1852 self, caption, options=qfiledialog_options) 

1853 if fn: 

1854 try: 

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

1856 self.associate_phases_to_events() 

1857 

1858 except Exception as e: 

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

1860 

1861 def read_markers(self, fn=None): 

1862 ''' 

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

1864 ''' 

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

1866 if not fn: 

1867 fn, _ = qw.QFileDialog.getOpenFileName( 

1868 self, caption, options=qfiledialog_options) 

1869 if fn: 

1870 try: 

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

1872 self.associate_phases_to_events() 

1873 

1874 except Exception as e: 

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

1876 

1877 def associate_phases_to_events(self): 

1878 associate_phases_to_events(self.markers) 

1879 

1880 def add_marker(self, marker): 

1881 # need index to inform QAbstactTableModel about upcoming change, 

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

1883 self.markers.insert(marker) 

1884 i = self.markers.remove(marker) 

1885 

1886 self.begin_markers_add.emit(i, i) 

1887 self.markers.insert(marker) 

1888 self.end_markers_add.emit() 

1889 self.markers_deltat_max = max( 

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

1891 

1892 def add_markers(self, markers): 

1893 if not self.markers: 

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

1895 self.markers.insert_many(markers) 

1896 self.end_markers_add.emit() 

1897 self.update_markers_deltat_max() 

1898 else: 

1899 for marker in markers: 

1900 self.add_marker(marker) 

1901 

1902 def update_markers_deltat_max(self): 

1903 if self.markers: 

1904 self.markers_deltat_max = max( 

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

1906 

1907 def remove_marker(self, marker): 

1908 ''' 

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

1910 

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

1912 ''' 

1913 

1914 if marker is self.active_event_marker: 

1915 self.deactivate_event_marker() 

1916 

1917 try: 

1918 i = self.markers.index(marker) 

1919 self.begin_markers_remove.emit(i, i) 

1920 self.markers.remove_at(i) 

1921 self.end_markers_remove.emit() 

1922 except ValueError: 

1923 pass 

1924 

1925 def remove_markers(self, markers): 

1926 ''' 

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

1928 

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

1930 instances 

1931 ''' 

1932 

1933 if markers is self.markers: 

1934 markers = list(markers) 

1935 

1936 for marker in markers: 

1937 self.remove_marker(marker) 

1938 

1939 self.update_markers_deltat_max() 

1940 

1941 def remove_selected_markers(self): 

1942 def delete_segment(istart, iend): 

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

1944 for _ in range(iend - istart): 

1945 self.markers.remove_at(istart) 

1946 

1947 self.end_markers_remove.emit() 

1948 

1949 istart = None 

1950 ipos = 0 

1951 markers = self.markers 

1952 nmarkers = len(self.markers) 

1953 while ipos < nmarkers: 

1954 marker = markers[ipos] 

1955 if marker.is_selected(): 

1956 if marker is self.active_event_marker: 

1957 self.deactivate_event_marker() 

1958 

1959 if istart is None: 

1960 istart = ipos 

1961 else: 

1962 if istart is not None: 

1963 delete_segment(istart, ipos) 

1964 nmarkers -= ipos - istart 

1965 ipos = istart - 1 

1966 istart = None 

1967 

1968 ipos += 1 

1969 

1970 if istart is not None: 

1971 delete_segment(istart, ipos) 

1972 

1973 self.update_markers_deltat_max() 

1974 

1975 def selected_markers(self): 

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

1977 

1978 def iter_selected_markers(self): 

1979 for marker in self.markers: 

1980 if marker.is_selected(): 

1981 yield marker 

1982 

1983 def get_markers(self): 

1984 return self.markers 

1985 

1986 def mousePressEvent(self, mouse_ev): 

1987 self.show_all = False 

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

1989 

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

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

1992 if self.picking: 

1993 if self.picking_down is None: 

1994 self.picking_down = ( 

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

1996 mouse_ev.y()) 

1997 

1998 elif marker is not None: 

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

2000 self.deselect_all() 

2001 marker.selected = True 

2002 self.emit_selected_markers() 

2003 self.update() 

2004 else: 

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

2006 self.track_trange = self.tmin, self.tmax 

2007 

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

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

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

2011 self.update_status() 

2012 

2013 def mouseReleaseEvent(self, mouse_ev): 

2014 if self.ignore_releases: 

2015 self.ignore_releases -= 1 

2016 return 

2017 

2018 if self.picking: 

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

2020 self.emit_selected_markers() 

2021 

2022 if self.track_start: 

2023 self.update() 

2024 

2025 self.track_start = None 

2026 self.track_trange = None 

2027 self.update_status() 

2028 

2029 def mouseDoubleClickEvent(self, mouse_ev): 

2030 self.show_all = False 

2031 self.start_picking(None) 

2032 self.ignore_releases = 1 

2033 

2034 def mouseMoveEvent(self, mouse_ev): 

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

2036 

2037 if self.picking: 

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

2039 

2040 elif self.track_start is not None: 

2041 x0, y0 = self.track_start 

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

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

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

2045 dy = 0 

2046 

2047 tmin0, tmax0 = self.track_trange 

2048 

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

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

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

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

2053 

2054 self.interrupt_following() 

2055 self.set_time_range( 

2056 tmin0 - dt - dtr*frac, 

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

2058 

2059 self.update() 

2060 else: 

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

2062 

2063 self.update_status() 

2064 

2065 def nslc_ids_under_cursor(self, x, y): 

2066 ftrack = self.track_to_screen.rev(y) 

2067 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2068 return nslc_ids 

2069 

2070 def marker_under_cursor(self, x, y): 

2071 mouset = self.time_projection.rev(x) 

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

2073 relevant_nslc_ids = None 

2074 for marker in self.markers: 

2075 if marker.kind not in self.visible_marker_kinds: 

2076 continue 

2077 

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

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

2080 

2081 if relevant_nslc_ids is None: 

2082 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2083 

2084 marker_nslc_ids = marker.get_nslc_ids() 

2085 if not marker_nslc_ids: 

2086 return marker 

2087 

2088 for nslc_id in marker_nslc_ids: 

2089 if nslc_id in relevant_nslc_ids: 

2090 return marker 

2091 

2092 def hoovering(self, x, y): 

2093 mouset = self.time_projection.rev(x) 

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

2095 needupdate = False 

2096 haveone = False 

2097 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2098 for marker in self.markers: 

2099 if marker.kind not in self.visible_marker_kinds: 

2100 continue 

2101 

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

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

2104 

2105 if state: 

2106 xstate = False 

2107 

2108 marker_nslc_ids = marker.get_nslc_ids() 

2109 if not marker_nslc_ids: 

2110 xstate = True 

2111 

2112 for nslc in relevant_nslc_ids: 

2113 if marker.match_nslc(nslc): 

2114 xstate = True 

2115 

2116 state = xstate 

2117 

2118 if state: 

2119 haveone = True 

2120 oldstate = marker.is_alerted() 

2121 if oldstate != state: 

2122 needupdate = True 

2123 marker.set_alerted(state) 

2124 if state: 

2125 self.message = marker.hoover_message() 

2126 

2127 if not haveone: 

2128 self.message = None 

2129 

2130 if needupdate: 

2131 self.update() 

2132 

2133 def event(self, event): 

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

2135 self.keyPressEvent(event) 

2136 return True 

2137 else: 

2138 return base.event(self, event) 

2139 

2140 def keyPressEvent(self, key_event): 

2141 self.show_all = False 

2142 dt = self.tmax - self.tmin 

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

2144 

2145 key = key_event.key() 

2146 try: 

2147 keytext = str(key_event.text()) 

2148 except UnicodeEncodeError: 

2149 return 

2150 

2151 if key == qc.Qt.Key_Space: 

2152 self.interrupt_following() 

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

2154 

2155 elif key == qc.Qt.Key_Up: 

2156 for m in self.selected_markers(): 

2157 if isinstance(m, PhaseMarker): 

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

2159 p = 0 

2160 else: 

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

2162 m.set_polarity(p) 

2163 

2164 elif key == qc.Qt.Key_Down: 

2165 for m in self.selected_markers(): 

2166 if isinstance(m, PhaseMarker): 

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

2168 p = 0 

2169 else: 

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

2171 m.set_polarity(p) 

2172 

2173 elif key == qc.Qt.Key_B: 

2174 dt = self.tmax - self.tmin 

2175 self.interrupt_following() 

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

2177 

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

2179 self.interrupt_following() 

2180 

2181 tgo = None 

2182 

2183 class TraceDummy(object): 

2184 def __init__(self, marker): 

2185 self._marker = marker 

2186 

2187 @property 

2188 def nslc_id(self): 

2189 return self._marker.one_nslc() 

2190 

2191 def marker_to_itrack(marker): 

2192 try: 

2193 return self.key_to_row.get( 

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

2195 

2196 except MarkerOneNSLCRequired: 

2197 return -1 

2198 

2199 emarker, pmarkers = self.get_active_markers() 

2200 pmarkers = [ 

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

2202 pmarkers.sort(key=lambda m: ( 

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

2204 

2205 if key == qc.Qt.Key_Backtab: 

2206 pmarkers.reverse() 

2207 

2208 smarkers = self.selected_markers() 

2209 iselected = [] 

2210 for sm in smarkers: 

2211 try: 

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

2213 except ValueError: 

2214 pass 

2215 

2216 if iselected: 

2217 icurrent = max(iselected) + 1 

2218 else: 

2219 icurrent = 0 

2220 

2221 if icurrent < len(pmarkers): 

2222 self.deselect_all() 

2223 cmarker = pmarkers[icurrent] 

2224 cmarker.selected = True 

2225 tgo = cmarker.tmin 

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

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

2228 

2229 itrack = marker_to_itrack(cmarker) 

2230 if itrack != -1: 

2231 if itrack < self.shown_tracks_range[0]: 

2232 self.scroll_tracks( 

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

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

2235 self.scroll_tracks( 

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

2237 

2238 if itrack not in self.track_to_nslc_ids: 

2239 self.go_to_selection() 

2240 

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

2242 smarkers = self.selected_markers() 

2243 tgo = None 

2244 dir = str(keytext) 

2245 if smarkers: 

2246 tmid = smarkers[0].tmin 

2247 for smarker in smarkers: 

2248 if dir == 'n': 

2249 tmid = max(smarker.tmin, tmid) 

2250 else: 

2251 tmid = min(smarker.tmin, tmid) 

2252 

2253 tgo = tmid 

2254 

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

2256 for marker in sorted( 

2257 self.markers, 

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

2259 

2260 t = marker.tmin 

2261 if t > tmid and \ 

2262 marker.kind in self.visible_marker_kinds and \ 

2263 (dir == 'n' or 

2264 isinstance(marker, EventMarker)): 

2265 

2266 self.deselect_all() 

2267 marker.selected = True 

2268 tgo = t 

2269 break 

2270 else: 

2271 for marker in sorted( 

2272 self.markers, 

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

2274 reverse=True): 

2275 

2276 t = marker.tmin 

2277 if t < tmid and \ 

2278 marker.kind in self.visible_marker_kinds and \ 

2279 (dir == 'p' or 

2280 isinstance(marker, EventMarker)): 

2281 self.deselect_all() 

2282 marker.selected = True 

2283 tgo = t 

2284 break 

2285 

2286 if tgo is not None: 

2287 self.interrupt_following() 

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

2289 

2290 elif keytext == 'r': 

2291 if self.pile.reload_modified(): 

2292 self.reloaded = True 

2293 

2294 elif keytext == 'R': 

2295 self.setup_snufflings() 

2296 

2297 elif key == qc.Qt.Key_Backspace: 

2298 self.remove_selected_markers() 

2299 

2300 elif keytext == 'a': 

2301 for marker in self.markers: 

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

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

2304 marker.kind in self.visible_marker_kinds): 

2305 marker.selected = True 

2306 else: 

2307 marker.selected = False 

2308 

2309 elif keytext == 'A': 

2310 for marker in self.markers: 

2311 if marker.kind in self.visible_marker_kinds: 

2312 marker.selected = True 

2313 

2314 elif keytext == 'd': 

2315 self.deselect_all() 

2316 

2317 elif keytext == 'E': 

2318 self.deactivate_event_marker() 

2319 

2320 elif keytext == 'e': 

2321 markers = self.selected_markers() 

2322 event_markers_in_spe = [ 

2323 marker for marker in markers 

2324 if not isinstance(marker, PhaseMarker)] 

2325 

2326 phase_markers = [ 

2327 marker for marker in markers 

2328 if isinstance(marker, PhaseMarker)] 

2329 

2330 if len(event_markers_in_spe) == 1: 

2331 event_marker = event_markers_in_spe[0] 

2332 if not isinstance(event_marker, EventMarker): 

2333 nslcs = list(event_marker.nslc_ids) 

2334 lat, lon = 0.0, 0.0 

2335 old = self.get_active_event() 

2336 if len(nslcs) == 1: 

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

2338 elif old is not None: 

2339 lat, lon = old.lat, old.lon 

2340 

2341 event_marker.convert_to_event_marker(lat, lon) 

2342 

2343 self.set_active_event_marker(event_marker) 

2344 event = event_marker.get_event() 

2345 for marker in phase_markers: 

2346 marker.set_event(event) 

2347 

2348 else: 

2349 for marker in event_markers_in_spe: 

2350 marker.convert_to_event_marker() 

2351 

2352 elif keytext in ('0', '1', '2', '3', '4', '5', '6', '7'): 

2353 for marker in self.selected_markers(): 

2354 marker.set_kind(int(keytext)) 

2355 self.emit_selected_markers() 

2356 

2357 elif key in fkey_map: 

2358 self.handle_fkeys(key) 

2359 

2360 elif key == qc.Qt.Key_Escape: 

2361 if self.picking: 

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

2363 

2364 elif key == qc.Qt.Key_PageDown: 

2365 self.scroll_tracks( 

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

2367 

2368 elif key == qc.Qt.Key_PageUp: 

2369 self.scroll_tracks( 

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

2371 

2372 elif key == qc.Qt.Key_Plus: 

2373 self.zoom_tracks(0., 1.) 

2374 

2375 elif key == qc.Qt.Key_Minus: 

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

2377 

2378 elif key == qc.Qt.Key_Equal: 

2379 ntracks_shown = self.shown_tracks_range[1] - \ 

2380 self.shown_tracks_range[0] 

2381 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2382 self.zoom_tracks(0., dtracks) 

2383 

2384 elif key == qc.Qt.Key_Colon: 

2385 self.want_input.emit() 

2386 

2387 elif keytext == 'f': 

2388 self.toggle_fullscreen() 

2389 

2390 elif keytext == 'g': 

2391 self.go_to_selection() 

2392 

2393 elif keytext == 'G': 

2394 self.go_to_selection(tight=True) 

2395 

2396 elif keytext == 'm': 

2397 self.toggle_marker_editor() 

2398 

2399 elif keytext == 'c': 

2400 self.toggle_main_controls() 

2401 

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

2403 dir = 1 

2404 amount = 1 

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

2406 dir = -1 

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

2408 amount = 10 

2409 self.nudge_selected_markers(dir*amount) 

2410 else: 

2411 super().keyPressEvent(key_event) 

2412 

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

2414 self.emit_selected_markers() 

2415 

2416 self.update() 

2417 self.update_status() 

2418 

2419 def handle_fkeys(self, key): 

2420 self.set_phase_kind( 

2421 self.selected_markers(), 

2422 fkey_map[key] + 1) 

2423 self.emit_selected_markers() 

2424 

2425 def emit_selected_markers(self): 

2426 ibounds = [] 

2427 last_selected = False 

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

2429 this_selected = marker.is_selected() 

2430 if this_selected != last_selected: 

2431 ibounds.append(imarker) 

2432 

2433 last_selected = this_selected 

2434 

2435 if last_selected: 

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

2437 

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

2439 self.n_selected_markers = sum( 

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

2441 self.marker_selection_changed.emit(chunks) 

2442 

2443 def toggle_marker_editor(self): 

2444 self.panel_parent.toggle_marker_editor() 

2445 

2446 def toggle_main_controls(self): 

2447 self.panel_parent.toggle_main_controls() 

2448 

2449 def nudge_selected_markers(self, npixels): 

2450 a, b = self.time_projection.ur 

2451 c, d = self.time_projection.xr 

2452 for marker in self.selected_markers(): 

2453 if not isinstance(marker, EventMarker): 

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

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

2456 

2457 def toggle_fullscreen(self): 

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

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

2460 self.window().showNormal() 

2461 else: 

2462 if is_macos: 

2463 self.window().showMaximized() 

2464 else: 

2465 self.window().showFullScreen() 

2466 

2467 def about(self): 

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

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

2470 txt = f.read() 

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

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

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

2474 

2475 def help(self): 

2476 class MyScrollArea(qw.QScrollArea): 

2477 

2478 def sizeHint(self): 

2479 s = qc.QSize() 

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

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

2482 return s 

2483 

2484 with open(pyrocko.util.data_file( 

2485 'snuffler_help.html')) as f: 

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

2487 

2488 with open(pyrocko.util.data_file( 

2489 'snuffler_help_epilog.html')) as f: 

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

2491 

2492 for h in [hcheat, hepilog]: 

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

2494 h.setWordWrap(True) 

2495 

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

2497 

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

2499 scroller = qw.QScrollArea() 

2500 frame = qw.QFrame(scroller) 

2501 frame.setLineWidth(0) 

2502 layout = qw.QVBoxLayout() 

2503 layout.setContentsMargins(0, 0, 0, 0) 

2504 layout.setSpacing(0) 

2505 frame.setLayout(layout) 

2506 scroller.setWidget(frame) 

2507 scroller.setWidgetResizable(True) 

2508 frame.setBackgroundRole(qg.QPalette.Base) 

2509 for h in labels: 

2510 h.setParent(frame) 

2511 h.setMargin(3) 

2512 h.setTextInteractionFlags( 

2513 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2514 h.setBackgroundRole(qg.QPalette.Base) 

2515 layout.addWidget(h) 

2516 h.linkActivated.connect( 

2517 self.open_link) 

2518 

2519 if self.panel_parent is not None: 

2520 if target == 'panel': 

2521 self.panel_parent.add_panel( 

2522 name, scroller, True, volatile=False) 

2523 else: 

2524 self.panel_parent.add_tab(name, scroller) 

2525 

2526 def open_link(self, link): 

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

2528 

2529 def wheelEvent(self, wheel_event): 

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

2531 

2532 n = self.wheel_pos // 120 

2533 self.wheel_pos = self.wheel_pos % 120 

2534 if n == 0: 

2535 return 

2536 

2537 amount = max( 

2538 1., 

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

2540 wdelta = amount * n 

2541 

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

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

2544 / (trmax-trmin) 

2545 

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

2547 self.zoom_tracks(anchor, wdelta) 

2548 else: 

2549 self.scroll_tracks(-wdelta) 

2550 

2551 def dragEnterEvent(self, event): 

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

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

2554 event.setDropAction(qc.Qt.LinkAction) 

2555 event.accept() 

2556 

2557 def dropEvent(self, event): 

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

2559 paths = list( 

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

2561 event.acceptProposedAction() 

2562 self.load(paths) 

2563 

2564 def get_phase_name(self, kind): 

2565 return self.config.get_phase_name(kind) 

2566 

2567 def set_phase_kind(self, markers, kind): 

2568 phasename = self.get_phase_name(kind) 

2569 

2570 for marker in markers: 

2571 if isinstance(marker, PhaseMarker): 

2572 if kind == 10: 

2573 marker.convert_to_marker() 

2574 else: 

2575 marker.set_phasename(phasename) 

2576 marker.set_event(self.get_active_event()) 

2577 

2578 elif isinstance(marker, EventMarker): 

2579 pass 

2580 

2581 else: 

2582 if kind != 10: 

2583 event = self.get_active_event() 

2584 marker.convert_to_phase_marker( 

2585 event, phasename, None, False) 

2586 

2587 def set_ntracks(self, ntracks): 

2588 if self.ntracks != ntracks: 

2589 self.ntracks = ntracks 

2590 if self.shown_tracks_range is not None: 

2591 l, h = self.shown_tracks_range 

2592 else: 

2593 l, h = 0, self.ntracks 

2594 

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

2596 

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

2598 

2599 low, high = range 

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

2601 high = min(self.ntracks, high) 

2602 low = max(0, low) 

2603 high = max(1, high) 

2604 

2605 if start is None: 

2606 start = float(low) 

2607 

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

2609 self.shown_tracks_range = low, high 

2610 self.shown_tracks_start = start 

2611 

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

2613 

2614 def scroll_tracks(self, shift): 

2615 shown = self.shown_tracks_range 

2616 shiftmin = -shown[0] 

2617 shiftmax = self.ntracks-shown[1] 

2618 shift = max(shiftmin, shift) 

2619 shift = min(shiftmax, shift) 

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

2621 

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

2623 

2624 self.update() 

2625 

2626 def zoom_tracks(self, anchor, delta): 

2627 ntracks_shown = self.shown_tracks_range[1] \ 

2628 - self.shown_tracks_range[0] 

2629 

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

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

2632 return 

2633 

2634 ntracks_shown += int(round(delta)) 

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

2636 

2637 u = self.shown_tracks_start 

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

2639 nv = nu + ntracks_shown 

2640 if nv > self.ntracks: 

2641 nu -= nv - self.ntracks 

2642 nv -= nv - self.ntracks 

2643 

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

2645 

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

2647 - self.shown_tracks_range[0] 

2648 

2649 self.update() 

2650 

2651 def content_time_range(self): 

2652 pile = self.get_pile() 

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

2654 if tmin is None: 

2655 tmin = initial_time_range[0] 

2656 if tmax is None: 

2657 tmax = initial_time_range[1] 

2658 

2659 return tmin, tmax 

2660 

2661 def content_deltat_range(self): 

2662 pile = self.get_pile() 

2663 

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

2665 

2666 if deltatmin is None: 

2667 deltatmin = 0.001 

2668 

2669 if deltatmax is None: 

2670 deltatmax = 1000.0 

2671 

2672 return deltatmin, deltatmax 

2673 

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

2675 if tmax < tmin: 

2676 tmin, tmax = tmax, tmin 

2677 

2678 deltatmin = self.content_deltat_range()[0] 

2679 dt = deltatmin * self.visible_length * 0.95 

2680 

2681 if dt == 0.0: 

2682 dt = 1.0 

2683 

2684 if tight: 

2685 if tmax != tmin: 

2686 dtm = tmax - tmin 

2687 tmin -= dtm*0.1 

2688 tmax += dtm*0.1 

2689 return tmin, tmax 

2690 else: 

2691 tcenter = (tmin + tmax) / 2. 

2692 tmin = tcenter - 0.5*dt 

2693 tmax = tcenter + 0.5*dt 

2694 return tmin, tmax 

2695 

2696 if tmax-tmin < dt: 

2697 vmin, vmax = self.get_time_range() 

2698 dt = min(vmax - vmin, dt) 

2699 

2700 tcenter = (tmin+tmax)/2. 

2701 etmin, etmax = tmin, tmax 

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

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

2704 dtm = tmax-tmin 

2705 if etmin == tmin: 

2706 tmin -= dtm*0.1 

2707 if etmax == tmax: 

2708 tmax += dtm*0.1 

2709 

2710 else: 

2711 dtm = tmax-tmin 

2712 tmin -= dtm*0.1 

2713 tmax += dtm*0.1 

2714 

2715 return tmin, tmax 

2716 

2717 def go_to_selection(self, tight=False): 

2718 markers = self.selected_markers() 

2719 if markers: 

2720 tmax, tmin = self.content_time_range() 

2721 for marker in markers: 

2722 tmin = min(tmin, marker.tmin) 

2723 tmax = max(tmax, marker.tmax) 

2724 

2725 else: 

2726 if tight: 

2727 vmin, vmax = self.get_time_range() 

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

2729 else: 

2730 tmin, tmax = self.content_time_range() 

2731 

2732 tmin, tmax = self.make_good_looking_time_range( 

2733 tmin, tmax, tight=tight) 

2734 

2735 self.interrupt_following() 

2736 self.set_time_range(tmin, tmax) 

2737 self.update() 

2738 

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

2740 tmax = t 

2741 if tlen is not None: 

2742 tmax = t+tlen 

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

2744 self.interrupt_following() 

2745 self.set_time_range(tmin, tmax) 

2746 self.update() 

2747 

2748 def go_to_event_by_name(self, name): 

2749 for marker in self.markers: 

2750 if isinstance(marker, EventMarker): 

2751 event = marker.get_event() 

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

2753 tmin, tmax = self.make_good_looking_time_range( 

2754 event.time, event.time) 

2755 

2756 self.interrupt_following() 

2757 self.set_time_range(tmin, tmax) 

2758 

2759 def printit(self): 

2760 from .qt_compat import qprint 

2761 printer = qprint.QPrinter() 

2762 printer.setOrientation(qprint.QPrinter.Landscape) 

2763 

2764 dialog = qprint.QPrintDialog(printer, self) 

2765 dialog.setWindowTitle('Print') 

2766 

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

2768 return 

2769 

2770 painter = qg.QPainter() 

2771 painter.begin(printer) 

2772 page = printer.pageRect() 

2773 self.drawit( 

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

2775 

2776 painter.end() 

2777 

2778 def savesvg(self, fn=None): 

2779 

2780 if not fn: 

2781 fn, _ = qw.QFileDialog.getSaveFileName( 

2782 self, 

2783 'Save as SVG|PNG', 

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

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

2786 options=qfiledialog_options) 

2787 

2788 if fn == '': 

2789 return 

2790 

2791 fn = str(fn) 

2792 

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

2794 try: 

2795 w, h = 842, 595 

2796 margin = 0.025 

2797 m = max(w, h)*margin 

2798 

2799 generator = qsvg.QSvgGenerator() 

2800 generator.setFileName(fn) 

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

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

2803 

2804 painter = qg.QPainter() 

2805 painter.begin(generator) 

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

2807 painter.end() 

2808 

2809 except Exception as e: 

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

2811 

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

2813 pixmap = self.grab() 

2814 

2815 try: 

2816 pixmap.save(fn) 

2817 

2818 except Exception as e: 

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

2820 

2821 else: 

2822 self.fail( 

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

2824 '".png".') 

2825 

2826 def paintEvent(self, paint_ev): 

2827 ''' 

2828 Called by QT whenever widget needs to be painted. 

2829 ''' 

2830 # Prevent a problem on macos with QOpenGLWidget, where paintEvent 

2831 # was called twice (by different threads?), causing segfaults. 

2832 if self.in_paint_event: 

2833 logger.warning('Blocking reentrant call to paintEvent().') 

2834 return 

2835 

2836 self.in_paint_event = True 

2837 

2838 painter = qg.QPainter(self) 

2839 

2840 if self.menuitem_antialias.isChecked(): 

2841 painter.setRenderHint(qg.QPainter.Antialiasing) 

2842 

2843 self.drawit(painter) 

2844 

2845 logger.debug( 

2846 'Time spent drawing: ' 

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

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

2849 (self.timer_draw - self.timer_cutout)) 

2850 

2851 logger.debug( 

2852 'Time spent processing:' 

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

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

2855 self.timer_cutout.get()) 

2856 

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

2858 self.time_last_painted = time.time() 

2859 self.in_paint_event = False 

2860 

2861 def determine_box_styles(self): 

2862 

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

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

2865 istyle = 0 

2866 trace_styles = {} 

2867 for itr, tr in enumerate(traces): 

2868 if itr > 0: 

2869 other = traces[itr-1] 

2870 if not ( 

2871 other.nslc_id == tr.nslc_id 

2872 and other.deltat == tr.deltat 

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

2874 < gap_lap_tolerance*tr.deltat): 

2875 

2876 istyle += 1 

2877 

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

2879 

2880 self.trace_styles = trace_styles 

2881 

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

2883 

2884 for v_projection in track_projections.values(): 

2885 v_projection.set_in_range(0., 1.) 

2886 

2887 def selector(x): 

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

2889 

2890 if self.trace_filter is not None: 

2891 def tselector(x): 

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

2893 

2894 else: 

2895 tselector = selector 

2896 

2897 traces = list(self.pile.iter_traces( 

2898 group_selector=selector, trace_selector=tselector)) 

2899 

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

2901 

2902 def drawbox(itrack, istyle, traces): 

2903 v_projection = track_projections[itrack] 

2904 dvmin = v_projection(0.) 

2905 dvmax = v_projection(1.) 

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

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

2908 

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

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

2911 p.fillRect(rect, style.fill_brush) 

2912 p.setPen(style.frame_pen) 

2913 p.drawRect(rect) 

2914 

2915 traces_by_style = {} 

2916 for itr, tr in enumerate(traces): 

2917 gt = self.gather(tr) 

2918 if gt not in self.key_to_row: 

2919 continue 

2920 

2921 itrack = self.key_to_row[gt] 

2922 if itrack not in track_projections: 

2923 continue 

2924 

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

2926 

2927 if len(traces) < 500: 

2928 drawbox(itrack, istyle, [tr]) 

2929 else: 

2930 if (itrack, istyle) not in traces_by_style: 

2931 traces_by_style[itrack, istyle] = [] 

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

2933 

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

2935 drawbox(itrack, istyle, traces) 

2936 

2937 def draw_visible_markers( 

2938 self, p, vcenter_projection, primary_pen): 

2939 

2940 try: 

2941 markers = self.markers.with_key_in_limited( 

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

2943 

2944 except pyrocko.pile.TooMany: 

2945 tmin = self.markers[0].tmin 

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

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

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

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

2950 v0, _ = vcenter_projection.get_out_range() 

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

2952 

2953 p.save() 

2954 

2955 pen = qg.QPen(primary_pen) 

2956 pen.setWidth(2) 

2957 pen.setStyle(qc.Qt.DotLine) 

2958 # pat = [5., 3.] 

2959 # pen.setDashPattern(pat) 

2960 p.setPen(pen) 

2961 

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

2963 s_selected = ' (all selected)' 

2964 elif self.n_selected_markers > 0: 

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

2966 else: 

2967 s_selected = '' 

2968 

2969 draw_label( 

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

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

2972 label_bg, 'LB') 

2973 

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

2975 p.drawLine(line) 

2976 p.restore() 

2977 

2978 return 

2979 

2980 for marker in markers: 

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

2982 and marker.kind in self.visible_marker_kinds: 

2983 

2984 marker.draw( 

2985 p, self.time_projection, vcenter_projection, 

2986 with_label=True) 

2987 

2988 def get_squirrel(self): 

2989 try: 

2990 return self.pile._squirrel 

2991 except AttributeError: 

2992 return None 

2993 

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

2995 sq = self.get_squirrel() 

2996 if sq is None: 

2997 return 

2998 

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

3000 v_projection = track_projections[itrack] 

3001 dvmin = v_projection(0.) 

3002 dvmax = v_projection(1.) 

3003 dtmin = time_projection.clipped(tmin, 0) 

3004 dtmax = time_projection.clipped(tmax, 1) 

3005 

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

3007 p.fillRect(rect, style.fill_brush) 

3008 p.setPen(style.frame_pen) 

3009 p.drawRect(rect) 

3010 

3011 pattern_list = [] 

3012 pattern_to_itrack = {} 

3013 for key in self.track_keys: 

3014 itrack = self.key_to_row[key] 

3015 if itrack not in track_projections: 

3016 continue 

3017 

3018 pattern = self.track_patterns[itrack] 

3019 pattern_to_itrack[tuple(pattern)] = itrack 

3020 pattern_list.append(tuple(pattern)) 

3021 

3022 vmin, vmax = self.get_time_range() 

3023 

3024 for kind in ['waveform', 'waveform_promise']: 

3025 for coverage in sq.get_coverage( 

3026 kind, vmin, vmax, pattern_list, limit=500): 

3027 itrack = pattern_to_itrack[coverage.pattern.nslc] 

3028 

3029 if coverage.changes is None: 

3030 drawbox( 

3031 itrack, coverage.tmin, coverage.tmax, 

3032 box_styles_coverage[kind][0]) 

3033 else: 

3034 t = None 

3035 pcount = 0 

3036 for tb, count in coverage.changes: 

3037 if t is not None and tb > t: 

3038 if pcount > 0: 

3039 drawbox( 

3040 itrack, t, tb, 

3041 box_styles_coverage[kind][ 

3042 min(len(box_styles_coverage)-1, 

3043 pcount)]) 

3044 

3045 t = tb 

3046 pcount = count 

3047 

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

3049 ''' 

3050 This performs the actual drawing. 

3051 ''' 

3052 

3053 self.timer_draw.start() 

3054 show_boxes = self.menuitem_showboxes.isChecked() 

3055 sq = self.get_squirrel() 

3056 

3057 if self.gather is None: 

3058 self.set_gathering() 

3059 

3060 if self.pile_has_changed: 

3061 

3062 if not self.sortingmode_change_delayed(): 

3063 self.sortingmode_change() 

3064 

3065 if show_boxes and sq is None: 

3066 self.determine_box_styles() 

3067 

3068 self.pile_has_changed = False 

3069 

3070 if h is None: 

3071 h = float(self.height()) 

3072 if w is None: 

3073 w = float(self.width()) 

3074 

3075 if printmode: 

3076 primary_color = (0, 0, 0) 

3077 else: 

3078 primary_color = pyrocko.plot.colors.g_nat_colors['nat_gray'] 

3079 

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

3081 

3082 ax_h = float(self.ax_height) 

3083 

3084 vbottom_ax_projection = Projection() 

3085 vtop_ax_projection = Projection() 

3086 vcenter_projection = Projection() 

3087 

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

3089 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3090 vtop_ax_projection.set_out_range(0., ax_h) 

3091 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3092 vcenter_projection.set_in_range(0., 1.) 

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

3094 

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

3096 track_projections = {} 

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

3098 proj = Projection() 

3099 proj.set_out_range( 

3100 self.track_to_screen(i+0.05), 

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

3102 

3103 track_projections[i] = proj 

3104 

3105 if self.tmin > self.tmax: 

3106 return 

3107 

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

3109 vbottom_ax_projection.set_in_range(0, ax_h) 

3110 

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

3112 

3113 yscaler = pyrocko.plot.AutoScaler() 

3114 

3115 p.setPen(primary_pen) 

3116 

3117 font = qg.QFont() 

3118 font.setBold(True) 

3119 

3120 axannotfont = qg.QFont() 

3121 axannotfont.setBold(True) 

3122 axannotfont.setPointSize(8) 

3123 

3124 processed_traces = self.prepare_cutout2( 

3125 self.tmin, self.tmax, 

3126 trace_selector=self.trace_selector, 

3127 degap=self.menuitem_degap.isChecked(), 

3128 demean=self.menuitem_demean.isChecked()) 

3129 

3130 if not printmode and show_boxes: 

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

3132 or (self.view_mode is ViewMode.Waterfall 

3133 and not processed_traces): 

3134 

3135 if sq is None: 

3136 self.draw_trace_boxes( 

3137 p, self.time_projection, track_projections) 

3138 

3139 else: 

3140 self.draw_coverage( 

3141 p, self.time_projection, track_projections) 

3142 

3143 p.setFont(font) 

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

3145 

3146 color_lookup = dict( 

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

3148 

3149 self.track_to_nslc_ids = {} 

3150 nticks = 0 

3151 annot_labels = [] 

3152 

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

3154 waterfall = self.waterfall 

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

3156 waterfall.set_traces(processed_traces) 

3157 waterfall.set_cmap(self.waterfall_cmap) 

3158 waterfall.set_integrate(self.waterfall_integrate) 

3159 waterfall.set_clip( 

3160 self.waterfall_clip_min, self.waterfall_clip_max) 

3161 waterfall.show_absolute_values( 

3162 self.waterfall_show_absolute) 

3163 

3164 rect = qc.QRectF( 

3165 0, self.ax_height, 

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

3167 ) 

3168 waterfall.draw_waterfall(p, rect=rect) 

3169 

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

3171 show_scales = self.menuitem_showscalerange.isChecked() \ 

3172 or self.menuitem_showscaleaxis.isChecked() 

3173 

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

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

3176 - self.track_to_screen(0.05) 

3177 

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

3179 

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

3181 if self.menuitem_showscaleaxis.isChecked() \ 

3182 else 15 

3183 

3184 yscaler = pyrocko.plot.AutoScaler( 

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

3186 snap=show_scales 

3187 and not self.menuitem_showscaleaxis.isChecked()) 

3188 

3189 data_ranges = pyrocko.trace.minmax( 

3190 processed_traces, 

3191 key=self.scaling_key, 

3192 mode=self.scaling_base[0], 

3193 outer_mode=self.scaling_base[1]) 

3194 

3195 if not self.menuitem_fixscalerange.isChecked(): 

3196 self.old_data_ranges = data_ranges 

3197 else: 

3198 data_ranges.update(self.old_data_ranges) 

3199 

3200 self.apply_scaling_hooks(data_ranges) 

3201 

3202 trace_to_itrack = {} 

3203 track_scaling_keys = {} 

3204 track_scaling_colors = {} 

3205 for trace in processed_traces: 

3206 gt = self.gather(trace) 

3207 if gt not in self.key_to_row: 

3208 continue 

3209 

3210 itrack = self.key_to_row[gt] 

3211 if itrack not in track_projections: 

3212 continue 

3213 

3214 trace_to_itrack[trace] = itrack 

3215 

3216 if itrack not in self.track_to_nslc_ids: 

3217 self.track_to_nslc_ids[itrack] = set() 

3218 

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

3220 

3221 if itrack not in track_scaling_keys: 

3222 track_scaling_keys[itrack] = set() 

3223 

3224 scaling_key = self.scaling_key(trace) 

3225 track_scaling_keys[itrack].add(scaling_key) 

3226 

3227 color = pyrocko.plot.color( 

3228 color_lookup[self.color_gather(trace)]) 

3229 

3230 k = itrack, scaling_key 

3231 if k not in track_scaling_colors \ 

3232 and self.menuitem_colortraces.isChecked(): 

3233 track_scaling_colors[k] = color 

3234 else: 

3235 track_scaling_colors[k] = primary_color 

3236 

3237 # y axes, zero lines 

3238 trace_projections = {} 

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

3240 if itrack not in track_scaling_keys: 

3241 continue 

3242 uoff = 0 

3243 for scaling_key in track_scaling_keys[itrack]: 

3244 data_range = data_ranges[scaling_key] 

3245 dymin, dymax = data_range 

3246 ymin, ymax, yinc = yscaler.make_scale( 

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

3248 iexp = yscaler.make_exp(yinc) 

3249 factor = 10**iexp 

3250 trace_projection = track_projections[itrack].copy() 

3251 trace_projection.set_in_range(ymax, ymin) 

3252 trace_projections[itrack, scaling_key] = \ 

3253 trace_projection 

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

3255 vmin, vmax = trace_projection.get_out_range() 

3256 umax_zeroline = umax 

3257 uoffnext = uoff 

3258 

3259 if show_scales: 

3260 pen = qg.QPen(primary_pen) 

3261 k = itrack, scaling_key 

3262 if k in track_scaling_colors: 

3263 c = qg.QColor(*track_scaling_colors[ 

3264 itrack, scaling_key]) 

3265 

3266 pen.setColor(c) 

3267 

3268 p.setPen(pen) 

3269 if nlinesavail > 3: 

3270 if self.menuitem_showscaleaxis.isChecked(): 

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

3272 ny_annot = int( 

3273 math.floor(ymax/yinc) 

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

3275 

3276 for iy_annot in range(ny_annot): 

3277 y = ymin_annot + iy_annot*yinc 

3278 v = trace_projection(y) 

3279 line = qc.QLineF( 

3280 umax-10-uoff, v, umax-uoff, v) 

3281 

3282 p.drawLine(line) 

3283 if iy_annot == ny_annot - 1 \ 

3284 and iexp != 0: 

3285 sexp = ' &times; ' \ 

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

3287 else: 

3288 sexp = '' 

3289 

3290 snum = num_to_html(y/factor) 

3291 lab = Label( 

3292 p, 

3293 umax-20-uoff, 

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

3295 label_bg=None, 

3296 anchor='MR', 

3297 font=axannotfont, 

3298 color=c) 

3299 

3300 uoffnext = max( 

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

3302 

3303 annot_labels.append(lab) 

3304 if y == 0.: 

3305 umax_zeroline = \ 

3306 umax - 20 \ 

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

3308 - uoff 

3309 else: 

3310 if not show_boxes: 

3311 qpoints = make_QPolygonF( 

3312 [umax-20-uoff, 

3313 umax-10-uoff, 

3314 umax-10-uoff, 

3315 umax-20-uoff], 

3316 [vmax, vmax, vmin, vmin]) 

3317 p.drawPolyline(qpoints) 

3318 

3319 snum = num_to_html(ymin) 

3320 labmin = Label( 

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

3322 label_bg=None, 

3323 anchor='BR', 

3324 font=axannotfont, 

3325 color=c) 

3326 

3327 annot_labels.append(labmin) 

3328 snum = num_to_html(ymax) 

3329 labmax = Label( 

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

3331 label_bg=None, 

3332 anchor='TR', 

3333 font=axannotfont, 

3334 color=c) 

3335 

3336 annot_labels.append(labmax) 

3337 

3338 for lab in (labmin, labmax): 

3339 uoffnext = max( 

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

3341 

3342 if self.menuitem_showzeroline.isChecked(): 

3343 v = trace_projection(0.) 

3344 if vmin <= v <= vmax: 

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

3346 p.drawLine(line) 

3347 

3348 uoff = uoffnext 

3349 

3350 p.setFont(font) 

3351 p.setPen(primary_pen) 

3352 for trace in processed_traces: 

3353 if self.view_mode is not ViewMode.Wiggle: 

3354 break 

3355 

3356 if trace not in trace_to_itrack: 

3357 continue 

3358 

3359 itrack = trace_to_itrack[trace] 

3360 scaling_key = self.scaling_key(trace) 

3361 trace_projection = trace_projections[ 

3362 itrack, scaling_key] 

3363 

3364 vdata = trace_projection(trace.get_ydata()) 

3365 

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

3367 udata_max = float(self.time_projection( 

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

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

3370 

3371 qpoints = make_QPolygonF(udata, vdata) 

3372 

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

3374 vmin, vmax = trace_projection.get_out_range() 

3375 

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

3377 

3378 if self.menuitem_cliptraces.isChecked(): 

3379 p.setClipRect(trackrect) 

3380 

3381 if self.menuitem_colortraces.isChecked(): 

3382 color = pyrocko.plot.color( 

3383 color_lookup[self.color_gather(trace)]) 

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

3385 p.setPen(pen) 

3386 

3387 p.drawPolyline(qpoints) 

3388 

3389 if self.floating_marker: 

3390 self.floating_marker.draw_trace( 

3391 self, p, trace, 

3392 self.time_projection, trace_projection, 1.0) 

3393 

3394 for marker in self.markers.with_key_in( 

3395 self.tmin - self.markers_deltat_max, 

3396 self.tmax): 

3397 

3398 if marker.tmin < self.tmax \ 

3399 and self.tmin < marker.tmax \ 

3400 and marker.kind \ 

3401 in self.visible_marker_kinds: 

3402 marker.draw_trace( 

3403 self, p, trace, self.time_projection, 

3404 trace_projection, 1.0) 

3405 

3406 p.setPen(primary_pen) 

3407 

3408 if self.menuitem_cliptraces.isChecked(): 

3409 p.setClipRect(0, 0, int(w), int(h)) 

3410 

3411 if self.floating_marker: 

3412 self.floating_marker.draw( 

3413 p, self.time_projection, vcenter_projection) 

3414 

3415 self.draw_visible_markers( 

3416 p, vcenter_projection, primary_pen) 

3417 

3418 p.setPen(primary_pen) 

3419 while font.pointSize() > 2: 

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

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

3422 - self.track_to_screen(0.05) 

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

3424 if nlinesavail > 1: 

3425 break 

3426 

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

3428 

3429 p.setFont(font) 

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

3431 

3432 for key in self.track_keys: 

3433 itrack = self.key_to_row[key] 

3434 if itrack in track_projections: 

3435 plabel = ' '.join( 

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

3437 lx = 10 

3438 ly = self.track_to_screen(itrack+0.5) 

3439 

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

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

3442 continue 

3443 

3444 contains_cursor = \ 

3445 self.track_to_screen(itrack) \ 

3446 < mouse_pos.y() \ 

3447 < self.track_to_screen(itrack+1) 

3448 

3449 if not contains_cursor: 

3450 continue 

3451 

3452 font_large = p.font() 

3453 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3454 p.setFont(font_large) 

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

3456 p.setFont(font) 

3457 

3458 for lab in annot_labels: 

3459 lab.draw() 

3460 

3461 self.timer_draw.stop() 

3462 

3463 def see_data_params(self): 

3464 

3465 min_deltat = self.content_deltat_range()[0] 

3466 

3467 # determine padding and downampling requirements 

3468 if self.lowpass is not None: 

3469 deltat_target = 1./self.lowpass * 0.25 

3470 ndecimate = min( 

3471 50, 

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

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

3474 else: 

3475 ndecimate = 1 

3476 tpad = min_deltat*5. 

3477 

3478 if self.highpass is not None: 

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

3480 

3481 nsee_points_per_trace = 5000*10 

3482 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3483 

3484 return ndecimate, tpad, tsee 

3485 

3486 def clean_update(self): 

3487 self.cached_processed_traces = None 

3488 self.update() 

3489 

3490 def get_adequate_tpad(self): 

3491 tpad = 0. 

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

3493 if f is not None: 

3494 tpad = max(tpad, 1.0/f) 

3495 

3496 for snuffling in self.snufflings: 

3497 if snuffling._post_process_hook_enabled \ 

3498 or snuffling._pre_process_hook_enabled: 

3499 

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

3501 

3502 return tpad 

3503 

3504 def prepare_cutout2( 

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

3506 demean=True, nmax=6000): 

3507 

3508 if self.pile.is_empty(): 

3509 return [] 

3510 

3511 nmax = self.visible_length 

3512 

3513 self.timer_cutout.start() 

3514 

3515 tsee = tmax-tmin 

3516 min_deltat_wo_decimate = tsee/nmax 

3517 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3518 

3519 min_deltat_allow = min_deltat_wo_decimate 

3520 if self.lowpass is not None: 

3521 target_deltat_lp = 0.25/self.lowpass 

3522 if target_deltat_lp > min_deltat_wo_decimate: 

3523 min_deltat_allow = min_deltat_w_decimate 

3524 

3525 min_deltat_allow = math.exp( 

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

3527 

3528 tmin_ = tmin 

3529 tmax_ = tmax 

3530 

3531 # fetch more than needed? 

3532 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3536 

3537 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3538 lphp = self.menuitem_lphp.isChecked() 

3539 ads = self.menuitem_allowdownsampling.isChecked() 

3540 

3541 tpad = self.get_adequate_tpad() 

3542 tpad = max(tpad, tsee) 

3543 

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

3545 vec = ( 

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

3547 self.highpass, fft_filtering, lphp, 

3548 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3549 ads, self.pile.get_update_count()) 

3550 

3551 if (self.cached_vec 

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

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

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

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

3556 and self.cached_processed_traces is not None): 

3557 

3558 logger.debug('Using cached traces') 

3559 processed_traces = self.cached_processed_traces 

3560 

3561 else: 

3562 processed_traces = [] 

3563 if self.pile.deltatmax >= min_deltat_allow: 

3564 

3565 if isinstance(self.pile, pyrocko.pile.Pile): 

3566 def group_selector(gr): 

3567 return gr.deltatmax >= min_deltat_allow 

3568 

3569 kwargs = dict(group_selector=group_selector) 

3570 else: 

3571 kwargs = {} 

3572 

3573 if trace_selector is not None: 

3574 def trace_selectorx(tr): 

3575 return tr.deltat >= min_deltat_allow \ 

3576 and trace_selector(tr) 

3577 else: 

3578 def trace_selectorx(tr): 

3579 return tr.deltat >= min_deltat_allow 

3580 

3581 for traces in self.pile.chopper( 

3582 tmin=tmin, tmax=tmax, tpad=tpad, 

3583 want_incomplete=True, 

3584 degap=degap, 

3585 maxgap=gap_lap_tolerance, 

3586 maxlap=gap_lap_tolerance, 

3587 keep_current_files_open=True, 

3588 trace_selector=trace_selectorx, 

3589 accessor_id=id(self), 

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

3591 include_last=True, **kwargs): 

3592 

3593 if demean: 

3594 for tr in traces: 

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

3596 continue 

3597 y = tr.get_ydata() 

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

3599 

3600 traces = self.pre_process_hooks(traces) 

3601 

3602 for trace in traces: 

3603 

3604 if not (trace.meta 

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

3606 

3607 if fft_filtering: 

3608 but = pyrocko.response.ButterworthResponse 

3609 multres = pyrocko.response.MultiplyResponse 

3610 if self.lowpass is not None \ 

3611 or self.highpass is not None: 

3612 

3613 it = num.arange( 

3614 trace.data_len(), dtype=float) 

3615 detr_data, m, b = detrend( 

3616 it, trace.get_ydata()) 

3617 

3618 trace.set_ydata(detr_data) 

3619 

3620 freqs, fdata = trace.spectrum( 

3621 pad_to_pow2=True, tfade=None) 

3622 

3623 nfreqs = fdata.size 

3624 

3625 key = (trace.deltat, nfreqs) 

3626 

3627 if key not in self.tf_cache: 

3628 resps = [] 

3629 if self.lowpass is not None: 

3630 resps.append(but( 

3631 order=4, 

3632 corner=self.lowpass, 

3633 type='low')) 

3634 

3635 if self.highpass is not None: 

3636 resps.append(but( 

3637 order=4, 

3638 corner=self.highpass, 

3639 type='high')) 

3640 

3641 resp = multres(resps) 

3642 self.tf_cache[key] = \ 

3643 resp.evaluate(freqs) 

3644 

3645 filtered_data = num.fft.irfft( 

3646 fdata*self.tf_cache[key] 

3647 )[:trace.data_len()] 

3648 

3649 retrended_data = retrend( 

3650 it, filtered_data, m, b) 

3651 

3652 trace.set_ydata(retrended_data) 

3653 

3654 else: 

3655 

3656 if ads and self.lowpass is not None: 

3657 while trace.deltat \ 

3658 < min_deltat_wo_decimate: 

3659 

3660 trace.downsample(2, demean=False) 

3661 

3662 fmax = 0.5/trace.deltat 

3663 if not lphp and ( 

3664 self.lowpass is not None 

3665 and self.highpass is not None 

3666 and self.lowpass < fmax 

3667 and self.highpass < fmax 

3668 and self.highpass < self.lowpass): 

3669 

3670 trace.bandpass( 

3671 2, self.highpass, self.lowpass) 

3672 else: 

3673 if self.lowpass is not None: 

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

3675 trace.lowpass( 

3676 4, self.lowpass, 

3677 demean=False) 

3678 

3679 if self.highpass is not None: 

3680 if self.lowpass is None \ 

3681 or self.highpass \ 

3682 < self.lowpass: 

3683 

3684 if self.highpass < \ 

3685 0.5/trace.deltat: 

3686 trace.highpass( 

3687 4, self.highpass, 

3688 demean=False) 

3689 

3690 processed_traces.append(trace) 

3691 

3692 if self.rotate != 0.0: 

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

3694 cphi = math.cos(phi) 

3695 sphi = math.sin(phi) 

3696 for a in processed_traces: 

3697 for b in processed_traces: 

3698 if (a.network == b.network 

3699 and a.station == b.station 

3700 and a.location == b.location 

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

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

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

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

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

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

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

3708 

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

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

3711 a.set_ydata(aydata) 

3712 b.set_ydata(bydata) 

3713 

3714 processed_traces = self.post_process_hooks(processed_traces) 

3715 

3716 self.cached_processed_traces = processed_traces 

3717 self.cached_vec = vec 

3718 

3719 chopped_traces = [] 

3720 for trace in processed_traces: 

3721 chop_tmin = tmin_ - trace.deltat*4 

3722 chop_tmax = tmax_ + trace.deltat*4 

3723 

3724 try: 

3725 ctrace = trace.chop( 

3726 chop_tmin, chop_tmax, 

3727 inplace=False) 

3728 

3729 except pyrocko.trace.NoData: 

3730 continue 

3731 

3732 if ctrace.data_len() < 2: 

3733 continue 

3734 

3735 chopped_traces.append(ctrace) 

3736 

3737 self.timer_cutout.stop() 

3738 return chopped_traces 

3739 

3740 def pre_process_hooks(self, traces): 

3741 for snuffling in self.snufflings: 

3742 if snuffling._pre_process_hook_enabled: 

3743 traces = snuffling.pre_process_hook(traces) 

3744 

3745 return traces 

3746 

3747 def post_process_hooks(self, traces): 

3748 for snuffling in self.snufflings: 

3749 if snuffling._post_process_hook_enabled: 

3750 traces = snuffling.post_process_hook(traces) 

3751 

3752 return traces 

3753 

3754 def visible_length_change(self, ignore=None): 

3755 for menuitem, vlen in self.menuitems_visible_length: 

3756 if menuitem.isChecked(): 

3757 self.visible_length = vlen 

3758 

3759 def scaling_base_change(self, ignore=None): 

3760 for menuitem, scaling_base in self.menuitems_scaling_base: 

3761 if menuitem.isChecked(): 

3762 self.scaling_base = scaling_base 

3763 

3764 def scalingmode_change(self, ignore=None): 

3765 for menuitem, scaling_key in self.menuitems_scaling: 

3766 if menuitem.isChecked(): 

3767 self.scaling_key = scaling_key 

3768 self.update() 

3769 

3770 def apply_scaling_hooks(self, data_ranges): 

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

3772 hook = self.scaling_hooks[k] 

3773 hook(data_ranges) 

3774 

3775 def viewmode_change(self, ignore=True): 

3776 for item, mode in self.menuitems_viewmode: 

3777 if item.isChecked(): 

3778 self.view_mode = mode 

3779 break 

3780 else: 

3781 raise AttributeError('unknown view mode') 

3782 

3783 items_waterfall_disabled = ( 

3784 self.menuitem_showscaleaxis, 

3785 self.menuitem_showscalerange, 

3786 self.menuitem_showzeroline, 

3787 self.menuitem_colortraces, 

3788 self.menuitem_cliptraces, 

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

3790 ) 

3791 

3792 if self.view_mode is ViewMode.Waterfall: 

3793 self.parent().show_colorbar_ctrl(True) 

3794 self.parent().show_gain_ctrl(False) 

3795 

3796 for item in items_waterfall_disabled: 

3797 item.setDisabled(True) 

3798 

3799 self.visible_length = 180. 

3800 else: 

3801 self.parent().show_colorbar_ctrl(False) 

3802 self.parent().show_gain_ctrl(True) 

3803 

3804 for item in items_waterfall_disabled: 

3805 item.setDisabled(False) 

3806 

3807 self.visible_length_change() 

3808 self.update() 

3809 

3810 def set_scaling_hook(self, k, hook): 

3811 self.scaling_hooks[k] = hook 

3812 

3813 def remove_scaling_hook(self, k): 

3814 del self.scaling_hooks[k] 

3815 

3816 def remove_scaling_hooks(self): 

3817 self.scaling_hooks = {} 

3818 

3819 def s_sortingmode_change(self, ignore=None): 

3820 for menuitem, valfunc in self.menuitems_ssorting: 

3821 if menuitem.isChecked(): 

3822 self._ssort = valfunc 

3823 

3824 self.sortingmode_change() 

3825 

3826 def sortingmode_change(self, ignore=None): 

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

3828 if menuitem.isChecked(): 

3829 self.set_gathering(gather, color) 

3830 

3831 self.sortingmode_change_time = time.time() 

3832 

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

3834 self.lowpass = value 

3835 self.passband_check() 

3836 self.tf_cache = {} 

3837 self.update() 

3838 

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

3840 self.highpass = value 

3841 self.passband_check() 

3842 self.tf_cache = {} 

3843 self.update() 

3844 

3845 def passband_check(self): 

3846 if self.highpass and self.lowpass \ 

3847 and self.highpass >= self.lowpass: 

3848 

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

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

3851 'deactivate the highpass.' 

3852 

3853 self.update_status() 

3854 else: 

3855 oldmess = self.message 

3856 self.message = None 

3857 if oldmess is not None: 

3858 self.update_status() 

3859 

3860 def gain_change(self, value, ignore): 

3861 self.gain = value 

3862 self.update() 

3863 

3864 def rot_change(self, value, ignore): 

3865 self.rotate = value 

3866 self.update() 

3867 

3868 def waterfall_cmap_change(self, cmap): 

3869 self.waterfall_cmap = cmap 

3870 self.update() 

3871 

3872 def waterfall_clip_change(self, clip_min, clip_max): 

3873 self.waterfall_clip_min = clip_min 

3874 self.waterfall_clip_max = clip_max 

3875 self.update() 

3876 

3877 def waterfall_show_absolute_change(self, toggle): 

3878 self.waterfall_show_absolute = toggle 

3879 self.update() 

3880 

3881 def waterfall_set_integrate(self, toggle): 

3882 self.waterfall_integrate = toggle 

3883 self.update() 

3884 

3885 def set_selected_markers(self, markers): 

3886 ''' 

3887 Set a list of markers selected 

3888 

3889 :param markers: list of markers 

3890 ''' 

3891 self.deselect_all() 

3892 for m in markers: 

3893 m.selected = True 

3894 

3895 self.update() 

3896 

3897 def deselect_all(self): 

3898 for marker in self.markers: 

3899 marker.selected = False 

3900 

3901 def animate_picking(self): 

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

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

3904 

3905 def get_nslc_ids_for_track(self, ftrack): 

3906 itrack = int(ftrack) 

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

3908 

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

3910 if self.picking: 

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

3912 self.picking = None 

3913 self.picking_down = None 

3914 self.picking_timer.stop() 

3915 self.picking_timer = None 

3916 if not abort: 

3917 self.add_marker(self.floating_marker) 

3918 self.floating_marker.selected = True 

3919 self.emit_selected_markers() 

3920 

3921 self.floating_marker = None 

3922 

3923 def start_picking(self, ignore): 

3924 

3925 if not self.picking: 

3926 self.deselect_all() 

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

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

3929 

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

3931 self.picking.setGeometry( 

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

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

3934 

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

3936 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3938 self.floating_marker.selected = True 

3939 

3940 self.picking_timer = qc.QTimer() 

3941 self.picking_timer.timeout.connect( 

3942 self.animate_picking) 

3943 

3944 self.picking_timer.setInterval(50) 

3945 self.picking_timer.start() 

3946 

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

3948 if self.picking: 

3949 mouset = self.time_projection.rev(x) 

3950 dt = 0.0 

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

3952 if mouset < self.tmin: 

3953 dt = -(self.tmin - mouset) 

3954 else: 

3955 dt = mouset - self.tmax 

3956 ddt = self.tmax-self.tmin 

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

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

3959 

3960 x0 = x 

3961 if self.picking_down is not None: 

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

3963 

3964 w = abs(x-x0) 

3965 x0 = min(x0, x) 

3966 

3967 tmin, tmax = ( 

3968 self.time_projection.rev(x0), 

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

3970 

3971 tmin, tmax = ( 

3972 max(working_system_time_range[0], tmin), 

3973 min(working_system_time_range[1], tmax)) 

3974 

3975 p1 = self.mapToGlobal(qc.QPoint(int(round(x0)), 0)) 

3976 

3977 self.picking.setGeometry( 

3978 p1.x(), p1.y(), int(round(max(w, 1))), self.height()) 

3979 

3980 ftrack = self.track_to_screen.rev(y) 

3981 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3983 

3984 if dt != 0.0 and doshift: 

3985 self.interrupt_following() 

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

3987 

3988 self.update() 

3989 

3990 def update_status(self): 

3991 

3992 if self.message is None: 

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

3994 

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

3996 if not is_working_time(mouse_t): 

3997 return 

3998 

3999 if self.floating_marker: 

4000 tmi, tma = ( 

4001 self.floating_marker.tmin, 

4002 self.floating_marker.tmax) 

4003 

4004 tt, ms = gmtime_x(tmi) 

4005 

4006 if tmi == tma: 

4007 message = mystrftime( 

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

4009 tt=tt, milliseconds=ms) 

4010 else: 

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

4012 message = mystrftime( 

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

4014 tt=tt, milliseconds=ms) 

4015 else: 

4016 tt, ms = gmtime_x(mouse_t) 

4017 

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

4019 else: 

4020 message = self.message 

4021 

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

4023 sb.clearMessage() 

4024 sb.showMessage(message) 

4025 

4026 def set_sortingmode_change_delay_time(self, dt): 

4027 self.sortingmode_change_delay_time = dt 

4028 

4029 def sortingmode_change_delayed(self): 

4030 now = time.time() 

4031 return ( 

4032 self.sortingmode_change_delay_time is not None 

4033 and now - self.sortingmode_change_time 

4034 < self.sortingmode_change_delay_time) 

4035 

4036 def set_visible_marker_kinds(self, kinds): 

4037 self.deselect_all() 

4038 self.visible_marker_kinds = tuple(kinds) 

4039 self.emit_selected_markers() 

4040 

4041 def following(self): 

4042 return self.follow_timer is not None \ 

4043 and not self.following_interrupted() 

4044 

4045 def interrupt_following(self): 

4046 self.interactive_range_change_time = time.time() 

4047 

4048 def following_interrupted(self, now=None): 

4049 if now is None: 

4050 now = time.time() 

4051 return now - self.interactive_range_change_time \ 

4052 < self.interactive_range_change_delay_time 

4053 

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

4055 if tmax_start is None: 

4056 tmax_start = time.time() 

4057 self.show_all = False 

4058 self.follow_time = tlen 

4059 self.follow_timer = qc.QTimer(self) 

4060 self.follow_timer.timeout.connect( 

4061 self.follow_update) 

4062 self.follow_timer.setInterval(interval) 

4063 self.follow_timer.start() 

4064 self.follow_started = time.time() 

4065 self.follow_lapse = lapse 

4066 self.follow_tshift = self.follow_started - tmax_start 

4067 self.interactive_range_change_time = 0.0 

4068 

4069 def unfollow(self): 

4070 if self.follow_timer is not None: 

4071 self.follow_timer.stop() 

4072 self.follow_timer = None 

4073 self.interactive_range_change_time = 0.0 

4074 

4075 def follow_update(self): 

4076 rnow = time.time() 

4077 if self.follow_lapse is None: 

4078 now = rnow 

4079 else: 

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

4081 * self.follow_lapse 

4082 

4083 if self.following_interrupted(rnow): 

4084 return 

4085 self.set_time_range( 

4086 now-self.follow_time-self.follow_tshift, 

4087 now-self.follow_tshift) 

4088 

4089 self.update() 

4090 

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

4092 self.return_tag = return_tag 

4093 self.window().close() 

4094 

4095 def cleanup(self): 

4096 self.about_to_close.emit() 

4097 self.timer.stop() 

4098 if self.follow_timer is not None: 

4099 self.follow_timer.stop() 

4100 

4101 for snuffling in list(self.snufflings): 

4102 self.remove_snuffling(snuffling) 

4103 

4104 def set_error_message(self, key, value): 

4105 if value is None: 

4106 if key in self.error_messages: 

4107 del self.error_messages[key] 

4108 else: 

4109 self.error_messages[key] = value 

4110 

4111 def inputline_changed(self, text): 

4112 pass 

4113 

4114 def inputline_finished(self, text): 

4115 line = str(text) 

4116 

4117 toks = line.split() 

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

4119 if len(toks) >= 1: 

4120 command = toks[0].lower() 

4121 

4122 try: 

4123 quick_filter_commands = { 

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

4125 's': '*.%s.*.*', 

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

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

4128 

4129 if command in quick_filter_commands: 

4130 if len(toks) >= 2: 

4131 patterns = [ 

4132 quick_filter_commands[toks[0]] % pat 

4133 for pat in toks[1:]] 

4134 self.set_quick_filter_patterns(patterns, line) 

4135 else: 

4136 self.set_quick_filter_patterns(None) 

4137 

4138 self.update() 

4139 

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

4141 if len(toks) >= 2: 

4142 patterns = [] 

4143 if len(toks) == 2: 

4144 patterns = [toks[1]] 

4145 elif len(toks) >= 3: 

4146 x = { 

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

4148 's': '*.%s.*.*', 

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

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

4151 

4152 if toks[1] in x: 

4153 patterns.extend( 

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

4155 

4156 for pattern in patterns: 

4157 if command == 'hide': 

4158 self.add_blacklist_pattern(pattern) 

4159 else: 

4160 self.remove_blacklist_pattern(pattern) 

4161 

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

4163 self.clear_blacklist() 

4164 

4165 clearit = True 

4166 

4167 self.update() 

4168 

4169 elif command == 'markers': 

4170 if len(toks) == 2: 

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

4172 kinds = self.all_marker_kinds 

4173 else: 

4174 kinds = [] 

4175 for x in toks[1]: 

4176 try: 

4177 kinds.append(int(x)) 

4178 except Exception: 

4179 pass 

4180 

4181 self.set_visible_marker_kinds(kinds) 

4182 

4183 elif len(toks) == 1: 

4184 self.set_visible_marker_kinds(()) 

4185 

4186 self.update() 

4187 

4188 elif command == 'scaling': 

4189 if len(toks) == 2: 

4190 hideit = False 

4191 error = 'wrong number of arguments' 

4192 

4193 if len(toks) >= 3: 

4194 vmin, vmax = [ 

4195 pyrocko.model.float_or_none(x) 

4196 for x in toks[-2:]] 

4197 

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

4199 if k in d: 

4200 if vmin is not None: 

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

4202 if vmax is not None: 

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

4204 

4205 if len(toks) == 1: 

4206 self.remove_scaling_hooks() 

4207 

4208 elif len(toks) == 3: 

4209 def hook(data_ranges): 

4210 for k in data_ranges: 

4211 upd(data_ranges, k, vmin, vmax) 

4212 

4213 self.set_scaling_hook('_', hook) 

4214 

4215 elif len(toks) == 4: 

4216 pattern = toks[1] 

4217 

4218 def hook(data_ranges): 

4219 for k in pyrocko.util.match_nslcs( 

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

4221 

4222 upd(data_ranges, k, vmin, vmax) 

4223 

4224 self.set_scaling_hook(pattern, hook) 

4225 

4226 elif command == 'goto': 

4227 toks2 = line.split(None, 1) 

4228 if len(toks2) == 2: 

4229 arg = toks2[1] 

4230 m = re.match( 

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

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

4233 if m: 

4234 tlen = None 

4235 if not m.group(1): 

4236 tlen = 12*32*24*60*60 

4237 elif not m.group(2): 

4238 tlen = 32*24*60*60 

4239 elif not m.group(3): 

4240 tlen = 24*60*60 

4241 elif not m.group(4): 

4242 tlen = 60*60 

4243 elif not m.group(5): 

4244 tlen = 60 

4245 

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

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

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

4249 t = pyrocko.util.str_to_time(arg) 

4250 self.go_to_time(t, tlen=tlen) 

4251 

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

4253 supl = '00:00:00' 

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

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

4256 tmin, tmax = self.get_time_range() 

4257 sdate = pyrocko.util.time_to_str( 

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

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

4260 self.go_to_time(t) 

4261 

4262 elif arg == 'today': 

4263 self.go_to_time( 

4264 day_start( 

4265 time.time()), tlen=24*60*60) 

4266 

4267 elif arg == 'yesterday': 

4268 self.go_to_time( 

4269 day_start( 

4270 time.time()-24*60*60), tlen=24*60*60) 

4271 

4272 else: 

4273 self.go_to_event_by_name(arg) 

4274 

4275 else: 

4276 raise PileViewerMainException( 

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

4278 

4279 except PileViewerMainException as e: 

4280 error = str(e) 

4281 hideit = False 

4282 

4283 return clearit, hideit, error 

4284 

4285 return PileViewerMain 

4286 

4287 

4288PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4289GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget) 

4290 

4291 

4292class LineEditWithAbort(qw.QLineEdit): 

4293 

4294 aborted = qc.pyqtSignal() 

4295 history_down = qc.pyqtSignal() 

4296 history_up = qc.pyqtSignal() 

4297 

4298 def keyPressEvent(self, key_event): 

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

4300 self.aborted.emit() 

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

4302 self.history_down.emit() 

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

4304 self.history_up.emit() 

4305 else: 

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

4307 

4308 

4309class PileViewer(qw.QFrame): 

4310 ''' 

4311 PileViewerMain + Controls + Inputline 

4312 ''' 

4313 

4314 def __init__( 

4315 self, pile, 

4316 ntracks_shown_max=20, 

4317 marker_editor_sortable=True, 

4318 use_opengl=None, 

4319 panel_parent=None, 

4320 *args): 

4321 

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

4323 

4324 layout = qw.QGridLayout() 

4325 layout.setContentsMargins(0, 0, 0, 0) 

4326 layout.setSpacing(0) 

4327 

4328 self.menu = PileViewerMenuBar(self) 

4329 # self.menu.setNativeMenuBar(False) # put menubar into window on mac 

4330 

4331 if use_opengl is None: 

4332 use_opengl = is_macos 

4333 

4334 if use_opengl: 

4335 self.viewer = GLPileViewerMain( 

4336 pile, 

4337 ntracks_shown_max=ntracks_shown_max, 

4338 panel_parent=panel_parent, 

4339 menu=self.menu) 

4340 else: 

4341 self.viewer = PileViewerMain( 

4342 pile, 

4343 ntracks_shown_max=ntracks_shown_max, 

4344 panel_parent=panel_parent, 

4345 menu=self.menu) 

4346 

4347 self.marker_editor_sortable = marker_editor_sortable 

4348 

4349 # self.setFrameShape(qw.QFrame.StyledPanel) 

4350 # self.setFrameShadow(qw.QFrame.Sunken) 

4351 

4352 self.input_area = qw.QFrame(self) 

4353 ia_layout = qw.QGridLayout() 

4354 ia_layout.setContentsMargins(11, 11, 11, 11) 

4355 self.input_area.setLayout(ia_layout) 

4356 

4357 self.inputline = LineEditWithAbort(self.input_area) 

4358 self.inputline.returnPressed.connect( 

4359 self.inputline_returnpressed) 

4360 self.inputline.editingFinished.connect( 

4361 self.inputline_finished) 

4362 self.inputline.aborted.connect( 

4363 self.inputline_aborted) 

4364 

4365 self.inputline.history_down.connect( 

4366 lambda: self.step_through_history(1)) 

4367 self.inputline.history_up.connect( 

4368 lambda: self.step_through_history(-1)) 

4369 

4370 self.inputline.textEdited.connect( 

4371 self.inputline_changed) 

4372 

4373 self.inputline.setPlaceholderText( 

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

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

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

4377 self.input_area.hide() 

4378 self.history = None 

4379 

4380 self.inputline_error_str = None 

4381 

4382 self.inputline_error = qw.QLabel() 

4383 self.inputline_error.hide() 

4384 

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

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

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

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

4389 

4390 pb = Progressbars(self) 

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

4392 self.progressbars = pb 

4393 

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

4395 self.scrollbar = scrollbar 

4396 layout.addWidget(scrollbar, 1, 1) 

4397 self.scrollbar.valueChanged.connect( 

4398 self.scrollbar_changed) 

4399 

4400 self.block_scrollbar_changes = False 

4401 

4402 self.viewer.want_input.connect( 

4403 self.inputline_show) 

4404 self.viewer.tracks_range_changed.connect( 

4405 self.tracks_range_changed) 

4406 self.viewer.pile_has_changed_signal.connect( 

4407 self.adjust_controls) 

4408 self.viewer.about_to_close.connect( 

4409 self.save_inputline_history) 

4410 

4411 self.setLayout(layout) 

4412 

4413 def cleanup(self): 

4414 self.viewer.cleanup() 

4415 

4416 def get_progressbars(self): 

4417 return self.progressbars 

4418 

4419 def inputline_show(self): 

4420 if not self.history: 

4421 self.load_inputline_history() 

4422 

4423 self.input_area.show() 

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

4425 self.inputline.selectAll() 

4426 

4427 def inputline_set_error(self, string): 

4428 self.inputline_error_str = string 

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

4430 self.inputline.selectAll() 

4431 self.inputline_error.setText(string) 

4432 self.input_area.show() 

4433 self.inputline_error.show() 

4434 

4435 def inputline_clear_error(self): 

4436 if self.inputline_error_str: 

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

4438 self.inputline_error_str = None 

4439 self.inputline_error.clear() 

4440 self.inputline_error.hide() 

4441 

4442 def inputline_changed(self, line): 

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

4444 self.inputline_clear_error() 

4445 

4446 def inputline_returnpressed(self): 

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

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

4449 

4450 if error: 

4451 self.inputline_set_error(error) 

4452 

4453 line = line.strip() 

4454 

4455 if line != '' and not error: 

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

4457 self.history.append(line) 

4458 

4459 if clearit: 

4460 

4461 self.inputline.blockSignals(True) 

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

4463 if qpat is None: 

4464 self.inputline.clear() 

4465 else: 

4466 self.inputline.setText(qinp) 

4467 self.inputline.blockSignals(False) 

4468 

4469 if hideit and not error: 

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

4471 self.input_area.hide() 

4472 

4473 self.hist_ind = len(self.history) 

4474 

4475 def inputline_aborted(self): 

4476 ''' 

4477 Hide the input line. 

4478 ''' 

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

4480 self.hist_ind = len(self.history) 

4481 self.input_area.hide() 

4482 

4483 def save_inputline_history(self): 

4484 ''' 

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

4486 ''' 

4487 if not self.history: 

4488 return 

4489 

4490 conf = pyrocko.config 

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

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

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

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

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

4496 

4497 def load_inputline_history(self): 

4498 ''' 

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

4500 ''' 

4501 conf = pyrocko.config 

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

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

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

4505 f.write('\n') 

4506 

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

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

4509 

4510 self.hist_ind = len(self.history) 

4511 

4512 def step_through_history(self, ud=1): 

4513 ''' 

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

4515 ''' 

4516 n = len(self.history) 

4517 self.hist_ind += ud 

4518 self.hist_ind %= (n + 1) 

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

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

4521 else: 

4522 self.inputline.setText('') 

4523 

4524 def inputline_finished(self): 

4525 pass 

4526 

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

4528 if self.block_scrollbar_changes: 

4529 return 

4530 

4531 self.scrollbar.blockSignals(True) 

4532 self.scrollbar.setPageStep(ihi-ilo) 

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

4534 self.scrollbar.setRange(0, vmax) 

4535 self.scrollbar.setValue(ilo) 

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

4537 self.scrollbar.blockSignals(False) 

4538 

4539 def scrollbar_changed(self, value): 

4540 self.block_scrollbar_changes = True 

4541 ilo = value 

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

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

4544 self.block_scrollbar_changes = False 

4545 self.update_contents() 

4546 

4547 def controls(self): 

4548 frame = qw.QFrame(self) 

4549 layout = qw.QGridLayout() 

4550 frame.setLayout(layout) 

4551 

4552 minfreq = 0.001 

4553 maxfreq = 1000.0 

4554 self.lowpass_control = ValControl(high_is_none=True) 

4555 self.lowpass_control.setup( 

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

4557 self.highpass_control = ValControl(low_is_none=True) 

4558 self.highpass_control.setup( 

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

4560 self.gain_control = ValControl() 

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

4562 self.rot_control = LinValControl() 

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

4564 self.colorbar_control = ColorbarControl(self) 

4565 

4566 self.lowpass_control.valchange.connect( 

4567 self.viewer.lowpass_change) 

4568 self.highpass_control.valchange.connect( 

4569 self.viewer.highpass_change) 

4570 self.gain_control.valchange.connect( 

4571 self.viewer.gain_change) 

4572 self.rot_control.valchange.connect( 

4573 self.viewer.rot_change) 

4574 self.colorbar_control.cmap_changed.connect( 

4575 self.viewer.waterfall_cmap_change 

4576 ) 

4577 self.colorbar_control.clip_changed.connect( 

4578 self.viewer.waterfall_clip_change 

4579 ) 

4580 self.colorbar_control.show_absolute_toggled.connect( 

4581 self.viewer.waterfall_show_absolute_change 

4582 ) 

4583 self.colorbar_control.show_integrate_toggled.connect( 

4584 self.viewer.waterfall_set_integrate 

4585 ) 

4586 

4587 for icontrol, control in enumerate(( 

4588 self.highpass_control, 

4589 self.lowpass_control, 

4590 self.gain_control, 

4591 self.rot_control, 

4592 self.colorbar_control)): 

4593 

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

4595 layout.addWidget(widget, icontrol, iwidget) 

4596 

4597 spacer = qw.QSpacerItem( 

4598 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4600 

4601 self.adjust_controls() 

4602 self.viewer.viewmode_change(ViewMode.Wiggle) 

4603 return frame 

4604 

4605 def marker_editor(self): 

4606 editor = pyrocko.gui.marker_editor.MarkerEditor( 

4607 self, sortable=self.marker_editor_sortable) 

4608 

4609 editor.set_viewer(self.get_view()) 

4610 editor.get_marker_model().dataChanged.connect( 

4611 self.update_contents) 

4612 return editor 

4613 

4614 def adjust_controls(self): 

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

4616 maxfreq = 0.5/dtmin 

4617 minfreq = (0.5/dtmax)*0.0001 

4618 self.lowpass_control.set_range(minfreq, maxfreq) 

4619 self.highpass_control.set_range(minfreq, maxfreq) 

4620 

4621 def setup_snufflings(self): 

4622 self.viewer.setup_snufflings() 

4623 

4624 def get_view(self): 

4625 return self.viewer 

4626 

4627 def update_contents(self): 

4628 self.viewer.update() 

4629 

4630 def get_pile(self): 

4631 return self.viewer.get_pile() 

4632 

4633 def show_colorbar_ctrl(self, show): 

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

4635 w.setVisible(show) 

4636 

4637 def show_gain_ctrl(self, show): 

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

4639 w.setVisible(show)