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 for tick in ticks: 

564 umin = xprojection(tick) 

565 

566 umin_approx_next = xprojection(tick+inc) 

567 umax = xprojection(tick) 

568 

569 pinc_approx = umin_approx_next - umin 

570 

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

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

573 

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

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

576 if l2: 

577 l2 = None 

578 elif l1: 

579 l1 = None 

580 

581 if l0_center: 

582 ushift = (umin_approx_next-umin)/2. 

583 else: 

584 ushift = 0. 

585 

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

587 label0 = l0x 

588 rect0 = fm.boundingRect(label0) 

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

590 break 

591 

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

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

594 

595 if first_tick_with_label is None: 

596 first_tick_with_label = tick 

597 p.drawText(qc.QPointF( 

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

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

600 

601 if l1: 

602 label1 = l1 

603 rect1 = fm.boundingRect(label1) 

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

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

606 

607 p.drawText(qc.QPointF( 

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

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

610 label1) 

611 

612 l1_hits += 1 

613 

614 if l2: 

615 label2 = l2 

616 rect2 = fm.boundingRect(label2) 

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

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

619 

620 p.drawText(qc.QPointF( 

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

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

623 ticklen), label2) 

624 

625 l2_hits += 1 

626 

627 if first_tick_with_label is None: 

628 first_tick_with_label = tmin 

629 

630 l1, l2 = l1_l2_tick(first_tick_with_label, inc) 

631 

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

633 tmax - tmin < 3600*24: 

634 

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

636 if l2: 

637 l2 = None 

638 elif l1: 

639 l1 = None 

640 

641 if l1_hits == 0 and l1: 

642 label1 = l1 

643 rect1 = fm.boundingRect(label1) 

644 p.drawText(qc.QPointF( 

645 uumin+pad, 

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

647 label1) 

648 

649 l1_hits += 1 

650 

651 if l2_hits == 0 and l2: 

652 label2 = l2 

653 rect2 = fm.boundingRect(label2) 

654 p.drawText(qc.QPointF( 

655 uumin+pad, 

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

657 label2) 

658 

659 v = yprojection(0) 

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

661 

662 

663class Projection(object): 

664 def __init__(self): 

665 self.xr = 0., 1. 

666 self.ur = 0., 1. 

667 

668 def set_in_range(self, xmin, xmax): 

669 if xmax == xmin: 

670 xmax = xmin + 1. 

671 

672 self.xr = xmin, xmax 

673 

674 def get_in_range(self): 

675 return self.xr 

676 

677 def set_out_range(self, umin, umax): 

678 if umax == umin: 

679 umax = umin + 1. 

680 

681 self.ur = umin, umax 

682 

683 def get_out_range(self): 

684 return self.ur 

685 

686 def __call__(self, x): 

687 umin, umax = self.ur 

688 xmin, xmax = self.xr 

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

690 

691 def clipped(self, x, umax_pad): 

692 umin, umax = self.ur 

693 xmin, xmax = self.xr 

694 return min( 

695 umax-umax_pad, 

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

697 

698 def rev(self, u): 

699 umin, umax = self.ur 

700 xmin, xmax = self.xr 

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

702 

703 def copy(self): 

704 return copy.copy(self) 

705 

706 

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

708 group = qw.QActionGroup(menu) 

709 group.setExclusive(True) 

710 menuitems = [] 

711 

712 for name, value, *shortcut in menudef: 

713 action = menu.addAction(name) 

714 action.setCheckable(True) 

715 action.setActionGroup(group) 

716 if shortcut: 

717 action.setShortcut(shortcut[0]) 

718 

719 menuitems.append((action, value)) 

720 if default is not None and ( 

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

722 value == default): 

723 action.setChecked(True) 

724 

725 group.triggered.connect(target) 

726 

727 if default is None: 

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

729 

730 return menuitems 

731 

732 

733def sort_actions(menu): 

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

735 for action in actions: 

736 menu.removeAction(action) 

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

738 

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

740 if help_action: 

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

742 for action in actions: 

743 menu.addAction(action) 

744 

745 

746fkey_map = dict(zip( 

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

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

749 range(10))) 

750 

751 

752class PileViewerMainException(Exception): 

753 pass 

754 

755 

756class PileViewerMenuBar(qw.QMenuBar): 

757 ... 

758 

759 

760class PileViewerMenu(qw.QMenu): 

761 ... 

762 

763 

764def MakePileViewerMainClass(base): 

765 

766 class PileViewerMain(base): 

767 

768 want_input = qc.pyqtSignal() 

769 about_to_close = qc.pyqtSignal() 

770 pile_has_changed_signal = qc.pyqtSignal() 

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

772 

773 begin_markers_add = qc.pyqtSignal(int, int) 

774 end_markers_add = qc.pyqtSignal() 

775 begin_markers_remove = qc.pyqtSignal(int, int) 

776 end_markers_remove = qc.pyqtSignal() 

777 

778 marker_selection_changed = qc.pyqtSignal(list) 

779 active_event_marker_changed = qc.pyqtSignal() 

780 

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

782 menu=None): 

783 base.__init__(self, *args) 

784 

785 self.pile = pile 

786 self.ax_height = 80 

787 self.panel_parent = panel_parent 

788 

789 self.click_tolerance = 5 

790 

791 self.ntracks_shown_max = ntracks_shown_max 

792 self.initial_ntracks_shown_max = ntracks_shown_max 

793 self.ntracks = 0 

794 self.show_all = True 

795 self.shown_tracks_range = None 

796 self.track_start = None 

797 self.track_trange = None 

798 

799 self.lowpass = None 

800 self.highpass = None 

801 self.gain = 1.0 

802 self.rotate = 0.0 

803 self.picking_down = None 

804 self.picking = None 

805 self.floating_marker = None 

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

807 self.markers_deltat_max = 0. 

808 self.n_selected_markers = 0 

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

810 self.visible_marker_kinds = self.all_marker_kinds 

811 self.active_event_marker = None 

812 self.ignore_releases = 0 

813 self.message = None 

814 self.reloaded = False 

815 self.pile_has_changed = False 

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

817 

818 self.tax = TimeAx() 

819 self.setBackgroundRole(qg.QPalette.Base) 

820 self.setAutoFillBackground(True) 

821 poli = qw.QSizePolicy( 

822 qw.QSizePolicy.Expanding, 

823 qw.QSizePolicy.Expanding) 

824 

825 self.setSizePolicy(poli) 

826 self.setMinimumSize(300, 200) 

827 self.setFocusPolicy(qc.Qt.ClickFocus) 

828 

829 self.menu = menu or PileViewerMenu(self) 

830 

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

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

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

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

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

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

837 

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

839 

840 self.snufflings_menu = self.toggle_panel_menu.addMenu( 

841 'Run Snuffling') 

842 self.toggle_panel_menu.addSeparator() 

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

844 help_menu.addSeparator() 

845 

846 file_menu.addAction( 

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

848 'Open waveform files...', 

849 self.open_waveforms, 

850 qg.QKeySequence.Open) 

851 

852 file_menu.addAction( 

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

854 'Open waveform directory...', 

855 self.open_waveform_directory) 

856 

857 file_menu.addAction( 

858 'Open station files...', 

859 self.open_stations) 

860 

861 file_menu.addAction( 

862 'Open StationXML files...', 

863 self.open_stations_xml) 

864 

865 file_menu.addAction( 

866 'Open event file...', 

867 self.read_events) 

868 

869 file_menu.addSeparator() 

870 file_menu.addAction( 

871 'Open marker file...', 

872 self.read_markers) 

873 

874 file_menu.addAction( 

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

876 'Save markers...', 

877 self.write_markers, 

878 qg.QKeySequence.Save) 

879 

880 file_menu.addAction( 

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

882 'Save selected markers...', 

883 self.write_selected_markers, 

884 qg.QKeySequence.SaveAs) 

885 

886 file_menu.addSeparator() 

887 file_menu.addAction( 

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

889 'Print', 

890 self.printit, 

891 qg.QKeySequence.Print) 

892 

893 file_menu.addAction( 

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

895 'Save as SVG or PNG', 

896 self.savesvg, 

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

898 

899 file_menu.addSeparator() 

900 close = file_menu.addAction( 

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

902 'Close', 

903 self.myclose) 

904 close.setShortcuts( 

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

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

907 

908 # Scale Menu 

909 menudef = [ 

910 ('Individual Scale', 

911 lambda tr: tr.nslc_id, 

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

913 ('Common Scale', 

914 lambda tr: None, 

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

916 ('Common Scale per Station', 

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

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

919 ('Common Scale per Station Location', 

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

921 ('Common Scale per Component', 

922 lambda tr: (tr.channel)), 

923 ] 

924 

925 self.menuitems_scaling = add_radiobuttongroup( 

926 scale_menu, menudef, self.scalingmode_change, 

927 default=self.config.trace_scale) 

928 scale_menu.addSeparator() 

929 

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

931 self.scaling_hooks = {} 

932 self.scalingmode_change() 

933 

934 menudef = [ 

935 ('Scaling based on Minimum and Maximum', 

936 ('minmax', 'minmax')), 

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

938 ('minmax', 'robust')), 

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

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

941 ] 

942 

943 self.menuitems_scaling_base = add_radiobuttongroup( 

944 scale_menu, menudef, self.scaling_base_change) 

945 

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

947 scale_menu.addSeparator() 

948 

949 self.menuitem_fixscalerange = scale_menu.addAction( 

950 'Fix Scale Ranges') 

951 self.menuitem_fixscalerange.setCheckable(True) 

952 

953 # Sort Menu 

954 def sector_dist(sta): 

955 if sta.dist_m is None: 

956 return None, None 

957 else: 

958 return ( 

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

960 m_float(sta.dist_m)) 

961 

962 menudef = [ 

963 ('Sort by Names', 

964 lambda tr: (), 

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

966 ('Sort by Distance', 

967 lambda tr: self.station_attrib( 

968 tr, 

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

970 lambda tr: (None,)), 

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

972 ('Sort by Azimuth', 

973 lambda tr: self.station_attrib( 

974 tr, 

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

976 lambda tr: (None,))), 

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

978 lambda tr: self.station_attrib( 

979 tr, 

980 sector_dist, 

981 lambda tr: (None, None))), 

982 ('Sort by Backazimuth', 

983 lambda tr: self.station_attrib( 

984 tr, 

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

986 lambda tr: (None,))), 

987 ] 

988 self.menuitems_ssorting = add_radiobuttongroup( 

989 sort_menu, menudef, self.s_sortingmode_change) 

990 sort_menu.addSeparator() 

991 

992 self._ssort = lambda tr: () 

993 

994 self.menu.addSeparator() 

995 

996 menudef = [ 

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

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

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

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

1001 ((0, 1, 3, 2), 

1002 lambda tr: tr.channel)), 

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

1004 ((1, 0, 3, 2), 

1005 lambda tr: tr.channel)), 

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

1007 ((2, 0, 1, 3), 

1008 lambda tr: tr.channel)), 

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

1010 ((3, 0, 1, 2), 

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

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

1013 ((0, 1, 3), 

1014 lambda tr: tr.location)), 

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

1016 ((1, 0, 3), 

1017 lambda tr: tr.location)), 

1018 ] 

1019 

1020 self.menuitems_sorting = add_radiobuttongroup( 

1021 sort_menu, menudef, self.sortingmode_change) 

1022 

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

1024 self.config.visible_length_setting] 

1025 

1026 # View menu 

1027 self.menuitems_visible_length = add_radiobuttongroup( 

1028 view_menu, menudef, 

1029 self.visible_length_change) 

1030 view_menu.addSeparator() 

1031 

1032 view_modes = [ 

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

1034 ('Waterfall', ViewMode.Waterfall) 

1035 ] 

1036 

1037 self.menuitems_viewmode = add_radiobuttongroup( 

1038 view_menu, view_modes, 

1039 self.viewmode_change, default=ViewMode.Wiggle) 

1040 view_menu.addSeparator() 

1041 

1042 self.menuitem_cliptraces = view_menu.addAction( 

1043 'Clip Traces') 

1044 self.menuitem_cliptraces.setCheckable(True) 

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

1046 

1047 self.menuitem_showboxes = view_menu.addAction( 

1048 'Show Boxes') 

1049 self.menuitem_showboxes.setCheckable(True) 

1050 self.menuitem_showboxes.setChecked( 

1051 self.config.show_boxes) 

1052 

1053 self.menuitem_colortraces = view_menu.addAction( 

1054 'Color Traces') 

1055 self.menuitem_colortraces.setCheckable(True) 

1056 self.menuitem_antialias = view_menu.addAction( 

1057 'Antialiasing') 

1058 self.menuitem_antialias.setCheckable(True) 

1059 

1060 view_menu.addSeparator() 

1061 self.menuitem_showscalerange = view_menu.addAction( 

1062 'Show Scale Ranges') 

1063 self.menuitem_showscalerange.setCheckable(True) 

1064 self.menuitem_showscalerange.setChecked( 

1065 self.config.show_scale_ranges) 

1066 

1067 self.menuitem_showscaleaxis = view_menu.addAction( 

1068 'Show Scale Axes') 

1069 self.menuitem_showscaleaxis.setCheckable(True) 

1070 self.menuitem_showscaleaxis.setChecked( 

1071 self.config.show_scale_axes) 

1072 

1073 self.menuitem_showzeroline = view_menu.addAction( 

1074 'Show Zero Lines') 

1075 self.menuitem_showzeroline.setCheckable(True) 

1076 

1077 view_menu.addSeparator() 

1078 view_menu.addAction( 

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

1080 'Fullscreen', 

1081 self.toggle_fullscreen, 

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

1083 

1084 # Options Menu 

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

1086 self.menuitem_demean.setCheckable(True) 

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

1088 self.menuitem_demean.setShortcut( 

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

1090 

1091 self.menuitem_distances_3d = options_menu.addAction( 

1092 '3D distances', 

1093 self.distances_3d_changed) 

1094 self.menuitem_distances_3d.setCheckable(True) 

1095 

1096 self.menuitem_allowdownsampling = options_menu.addAction( 

1097 'Allow Downsampling') 

1098 self.menuitem_allowdownsampling.setCheckable(True) 

1099 self.menuitem_allowdownsampling.setChecked(True) 

1100 

1101 self.menuitem_degap = options_menu.addAction( 

1102 'Allow Degapping') 

1103 self.menuitem_degap.setCheckable(True) 

1104 self.menuitem_degap.setChecked(True) 

1105 

1106 options_menu.addSeparator() 

1107 

1108 self.menuitem_fft_filtering = options_menu.addAction( 

1109 'FFT Filtering') 

1110 self.menuitem_fft_filtering.setCheckable(True) 

1111 

1112 self.menuitem_lphp = options_menu.addAction( 

1113 'Bandpass is Low- + Highpass') 

1114 self.menuitem_lphp.setCheckable(True) 

1115 self.menuitem_lphp.setChecked(True) 

1116 

1117 options_menu.addSeparator() 

1118 self.menuitem_watch = options_menu.addAction( 

1119 'Watch Files') 

1120 self.menuitem_watch.setCheckable(True) 

1121 

1122 self.menuitem_liberal_fetch = options_menu.addAction( 

1123 'Liberal Fetch Optimization') 

1124 self.menuitem_liberal_fetch.setCheckable(True) 

1125 

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

1127 

1128 self.snufflings_menu.addAction( 

1129 'Reload Snufflings', 

1130 self.setup_snufflings) 

1131 

1132 # Disable ShadowPileTest 

1133 if False: 

1134 test_action = self.menu.addAction( 

1135 'Test', 

1136 self.toggletest) 

1137 test_action.setCheckable(True) 

1138 

1139 help_menu.addAction( 

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

1141 'Snuffler Controls', 

1142 self.help, 

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

1144 

1145 help_menu.addAction( 

1146 'About', 

1147 self.about) 

1148 

1149 self.time_projection = Projection() 

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

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

1152 

1153 self.gather = None 

1154 

1155 self.trace_filter = None 

1156 self.quick_filter = None 

1157 self.quick_filter_patterns = None, None 

1158 self.blacklist = [] 

1159 

1160 self.track_to_screen = Projection() 

1161 self.track_to_nslc_ids = {} 

1162 

1163 self.cached_vec = None 

1164 self.cached_processed_traces = None 

1165 

1166 self.timer = qc.QTimer(self) 

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

1168 self.timer.setInterval(1000) 

1169 self.timer.start() 

1170 self.pile.add_listener(self) 

1171 self.trace_styles = {} 

1172 if self.get_squirrel() is None: 

1173 self.determine_box_styles() 

1174 

1175 self.setMouseTracking(True) 

1176 

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

1178 self.snuffling_modules = {} 

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

1180 self.default_snufflings = None 

1181 self.snufflings = [] 

1182 

1183 self.stations = {} 

1184 

1185 self.timer_draw = Timer() 

1186 self.timer_cutout = Timer() 

1187 self.time_spent_painting = 0.0 

1188 self.time_last_painted = time.time() 

1189 

1190 self.interactive_range_change_time = 0.0 

1191 self.interactive_range_change_delay_time = 10.0 

1192 self.follow_timer = None 

1193 

1194 self.sortingmode_change_time = 0.0 

1195 self.sortingmode_change_delay_time = None 

1196 

1197 self.old_data_ranges = {} 

1198 

1199 self.error_messages = {} 

1200 self.return_tag = None 

1201 self.wheel_pos = 60 

1202 

1203 self.setAcceptDrops(True) 

1204 self._paths_to_load = [] 

1205 

1206 self.tf_cache = {} 

1207 

1208 self.waterfall = TraceWaterfall() 

1209 self.waterfall_cmap = 'viridis' 

1210 self.waterfall_clip_min = 0. 

1211 self.waterfall_clip_max = 1. 

1212 self.waterfall_show_absolute = False 

1213 self.waterfall_integrate = False 

1214 self.view_mode = ViewMode.Wiggle 

1215 

1216 self.automatic_updates = True 

1217 

1218 self.closing = False 

1219 self.in_paint_event = False 

1220 

1221 def fail(self, reason): 

1222 box = qw.QMessageBox(self) 

1223 box.setText(reason) 

1224 box.exec_() 

1225 

1226 def set_trace_filter(self, filter_func): 

1227 self.trace_filter = filter_func 

1228 self.sortingmode_change() 

1229 

1230 def update_trace_filter(self): 

1231 if self.blacklist: 

1232 

1233 def blacklist_func(tr): 

1234 return not pyrocko.util.match_nslc( 

1235 self.blacklist, tr.nslc_id) 

1236 

1237 else: 

1238 blacklist_func = None 

1239 

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

1241 self.set_trace_filter(None) 

1242 elif self.quick_filter is None: 

1243 self.set_trace_filter(blacklist_func) 

1244 elif blacklist_func is None: 

1245 self.set_trace_filter(self.quick_filter) 

1246 else: 

1247 self.set_trace_filter( 

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

1249 

1250 def set_quick_filter(self, filter_func): 

1251 self.quick_filter = filter_func 

1252 self.update_trace_filter() 

1253 

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

1255 if patterns is not None: 

1256 self.set_quick_filter( 

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

1258 else: 

1259 self.set_quick_filter(None) 

1260 

1261 self.quick_filter_patterns = patterns, inputline 

1262 

1263 def get_quick_filter_patterns(self): 

1264 return self.quick_filter_patterns 

1265 

1266 def add_blacklist_pattern(self, pattern): 

1267 if pattern == 'empty': 

1268 keys = set(self.pile.nslc_ids) 

1269 trs = self.pile.all( 

1270 tmin=self.tmin, 

1271 tmax=self.tmax, 

1272 load_data=False, 

1273 degap=False) 

1274 

1275 for tr in trs: 

1276 if tr.nslc_id in keys: 

1277 keys.remove(tr.nslc_id) 

1278 

1279 for key in keys: 

1280 xpattern = '.'.join(key) 

1281 if xpattern not in self.blacklist: 

1282 self.blacklist.append(xpattern) 

1283 

1284 else: 

1285 if pattern in self.blacklist: 

1286 self.blacklist.remove(pattern) 

1287 

1288 self.blacklist.append(pattern) 

1289 

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

1291 self.update_trace_filter() 

1292 

1293 def remove_blacklist_pattern(self, pattern): 

1294 if pattern in self.blacklist: 

1295 self.blacklist.remove(pattern) 

1296 else: 

1297 raise PileViewerMainException( 

1298 'Pattern not found in blacklist.') 

1299 

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

1301 self.update_trace_filter() 

1302 

1303 def clear_blacklist(self): 

1304 self.blacklist = [] 

1305 self.update_trace_filter() 

1306 

1307 def ssort(self, tr): 

1308 return self._ssort(tr) 

1309 

1310 def station_key(self, x): 

1311 return x.network, x.station 

1312 

1313 def station_keys(self, x): 

1314 return [ 

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

1316 (x.network, x.station)] 

1317 

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

1319 for sk in self.station_keys(tr): 

1320 if sk in self.stations: 

1321 station = self.stations[sk] 

1322 return getter(station) 

1323 

1324 return default_getter(tr) 

1325 

1326 def get_station(self, sk): 

1327 return self.stations[sk] 

1328 

1329 def has_station(self, station): 

1330 for sk in self.station_keys(station): 

1331 if sk in self.stations: 

1332 return True 

1333 

1334 return False 

1335 

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

1337 return self.station_attrib( 

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

1339 

1340 def set_stations(self, stations): 

1341 self.stations = {} 

1342 self.add_stations(stations) 

1343 

1344 def add_stations(self, stations): 

1345 for station in stations: 

1346 for sk in self.station_keys(station): 

1347 self.stations[sk] = station 

1348 

1349 ev = self.get_active_event() 

1350 if ev: 

1351 self.set_origin(ev) 

1352 

1353 def add_event(self, event): 

1354 marker = EventMarker(event) 

1355 self.add_marker(marker) 

1356 

1357 def add_events(self, events): 

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

1359 self.add_markers(markers) 

1360 

1361 def set_event_marker_as_origin(self, ignore=None): 

1362 selected = self.selected_markers() 

1363 if not selected: 

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

1365 return 

1366 

1367 m = selected[0] 

1368 if not isinstance(m, EventMarker): 

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

1370 return 

1371 

1372 self.set_active_event_marker(m) 

1373 

1374 def deactivate_event_marker(self): 

1375 if self.active_event_marker: 

1376 self.active_event_marker.active = False 

1377 

1378 self.active_event_marker_changed.emit() 

1379 self.active_event_marker = None 

1380 

1381 def set_active_event_marker(self, event_marker): 

1382 if self.active_event_marker: 

1383 self.active_event_marker.active = False 

1384 

1385 self.active_event_marker = event_marker 

1386 event_marker.active = True 

1387 event = event_marker.get_event() 

1388 self.set_origin(event) 

1389 self.active_event_marker_changed.emit() 

1390 

1391 def set_active_event(self, event): 

1392 for marker in self.markers: 

1393 if isinstance(marker, EventMarker): 

1394 if marker.get_event() is event: 

1395 self.set_active_event_marker(marker) 

1396 

1397 def get_active_event_marker(self): 

1398 return self.active_event_marker 

1399 

1400 def get_active_event(self): 

1401 m = self.get_active_event_marker() 

1402 if m is not None: 

1403 return m.get_event() 

1404 else: 

1405 return None 

1406 

1407 def get_active_markers(self): 

1408 emarker = self.get_active_event_marker() 

1409 if emarker is None: 

1410 return None, [] 

1411 

1412 else: 

1413 ev = emarker.get_event() 

1414 pmarkers = [ 

1415 m for m in self.markers 

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

1417 

1418 return emarker, pmarkers 

1419 

1420 def set_origin(self, location): 

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

1422 station.set_event_relative_data( 

1423 location, 

1424 distance_3d=self.menuitem_distances_3d.isChecked()) 

1425 

1426 self.sortingmode_change() 

1427 

1428 def distances_3d_changed(self): 

1429 ignore = self.menuitem_distances_3d.isChecked() 

1430 self.set_event_marker_as_origin(ignore) 

1431 

1432 def iter_snuffling_modules(self): 

1433 pjoin = os.path.join 

1434 for path in self.snuffling_paths: 

1435 

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

1437 os.mkdir(path) 

1438 

1439 for entry in os.listdir(path): 

1440 directory = path 

1441 fn = entry 

1442 d = pjoin(path, entry) 

1443 if os.path.isdir(d): 

1444 directory = d 

1445 if os.path.isfile( 

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

1447 fn = 'snuffling.py' 

1448 

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

1450 continue 

1451 

1452 name = fn[:-3] 

1453 

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

1455 self.snuffling_modules[directory, name] = \ 

1456 pyrocko.gui.snuffling.SnufflingModule( 

1457 directory, name, self) 

1458 

1459 yield self.snuffling_modules[directory, name] 

1460 

1461 def setup_snufflings(self): 

1462 # user snufflings 

1463 for mod in self.iter_snuffling_modules(): 

1464 try: 

1465 mod.load_if_needed() 

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

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

1468 

1469 # load the default snufflings on first run 

1470 if self.default_snufflings is None: 

1471 self.default_snufflings = pyrocko.gui\ 

1472 .snufflings.__snufflings__() 

1473 for snuffling in self.default_snufflings: 

1474 self.add_snuffling(snuffling) 

1475 

1476 def set_panel_parent(self, panel_parent): 

1477 self.panel_parent = panel_parent 

1478 

1479 def get_panel_parent(self): 

1480 return self.panel_parent 

1481 

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

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

1484 snuffling.init_gui( 

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

1486 self.snufflings.append(snuffling) 

1487 self.update() 

1488 

1489 def remove_snuffling(self, snuffling): 

1490 snuffling.delete_gui() 

1491 self.update() 

1492 self.snufflings.remove(snuffling) 

1493 snuffling.pre_destroy() 

1494 

1495 def add_snuffling_menuitem(self, item): 

1496 self.snufflings_menu.addAction(item) 

1497 item.setParent(self.snufflings_menu) 

1498 sort_actions(self.snufflings_menu) 

1499 

1500 def remove_snuffling_menuitem(self, item): 

1501 self.snufflings_menu.removeAction(item) 

1502 

1503 def add_snuffling_help_menuitem(self, item): 

1504 self.snuffling_help.addAction(item) 

1505 item.setParent(self.snuffling_help) 

1506 sort_actions(self.snuffling_help) 

1507 

1508 def remove_snuffling_help_menuitem(self, item): 

1509 self.snuffling_help.removeAction(item) 

1510 

1511 def add_panel_toggler(self, item): 

1512 self.toggle_panel_menu.addAction(item) 

1513 item.setParent(self.toggle_panel_menu) 

1514 sort_actions(self.toggle_panel_menu) 

1515 

1516 def remove_panel_toggler(self, item): 

1517 self.toggle_panel_menu.removeAction(item) 

1518 

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

1520 cache_dir=None, force_cache=False): 

1521 

1522 if cache_dir is None: 

1523 cache_dir = pyrocko.config.config().cache_dir 

1524 if isinstance(paths, str): 

1525 paths = [paths] 

1526 

1527 fns = pyrocko.util.select_files( 

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

1529 

1530 if not fns: 

1531 return 

1532 

1533 cache = pyrocko.pile.get_cache(cache_dir) 

1534 

1535 t = [time.time()] 

1536 

1537 def update_bar(label, value): 

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

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

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

1541 else: 

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

1543 

1544 return pbs.set_status(label, value) 

1545 

1546 def update_progress(label, i, n): 

1547 abort = False 

1548 

1549 qw.qApp.processEvents() 

1550 if n != 0: 

1551 perc = i*100/n 

1552 else: 

1553 perc = 100 

1554 abort |= update_bar(label, perc) 

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

1556 

1557 tnow = time.time() 

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

1559 self.update() 

1560 t[0] = tnow 

1561 

1562 return abort 

1563 

1564 self.automatic_updates = False 

1565 

1566 self.pile.load_files( 

1567 sorted(fns), 

1568 filename_attributes=regex, 

1569 cache=cache, 

1570 fileformat=format, 

1571 show_progress=False, 

1572 update_progress=update_progress) 

1573 

1574 self.automatic_updates = True 

1575 self.update() 

1576 

1577 def load_queued(self): 

1578 if not self._paths_to_load: 

1579 return 

1580 paths = self._paths_to_load 

1581 self._paths_to_load = [] 

1582 self.load(paths) 

1583 

1584 def load_soon(self, paths): 

1585 self._paths_to_load.extend(paths) 

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

1587 

1588 def open_waveforms(self): 

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

1590 

1591 fns, _ = qw.QFileDialog.getOpenFileNames( 

1592 self, caption, options=qfiledialog_options) 

1593 

1594 if fns: 

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

1596 

1597 def open_waveform_directory(self): 

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

1599 

1600 dn = qw.QFileDialog.getExistingDirectory( 

1601 self, caption, options=qfiledialog_options) 

1602 

1603 if dn: 

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

1605 

1606 def open_stations(self, fns=None): 

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

1608 

1609 if not fns: 

1610 fns, _ = qw.QFileDialog.getOpenFileNames( 

1611 self, caption, options=qfiledialog_options) 

1612 

1613 try: 

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

1615 for stat in stations: 

1616 self.add_stations(stat) 

1617 

1618 except Exception as e: 

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

1620 

1621 def open_stations_xml(self, fns=None): 

1622 from pyrocko.io import stationxml 

1623 

1624 caption = 'Select one or more StationXML files' 

1625 if not fns: 

1626 fns, _ = qw.QFileDialog.getOpenFileNames( 

1627 self, caption, options=qfiledialog_options, 

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

1629 ';;All files (*)') 

1630 

1631 try: 

1632 stations = [ 

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

1634 for x in fns] 

1635 

1636 for stat in stations: 

1637 self.add_stations(stat) 

1638 

1639 except Exception as e: 

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

1641 

1642 def add_traces(self, traces): 

1643 if traces: 

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

1645 self.pile.add_file(mtf) 

1646 ticket = (self.pile, mtf) 

1647 return ticket 

1648 else: 

1649 return (None, None) 

1650 

1651 def release_data(self, tickets): 

1652 for ticket in tickets: 

1653 pile, mtf = ticket 

1654 if pile is not None: 

1655 pile.remove_file(mtf) 

1656 

1657 def periodical(self): 

1658 if self.menuitem_watch.isChecked(): 

1659 if self.pile.reload_modified(): 

1660 self.update() 

1661 

1662 def get_pile(self): 

1663 return self.pile 

1664 

1665 def pile_changed(self, what): 

1666 self.pile_has_changed = True 

1667 self.pile_has_changed_signal.emit() 

1668 if self.automatic_updates: 

1669 self.update() 

1670 

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

1672 

1673 if gather is None: 

1674 def gather_func(tr): 

1675 return tr.nslc_id 

1676 

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

1678 

1679 else: 

1680 def gather_func(tr): 

1681 return ( 

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

1683 

1684 if color is None: 

1685 def color(tr): 

1686 return tr.location 

1687 

1688 self.gather = gather_func 

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

1690 

1691 self.color_gather = color 

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

1693 previous_ntracks = self.ntracks 

1694 self.set_ntracks(len(keys)) 

1695 

1696 if self.shown_tracks_range is None or \ 

1697 previous_ntracks == 0 or \ 

1698 self.show_all: 

1699 

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

1701 key_at_top = None 

1702 n = high-low 

1703 

1704 else: 

1705 low, high = self.shown_tracks_range 

1706 key_at_top = self.track_keys[low] 

1707 n = high-low 

1708 

1709 self.track_keys = sorted(keys) 

1710 

1711 track_patterns = [] 

1712 for k in self.track_keys: 

1713 pat = ['*', '*', '*', '*'] 

1714 for i, j in enumerate(gather): 

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

1716 

1717 track_patterns.append(pat) 

1718 

1719 self.track_patterns = track_patterns 

1720 

1721 if key_at_top is not None: 

1722 try: 

1723 ind = self.track_keys.index(key_at_top) 

1724 low = ind 

1725 high = low+n 

1726 except Exception: 

1727 pass 

1728 

1729 self.set_tracks_range((low, high)) 

1730 

1731 self.key_to_row = dict( 

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

1733 

1734 def inrange(x, r): 

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

1736 

1737 def trace_selector(trace): 

1738 gt = self.gather(trace) 

1739 return ( 

1740 gt in self.key_to_row and 

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

1742 

1743 self.trace_selector = lambda x: \ 

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

1745 and trace_selector(x) 

1746 

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

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

1749 self.show_all: 

1750 

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

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

1753 tlen = (tmax - tmin) 

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

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

1756 

1757 def set_time_range(self, tmin, tmax): 

1758 if tmin is None: 

1759 tmin = initial_time_range[0] 

1760 

1761 if tmax is None: 

1762 tmax = initial_time_range[1] 

1763 

1764 if tmin > tmax: 

1765 tmin, tmax = tmax, tmin 

1766 

1767 if tmin == tmax: 

1768 tmin -= 1. 

1769 tmax += 1. 

1770 

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

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

1773 

1774 min_deltat = self.content_deltat_range()[0] 

1775 if (tmax - tmin < min_deltat): 

1776 m = (tmin + tmax) / 2. 

1777 tmin = m - min_deltat/2. 

1778 tmax = m + min_deltat/2. 

1779 

1780 self.time_projection.set_in_range(tmin, tmax) 

1781 self.tmin, self.tmax = tmin, tmax 

1782 

1783 def get_time_range(self): 

1784 return self.tmin, self.tmax 

1785 

1786 def ypart(self, y): 

1787 if y < self.ax_height: 

1788 return -1 

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

1790 return 1 

1791 else: 

1792 return 0 

1793 

1794 def time_fractional_digits(self): 

1795 min_deltat = self.content_deltat_range()[0] 

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

1797 

1798 def write_markers(self, fn=None): 

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

1800 if not fn: 

1801 fn, _ = qw.QFileDialog.getSaveFileName( 

1802 self, caption, options=qfiledialog_options) 

1803 if fn: 

1804 try: 

1805 Marker.save_markers( 

1806 self.markers, fn, 

1807 fdigits=self.time_fractional_digits()) 

1808 

1809 except Exception as e: 

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

1811 

1812 def write_selected_markers(self, fn=None): 

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

1814 if not fn: 

1815 fn, _ = qw.QFileDialog.getSaveFileName( 

1816 self, caption, options=qfiledialog_options) 

1817 if fn: 

1818 try: 

1819 Marker.save_markers( 

1820 self.iter_selected_markers(), 

1821 fn, 

1822 fdigits=self.time_fractional_digits()) 

1823 

1824 except Exception as e: 

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

1826 

1827 def read_events(self, fn=None): 

1828 ''' 

1829 Open QFileDialog to open, read and add 

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

1831 representation to the pile viewer. 

1832 ''' 

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

1834 if not fn: 

1835 fn, _ = qw.QFileDialog.getOpenFileName( 

1836 self, caption, options=qfiledialog_options) 

1837 if fn: 

1838 try: 

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

1840 self.associate_phases_to_events() 

1841 

1842 except Exception as e: 

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

1844 

1845 def read_markers(self, fn=None): 

1846 ''' 

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

1848 ''' 

1849 caption = "Selet one or more marker 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_markers(Marker.load_markers(fn)) 

1856 self.associate_phases_to_events() 

1857 

1858 except Exception as e: 

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

1860 

1861 def associate_phases_to_events(self): 

1862 associate_phases_to_events(self.markers) 

1863 

1864 def add_marker(self, marker): 

1865 # need index to inform QAbstactTableModel about upcoming change, 

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

1867 self.markers.insert(marker) 

1868 i = self.markers.remove(marker) 

1869 

1870 self.begin_markers_add.emit(i, i) 

1871 self.markers.insert(marker) 

1872 self.end_markers_add.emit() 

1873 self.markers_deltat_max = max( 

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

1875 

1876 def add_markers(self, markers): 

1877 if not self.markers: 

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

1879 self.markers.insert_many(markers) 

1880 self.end_markers_add.emit() 

1881 self.update_markers_deltat_max() 

1882 else: 

1883 for marker in markers: 

1884 self.add_marker(marker) 

1885 

1886 def update_markers_deltat_max(self): 

1887 if self.markers: 

1888 self.markers_deltat_max = max( 

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

1890 

1891 def remove_marker(self, marker): 

1892 ''' 

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

1894 

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

1896 ''' 

1897 

1898 if marker is self.active_event_marker: 

1899 self.deactivate_event_marker() 

1900 

1901 try: 

1902 i = self.markers.index(marker) 

1903 self.begin_markers_remove.emit(i, i) 

1904 self.markers.remove_at(i) 

1905 self.end_markers_remove.emit() 

1906 except ValueError: 

1907 pass 

1908 

1909 def remove_markers(self, markers): 

1910 ''' 

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

1912 

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

1914 instances 

1915 ''' 

1916 

1917 if markers is self.markers: 

1918 markers = list(markers) 

1919 

1920 for marker in markers: 

1921 self.remove_marker(marker) 

1922 

1923 self.update_markers_deltat_max() 

1924 

1925 def remove_selected_markers(self): 

1926 def delete_segment(istart, iend): 

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

1928 for _ in range(iend - istart): 

1929 self.markers.remove_at(istart) 

1930 

1931 self.end_markers_remove.emit() 

1932 

1933 istart = None 

1934 ipos = 0 

1935 markers = self.markers 

1936 nmarkers = len(self.markers) 

1937 while ipos < nmarkers: 

1938 marker = markers[ipos] 

1939 if marker.is_selected(): 

1940 if marker is self.active_event_marker: 

1941 self.deactivate_event_marker() 

1942 

1943 if istart is None: 

1944 istart = ipos 

1945 else: 

1946 if istart is not None: 

1947 delete_segment(istart, ipos) 

1948 nmarkers -= ipos - istart 

1949 ipos = istart - 1 

1950 istart = None 

1951 

1952 ipos += 1 

1953 

1954 if istart is not None: 

1955 delete_segment(istart, ipos) 

1956 

1957 self.update_markers_deltat_max() 

1958 

1959 def selected_markers(self): 

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

1961 

1962 def iter_selected_markers(self): 

1963 for marker in self.markers: 

1964 if marker.is_selected(): 

1965 yield marker 

1966 

1967 def get_markers(self): 

1968 return self.markers 

1969 

1970 def mousePressEvent(self, mouse_ev): 

1971 self.show_all = False 

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

1973 

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

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

1976 if self.picking: 

1977 if self.picking_down is None: 

1978 self.picking_down = ( 

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

1980 mouse_ev.y()) 

1981 

1982 elif marker is not None: 

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

1984 self.deselect_all() 

1985 marker.selected = True 

1986 self.emit_selected_markers() 

1987 self.update() 

1988 else: 

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

1990 self.track_trange = self.tmin, self.tmax 

1991 

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

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

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

1995 self.update_status() 

1996 

1997 def mouseReleaseEvent(self, mouse_ev): 

1998 if self.ignore_releases: 

1999 self.ignore_releases -= 1 

2000 return 

2001 

2002 if self.picking: 

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

2004 self.emit_selected_markers() 

2005 

2006 if self.track_start: 

2007 self.update() 

2008 

2009 self.track_start = None 

2010 self.track_trange = None 

2011 self.update_status() 

2012 

2013 def mouseDoubleClickEvent(self, mouse_ev): 

2014 self.show_all = False 

2015 self.start_picking(None) 

2016 self.ignore_releases = 1 

2017 

2018 def mouseMoveEvent(self, mouse_ev): 

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

2020 

2021 if self.picking: 

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

2023 

2024 elif self.track_start is not None: 

2025 x0, y0 = self.track_start 

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

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

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

2029 dy = 0 

2030 

2031 tmin0, tmax0 = self.track_trange 

2032 

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

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

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

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

2037 

2038 self.interrupt_following() 

2039 self.set_time_range( 

2040 tmin0 - dt - dtr*frac, 

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

2042 

2043 self.update() 

2044 else: 

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

2046 

2047 self.update_status() 

2048 

2049 def nslc_ids_under_cursor(self, x, y): 

2050 ftrack = self.track_to_screen.rev(y) 

2051 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2052 return nslc_ids 

2053 

2054 def marker_under_cursor(self, x, y): 

2055 mouset = self.time_projection.rev(x) 

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

2057 relevant_nslc_ids = None 

2058 for marker in self.markers: 

2059 if marker.kind not in self.visible_marker_kinds: 

2060 continue 

2061 

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

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

2064 

2065 if relevant_nslc_ids is None: 

2066 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2067 

2068 marker_nslc_ids = marker.get_nslc_ids() 

2069 if not marker_nslc_ids: 

2070 return marker 

2071 

2072 for nslc_id in marker_nslc_ids: 

2073 if nslc_id in relevant_nslc_ids: 

2074 return marker 

2075 

2076 def hoovering(self, x, y): 

2077 mouset = self.time_projection.rev(x) 

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

2079 needupdate = False 

2080 haveone = False 

2081 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2082 for marker in self.markers: 

2083 if marker.kind not in self.visible_marker_kinds: 

2084 continue 

2085 

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

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

2088 

2089 if state: 

2090 xstate = False 

2091 

2092 marker_nslc_ids = marker.get_nslc_ids() 

2093 if not marker_nslc_ids: 

2094 xstate = True 

2095 

2096 for nslc in relevant_nslc_ids: 

2097 if marker.match_nslc(nslc): 

2098 xstate = True 

2099 

2100 state = xstate 

2101 

2102 if state: 

2103 haveone = True 

2104 oldstate = marker.is_alerted() 

2105 if oldstate != state: 

2106 needupdate = True 

2107 marker.set_alerted(state) 

2108 if state: 

2109 self.message = marker.hoover_message() 

2110 

2111 if not haveone: 

2112 self.message = None 

2113 

2114 if needupdate: 

2115 self.update() 

2116 

2117 def event(self, event): 

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

2119 self.keyPressEvent(event) 

2120 return True 

2121 else: 

2122 return base.event(self, event) 

2123 

2124 def keyPressEvent(self, key_event): 

2125 self.show_all = False 

2126 dt = self.tmax - self.tmin 

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

2128 

2129 key = key_event.key() 

2130 try: 

2131 keytext = str(key_event.text()) 

2132 except UnicodeEncodeError: 

2133 return 

2134 

2135 if key == qc.Qt.Key_Space: 

2136 self.interrupt_following() 

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

2138 

2139 elif key == qc.Qt.Key_Up: 

2140 for m in self.selected_markers(): 

2141 if isinstance(m, PhaseMarker): 

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

2143 p = 0 

2144 else: 

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

2146 m.set_polarity(p) 

2147 

2148 elif key == qc.Qt.Key_Down: 

2149 for m in self.selected_markers(): 

2150 if isinstance(m, PhaseMarker): 

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

2152 p = 0 

2153 else: 

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

2155 m.set_polarity(p) 

2156 

2157 elif key == qc.Qt.Key_B: 

2158 dt = self.tmax - self.tmin 

2159 self.interrupt_following() 

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

2161 

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

2163 self.interrupt_following() 

2164 

2165 tgo = None 

2166 

2167 class TraceDummy(object): 

2168 def __init__(self, marker): 

2169 self._marker = marker 

2170 

2171 @property 

2172 def nslc_id(self): 

2173 return self._marker.one_nslc() 

2174 

2175 def marker_to_itrack(marker): 

2176 try: 

2177 return self.key_to_row.get( 

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

2179 

2180 except MarkerOneNSLCRequired: 

2181 return -1 

2182 

2183 emarker, pmarkers = self.get_active_markers() 

2184 pmarkers = [ 

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

2186 pmarkers.sort(key=lambda m: ( 

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

2188 

2189 if key == qc.Qt.Key_Backtab: 

2190 pmarkers.reverse() 

2191 

2192 smarkers = self.selected_markers() 

2193 iselected = [] 

2194 for sm in smarkers: 

2195 try: 

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

2197 except ValueError: 

2198 pass 

2199 

2200 if iselected: 

2201 icurrent = max(iselected) + 1 

2202 else: 

2203 icurrent = 0 

2204 

2205 if icurrent < len(pmarkers): 

2206 self.deselect_all() 

2207 cmarker = pmarkers[icurrent] 

2208 cmarker.selected = True 

2209 tgo = cmarker.tmin 

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

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

2212 

2213 itrack = marker_to_itrack(cmarker) 

2214 if itrack != -1: 

2215 if itrack < self.shown_tracks_range[0]: 

2216 self.scroll_tracks( 

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

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

2219 self.scroll_tracks( 

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

2221 

2222 if itrack not in self.track_to_nslc_ids: 

2223 self.go_to_selection() 

2224 

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

2226 smarkers = self.selected_markers() 

2227 tgo = None 

2228 dir = str(keytext) 

2229 if smarkers: 

2230 tmid = smarkers[0].tmin 

2231 for smarker in smarkers: 

2232 if dir == 'n': 

2233 tmid = max(smarker.tmin, tmid) 

2234 else: 

2235 tmid = min(smarker.tmin, tmid) 

2236 

2237 tgo = tmid 

2238 

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

2240 for marker in sorted( 

2241 self.markers, 

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

2243 

2244 t = marker.tmin 

2245 if t > tmid and \ 

2246 marker.kind in self.visible_marker_kinds and \ 

2247 (dir == 'n' or 

2248 isinstance(marker, EventMarker)): 

2249 

2250 self.deselect_all() 

2251 marker.selected = True 

2252 tgo = t 

2253 break 

2254 else: 

2255 for marker in sorted( 

2256 self.markers, 

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

2258 reverse=True): 

2259 

2260 t = marker.tmin 

2261 if t < tmid and \ 

2262 marker.kind in self.visible_marker_kinds and \ 

2263 (dir == 'p' or 

2264 isinstance(marker, EventMarker)): 

2265 self.deselect_all() 

2266 marker.selected = True 

2267 tgo = t 

2268 break 

2269 

2270 if tgo is not None: 

2271 self.interrupt_following() 

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

2273 

2274 elif keytext == 'r': 

2275 if self.pile.reload_modified(): 

2276 self.reloaded = True 

2277 

2278 elif keytext == 'R': 

2279 self.setup_snufflings() 

2280 

2281 elif key == qc.Qt.Key_Backspace: 

2282 self.remove_selected_markers() 

2283 

2284 elif keytext == 'a': 

2285 for marker in self.markers: 

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

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

2288 marker.kind in self.visible_marker_kinds): 

2289 marker.selected = True 

2290 else: 

2291 marker.selected = False 

2292 

2293 elif keytext == 'A': 

2294 for marker in self.markers: 

2295 if marker.kind in self.visible_marker_kinds: 

2296 marker.selected = True 

2297 

2298 elif keytext == 'd': 

2299 self.deselect_all() 

2300 

2301 elif keytext == 'E': 

2302 self.deactivate_event_marker() 

2303 

2304 elif keytext == 'e': 

2305 markers = self.selected_markers() 

2306 event_markers_in_spe = [ 

2307 marker for marker in markers 

2308 if not isinstance(marker, PhaseMarker)] 

2309 

2310 phase_markers = [ 

2311 marker for marker in markers 

2312 if isinstance(marker, PhaseMarker)] 

2313 

2314 if len(event_markers_in_spe) == 1: 

2315 event_marker = event_markers_in_spe[0] 

2316 if not isinstance(event_marker, EventMarker): 

2317 nslcs = list(event_marker.nslc_ids) 

2318 lat, lon = 0.0, 0.0 

2319 old = self.get_active_event() 

2320 if len(nslcs) == 1: 

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

2322 elif old is not None: 

2323 lat, lon = old.lat, old.lon 

2324 

2325 event_marker.convert_to_event_marker(lat, lon) 

2326 

2327 self.set_active_event_marker(event_marker) 

2328 event = event_marker.get_event() 

2329 for marker in phase_markers: 

2330 marker.set_event(event) 

2331 

2332 else: 

2333 for marker in event_markers_in_spe: 

2334 marker.convert_to_event_marker() 

2335 

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

2337 for marker in self.selected_markers(): 

2338 marker.set_kind(int(keytext)) 

2339 self.emit_selected_markers() 

2340 

2341 elif key in fkey_map: 

2342 self.handle_fkeys(key) 

2343 

2344 elif key == qc.Qt.Key_Escape: 

2345 if self.picking: 

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

2347 

2348 elif key == qc.Qt.Key_PageDown: 

2349 self.scroll_tracks( 

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

2351 

2352 elif key == qc.Qt.Key_PageUp: 

2353 self.scroll_tracks( 

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

2355 

2356 elif key == qc.Qt.Key_Plus: 

2357 self.zoom_tracks(0., 1.) 

2358 

2359 elif key == qc.Qt.Key_Minus: 

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

2361 

2362 elif key == qc.Qt.Key_Equal: 

2363 ntracks_shown = self.shown_tracks_range[1] - \ 

2364 self.shown_tracks_range[0] 

2365 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2366 self.zoom_tracks(0., dtracks) 

2367 

2368 elif key == qc.Qt.Key_Colon: 

2369 self.want_input.emit() 

2370 

2371 elif keytext == 'f': 

2372 self.toggle_fullscreen() 

2373 

2374 elif keytext == 'g': 

2375 self.go_to_selection() 

2376 

2377 elif keytext == 'G': 

2378 self.go_to_selection(tight=True) 

2379 

2380 elif keytext == 'm': 

2381 self.toggle_marker_editor() 

2382 

2383 elif keytext == 'c': 

2384 self.toggle_main_controls() 

2385 

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

2387 dir = 1 

2388 amount = 1 

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

2390 dir = -1 

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

2392 amount = 10 

2393 self.nudge_selected_markers(dir*amount) 

2394 else: 

2395 super().keyPressEvent(key_event) 

2396 

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

2398 self.emit_selected_markers() 

2399 

2400 self.update() 

2401 self.update_status() 

2402 

2403 def handle_fkeys(self, key): 

2404 self.set_phase_kind( 

2405 self.selected_markers(), 

2406 fkey_map[key] + 1) 

2407 self.emit_selected_markers() 

2408 

2409 def emit_selected_markers(self): 

2410 ibounds = [] 

2411 last_selected = False 

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

2413 this_selected = marker.is_selected() 

2414 if this_selected != last_selected: 

2415 ibounds.append(imarker) 

2416 

2417 last_selected = this_selected 

2418 

2419 if last_selected: 

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

2421 

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

2423 self.n_selected_markers = sum( 

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

2425 self.marker_selection_changed.emit(chunks) 

2426 

2427 def toggle_marker_editor(self): 

2428 self.panel_parent.toggle_marker_editor() 

2429 

2430 def toggle_main_controls(self): 

2431 self.panel_parent.toggle_main_controls() 

2432 

2433 def nudge_selected_markers(self, npixels): 

2434 a, b = self.time_projection.ur 

2435 c, d = self.time_projection.xr 

2436 for marker in self.selected_markers(): 

2437 if not isinstance(marker, EventMarker): 

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

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

2440 

2441 def toggle_fullscreen(self): 

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

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

2444 self.window().showNormal() 

2445 else: 

2446 if is_macos: 

2447 self.window().showMaximized() 

2448 else: 

2449 self.window().showFullScreen() 

2450 

2451 def about(self): 

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

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

2454 txt = f.read() 

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

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

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

2458 

2459 def help(self): 

2460 class MyScrollArea(qw.QScrollArea): 

2461 

2462 def sizeHint(self): 

2463 s = qc.QSize() 

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

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

2466 return s 

2467 

2468 with open(pyrocko.util.data_file( 

2469 'snuffler_help.html')) as f: 

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

2471 

2472 with open(pyrocko.util.data_file( 

2473 'snuffler_help_epilog.html')) as f: 

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

2475 

2476 for h in [hcheat, hepilog]: 

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

2478 h.setWordWrap(True) 

2479 

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

2481 

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

2483 scroller = qw.QScrollArea() 

2484 frame = qw.QFrame(scroller) 

2485 frame.setLineWidth(0) 

2486 layout = qw.QVBoxLayout() 

2487 layout.setContentsMargins(0, 0, 0, 0) 

2488 layout.setSpacing(0) 

2489 frame.setLayout(layout) 

2490 scroller.setWidget(frame) 

2491 scroller.setWidgetResizable(True) 

2492 frame.setBackgroundRole(qg.QPalette.Base) 

2493 for h in labels: 

2494 h.setParent(frame) 

2495 h.setMargin(3) 

2496 h.setTextInteractionFlags( 

2497 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2498 h.setBackgroundRole(qg.QPalette.Base) 

2499 layout.addWidget(h) 

2500 h.linkActivated.connect( 

2501 self.open_link) 

2502 

2503 if self.panel_parent is not None: 

2504 if target == 'panel': 

2505 self.panel_parent.add_panel( 

2506 name, scroller, True, volatile=False) 

2507 else: 

2508 self.panel_parent.add_tab(name, scroller) 

2509 

2510 def open_link(self, link): 

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

2512 

2513 def wheelEvent(self, wheel_event): 

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

2515 

2516 n = self.wheel_pos // 120 

2517 self.wheel_pos = self.wheel_pos % 120 

2518 if n == 0: 

2519 return 

2520 

2521 amount = max( 

2522 1., 

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

2524 wdelta = amount * n 

2525 

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

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

2528 / (trmax-trmin) 

2529 

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

2531 self.zoom_tracks(anchor, wdelta) 

2532 else: 

2533 self.scroll_tracks(-wdelta) 

2534 

2535 def dragEnterEvent(self, event): 

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

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

2538 event.setDropAction(qc.Qt.LinkAction) 

2539 event.accept() 

2540 

2541 def dropEvent(self, event): 

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

2543 paths = list( 

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

2545 event.acceptProposedAction() 

2546 self.load(paths) 

2547 

2548 def get_phase_name(self, kind): 

2549 return self.config.get_phase_name(kind) 

2550 

2551 def set_phase_kind(self, markers, kind): 

2552 phasename = self.get_phase_name(kind) 

2553 

2554 for marker in markers: 

2555 if isinstance(marker, PhaseMarker): 

2556 if kind == 10: 

2557 marker.convert_to_marker() 

2558 else: 

2559 marker.set_phasename(phasename) 

2560 marker.set_event(self.get_active_event()) 

2561 

2562 elif isinstance(marker, EventMarker): 

2563 pass 

2564 

2565 else: 

2566 if kind != 10: 

2567 event = self.get_active_event() 

2568 marker.convert_to_phase_marker( 

2569 event, phasename, None, False) 

2570 

2571 def set_ntracks(self, ntracks): 

2572 if self.ntracks != ntracks: 

2573 self.ntracks = ntracks 

2574 if self.shown_tracks_range is not None: 

2575 l, h = self.shown_tracks_range 

2576 else: 

2577 l, h = 0, self.ntracks 

2578 

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

2580 

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

2582 

2583 low, high = range 

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

2585 high = min(self.ntracks, high) 

2586 low = max(0, low) 

2587 high = max(1, high) 

2588 

2589 if start is None: 

2590 start = float(low) 

2591 

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

2593 self.shown_tracks_range = low, high 

2594 self.shown_tracks_start = start 

2595 

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

2597 

2598 def scroll_tracks(self, shift): 

2599 shown = self.shown_tracks_range 

2600 shiftmin = -shown[0] 

2601 shiftmax = self.ntracks-shown[1] 

2602 shift = max(shiftmin, shift) 

2603 shift = min(shiftmax, shift) 

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

2605 

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

2607 

2608 self.update() 

2609 

2610 def zoom_tracks(self, anchor, delta): 

2611 ntracks_shown = self.shown_tracks_range[1] \ 

2612 - self.shown_tracks_range[0] 

2613 

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

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

2616 return 

2617 

2618 ntracks_shown += int(round(delta)) 

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

2620 

2621 u = self.shown_tracks_start 

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

2623 nv = nu + ntracks_shown 

2624 if nv > self.ntracks: 

2625 nu -= nv - self.ntracks 

2626 nv -= nv - self.ntracks 

2627 

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

2629 

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

2631 - self.shown_tracks_range[0] 

2632 

2633 self.update() 

2634 

2635 def content_time_range(self): 

2636 pile = self.get_pile() 

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

2638 if tmin is None: 

2639 tmin = initial_time_range[0] 

2640 if tmax is None: 

2641 tmax = initial_time_range[1] 

2642 

2643 return tmin, tmax 

2644 

2645 def content_deltat_range(self): 

2646 pile = self.get_pile() 

2647 

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

2649 

2650 if deltatmin is None: 

2651 deltatmin = 0.001 

2652 

2653 if deltatmax is None: 

2654 deltatmax = 1000.0 

2655 

2656 return deltatmin, deltatmax 

2657 

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

2659 if tmax < tmin: 

2660 tmin, tmax = tmax, tmin 

2661 

2662 deltatmin = self.content_deltat_range()[0] 

2663 dt = deltatmin * self.visible_length * 0.95 

2664 

2665 if dt == 0.0: 

2666 dt = 1.0 

2667 

2668 if tight: 

2669 if tmax != tmin: 

2670 dtm = tmax - tmin 

2671 tmin -= dtm*0.1 

2672 tmax += dtm*0.1 

2673 return tmin, tmax 

2674 else: 

2675 tcenter = (tmin + tmax) / 2. 

2676 tmin = tcenter - 0.5*dt 

2677 tmax = tcenter + 0.5*dt 

2678 return tmin, tmax 

2679 

2680 if tmax-tmin < dt: 

2681 vmin, vmax = self.get_time_range() 

2682 dt = min(vmax - vmin, dt) 

2683 

2684 tcenter = (tmin+tmax)/2. 

2685 etmin, etmax = tmin, tmax 

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

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

2688 dtm = tmax-tmin 

2689 if etmin == tmin: 

2690 tmin -= dtm*0.1 

2691 if etmax == tmax: 

2692 tmax += dtm*0.1 

2693 

2694 else: 

2695 dtm = tmax-tmin 

2696 tmin -= dtm*0.1 

2697 tmax += dtm*0.1 

2698 

2699 return tmin, tmax 

2700 

2701 def go_to_selection(self, tight=False): 

2702 markers = self.selected_markers() 

2703 if markers: 

2704 tmax, tmin = self.content_time_range() 

2705 for marker in markers: 

2706 tmin = min(tmin, marker.tmin) 

2707 tmax = max(tmax, marker.tmax) 

2708 

2709 else: 

2710 if tight: 

2711 vmin, vmax = self.get_time_range() 

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

2713 else: 

2714 tmin, tmax = self.content_time_range() 

2715 

2716 tmin, tmax = self.make_good_looking_time_range( 

2717 tmin, tmax, tight=tight) 

2718 

2719 self.interrupt_following() 

2720 self.set_time_range(tmin, tmax) 

2721 self.update() 

2722 

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

2724 tmax = t 

2725 if tlen is not None: 

2726 tmax = t+tlen 

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

2728 self.interrupt_following() 

2729 self.set_time_range(tmin, tmax) 

2730 self.update() 

2731 

2732 def go_to_event_by_name(self, name): 

2733 for marker in self.markers: 

2734 if isinstance(marker, EventMarker): 

2735 event = marker.get_event() 

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

2737 tmin, tmax = self.make_good_looking_time_range( 

2738 event.time, event.time) 

2739 

2740 self.interrupt_following() 

2741 self.set_time_range(tmin, tmax) 

2742 

2743 def printit(self): 

2744 from .qt_compat import qprint 

2745 printer = qprint.QPrinter() 

2746 printer.setOrientation(qprint.QPrinter.Landscape) 

2747 

2748 dialog = qprint.QPrintDialog(printer, self) 

2749 dialog.setWindowTitle('Print') 

2750 

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

2752 return 

2753 

2754 painter = qg.QPainter() 

2755 painter.begin(printer) 

2756 page = printer.pageRect() 

2757 self.drawit( 

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

2759 

2760 painter.end() 

2761 

2762 def savesvg(self, fn=None): 

2763 

2764 if not fn: 

2765 fn, _ = qw.QFileDialog.getSaveFileName( 

2766 self, 

2767 'Save as SVG|PNG', 

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

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

2770 options=qfiledialog_options) 

2771 

2772 if fn == '': 

2773 return 

2774 

2775 fn = str(fn) 

2776 

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

2778 try: 

2779 w, h = 842, 595 

2780 margin = 0.025 

2781 m = max(w, h)*margin 

2782 

2783 generator = qsvg.QSvgGenerator() 

2784 generator.setFileName(fn) 

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

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

2787 

2788 painter = qg.QPainter() 

2789 painter.begin(generator) 

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

2791 painter.end() 

2792 

2793 except Exception as e: 

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

2795 

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

2797 pixmap = self.grab() 

2798 

2799 try: 

2800 pixmap.save(fn) 

2801 

2802 except Exception as e: 

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

2804 

2805 else: 

2806 self.fail( 

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

2808 '".png".') 

2809 

2810 def paintEvent(self, paint_ev): 

2811 ''' 

2812 Called by QT whenever widget needs to be painted. 

2813 ''' 

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

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

2816 if self.in_paint_event: 

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

2818 return 

2819 

2820 self.in_paint_event = True 

2821 

2822 painter = qg.QPainter(self) 

2823 

2824 if self.menuitem_antialias.isChecked(): 

2825 painter.setRenderHint(qg.QPainter.Antialiasing) 

2826 

2827 self.drawit(painter) 

2828 

2829 logger.debug( 

2830 'Time spent drawing: ' 

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

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

2833 (self.timer_draw - self.timer_cutout)) 

2834 

2835 logger.debug( 

2836 'Time spent processing:' 

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

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

2839 self.timer_cutout.get()) 

2840 

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

2842 self.time_last_painted = time.time() 

2843 self.in_paint_event = False 

2844 

2845 def determine_box_styles(self): 

2846 

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

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

2849 istyle = 0 

2850 trace_styles = {} 

2851 for itr, tr in enumerate(traces): 

2852 if itr > 0: 

2853 other = traces[itr-1] 

2854 if not ( 

2855 other.nslc_id == tr.nslc_id 

2856 and other.deltat == tr.deltat 

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

2858 < gap_lap_tolerance*tr.deltat): 

2859 

2860 istyle += 1 

2861 

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

2863 

2864 self.trace_styles = trace_styles 

2865 

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

2867 

2868 for v_projection in track_projections.values(): 

2869 v_projection.set_in_range(0., 1.) 

2870 

2871 def selector(x): 

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

2873 

2874 if self.trace_filter is not None: 

2875 def tselector(x): 

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

2877 

2878 else: 

2879 tselector = selector 

2880 

2881 traces = list(self.pile.iter_traces( 

2882 group_selector=selector, trace_selector=tselector)) 

2883 

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

2885 

2886 def drawbox(itrack, istyle, traces): 

2887 v_projection = track_projections[itrack] 

2888 dvmin = v_projection(0.) 

2889 dvmax = v_projection(1.) 

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

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

2892 

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

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

2895 p.fillRect(rect, style.fill_brush) 

2896 p.setPen(style.frame_pen) 

2897 p.drawRect(rect) 

2898 

2899 traces_by_style = {} 

2900 for itr, tr in enumerate(traces): 

2901 gt = self.gather(tr) 

2902 if gt not in self.key_to_row: 

2903 continue 

2904 

2905 itrack = self.key_to_row[gt] 

2906 if itrack not in track_projections: 

2907 continue 

2908 

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

2910 

2911 if len(traces) < 500: 

2912 drawbox(itrack, istyle, [tr]) 

2913 else: 

2914 if (itrack, istyle) not in traces_by_style: 

2915 traces_by_style[itrack, istyle] = [] 

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

2917 

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

2919 drawbox(itrack, istyle, traces) 

2920 

2921 def draw_visible_markers( 

2922 self, p, vcenter_projection, primary_pen): 

2923 

2924 try: 

2925 markers = self.markers.with_key_in_limited( 

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

2927 

2928 except pyrocko.pile.TooMany: 

2929 tmin = self.markers[0].tmin 

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

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

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

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

2934 v0, _ = vcenter_projection.get_out_range() 

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

2936 

2937 p.save() 

2938 

2939 pen = qg.QPen(primary_pen) 

2940 pen.setWidth(2) 

2941 pen.setStyle(qc.Qt.DotLine) 

2942 # pat = [5., 3.] 

2943 # pen.setDashPattern(pat) 

2944 p.setPen(pen) 

2945 

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

2947 s_selected = ' (all selected)' 

2948 elif self.n_selected_markers > 0: 

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

2950 else: 

2951 s_selected = '' 

2952 

2953 draw_label( 

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

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

2956 label_bg, 'LB') 

2957 

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

2959 p.drawLine(line) 

2960 p.restore() 

2961 

2962 return 

2963 

2964 for marker in markers: 

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

2966 and marker.kind in self.visible_marker_kinds: 

2967 

2968 marker.draw( 

2969 p, self.time_projection, vcenter_projection, 

2970 with_label=True) 

2971 

2972 def get_squirrel(self): 

2973 try: 

2974 return self.pile._squirrel 

2975 except AttributeError: 

2976 return None 

2977 

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

2979 sq = self.get_squirrel() 

2980 if sq is None: 

2981 return 

2982 

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

2984 v_projection = track_projections[itrack] 

2985 dvmin = v_projection(0.) 

2986 dvmax = v_projection(1.) 

2987 dtmin = time_projection.clipped(tmin, 0) 

2988 dtmax = time_projection.clipped(tmax, 1) 

2989 

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

2991 p.fillRect(rect, style.fill_brush) 

2992 p.setPen(style.frame_pen) 

2993 p.drawRect(rect) 

2994 

2995 pattern_list = [] 

2996 pattern_to_itrack = {} 

2997 for key in self.track_keys: 

2998 itrack = self.key_to_row[key] 

2999 if itrack not in track_projections: 

3000 continue 

3001 

3002 pattern = self.track_patterns[itrack] 

3003 pattern_to_itrack[tuple(pattern)] = itrack 

3004 pattern_list.append(tuple(pattern)) 

3005 

3006 vmin, vmax = self.get_time_range() 

3007 

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

3009 for coverage in sq.get_coverage( 

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

3011 itrack = pattern_to_itrack[coverage.pattern.nslc] 

3012 

3013 if coverage.changes is None: 

3014 drawbox( 

3015 itrack, coverage.tmin, coverage.tmax, 

3016 box_styles_coverage[kind][0]) 

3017 else: 

3018 t = None 

3019 pcount = 0 

3020 for tb, count in coverage.changes: 

3021 if t is not None and tb > t: 

3022 if pcount > 0: 

3023 drawbox( 

3024 itrack, t, tb, 

3025 box_styles_coverage[kind][ 

3026 min(len(box_styles_coverage)-1, 

3027 pcount)]) 

3028 

3029 t = tb 

3030 pcount = count 

3031 

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

3033 ''' 

3034 This performs the actual drawing. 

3035 ''' 

3036 

3037 self.timer_draw.start() 

3038 show_boxes = self.menuitem_showboxes.isChecked() 

3039 sq = self.get_squirrel() 

3040 

3041 if self.gather is None: 

3042 self.set_gathering() 

3043 

3044 if self.pile_has_changed: 

3045 

3046 if not self.sortingmode_change_delayed(): 

3047 self.sortingmode_change() 

3048 

3049 if show_boxes and sq is None: 

3050 self.determine_box_styles() 

3051 

3052 self.pile_has_changed = False 

3053 

3054 if h is None: 

3055 h = float(self.height()) 

3056 if w is None: 

3057 w = float(self.width()) 

3058 

3059 if printmode: 

3060 primary_color = (0, 0, 0) 

3061 else: 

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

3063 

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

3065 

3066 ax_h = float(self.ax_height) 

3067 

3068 vbottom_ax_projection = Projection() 

3069 vtop_ax_projection = Projection() 

3070 vcenter_projection = Projection() 

3071 

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

3073 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3074 vtop_ax_projection.set_out_range(0., ax_h) 

3075 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3076 vcenter_projection.set_in_range(0., 1.) 

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

3078 

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

3080 track_projections = {} 

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

3082 proj = Projection() 

3083 proj.set_out_range( 

3084 self.track_to_screen(i+0.05), 

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

3086 

3087 track_projections[i] = proj 

3088 

3089 if self.tmin > self.tmax: 

3090 return 

3091 

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

3093 vbottom_ax_projection.set_in_range(0, ax_h) 

3094 

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

3096 

3097 yscaler = pyrocko.plot.AutoScaler() 

3098 

3099 p.setPen(primary_pen) 

3100 

3101 font = qg.QFont() 

3102 font.setBold(True) 

3103 

3104 axannotfont = qg.QFont() 

3105 axannotfont.setBold(True) 

3106 axannotfont.setPointSize(8) 

3107 

3108 processed_traces = self.prepare_cutout2( 

3109 self.tmin, self.tmax, 

3110 trace_selector=self.trace_selector, 

3111 degap=self.menuitem_degap.isChecked(), 

3112 demean=self.menuitem_demean.isChecked()) 

3113 

3114 if not printmode and show_boxes: 

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

3116 or (self.view_mode is ViewMode.Waterfall 

3117 and not processed_traces): 

3118 

3119 if sq is None: 

3120 self.draw_trace_boxes( 

3121 p, self.time_projection, track_projections) 

3122 

3123 else: 

3124 self.draw_coverage( 

3125 p, self.time_projection, track_projections) 

3126 

3127 p.setFont(font) 

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

3129 

3130 color_lookup = dict( 

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

3132 

3133 self.track_to_nslc_ids = {} 

3134 nticks = 0 

3135 annot_labels = [] 

3136 

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

3138 waterfall = self.waterfall 

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

3140 waterfall.set_traces(processed_traces) 

3141 waterfall.set_cmap(self.waterfall_cmap) 

3142 waterfall.set_integrate(self.waterfall_integrate) 

3143 waterfall.set_clip( 

3144 self.waterfall_clip_min, self.waterfall_clip_max) 

3145 waterfall.show_absolute_values( 

3146 self.waterfall_show_absolute) 

3147 

3148 rect = qc.QRectF( 

3149 0, self.ax_height, 

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

3151 ) 

3152 waterfall.draw_waterfall(p, rect=rect) 

3153 

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

3155 show_scales = self.menuitem_showscalerange.isChecked() \ 

3156 or self.menuitem_showscaleaxis.isChecked() 

3157 

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

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

3160 - self.track_to_screen(0.05) 

3161 

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

3163 

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

3165 if self.menuitem_showscaleaxis.isChecked() \ 

3166 else 15 

3167 

3168 yscaler = pyrocko.plot.AutoScaler( 

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

3170 snap=show_scales 

3171 and not self.menuitem_showscaleaxis.isChecked()) 

3172 

3173 data_ranges = pyrocko.trace.minmax( 

3174 processed_traces, 

3175 key=self.scaling_key, 

3176 mode=self.scaling_base[0], 

3177 outer_mode=self.scaling_base[1]) 

3178 

3179 if not self.menuitem_fixscalerange.isChecked(): 

3180 self.old_data_ranges = data_ranges 

3181 else: 

3182 data_ranges.update(self.old_data_ranges) 

3183 

3184 self.apply_scaling_hooks(data_ranges) 

3185 

3186 trace_to_itrack = {} 

3187 track_scaling_keys = {} 

3188 track_scaling_colors = {} 

3189 for trace in processed_traces: 

3190 gt = self.gather(trace) 

3191 if gt not in self.key_to_row: 

3192 continue 

3193 

3194 itrack = self.key_to_row[gt] 

3195 if itrack not in track_projections: 

3196 continue 

3197 

3198 trace_to_itrack[trace] = itrack 

3199 

3200 if itrack not in self.track_to_nslc_ids: 

3201 self.track_to_nslc_ids[itrack] = set() 

3202 

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

3204 

3205 if itrack not in track_scaling_keys: 

3206 track_scaling_keys[itrack] = set() 

3207 

3208 scaling_key = self.scaling_key(trace) 

3209 track_scaling_keys[itrack].add(scaling_key) 

3210 

3211 color = pyrocko.plot.color( 

3212 color_lookup[self.color_gather(trace)]) 

3213 

3214 k = itrack, scaling_key 

3215 if k not in track_scaling_colors \ 

3216 and self.menuitem_colortraces.isChecked(): 

3217 track_scaling_colors[k] = color 

3218 else: 

3219 track_scaling_colors[k] = primary_color 

3220 

3221 # y axes, zero lines 

3222 trace_projections = {} 

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

3224 if itrack not in track_scaling_keys: 

3225 continue 

3226 uoff = 0 

3227 for scaling_key in track_scaling_keys[itrack]: 

3228 data_range = data_ranges[scaling_key] 

3229 dymin, dymax = data_range 

3230 ymin, ymax, yinc = yscaler.make_scale( 

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

3232 iexp = yscaler.make_exp(yinc) 

3233 factor = 10**iexp 

3234 trace_projection = track_projections[itrack].copy() 

3235 trace_projection.set_in_range(ymax, ymin) 

3236 trace_projections[itrack, scaling_key] = \ 

3237 trace_projection 

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

3239 vmin, vmax = trace_projection.get_out_range() 

3240 umax_zeroline = umax 

3241 uoffnext = uoff 

3242 

3243 if show_scales: 

3244 pen = qg.QPen(primary_pen) 

3245 k = itrack, scaling_key 

3246 if k in track_scaling_colors: 

3247 c = qg.QColor(*track_scaling_colors[ 

3248 itrack, scaling_key]) 

3249 

3250 pen.setColor(c) 

3251 

3252 p.setPen(pen) 

3253 if nlinesavail > 3: 

3254 if self.menuitem_showscaleaxis.isChecked(): 

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

3256 ny_annot = int( 

3257 math.floor(ymax/yinc) 

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

3259 

3260 for iy_annot in range(ny_annot): 

3261 y = ymin_annot + iy_annot*yinc 

3262 v = trace_projection(y) 

3263 line = qc.QLineF( 

3264 umax-10-uoff, v, umax-uoff, v) 

3265 

3266 p.drawLine(line) 

3267 if iy_annot == ny_annot - 1 \ 

3268 and iexp != 0: 

3269 sexp = ' &times; ' \ 

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

3271 else: 

3272 sexp = '' 

3273 

3274 snum = num_to_html(y/factor) 

3275 lab = Label( 

3276 p, 

3277 umax-20-uoff, 

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

3279 label_bg=None, 

3280 anchor='MR', 

3281 font=axannotfont, 

3282 color=c) 

3283 

3284 uoffnext = max( 

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

3286 

3287 annot_labels.append(lab) 

3288 if y == 0.: 

3289 umax_zeroline = \ 

3290 umax - 20 \ 

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

3292 - uoff 

3293 else: 

3294 if not show_boxes: 

3295 qpoints = make_QPolygonF( 

3296 [umax-20-uoff, 

3297 umax-10-uoff, 

3298 umax-10-uoff, 

3299 umax-20-uoff], 

3300 [vmax, vmax, vmin, vmin]) 

3301 p.drawPolyline(qpoints) 

3302 

3303 snum = num_to_html(ymin) 

3304 labmin = Label( 

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

3306 label_bg=None, 

3307 anchor='BR', 

3308 font=axannotfont, 

3309 color=c) 

3310 

3311 annot_labels.append(labmin) 

3312 snum = num_to_html(ymax) 

3313 labmax = Label( 

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

3315 label_bg=None, 

3316 anchor='TR', 

3317 font=axannotfont, 

3318 color=c) 

3319 

3320 annot_labels.append(labmax) 

3321 

3322 for lab in (labmin, labmax): 

3323 uoffnext = max( 

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

3325 

3326 if self.menuitem_showzeroline.isChecked(): 

3327 v = trace_projection(0.) 

3328 if vmin <= v <= vmax: 

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

3330 p.drawLine(line) 

3331 

3332 uoff = uoffnext 

3333 

3334 p.setFont(font) 

3335 p.setPen(primary_pen) 

3336 for trace in processed_traces: 

3337 if self.view_mode is not ViewMode.Wiggle: 

3338 break 

3339 

3340 if trace not in trace_to_itrack: 

3341 continue 

3342 

3343 itrack = trace_to_itrack[trace] 

3344 scaling_key = self.scaling_key(trace) 

3345 trace_projection = trace_projections[ 

3346 itrack, scaling_key] 

3347 

3348 vdata = trace_projection(trace.get_ydata()) 

3349 

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

3351 udata_max = float(self.time_projection( 

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

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

3354 

3355 qpoints = make_QPolygonF(udata, vdata) 

3356 

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

3358 vmin, vmax = trace_projection.get_out_range() 

3359 

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

3361 

3362 if self.menuitem_cliptraces.isChecked(): 

3363 p.setClipRect(trackrect) 

3364 

3365 if self.menuitem_colortraces.isChecked(): 

3366 color = pyrocko.plot.color( 

3367 color_lookup[self.color_gather(trace)]) 

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

3369 p.setPen(pen) 

3370 

3371 p.drawPolyline(qpoints) 

3372 

3373 if self.floating_marker: 

3374 self.floating_marker.draw_trace( 

3375 self, p, trace, 

3376 self.time_projection, trace_projection, 1.0) 

3377 

3378 for marker in self.markers.with_key_in( 

3379 self.tmin - self.markers_deltat_max, 

3380 self.tmax): 

3381 

3382 if marker.tmin < self.tmax \ 

3383 and self.tmin < marker.tmax \ 

3384 and marker.kind \ 

3385 in self.visible_marker_kinds: 

3386 marker.draw_trace( 

3387 self, p, trace, self.time_projection, 

3388 trace_projection, 1.0) 

3389 

3390 p.setPen(primary_pen) 

3391 

3392 if self.menuitem_cliptraces.isChecked(): 

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

3394 

3395 if self.floating_marker: 

3396 self.floating_marker.draw( 

3397 p, self.time_projection, vcenter_projection) 

3398 

3399 self.draw_visible_markers( 

3400 p, vcenter_projection, primary_pen) 

3401 

3402 p.setPen(primary_pen) 

3403 while font.pointSize() > 2: 

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

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

3406 - self.track_to_screen(0.05) 

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

3408 if nlinesavail > 1: 

3409 break 

3410 

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

3412 

3413 p.setFont(font) 

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

3415 

3416 for key in self.track_keys: 

3417 itrack = self.key_to_row[key] 

3418 if itrack in track_projections: 

3419 plabel = ' '.join( 

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

3421 lx = 10 

3422 ly = self.track_to_screen(itrack+0.5) 

3423 

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

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

3426 continue 

3427 

3428 contains_cursor = \ 

3429 self.track_to_screen(itrack) \ 

3430 < mouse_pos.y() \ 

3431 < self.track_to_screen(itrack+1) 

3432 

3433 if not contains_cursor: 

3434 continue 

3435 

3436 font_large = p.font() 

3437 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3438 p.setFont(font_large) 

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

3440 p.setFont(font) 

3441 

3442 for lab in annot_labels: 

3443 lab.draw() 

3444 

3445 self.timer_draw.stop() 

3446 

3447 def see_data_params(self): 

3448 

3449 min_deltat = self.content_deltat_range()[0] 

3450 

3451 # determine padding and downampling requirements 

3452 if self.lowpass is not None: 

3453 deltat_target = 1./self.lowpass * 0.25 

3454 ndecimate = min( 

3455 50, 

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

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

3458 else: 

3459 ndecimate = 1 

3460 tpad = min_deltat*5. 

3461 

3462 if self.highpass is not None: 

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

3464 

3465 nsee_points_per_trace = 5000*10 

3466 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3467 

3468 return ndecimate, tpad, tsee 

3469 

3470 def clean_update(self): 

3471 self.cached_processed_traces = None 

3472 self.update() 

3473 

3474 def get_adequate_tpad(self): 

3475 tpad = 0. 

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

3477 if f is not None: 

3478 tpad = max(tpad, 1.0/f) 

3479 

3480 for snuffling in self.snufflings: 

3481 if snuffling._post_process_hook_enabled \ 

3482 or snuffling._pre_process_hook_enabled: 

3483 

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

3485 

3486 return tpad 

3487 

3488 def prepare_cutout2( 

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

3490 demean=True, nmax=6000): 

3491 

3492 if self.pile.is_empty(): 

3493 return [] 

3494 

3495 nmax = self.visible_length 

3496 

3497 self.timer_cutout.start() 

3498 

3499 tsee = tmax-tmin 

3500 min_deltat_wo_decimate = tsee/nmax 

3501 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3502 

3503 min_deltat_allow = min_deltat_wo_decimate 

3504 if self.lowpass is not None: 

3505 target_deltat_lp = 0.25/self.lowpass 

3506 if target_deltat_lp > min_deltat_wo_decimate: 

3507 min_deltat_allow = min_deltat_w_decimate 

3508 

3509 min_deltat_allow = math.exp( 

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

3511 

3512 tmin_ = tmin 

3513 tmax_ = tmax 

3514 

3515 # fetch more than needed? 

3516 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3520 

3521 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3522 lphp = self.menuitem_lphp.isChecked() 

3523 ads = self.menuitem_allowdownsampling.isChecked() 

3524 

3525 tpad = self.get_adequate_tpad() 

3526 tpad = max(tpad, tsee) 

3527 

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

3529 vec = ( 

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

3531 self.highpass, fft_filtering, lphp, 

3532 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3533 ads, self.pile.get_update_count()) 

3534 

3535 if (self.cached_vec 

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

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

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

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

3540 and self.cached_processed_traces is not None): 

3541 

3542 logger.debug('Using cached traces') 

3543 processed_traces = self.cached_processed_traces 

3544 

3545 else: 

3546 processed_traces = [] 

3547 if self.pile.deltatmax >= min_deltat_allow: 

3548 

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

3550 def group_selector(gr): 

3551 return gr.deltatmax >= min_deltat_allow 

3552 

3553 kwargs = dict(group_selector=group_selector) 

3554 else: 

3555 kwargs = {} 

3556 

3557 if trace_selector is not None: 

3558 def trace_selectorx(tr): 

3559 return tr.deltat >= min_deltat_allow \ 

3560 and trace_selector(tr) 

3561 else: 

3562 def trace_selectorx(tr): 

3563 return tr.deltat >= min_deltat_allow 

3564 

3565 for traces in self.pile.chopper( 

3566 tmin=tmin, tmax=tmax, tpad=tpad, 

3567 want_incomplete=True, 

3568 degap=degap, 

3569 maxgap=gap_lap_tolerance, 

3570 maxlap=gap_lap_tolerance, 

3571 keep_current_files_open=True, 

3572 trace_selector=trace_selectorx, 

3573 accessor_id=id(self), 

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

3575 include_last=True, **kwargs): 

3576 

3577 if demean: 

3578 for tr in traces: 

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

3580 continue 

3581 y = tr.get_ydata() 

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

3583 

3584 traces = self.pre_process_hooks(traces) 

3585 

3586 for trace in traces: 

3587 

3588 if not (trace.meta 

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

3590 

3591 if fft_filtering: 

3592 but = pyrocko.response.ButterworthResponse 

3593 multres = pyrocko.response.MultiplyResponse 

3594 if self.lowpass is not None \ 

3595 or self.highpass is not None: 

3596 

3597 it = num.arange( 

3598 trace.data_len(), dtype=float) 

3599 detr_data, m, b = detrend( 

3600 it, trace.get_ydata()) 

3601 

3602 trace.set_ydata(detr_data) 

3603 

3604 freqs, fdata = trace.spectrum( 

3605 pad_to_pow2=True, tfade=None) 

3606 

3607 nfreqs = fdata.size 

3608 

3609 key = (trace.deltat, nfreqs) 

3610 

3611 if key not in self.tf_cache: 

3612 resps = [] 

3613 if self.lowpass is not None: 

3614 resps.append(but( 

3615 order=4, 

3616 corner=self.lowpass, 

3617 type='low')) 

3618 

3619 if self.highpass is not None: 

3620 resps.append(but( 

3621 order=4, 

3622 corner=self.highpass, 

3623 type='high')) 

3624 

3625 resp = multres(resps) 

3626 self.tf_cache[key] = \ 

3627 resp.evaluate(freqs) 

3628 

3629 filtered_data = num.fft.irfft( 

3630 fdata*self.tf_cache[key] 

3631 )[:trace.data_len()] 

3632 

3633 retrended_data = retrend( 

3634 it, filtered_data, m, b) 

3635 

3636 trace.set_ydata(retrended_data) 

3637 

3638 else: 

3639 

3640 if ads and self.lowpass is not None: 

3641 while trace.deltat \ 

3642 < min_deltat_wo_decimate: 

3643 

3644 trace.downsample(2, demean=False) 

3645 

3646 fmax = 0.5/trace.deltat 

3647 if not lphp and ( 

3648 self.lowpass is not None 

3649 and self.highpass is not None 

3650 and self.lowpass < fmax 

3651 and self.highpass < fmax 

3652 and self.highpass < self.lowpass): 

3653 

3654 trace.bandpass( 

3655 2, self.highpass, self.lowpass) 

3656 else: 

3657 if self.lowpass is not None: 

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

3659 trace.lowpass( 

3660 4, self.lowpass, 

3661 demean=False) 

3662 

3663 if self.highpass is not None: 

3664 if self.lowpass is None \ 

3665 or self.highpass \ 

3666 < self.lowpass: 

3667 

3668 if self.highpass < \ 

3669 0.5/trace.deltat: 

3670 trace.highpass( 

3671 4, self.highpass, 

3672 demean=False) 

3673 

3674 processed_traces.append(trace) 

3675 

3676 if self.rotate != 0.0: 

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

3678 cphi = math.cos(phi) 

3679 sphi = math.sin(phi) 

3680 for a in processed_traces: 

3681 for b in processed_traces: 

3682 if (a.network == b.network 

3683 and a.station == b.station 

3684 and a.location == b.location 

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

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

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

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

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

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

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

3692 

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

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

3695 a.set_ydata(aydata) 

3696 b.set_ydata(bydata) 

3697 

3698 processed_traces = self.post_process_hooks(processed_traces) 

3699 

3700 self.cached_processed_traces = processed_traces 

3701 self.cached_vec = vec 

3702 

3703 chopped_traces = [] 

3704 for trace in processed_traces: 

3705 chop_tmin = tmin_ - trace.deltat*4 

3706 chop_tmax = tmax_ + trace.deltat*4 

3707 

3708 try: 

3709 ctrace = trace.chop( 

3710 chop_tmin, chop_tmax, 

3711 inplace=False) 

3712 

3713 except pyrocko.trace.NoData: 

3714 continue 

3715 

3716 if ctrace.data_len() < 2: 

3717 continue 

3718 

3719 chopped_traces.append(ctrace) 

3720 

3721 self.timer_cutout.stop() 

3722 return chopped_traces 

3723 

3724 def pre_process_hooks(self, traces): 

3725 for snuffling in self.snufflings: 

3726 if snuffling._pre_process_hook_enabled: 

3727 traces = snuffling.pre_process_hook(traces) 

3728 

3729 return traces 

3730 

3731 def post_process_hooks(self, traces): 

3732 for snuffling in self.snufflings: 

3733 if snuffling._post_process_hook_enabled: 

3734 traces = snuffling.post_process_hook(traces) 

3735 

3736 return traces 

3737 

3738 def visible_length_change(self, ignore=None): 

3739 for menuitem, vlen in self.menuitems_visible_length: 

3740 if menuitem.isChecked(): 

3741 self.visible_length = vlen 

3742 

3743 def scaling_base_change(self, ignore=None): 

3744 for menuitem, scaling_base in self.menuitems_scaling_base: 

3745 if menuitem.isChecked(): 

3746 self.scaling_base = scaling_base 

3747 

3748 def scalingmode_change(self, ignore=None): 

3749 for menuitem, scaling_key in self.menuitems_scaling: 

3750 if menuitem.isChecked(): 

3751 self.scaling_key = scaling_key 

3752 self.update() 

3753 

3754 def apply_scaling_hooks(self, data_ranges): 

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

3756 hook = self.scaling_hooks[k] 

3757 hook(data_ranges) 

3758 

3759 def viewmode_change(self, ignore=True): 

3760 for item, mode in self.menuitems_viewmode: 

3761 if item.isChecked(): 

3762 self.view_mode = mode 

3763 break 

3764 else: 

3765 raise AttributeError('unknown view mode') 

3766 

3767 items_waterfall_disabled = ( 

3768 self.menuitem_showscaleaxis, 

3769 self.menuitem_showscalerange, 

3770 self.menuitem_showzeroline, 

3771 self.menuitem_colortraces, 

3772 self.menuitem_cliptraces, 

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

3774 ) 

3775 

3776 if self.view_mode is ViewMode.Waterfall: 

3777 self.parent().show_colorbar_ctrl(True) 

3778 self.parent().show_gain_ctrl(False) 

3779 

3780 for item in items_waterfall_disabled: 

3781 item.setDisabled(True) 

3782 

3783 self.visible_length = 180. 

3784 else: 

3785 self.parent().show_colorbar_ctrl(False) 

3786 self.parent().show_gain_ctrl(True) 

3787 

3788 for item in items_waterfall_disabled: 

3789 item.setDisabled(False) 

3790 

3791 self.visible_length_change() 

3792 self.update() 

3793 

3794 def set_scaling_hook(self, k, hook): 

3795 self.scaling_hooks[k] = hook 

3796 

3797 def remove_scaling_hook(self, k): 

3798 del self.scaling_hooks[k] 

3799 

3800 def remove_scaling_hooks(self): 

3801 self.scaling_hooks = {} 

3802 

3803 def s_sortingmode_change(self, ignore=None): 

3804 for menuitem, valfunc in self.menuitems_ssorting: 

3805 if menuitem.isChecked(): 

3806 self._ssort = valfunc 

3807 

3808 self.sortingmode_change() 

3809 

3810 def sortingmode_change(self, ignore=None): 

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

3812 if menuitem.isChecked(): 

3813 self.set_gathering(gather, color) 

3814 

3815 self.sortingmode_change_time = time.time() 

3816 

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

3818 self.lowpass = value 

3819 self.passband_check() 

3820 self.tf_cache = {} 

3821 self.update() 

3822 

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

3824 self.highpass = value 

3825 self.passband_check() 

3826 self.tf_cache = {} 

3827 self.update() 

3828 

3829 def passband_check(self): 

3830 if self.highpass and self.lowpass \ 

3831 and self.highpass >= self.lowpass: 

3832 

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

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

3835 'deactivate the highpass.' 

3836 

3837 self.update_status() 

3838 else: 

3839 oldmess = self.message 

3840 self.message = None 

3841 if oldmess is not None: 

3842 self.update_status() 

3843 

3844 def gain_change(self, value, ignore): 

3845 self.gain = value 

3846 self.update() 

3847 

3848 def rot_change(self, value, ignore): 

3849 self.rotate = value 

3850 self.update() 

3851 

3852 def waterfall_cmap_change(self, cmap): 

3853 self.waterfall_cmap = cmap 

3854 self.update() 

3855 

3856 def waterfall_clip_change(self, clip_min, clip_max): 

3857 self.waterfall_clip_min = clip_min 

3858 self.waterfall_clip_max = clip_max 

3859 self.update() 

3860 

3861 def waterfall_show_absolute_change(self, toggle): 

3862 self.waterfall_show_absolute = toggle 

3863 self.update() 

3864 

3865 def waterfall_set_integrate(self, toggle): 

3866 self.waterfall_integrate = toggle 

3867 self.update() 

3868 

3869 def set_selected_markers(self, markers): 

3870 ''' 

3871 Set a list of markers selected 

3872 

3873 :param markers: list of markers 

3874 ''' 

3875 self.deselect_all() 

3876 for m in markers: 

3877 m.selected = True 

3878 

3879 self.update() 

3880 

3881 def deselect_all(self): 

3882 for marker in self.markers: 

3883 marker.selected = False 

3884 

3885 def animate_picking(self): 

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

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

3888 

3889 def get_nslc_ids_for_track(self, ftrack): 

3890 itrack = int(ftrack) 

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

3892 

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

3894 if self.picking: 

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

3896 self.picking = None 

3897 self.picking_down = None 

3898 self.picking_timer.stop() 

3899 self.picking_timer = None 

3900 if not abort: 

3901 self.add_marker(self.floating_marker) 

3902 self.floating_marker.selected = True 

3903 self.emit_selected_markers() 

3904 

3905 self.floating_marker = None 

3906 

3907 def start_picking(self, ignore): 

3908 

3909 if not self.picking: 

3910 self.deselect_all() 

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

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

3913 

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

3915 self.picking.setGeometry( 

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

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

3918 

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

3920 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3922 self.floating_marker.selected = True 

3923 

3924 self.picking_timer = qc.QTimer() 

3925 self.picking_timer.timeout.connect( 

3926 self.animate_picking) 

3927 

3928 self.picking_timer.setInterval(50) 

3929 self.picking_timer.start() 

3930 

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

3932 if self.picking: 

3933 mouset = self.time_projection.rev(x) 

3934 dt = 0.0 

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

3936 if mouset < self.tmin: 

3937 dt = -(self.tmin - mouset) 

3938 else: 

3939 dt = mouset - self.tmax 

3940 ddt = self.tmax-self.tmin 

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

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

3943 

3944 x0 = x 

3945 if self.picking_down is not None: 

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

3947 

3948 w = abs(x-x0) 

3949 x0 = min(x0, x) 

3950 

3951 tmin, tmax = ( 

3952 self.time_projection.rev(x0), 

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

3954 

3955 tmin, tmax = ( 

3956 max(working_system_time_range[0], tmin), 

3957 min(working_system_time_range[1], tmax)) 

3958 

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

3960 

3961 self.picking.setGeometry( 

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

3963 

3964 ftrack = self.track_to_screen.rev(y) 

3965 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3967 

3968 if dt != 0.0 and doshift: 

3969 self.interrupt_following() 

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

3971 

3972 self.update() 

3973 

3974 def update_status(self): 

3975 

3976 if self.message is None: 

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

3978 

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

3980 if not is_working_time(mouse_t): 

3981 return 

3982 

3983 if self.floating_marker: 

3984 tmi, tma = ( 

3985 self.floating_marker.tmin, 

3986 self.floating_marker.tmax) 

3987 

3988 tt, ms = gmtime_x(tmi) 

3989 

3990 if tmi == tma: 

3991 message = mystrftime( 

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

3993 tt=tt, milliseconds=ms) 

3994 else: 

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

3996 message = mystrftime( 

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

3998 tt=tt, milliseconds=ms) 

3999 else: 

4000 tt, ms = gmtime_x(mouse_t) 

4001 

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

4003 else: 

4004 message = self.message 

4005 

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

4007 sb.clearMessage() 

4008 sb.showMessage(message) 

4009 

4010 def set_sortingmode_change_delay_time(self, dt): 

4011 self.sortingmode_change_delay_time = dt 

4012 

4013 def sortingmode_change_delayed(self): 

4014 now = time.time() 

4015 return ( 

4016 self.sortingmode_change_delay_time is not None 

4017 and now - self.sortingmode_change_time 

4018 < self.sortingmode_change_delay_time) 

4019 

4020 def set_visible_marker_kinds(self, kinds): 

4021 self.deselect_all() 

4022 self.visible_marker_kinds = tuple(kinds) 

4023 self.emit_selected_markers() 

4024 

4025 def following(self): 

4026 return self.follow_timer is not None \ 

4027 and not self.following_interrupted() 

4028 

4029 def interrupt_following(self): 

4030 self.interactive_range_change_time = time.time() 

4031 

4032 def following_interrupted(self, now=None): 

4033 if now is None: 

4034 now = time.time() 

4035 return now - self.interactive_range_change_time \ 

4036 < self.interactive_range_change_delay_time 

4037 

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

4039 if tmax_start is None: 

4040 tmax_start = time.time() 

4041 self.show_all = False 

4042 self.follow_time = tlen 

4043 self.follow_timer = qc.QTimer(self) 

4044 self.follow_timer.timeout.connect( 

4045 self.follow_update) 

4046 self.follow_timer.setInterval(interval) 

4047 self.follow_timer.start() 

4048 self.follow_started = time.time() 

4049 self.follow_lapse = lapse 

4050 self.follow_tshift = self.follow_started - tmax_start 

4051 self.interactive_range_change_time = 0.0 

4052 

4053 def unfollow(self): 

4054 if self.follow_timer is not None: 

4055 self.follow_timer.stop() 

4056 self.follow_timer = None 

4057 self.interactive_range_change_time = 0.0 

4058 

4059 def follow_update(self): 

4060 rnow = time.time() 

4061 if self.follow_lapse is None: 

4062 now = rnow 

4063 else: 

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

4065 * self.follow_lapse 

4066 

4067 if self.following_interrupted(rnow): 

4068 return 

4069 self.set_time_range( 

4070 now-self.follow_time-self.follow_tshift, 

4071 now-self.follow_tshift) 

4072 

4073 self.update() 

4074 

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

4076 self.return_tag = return_tag 

4077 self.window().close() 

4078 

4079 def cleanup(self): 

4080 self.about_to_close.emit() 

4081 self.timer.stop() 

4082 if self.follow_timer is not None: 

4083 self.follow_timer.stop() 

4084 

4085 for snuffling in list(self.snufflings): 

4086 self.remove_snuffling(snuffling) 

4087 

4088 def set_error_message(self, key, value): 

4089 if value is None: 

4090 if key in self.error_messages: 

4091 del self.error_messages[key] 

4092 else: 

4093 self.error_messages[key] = value 

4094 

4095 def inputline_changed(self, text): 

4096 pass 

4097 

4098 def inputline_finished(self, text): 

4099 line = str(text) 

4100 

4101 toks = line.split() 

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

4103 if len(toks) >= 1: 

4104 command = toks[0].lower() 

4105 

4106 try: 

4107 quick_filter_commands = { 

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

4109 's': '*.%s.*.*', 

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

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

4112 

4113 if command in quick_filter_commands: 

4114 if len(toks) >= 2: 

4115 patterns = [ 

4116 quick_filter_commands[toks[0]] % pat 

4117 for pat in toks[1:]] 

4118 self.set_quick_filter_patterns(patterns, line) 

4119 else: 

4120 self.set_quick_filter_patterns(None) 

4121 

4122 self.update() 

4123 

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

4125 if len(toks) >= 2: 

4126 patterns = [] 

4127 if len(toks) == 2: 

4128 patterns = [toks[1]] 

4129 elif len(toks) >= 3: 

4130 x = { 

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

4132 's': '*.%s.*.*', 

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

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

4135 

4136 if toks[1] in x: 

4137 patterns.extend( 

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

4139 

4140 for pattern in patterns: 

4141 if command == 'hide': 

4142 self.add_blacklist_pattern(pattern) 

4143 else: 

4144 self.remove_blacklist_pattern(pattern) 

4145 

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

4147 self.clear_blacklist() 

4148 

4149 clearit = True 

4150 

4151 self.update() 

4152 

4153 elif command == 'markers': 

4154 if len(toks) == 2: 

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

4156 kinds = self.all_marker_kinds 

4157 else: 

4158 kinds = [] 

4159 for x in toks[1]: 

4160 try: 

4161 kinds.append(int(x)) 

4162 except Exception: 

4163 pass 

4164 

4165 self.set_visible_marker_kinds(kinds) 

4166 

4167 elif len(toks) == 1: 

4168 self.set_visible_marker_kinds(()) 

4169 

4170 self.update() 

4171 

4172 elif command == 'scaling': 

4173 if len(toks) == 2: 

4174 hideit = False 

4175 error = 'wrong number of arguments' 

4176 

4177 if len(toks) >= 3: 

4178 vmin, vmax = [ 

4179 pyrocko.model.float_or_none(x) 

4180 for x in toks[-2:]] 

4181 

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

4183 if k in d: 

4184 if vmin is not None: 

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

4186 if vmax is not None: 

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

4188 

4189 if len(toks) == 1: 

4190 self.remove_scaling_hooks() 

4191 

4192 elif len(toks) == 3: 

4193 def hook(data_ranges): 

4194 for k in data_ranges: 

4195 upd(data_ranges, k, vmin, vmax) 

4196 

4197 self.set_scaling_hook('_', hook) 

4198 

4199 elif len(toks) == 4: 

4200 pattern = toks[1] 

4201 

4202 def hook(data_ranges): 

4203 for k in pyrocko.util.match_nslcs( 

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

4205 

4206 upd(data_ranges, k, vmin, vmax) 

4207 

4208 self.set_scaling_hook(pattern, hook) 

4209 

4210 elif command == 'goto': 

4211 toks2 = line.split(None, 1) 

4212 if len(toks2) == 2: 

4213 arg = toks2[1] 

4214 m = re.match( 

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

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

4217 if m: 

4218 tlen = None 

4219 if not m.group(1): 

4220 tlen = 12*32*24*60*60 

4221 elif not m.group(2): 

4222 tlen = 32*24*60*60 

4223 elif not m.group(3): 

4224 tlen = 24*60*60 

4225 elif not m.group(4): 

4226 tlen = 60*60 

4227 elif not m.group(5): 

4228 tlen = 60 

4229 

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

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

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

4233 t = pyrocko.util.str_to_time(arg) 

4234 self.go_to_time(t, tlen=tlen) 

4235 

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

4237 supl = '00:00:00' 

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

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

4240 tmin, tmax = self.get_time_range() 

4241 sdate = pyrocko.util.time_to_str( 

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

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

4244 self.go_to_time(t) 

4245 

4246 elif arg == 'today': 

4247 self.go_to_time( 

4248 day_start( 

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

4250 

4251 elif arg == 'yesterday': 

4252 self.go_to_time( 

4253 day_start( 

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

4255 

4256 else: 

4257 self.go_to_event_by_name(arg) 

4258 

4259 else: 

4260 raise PileViewerMainException( 

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

4262 

4263 except PileViewerMainException as e: 

4264 error = str(e) 

4265 hideit = False 

4266 

4267 return clearit, hideit, error 

4268 

4269 return PileViewerMain 

4270 

4271 

4272PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4273GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget) 

4274 

4275 

4276class LineEditWithAbort(qw.QLineEdit): 

4277 

4278 aborted = qc.pyqtSignal() 

4279 history_down = qc.pyqtSignal() 

4280 history_up = qc.pyqtSignal() 

4281 

4282 def keyPressEvent(self, key_event): 

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

4284 self.aborted.emit() 

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

4286 self.history_down.emit() 

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

4288 self.history_up.emit() 

4289 else: 

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

4291 

4292 

4293class PileViewer(qw.QFrame): 

4294 ''' 

4295 PileViewerMain + Controls + Inputline 

4296 ''' 

4297 

4298 def __init__( 

4299 self, pile, 

4300 ntracks_shown_max=20, 

4301 marker_editor_sortable=True, 

4302 use_opengl=None, 

4303 panel_parent=None, 

4304 *args): 

4305 

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

4307 

4308 layout = qw.QGridLayout() 

4309 layout.setContentsMargins(0, 0, 0, 0) 

4310 layout.setSpacing(0) 

4311 

4312 self.menu = PileViewerMenuBar(self) 

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

4314 

4315 if use_opengl is None: 

4316 use_opengl = is_macos 

4317 

4318 if use_opengl: 

4319 self.viewer = GLPileViewerMain( 

4320 pile, 

4321 ntracks_shown_max=ntracks_shown_max, 

4322 panel_parent=panel_parent, 

4323 menu=self.menu) 

4324 else: 

4325 self.viewer = PileViewerMain( 

4326 pile, 

4327 ntracks_shown_max=ntracks_shown_max, 

4328 panel_parent=panel_parent, 

4329 menu=self.menu) 

4330 

4331 self.marker_editor_sortable = marker_editor_sortable 

4332 

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

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

4335 

4336 self.input_area = qw.QFrame(self) 

4337 ia_layout = qw.QGridLayout() 

4338 ia_layout.setContentsMargins(11, 11, 11, 11) 

4339 self.input_area.setLayout(ia_layout) 

4340 

4341 self.inputline = LineEditWithAbort(self.input_area) 

4342 self.inputline.returnPressed.connect( 

4343 self.inputline_returnpressed) 

4344 self.inputline.editingFinished.connect( 

4345 self.inputline_finished) 

4346 self.inputline.aborted.connect( 

4347 self.inputline_aborted) 

4348 

4349 self.inputline.history_down.connect( 

4350 lambda: self.step_through_history(1)) 

4351 self.inputline.history_up.connect( 

4352 lambda: self.step_through_history(-1)) 

4353 

4354 self.inputline.textEdited.connect( 

4355 self.inputline_changed) 

4356 

4357 self.inputline.setPlaceholderText( 

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

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

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

4361 self.input_area.hide() 

4362 self.history = None 

4363 

4364 self.inputline_error_str = None 

4365 

4366 self.inputline_error = qw.QLabel() 

4367 self.inputline_error.hide() 

4368 

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

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

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

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

4373 

4374 pb = Progressbars(self) 

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

4376 self.progressbars = pb 

4377 

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

4379 self.scrollbar = scrollbar 

4380 layout.addWidget(scrollbar, 1, 1) 

4381 self.scrollbar.valueChanged.connect( 

4382 self.scrollbar_changed) 

4383 

4384 self.block_scrollbar_changes = False 

4385 

4386 self.viewer.want_input.connect( 

4387 self.inputline_show) 

4388 self.viewer.tracks_range_changed.connect( 

4389 self.tracks_range_changed) 

4390 self.viewer.pile_has_changed_signal.connect( 

4391 self.adjust_controls) 

4392 self.viewer.about_to_close.connect( 

4393 self.save_inputline_history) 

4394 

4395 self.setLayout(layout) 

4396 

4397 def cleanup(self): 

4398 self.viewer.cleanup() 

4399 

4400 def get_progressbars(self): 

4401 return self.progressbars 

4402 

4403 def inputline_show(self): 

4404 if not self.history: 

4405 self.load_inputline_history() 

4406 

4407 self.input_area.show() 

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

4409 self.inputline.selectAll() 

4410 

4411 def inputline_set_error(self, string): 

4412 self.inputline_error_str = string 

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

4414 self.inputline.selectAll() 

4415 self.inputline_error.setText(string) 

4416 self.input_area.show() 

4417 self.inputline_error.show() 

4418 

4419 def inputline_clear_error(self): 

4420 if self.inputline_error_str: 

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

4422 self.inputline_error_str = None 

4423 self.inputline_error.clear() 

4424 self.inputline_error.hide() 

4425 

4426 def inputline_changed(self, line): 

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

4428 self.inputline_clear_error() 

4429 

4430 def inputline_returnpressed(self): 

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

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

4433 

4434 if error: 

4435 self.inputline_set_error(error) 

4436 

4437 line = line.strip() 

4438 

4439 if line != '' and not error: 

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

4441 self.history.append(line) 

4442 

4443 if clearit: 

4444 

4445 self.inputline.blockSignals(True) 

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

4447 if qpat is None: 

4448 self.inputline.clear() 

4449 else: 

4450 self.inputline.setText(qinp) 

4451 self.inputline.blockSignals(False) 

4452 

4453 if hideit and not error: 

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

4455 self.input_area.hide() 

4456 

4457 self.hist_ind = len(self.history) 

4458 

4459 def inputline_aborted(self): 

4460 ''' 

4461 Hide the input line. 

4462 ''' 

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

4464 self.hist_ind = len(self.history) 

4465 self.input_area.hide() 

4466 

4467 def save_inputline_history(self): 

4468 ''' 

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

4470 ''' 

4471 if not self.history: 

4472 return 

4473 

4474 conf = pyrocko.config 

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

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

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

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

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

4480 

4481 def load_inputline_history(self): 

4482 ''' 

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

4484 ''' 

4485 conf = pyrocko.config 

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

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

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

4489 f.write('\n') 

4490 

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

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

4493 

4494 self.hist_ind = len(self.history) 

4495 

4496 def step_through_history(self, ud=1): 

4497 ''' 

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

4499 ''' 

4500 n = len(self.history) 

4501 self.hist_ind += ud 

4502 self.hist_ind %= (n + 1) 

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

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

4505 else: 

4506 self.inputline.setText('') 

4507 

4508 def inputline_finished(self): 

4509 pass 

4510 

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

4512 if self.block_scrollbar_changes: 

4513 return 

4514 

4515 self.scrollbar.blockSignals(True) 

4516 self.scrollbar.setPageStep(ihi-ilo) 

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

4518 self.scrollbar.setRange(0, vmax) 

4519 self.scrollbar.setValue(ilo) 

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

4521 self.scrollbar.blockSignals(False) 

4522 

4523 def scrollbar_changed(self, value): 

4524 self.block_scrollbar_changes = True 

4525 ilo = value 

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

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

4528 self.block_scrollbar_changes = False 

4529 self.update_contents() 

4530 

4531 def controls(self): 

4532 frame = qw.QFrame(self) 

4533 layout = qw.QGridLayout() 

4534 frame.setLayout(layout) 

4535 

4536 minfreq = 0.001 

4537 maxfreq = 1000.0 

4538 self.lowpass_control = ValControl(high_is_none=True) 

4539 self.lowpass_control.setup( 

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

4541 self.highpass_control = ValControl(low_is_none=True) 

4542 self.highpass_control.setup( 

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

4544 self.gain_control = ValControl() 

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

4546 self.rot_control = LinValControl() 

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

4548 self.colorbar_control = ColorbarControl(self) 

4549 

4550 self.lowpass_control.valchange.connect( 

4551 self.viewer.lowpass_change) 

4552 self.highpass_control.valchange.connect( 

4553 self.viewer.highpass_change) 

4554 self.gain_control.valchange.connect( 

4555 self.viewer.gain_change) 

4556 self.rot_control.valchange.connect( 

4557 self.viewer.rot_change) 

4558 self.colorbar_control.cmap_changed.connect( 

4559 self.viewer.waterfall_cmap_change 

4560 ) 

4561 self.colorbar_control.clip_changed.connect( 

4562 self.viewer.waterfall_clip_change 

4563 ) 

4564 self.colorbar_control.show_absolute_toggled.connect( 

4565 self.viewer.waterfall_show_absolute_change 

4566 ) 

4567 self.colorbar_control.show_integrate_toggled.connect( 

4568 self.viewer.waterfall_set_integrate 

4569 ) 

4570 

4571 for icontrol, control in enumerate(( 

4572 self.highpass_control, 

4573 self.lowpass_control, 

4574 self.gain_control, 

4575 self.rot_control, 

4576 self.colorbar_control)): 

4577 

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

4579 layout.addWidget(widget, icontrol, iwidget) 

4580 

4581 spacer = qw.QSpacerItem( 

4582 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4584 

4585 self.adjust_controls() 

4586 self.viewer.viewmode_change(ViewMode.Wiggle) 

4587 return frame 

4588 

4589 def marker_editor(self): 

4590 editor = pyrocko.gui.marker_editor.MarkerEditor( 

4591 self, sortable=self.marker_editor_sortable) 

4592 

4593 editor.set_viewer(self.get_view()) 

4594 editor.get_marker_model().dataChanged.connect( 

4595 self.update_contents) 

4596 return editor 

4597 

4598 def adjust_controls(self): 

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

4600 maxfreq = 0.5/dtmin 

4601 minfreq = (0.5/dtmax)*0.001 

4602 self.lowpass_control.set_range(minfreq, maxfreq) 

4603 self.highpass_control.set_range(minfreq, maxfreq) 

4604 

4605 def setup_snufflings(self): 

4606 self.viewer.setup_snufflings() 

4607 

4608 def get_view(self): 

4609 return self.viewer 

4610 

4611 def update_contents(self): 

4612 self.viewer.update() 

4613 

4614 def get_pile(self): 

4615 return self.viewer.get_pile() 

4616 

4617 def show_colorbar_ctrl(self, show): 

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

4619 w.setVisible(show) 

4620 

4621 def show_gain_ctrl(self, show): 

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

4623 w.setVisible(show)