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.gui.snuffling 

27import pyrocko.gui.snufflings 

28import pyrocko.gui.marker_editor 

29 

30from pyrocko.util import hpfloat, gmtime_x, mystrftime 

31 

32from .marker import associate_phases_to_events, MarkerOneNSLCRequired 

33 

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

35 PhaseMarker, make_QPolygonF, draw_label, Label, 

36 Progressbars, ColorbarControl) 

37 

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

39 

40from .pile_viewer_waterfall import TraceWaterfall 

41 

42import scipy.stats as sstats 

43import platform 

44 

45MIN_LABEL_SIZE_PT = 6 

46 

47 

48qc.QString = str 

49 

50qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \ 

51 qw.QFileDialog.DontUseSheet 

52 

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

54 

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

56 

57 

58def detrend(x, y): 

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

60 y_detrended = y - slope * x - offset 

61 return y_detrended, slope, offset 

62 

63 

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

65 return x * slope + y_detrended + offset 

66 

67 

68class Global(object): 

69 appOnDemand = None 

70 

71 

72class NSLC(object): 

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

74 self.network = n 

75 self.station = s 

76 self.location = l 

77 self.channel = c 

78 

79 

80class m_float(float): 

81 

82 def __str__(self): 

83 if abs(self) >= 10000.: 

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

85 elif abs(self) >= 1000.: 

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

87 else: 

88 return '%.5g m' % self 

89 

90 def __lt__(self, other): 

91 if other is None: 

92 return True 

93 return float(self) < float(other) 

94 

95 def __gt__(self, other): 

96 if other is None: 

97 return False 

98 return float(self) > float(other) 

99 

100 

101def m_float_or_none(x): 

102 if x is None: 

103 return None 

104 else: 

105 return m_float(x) 

106 

107 

108def make_chunks(items): 

109 ''' 

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

111 ''' 

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

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

114 

115 

116class deg_float(float): 

117 

118 def __str__(self): 

119 return '%4.0f' % self 

120 

121 def __lt__(self, other): 

122 if other is None: 

123 return True 

124 return float(self) < float(other) 

125 

126 def __gt__(self, other): 

127 if other is None: 

128 return False 

129 return float(self) > float(other) 

130 

131 

132def deg_float_or_none(x): 

133 if x is None: 

134 return None 

135 else: 

136 return deg_float(x) 

137 

138 

139class sector_int(int): 

140 

141 def __str__(self): 

142 return '[%i]' % self 

143 

144 def __lt__(self, other): 

145 if other is None: 

146 return True 

147 return int(self) < int(other) 

148 

149 def __gt__(self, other): 

150 if other is None: 

151 return False 

152 return int(self) > int(other) 

153 

154 

155def num_to_html(num): 

156 snum = '%g' % num 

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

158 if m: 

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

160 

161 return snum 

162 

163 

164gap_lap_tolerance = 5. 

165 

166 

167class ViewMode(enum.Enum): 

168 Wiggle = 1 

169 Waterfall = 2 

170 

171 

172class Timer(object): 

173 def __init__(self): 

174 self._start = None 

175 self._stop = None 

176 

177 def start(self): 

178 self._start = os.times() 

179 

180 def stop(self): 

181 self._stop = os.times() 

182 

183 def get(self): 

184 a = self._start 

185 b = self._stop 

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

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

188 else: 

189 return tuple([0.] * 5) 

190 

191 def __sub__(self, other): 

192 a = self.get() 

193 b = other.get() 

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

195 

196 

197class ObjectStyle(object): 

198 def __init__(self, frame_pen, fill_brush): 

199 self.frame_pen = frame_pen 

200 self.fill_brush = fill_brush 

201 

202 

203box_styles = [] 

204box_alpha = 100 

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

206 'scarletred'.split(): 

207 

208 box_styles.append(ObjectStyle( 

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

210 qg.QBrush(qg.QColor( 

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

212 )) 

213 

214box_styles_coverage = {} 

215 

216box_styles_coverage['waveform'] = [ 

217 ObjectStyle( 

218 qg.QPen( 

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

220 1, qc.Qt.DashLine), 

221 qg.QBrush(qg.QColor( 

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

223 ), 

224 ObjectStyle( 

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

226 qg.QBrush(qg.QColor( 

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

228 ), 

229 ObjectStyle( 

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

231 qg.QBrush(qg.QColor( 

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

233 )] 

234 

235box_styles_coverage['waveform_promise'] = [ 

236 ObjectStyle( 

237 qg.QPen( 

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

239 1, qc.Qt.DashLine), 

240 qg.QBrush(qg.QColor( 

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

242 ), 

243 ObjectStyle( 

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

245 qg.QBrush(qg.QColor( 

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

247 ), 

248 ObjectStyle( 

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

250 qg.QBrush(qg.QColor( 

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

252 )] 

253 

254sday = 60*60*24. # \ 

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

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

257 

258acceptable_tincs = num.array([ 

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

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

261 

262 

263working_system_time_range = \ 

264 pyrocko.util.working_system_time_range() 

265 

266initial_time_range = [] 

267 

268try: 

269 initial_time_range.append( 

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

271except Exception: 

272 initial_time_range.append(working_system_time_range[0]) 

273 

274try: 

275 initial_time_range.append( 

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

277except Exception: 

278 initial_time_range.append(working_system_time_range[1]) 

279 

280 

281def is_working_time(t): 

282 return working_system_time_range[0] <= t and \ 

283 t <= working_system_time_range[1] 

284 

285 

286def fancy_time_ax_format(inc): 

287 l0_fmt_brief = '' 

288 l2_fmt = '' 

289 l2_trig = 0 

290 if inc < 0.000001: 

291 l0_fmt = '.%n' 

292 l0_center = False 

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

294 l1_trig = 6 

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

296 l2_trig = 3 

297 elif inc < 0.001: 

298 l0_fmt = '.%u' 

299 l0_center = False 

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

301 l1_trig = 6 

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

303 l2_trig = 3 

304 elif inc < 1: 

305 l0_fmt = '.%r' 

306 l0_center = False 

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

308 l1_trig = 6 

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

310 l2_trig = 3 

311 elif inc < 60: 

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

313 l0_center = False 

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

315 l1_trig = 3 

316 elif inc < 3600: 

317 l0_fmt = '%H:%M' 

318 l0_center = False 

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

320 l1_trig = 3 

321 elif inc < sday: 

322 l0_fmt = '%H:%M' 

323 l0_center = False 

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

325 l1_trig = 3 

326 elif inc < smonth: 

327 l0_fmt = '%a %d' 

328 l0_fmt_brief = '%d' 

329 l0_center = True 

330 l1_fmt = '%b, %Y' 

331 l1_trig = 2 

332 elif inc < syear: 

333 l0_fmt = '%b' 

334 l0_center = True 

335 l1_fmt = '%Y' 

336 l1_trig = 1 

337 else: 

338 l0_fmt = '%Y' 

339 l0_center = False 

340 l1_fmt = '' 

341 l1_trig = 0 

342 

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

344 

345 

346def day_start(timestamp): 

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

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

349 return calendar.timegm(tts) 

350 

351 

352def month_start(timestamp): 

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

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

355 return calendar.timegm(tts) 

356 

357 

358def year_start(timestamp): 

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

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

361 return calendar.timegm(tts) 

362 

363 

364def time_nice_value(inc0): 

365 if inc0 < acceptable_tincs[0]: 

366 return pyrocko.plot.nice_value(inc0) 

367 elif inc0 > acceptable_tincs[-1]: 

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

369 else: 

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

371 return acceptable_tincs[i] 

372 

373 

374class TimeScaler(pyrocko.plot.AutoScaler): 

375 def __init__(self): 

376 pyrocko.plot.AutoScaler.__init__(self) 

377 self.mode = 'min-max' 

378 

379 def make_scale(self, data_range): 

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

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

382 

383 data_min = min(data_range) 

384 data_max = max(data_range) 

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

386 

387 mi, ma = data_min, data_max 

388 nmi = mi 

389 if self.mode != 'off': 

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

391 

392 nma = ma 

393 if self.mode != 'off': 

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

395 

396 mi, ma = nmi, nma 

397 

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

399 mi -= 1.0 

400 ma += 1.0 

401 

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

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

404 

405 # make nice tick increment 

406 if self.inc is not None: 

407 inc = self.inc 

408 else: 

409 if self.approx_ticks > 0.: 

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

411 else: 

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

413 

414 if inc == 0.0: 

415 inc = 1.0 

416 

417 if is_reverse: 

418 return ma, mi, -inc 

419 else: 

420 return mi, ma, inc 

421 

422 def make_ticks(self, data_range): 

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

424 

425 is_reverse = False 

426 if inc < 0: 

427 mi, ma, inc = ma, mi, -inc 

428 is_reverse = True 

429 

430 ticks = [] 

431 

432 if inc < sday: 

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

434 if inc < 0.001: 

435 mi_day = hpfloat(mi_day) 

436 

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

438 if inc < 0.001: 

439 base = hpfloat(base) 

440 

441 base_day = mi_day 

442 i = 0 

443 while True: 

444 tick = base+i*inc 

445 if tick > ma: 

446 break 

447 

448 tick_day = day_start(tick) 

449 if tick_day > base_day: 

450 base_day = tick_day 

451 base = base_day 

452 i = 0 

453 else: 

454 ticks.append(tick) 

455 i += 1 

456 

457 elif inc < smonth: 

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

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

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

461 if mi_day == mi: 

462 dt_base += delta 

463 i = 0 

464 while True: 

465 current = dt_base + i*delta 

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

467 if tick > ma: 

468 break 

469 ticks.append(tick) 

470 i += 1 

471 

472 elif inc < syear: 

473 mi_month = month_start(max( 

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

475 

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

477 while True: 

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

479 m += 1 

480 if m > 12: 

481 y, m = y+1, 1 

482 

483 if tick > ma: 

484 break 

485 

486 if tick >= mi: 

487 ticks.append(tick) 

488 

489 else: 

490 mi_year = year_start(max( 

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

492 

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

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

495 

496 while True: 

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

498 y += incy 

499 if tick > ma: 

500 break 

501 if tick >= mi: 

502 ticks.append(tick) 

503 

504 if is_reverse: 

505 ticks.reverse() 

506 

507 return ticks, inc 

508 

509 

510def need_l1_tick(tt, ms, l1_trig): 

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

512 

513 

514def tick_to_labels(tick, inc): 

515 tt, ms = gmtime_x(tick) 

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

517 fancy_time_ax_format(inc) 

518 

519 l0 = mystrftime(l0_fmt, tt, ms) 

520 l0_brief = mystrftime(l0_fmt_brief, tt, ms) 

521 l1, l2 = None, None 

522 if need_l1_tick(tt, ms, l1_trig): 

523 l1 = mystrftime(l1_fmt, tt, ms) 

524 if need_l1_tick(tt, ms, l2_trig): 

525 l2 = mystrftime(l2_fmt, tt, ms) 

526 

527 return l0, l0_brief, l0_center, l1, l2 

528 

529 

530def l1_l2_tick(tick, inc): 

531 tt, ms = gmtime_x(tick) 

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

533 fancy_time_ax_format(inc) 

534 

535 l1 = mystrftime(l1_fmt, tt, ms) 

536 l2 = mystrftime(l2_fmt, tt, ms) 

537 return l1, l2 

538 

539 

540class TimeAx(TimeScaler): 

541 def __init__(self, *args): 

542 TimeScaler.__init__(self, *args) 

543 

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

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

546 p.setPen(pen) 

547 font = qg.QFont() 

548 font.setBold(True) 

549 p.setFont(font) 

550 fm = p.fontMetrics() 

551 ticklen = 10 

552 pad = 10 

553 tmin, tmax = xprojection.get_in_range() 

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

555 l1_hits = 0 

556 l2_hits = 0 

557 

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

559 uumin, uumax = xprojection.get_out_range() 

560 first_tick_with_label = None 

561 

562 data = [] 

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 

576 # (we assume that synthetic data is shown) 

577 if l2: 

578 l2 = None 

579 elif l1: 

580 l1 = None 

581 

582 if l0_center: 

583 ushift = (umin_approx_next-umin)/2. 

584 else: 

585 ushift = 0. 

586 

587 abbr_level = 0 

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

589 label0 = l0x 

590 rect0 = fm.boundingRect(label0) 

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

592 break 

593 

594 abbr_level += 1 

595 

596 data.append(( 

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

598 pinc_approx)) 

599 

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

601 pinc_approx) in data: 

602 

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

604 rect0 = fm.boundingRect(label0) 

605 

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

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

608 

609 if first_tick_with_label is None: 

610 first_tick_with_label = tick 

611 p.drawText(qc.QPointF( 

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

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

614 

615 if l1: 

616 label1 = l1 

617 rect1 = fm.boundingRect(label1) 

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

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

620 

621 p.drawText(qc.QPointF( 

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

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

624 label1) 

625 

626 l1_hits += 1 

627 

628 if l2: 

629 label2 = l2 

630 rect2 = fm.boundingRect(label2) 

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

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

633 

634 p.drawText(qc.QPointF( 

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

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

637 ticklen), label2) 

638 

639 l2_hits += 1 

640 

641 if first_tick_with_label is None: 

642 first_tick_with_label = tmin 

643 

644 l1, l2 = l1_l2_tick(first_tick_with_label, inc) 

645 

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

647 tmax - tmin < 3600*24: 

648 

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

650 if l2: 

651 l2 = None 

652 elif l1: 

653 l1 = None 

654 

655 if l1_hits == 0 and l1: 

656 label1 = l1 

657 rect1 = fm.boundingRect(label1) 

658 p.drawText(qc.QPointF( 

659 uumin+pad, 

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

661 label1) 

662 

663 l1_hits += 1 

664 

665 if l2_hits == 0 and l2: 

666 label2 = l2 

667 rect2 = fm.boundingRect(label2) 

668 p.drawText(qc.QPointF( 

669 uumin+pad, 

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

671 label2) 

672 

673 v = yprojection(0) 

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

675 

676 

677class Projection(object): 

678 def __init__(self): 

679 self.xr = 0., 1. 

680 self.ur = 0., 1. 

681 

682 def set_in_range(self, xmin, xmax): 

683 if xmax == xmin: 

684 xmax = xmin + 1. 

685 

686 self.xr = xmin, xmax 

687 

688 def get_in_range(self): 

689 return self.xr 

690 

691 def set_out_range(self, umin, umax): 

692 if umax == umin: 

693 umax = umin + 1. 

694 

695 self.ur = umin, umax 

696 

697 def get_out_range(self): 

698 return self.ur 

699 

700 def __call__(self, x): 

701 umin, umax = self.ur 

702 xmin, xmax = self.xr 

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

704 

705 def clipped(self, x, umax_pad): 

706 umin, umax = self.ur 

707 xmin, xmax = self.xr 

708 return min( 

709 umax-umax_pad, 

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

711 

712 def rev(self, u): 

713 umin, umax = self.ur 

714 xmin, xmax = self.xr 

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

716 

717 def copy(self): 

718 return copy.copy(self) 

719 

720 

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

722 group = qw.QActionGroup(menu) 

723 group.setExclusive(True) 

724 menuitems = [] 

725 

726 for name, value, *shortcut in menudef: 

727 action = menu.addAction(name) 

728 action.setCheckable(True) 

729 action.setActionGroup(group) 

730 if shortcut: 

731 action.setShortcut(shortcut[0]) 

732 

733 menuitems.append((action, value)) 

734 if default is not None and ( 

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

736 value == default): 

737 action.setChecked(True) 

738 

739 group.triggered.connect(target) 

740 

741 if default is None: 

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

743 

744 return menuitems 

745 

746 

747def sort_actions(menu): 

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

749 for action in actions: 

750 menu.removeAction(action) 

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

752 

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

754 if help_action: 

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

756 for action in actions: 

757 menu.addAction(action) 

758 

759 

760fkey_map = dict(zip( 

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

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

763 range(10))) 

764 

765 

766class PileViewerMainException(Exception): 

767 pass 

768 

769 

770class PileViewerMenuBar(qw.QMenuBar): 

771 ... 

772 

773 

774class PileViewerMenu(qw.QMenu): 

775 ... 

776 

777 

778def MakePileViewerMainClass(base): 

779 

780 class PileViewerMain(base): 

781 

782 want_input = qc.pyqtSignal() 

783 about_to_close = qc.pyqtSignal() 

784 pile_has_changed_signal = qc.pyqtSignal() 

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

786 

787 begin_markers_add = qc.pyqtSignal(int, int) 

788 end_markers_add = qc.pyqtSignal() 

789 begin_markers_remove = qc.pyqtSignal(int, int) 

790 end_markers_remove = qc.pyqtSignal() 

791 

792 marker_selection_changed = qc.pyqtSignal(list) 

793 active_event_marker_changed = qc.pyqtSignal() 

794 

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

796 menu=None): 

797 base.__init__(self, *args) 

798 

799 self.pile = pile 

800 self.ax_height = 80 

801 self.panel_parent = panel_parent 

802 

803 self.click_tolerance = 5 

804 

805 self.ntracks_shown_max = ntracks_shown_max 

806 self.initial_ntracks_shown_max = ntracks_shown_max 

807 self.ntracks = 0 

808 self.show_all = True 

809 self.shown_tracks_range = None 

810 self.track_start = None 

811 self.track_trange = None 

812 

813 self.lowpass = None 

814 self.highpass = None 

815 self.gain = 1.0 

816 self.rotate = 0.0 

817 self.picking_down = None 

818 self.picking = None 

819 self.floating_marker = None 

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

821 self.markers_deltat_max = 0. 

822 self.n_selected_markers = 0 

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

824 self.visible_marker_kinds = self.all_marker_kinds 

825 self.active_event_marker = None 

826 self.ignore_releases = 0 

827 self.message = None 

828 self.reloaded = False 

829 self.pile_has_changed = False 

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

831 

832 self.tax = TimeAx() 

833 self.setBackgroundRole(qg.QPalette.Base) 

834 self.setAutoFillBackground(True) 

835 poli = qw.QSizePolicy( 

836 qw.QSizePolicy.Expanding, 

837 qw.QSizePolicy.Expanding) 

838 

839 self.setSizePolicy(poli) 

840 self.setMinimumSize(300, 200) 

841 self.setFocusPolicy(qc.Qt.ClickFocus) 

842 

843 self.menu = menu or PileViewerMenu(self) 

844 

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

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

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

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

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

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

851 

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

853 

854 self.snufflings_menu = self.toggle_panel_menu.addMenu( 

855 'Run Snuffling') 

856 self.toggle_panel_menu.addSeparator() 

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

858 help_menu.addSeparator() 

859 

860 file_menu.addAction( 

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

862 'Open waveform files...', 

863 self.open_waveforms, 

864 qg.QKeySequence.Open) 

865 

866 file_menu.addAction( 

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

868 'Open waveform directory...', 

869 self.open_waveform_directory) 

870 

871 file_menu.addAction( 

872 'Open station files...', 

873 self.open_stations) 

874 

875 file_menu.addAction( 

876 'Open StationXML files...', 

877 self.open_stations_xml) 

878 

879 file_menu.addAction( 

880 'Open event file...', 

881 self.read_events) 

882 

883 file_menu.addSeparator() 

884 file_menu.addAction( 

885 'Open marker file...', 

886 self.read_markers) 

887 

888 file_menu.addAction( 

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

890 'Save markers...', 

891 self.write_markers, 

892 qg.QKeySequence.Save) 

893 

894 file_menu.addAction( 

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

896 'Save selected markers...', 

897 self.write_selected_markers, 

898 qg.QKeySequence.SaveAs) 

899 

900 file_menu.addSeparator() 

901 file_menu.addAction( 

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

903 'Print', 

904 self.printit, 

905 qg.QKeySequence.Print) 

906 

907 file_menu.addAction( 

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

909 'Save as SVG or PNG', 

910 self.savesvg, 

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

912 

913 file_menu.addSeparator() 

914 close = file_menu.addAction( 

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

916 'Close', 

917 self.myclose) 

918 close.setShortcuts( 

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

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

921 

922 # Scale Menu 

923 menudef = [ 

924 ('Individual Scale', 

925 lambda tr: tr.nslc_id, 

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

927 ('Common Scale', 

928 lambda tr: None, 

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

930 ('Common Scale per Station', 

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

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

933 ('Common Scale per Station Location', 

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

935 ('Common Scale per Component', 

936 lambda tr: (tr.channel)), 

937 ] 

938 

939 self.menuitems_scaling = add_radiobuttongroup( 

940 scale_menu, menudef, self.scalingmode_change, 

941 default=self.config.trace_scale) 

942 scale_menu.addSeparator() 

943 

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

945 self.scaling_hooks = {} 

946 self.scalingmode_change() 

947 

948 menudef = [ 

949 ('Scaling based on Minimum and Maximum', 

950 ('minmax', 'minmax')), 

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

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

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

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

955 ] 

956 

957 self.menuitems_scaling_base = add_radiobuttongroup( 

958 scale_menu, menudef, self.scaling_base_change) 

959 

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

961 scale_menu.addSeparator() 

962 

963 self.menuitem_fixscalerange = scale_menu.addAction( 

964 'Fix Scale Ranges') 

965 self.menuitem_fixscalerange.setCheckable(True) 

966 

967 # Sort Menu 

968 def sector_dist(sta): 

969 if sta.dist_m is None: 

970 return None, None 

971 else: 

972 return ( 

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

974 m_float(sta.dist_m)) 

975 

976 menudef = [ 

977 ('Sort by Names', 

978 lambda tr: (), 

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

980 ('Sort by Distance', 

981 lambda tr: self.station_attrib( 

982 tr, 

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

984 lambda tr: (None,)), 

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

986 ('Sort by Azimuth', 

987 lambda tr: self.station_attrib( 

988 tr, 

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

990 lambda tr: (None,))), 

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

992 lambda tr: self.station_attrib( 

993 tr, 

994 sector_dist, 

995 lambda tr: (None, None))), 

996 ('Sort by Backazimuth', 

997 lambda tr: self.station_attrib( 

998 tr, 

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

1000 lambda tr: (None,))), 

1001 ] 

1002 self.menuitems_ssorting = add_radiobuttongroup( 

1003 sort_menu, menudef, self.s_sortingmode_change) 

1004 sort_menu.addSeparator() 

1005 

1006 self._ssort = lambda tr: () 

1007 

1008 self.menu.addSeparator() 

1009 

1010 menudef = [ 

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

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

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

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

1015 ((0, 1, 3, 2), 

1016 lambda tr: tr.channel)), 

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

1018 ((1, 0, 3, 2), 

1019 lambda tr: tr.channel)), 

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

1021 ((2, 0, 1, 3), 

1022 lambda tr: tr.channel)), 

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

1024 ((3, 0, 1, 2), 

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

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

1027 ((0, 1, 3), 

1028 lambda tr: tr.location)), 

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

1030 ((1, 0, 3), 

1031 lambda tr: tr.location)), 

1032 ] 

1033 

1034 self.menuitems_sorting = add_radiobuttongroup( 

1035 sort_menu, menudef, self.sortingmode_change) 

1036 

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

1038 self.config.visible_length_setting] 

1039 

1040 # View menu 

1041 self.menuitems_visible_length = add_radiobuttongroup( 

1042 view_menu, menudef, 

1043 self.visible_length_change) 

1044 view_menu.addSeparator() 

1045 

1046 view_modes = [ 

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

1048 ('Waterfall', ViewMode.Waterfall) 

1049 ] 

1050 

1051 self.menuitems_viewmode = add_radiobuttongroup( 

1052 view_menu, view_modes, 

1053 self.viewmode_change, default=ViewMode.Wiggle) 

1054 view_menu.addSeparator() 

1055 

1056 self.menuitem_cliptraces = view_menu.addAction( 

1057 'Clip Traces') 

1058 self.menuitem_cliptraces.setCheckable(True) 

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

1060 

1061 self.menuitem_showboxes = view_menu.addAction( 

1062 'Show Boxes') 

1063 self.menuitem_showboxes.setCheckable(True) 

1064 self.menuitem_showboxes.setChecked( 

1065 self.config.show_boxes) 

1066 

1067 self.menuitem_colortraces = view_menu.addAction( 

1068 'Color Traces') 

1069 self.menuitem_colortraces.setCheckable(True) 

1070 self.menuitem_antialias = view_menu.addAction( 

1071 'Antialiasing') 

1072 self.menuitem_antialias.setCheckable(True) 

1073 

1074 view_menu.addSeparator() 

1075 self.menuitem_showscalerange = view_menu.addAction( 

1076 'Show Scale Ranges') 

1077 self.menuitem_showscalerange.setCheckable(True) 

1078 self.menuitem_showscalerange.setChecked( 

1079 self.config.show_scale_ranges) 

1080 

1081 self.menuitem_showscaleaxis = view_menu.addAction( 

1082 'Show Scale Axes') 

1083 self.menuitem_showscaleaxis.setCheckable(True) 

1084 self.menuitem_showscaleaxis.setChecked( 

1085 self.config.show_scale_axes) 

1086 

1087 self.menuitem_showzeroline = view_menu.addAction( 

1088 'Show Zero Lines') 

1089 self.menuitem_showzeroline.setCheckable(True) 

1090 

1091 view_menu.addSeparator() 

1092 view_menu.addAction( 

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

1094 'Fullscreen', 

1095 self.toggle_fullscreen, 

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

1097 

1098 # Options Menu 

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

1100 self.menuitem_demean.setCheckable(True) 

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

1102 self.menuitem_demean.setShortcut( 

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

1104 

1105 self.menuitem_distances_3d = options_menu.addAction( 

1106 '3D distances', 

1107 self.distances_3d_changed) 

1108 self.menuitem_distances_3d.setCheckable(True) 

1109 

1110 self.menuitem_allowdownsampling = options_menu.addAction( 

1111 'Allow Downsampling') 

1112 self.menuitem_allowdownsampling.setCheckable(True) 

1113 self.menuitem_allowdownsampling.setChecked(True) 

1114 

1115 self.menuitem_degap = options_menu.addAction( 

1116 'Allow Degapping') 

1117 self.menuitem_degap.setCheckable(True) 

1118 self.menuitem_degap.setChecked(True) 

1119 

1120 options_menu.addSeparator() 

1121 

1122 self.menuitem_fft_filtering = options_menu.addAction( 

1123 'FFT Filtering') 

1124 self.menuitem_fft_filtering.setCheckable(True) 

1125 

1126 self.menuitem_lphp = options_menu.addAction( 

1127 'Bandpass is Low- + Highpass') 

1128 self.menuitem_lphp.setCheckable(True) 

1129 self.menuitem_lphp.setChecked(True) 

1130 

1131 options_menu.addSeparator() 

1132 self.menuitem_watch = options_menu.addAction( 

1133 'Watch Files') 

1134 self.menuitem_watch.setCheckable(True) 

1135 

1136 self.menuitem_liberal_fetch = options_menu.addAction( 

1137 'Liberal Fetch Optimization') 

1138 self.menuitem_liberal_fetch.setCheckable(True) 

1139 

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

1141 

1142 self.snufflings_menu.addAction( 

1143 'Reload Snufflings', 

1144 self.setup_snufflings) 

1145 

1146 # Disable ShadowPileTest 

1147 if False: 

1148 test_action = self.menu.addAction( 

1149 'Test', 

1150 self.toggletest) 

1151 test_action.setCheckable(True) 

1152 

1153 help_menu.addAction( 

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

1155 'Snuffler Controls', 

1156 self.help, 

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

1158 

1159 help_menu.addAction( 

1160 'About', 

1161 self.about) 

1162 

1163 self.time_projection = Projection() 

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

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

1166 

1167 self.gather = None 

1168 

1169 self.trace_filter = None 

1170 self.quick_filter = None 

1171 self.quick_filter_patterns = None, None 

1172 self.blacklist = [] 

1173 

1174 self.track_to_screen = Projection() 

1175 self.track_to_nslc_ids = {} 

1176 

1177 self.cached_vec = None 

1178 self.cached_processed_traces = None 

1179 

1180 self.timer = qc.QTimer(self) 

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

1182 self.timer.setInterval(1000) 

1183 self.timer.start() 

1184 self.pile.add_listener(self) 

1185 self.trace_styles = {} 

1186 if self.get_squirrel() is None: 

1187 self.determine_box_styles() 

1188 

1189 self.setMouseTracking(True) 

1190 

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

1192 self.snuffling_modules = {} 

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

1194 self.default_snufflings = None 

1195 self.snufflings = [] 

1196 

1197 self.stations = {} 

1198 

1199 self.timer_draw = Timer() 

1200 self.timer_cutout = Timer() 

1201 self.time_spent_painting = 0.0 

1202 self.time_last_painted = time.time() 

1203 

1204 self.interactive_range_change_time = 0.0 

1205 self.interactive_range_change_delay_time = 10.0 

1206 self.follow_timer = None 

1207 

1208 self.sortingmode_change_time = 0.0 

1209 self.sortingmode_change_delay_time = None 

1210 

1211 self.old_data_ranges = {} 

1212 

1213 self.error_messages = {} 

1214 self.return_tag = None 

1215 self.wheel_pos = 60 

1216 

1217 self.setAcceptDrops(True) 

1218 self._paths_to_load = [] 

1219 

1220 self.tf_cache = {} 

1221 

1222 self.waterfall = TraceWaterfall() 

1223 self.waterfall_cmap = 'viridis' 

1224 self.waterfall_clip_min = 0. 

1225 self.waterfall_clip_max = 1. 

1226 self.waterfall_show_absolute = False 

1227 self.waterfall_integrate = False 

1228 self.view_mode = ViewMode.Wiggle 

1229 

1230 self.automatic_updates = True 

1231 

1232 self.closing = False 

1233 self.in_paint_event = False 

1234 

1235 def fail(self, reason): 

1236 box = qw.QMessageBox(self) 

1237 box.setText(reason) 

1238 box.exec_() 

1239 

1240 def set_trace_filter(self, filter_func): 

1241 self.trace_filter = filter_func 

1242 self.sortingmode_change() 

1243 

1244 def update_trace_filter(self): 

1245 if self.blacklist: 

1246 

1247 def blacklist_func(tr): 

1248 return not pyrocko.util.match_nslc( 

1249 self.blacklist, tr.nslc_id) 

1250 

1251 else: 

1252 blacklist_func = None 

1253 

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

1255 self.set_trace_filter(None) 

1256 elif self.quick_filter is None: 

1257 self.set_trace_filter(blacklist_func) 

1258 elif blacklist_func is None: 

1259 self.set_trace_filter(self.quick_filter) 

1260 else: 

1261 self.set_trace_filter( 

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

1263 

1264 def set_quick_filter(self, filter_func): 

1265 self.quick_filter = filter_func 

1266 self.update_trace_filter() 

1267 

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

1269 if patterns is not None: 

1270 self.set_quick_filter( 

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

1272 else: 

1273 self.set_quick_filter(None) 

1274 

1275 self.quick_filter_patterns = patterns, inputline 

1276 

1277 def get_quick_filter_patterns(self): 

1278 return self.quick_filter_patterns 

1279 

1280 def add_blacklist_pattern(self, pattern): 

1281 if pattern == 'empty': 

1282 keys = set(self.pile.nslc_ids) 

1283 trs = self.pile.all( 

1284 tmin=self.tmin, 

1285 tmax=self.tmax, 

1286 load_data=False, 

1287 degap=False) 

1288 

1289 for tr in trs: 

1290 if tr.nslc_id in keys: 

1291 keys.remove(tr.nslc_id) 

1292 

1293 for key in keys: 

1294 xpattern = '.'.join(key) 

1295 if xpattern not in self.blacklist: 

1296 self.blacklist.append(xpattern) 

1297 

1298 else: 

1299 if pattern in self.blacklist: 

1300 self.blacklist.remove(pattern) 

1301 

1302 self.blacklist.append(pattern) 

1303 

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

1305 self.update_trace_filter() 

1306 

1307 def remove_blacklist_pattern(self, pattern): 

1308 if pattern in self.blacklist: 

1309 self.blacklist.remove(pattern) 

1310 else: 

1311 raise PileViewerMainException( 

1312 'Pattern not found in blacklist.') 

1313 

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

1315 self.update_trace_filter() 

1316 

1317 def clear_blacklist(self): 

1318 self.blacklist = [] 

1319 self.update_trace_filter() 

1320 

1321 def ssort(self, tr): 

1322 return self._ssort(tr) 

1323 

1324 def station_key(self, x): 

1325 return x.network, x.station 

1326 

1327 def station_keys(self, x): 

1328 return [ 

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

1330 (x.network, x.station)] 

1331 

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

1333 for sk in self.station_keys(tr): 

1334 if sk in self.stations: 

1335 station = self.stations[sk] 

1336 return getter(station) 

1337 

1338 return default_getter(tr) 

1339 

1340 def get_station(self, sk): 

1341 return self.stations[sk] 

1342 

1343 def has_station(self, station): 

1344 for sk in self.station_keys(station): 

1345 if sk in self.stations: 

1346 return True 

1347 

1348 return False 

1349 

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

1351 return self.station_attrib( 

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

1353 

1354 def set_stations(self, stations): 

1355 self.stations = {} 

1356 self.add_stations(stations) 

1357 

1358 def add_stations(self, stations): 

1359 for station in stations: 

1360 for sk in self.station_keys(station): 

1361 self.stations[sk] = station 

1362 

1363 ev = self.get_active_event() 

1364 if ev: 

1365 self.set_origin(ev) 

1366 

1367 def add_event(self, event): 

1368 marker = EventMarker(event) 

1369 self.add_marker(marker) 

1370 

1371 def add_events(self, events): 

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

1373 self.add_markers(markers) 

1374 

1375 def set_event_marker_as_origin(self, ignore=None): 

1376 selected = self.selected_markers() 

1377 if not selected: 

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

1379 return 

1380 

1381 m = selected[0] 

1382 if not isinstance(m, EventMarker): 

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

1384 return 

1385 

1386 self.set_active_event_marker(m) 

1387 

1388 def deactivate_event_marker(self): 

1389 if self.active_event_marker: 

1390 self.active_event_marker.active = False 

1391 

1392 self.active_event_marker_changed.emit() 

1393 self.active_event_marker = None 

1394 

1395 def set_active_event_marker(self, event_marker): 

1396 if self.active_event_marker: 

1397 self.active_event_marker.active = False 

1398 

1399 self.active_event_marker = event_marker 

1400 event_marker.active = True 

1401 event = event_marker.get_event() 

1402 self.set_origin(event) 

1403 self.active_event_marker_changed.emit() 

1404 

1405 def set_active_event(self, event): 

1406 for marker in self.markers: 

1407 if isinstance(marker, EventMarker): 

1408 if marker.get_event() is event: 

1409 self.set_active_event_marker(marker) 

1410 

1411 def get_active_event_marker(self): 

1412 return self.active_event_marker 

1413 

1414 def get_active_event(self): 

1415 m = self.get_active_event_marker() 

1416 if m is not None: 

1417 return m.get_event() 

1418 else: 

1419 return None 

1420 

1421 def get_active_markers(self): 

1422 emarker = self.get_active_event_marker() 

1423 if emarker is None: 

1424 return None, [] 

1425 

1426 else: 

1427 ev = emarker.get_event() 

1428 pmarkers = [ 

1429 m for m in self.markers 

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

1431 

1432 return emarker, pmarkers 

1433 

1434 def set_origin(self, location): 

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

1436 station.set_event_relative_data( 

1437 location, 

1438 distance_3d=self.menuitem_distances_3d.isChecked()) 

1439 

1440 self.sortingmode_change() 

1441 

1442 def distances_3d_changed(self): 

1443 ignore = self.menuitem_distances_3d.isChecked() 

1444 self.set_event_marker_as_origin(ignore) 

1445 

1446 def iter_snuffling_modules(self): 

1447 pjoin = os.path.join 

1448 for path in self.snuffling_paths: 

1449 

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

1451 os.mkdir(path) 

1452 

1453 for entry in os.listdir(path): 

1454 directory = path 

1455 fn = entry 

1456 d = pjoin(path, entry) 

1457 if os.path.isdir(d): 

1458 directory = d 

1459 if os.path.isfile( 

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

1461 fn = 'snuffling.py' 

1462 

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

1464 continue 

1465 

1466 name = fn[:-3] 

1467 

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

1469 self.snuffling_modules[directory, name] = \ 

1470 pyrocko.gui.snuffling.SnufflingModule( 

1471 directory, name, self) 

1472 

1473 yield self.snuffling_modules[directory, name] 

1474 

1475 def setup_snufflings(self): 

1476 # user snufflings 

1477 for mod in self.iter_snuffling_modules(): 

1478 try: 

1479 mod.load_if_needed() 

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

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

1482 

1483 # load the default snufflings on first run 

1484 if self.default_snufflings is None: 

1485 self.default_snufflings = pyrocko.gui\ 

1486 .snufflings.__snufflings__() 

1487 for snuffling in self.default_snufflings: 

1488 self.add_snuffling(snuffling) 

1489 

1490 def set_panel_parent(self, panel_parent): 

1491 self.panel_parent = panel_parent 

1492 

1493 def get_panel_parent(self): 

1494 return self.panel_parent 

1495 

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

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

1498 snuffling.init_gui( 

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

1500 self.snufflings.append(snuffling) 

1501 self.update() 

1502 

1503 def remove_snuffling(self, snuffling): 

1504 snuffling.delete_gui() 

1505 self.update() 

1506 self.snufflings.remove(snuffling) 

1507 snuffling.pre_destroy() 

1508 

1509 def add_snuffling_menuitem(self, item): 

1510 self.snufflings_menu.addAction(item) 

1511 item.setParent(self.snufflings_menu) 

1512 sort_actions(self.snufflings_menu) 

1513 

1514 def remove_snuffling_menuitem(self, item): 

1515 self.snufflings_menu.removeAction(item) 

1516 

1517 def add_snuffling_help_menuitem(self, item): 

1518 self.snuffling_help.addAction(item) 

1519 item.setParent(self.snuffling_help) 

1520 sort_actions(self.snuffling_help) 

1521 

1522 def remove_snuffling_help_menuitem(self, item): 

1523 self.snuffling_help.removeAction(item) 

1524 

1525 def add_panel_toggler(self, item): 

1526 self.toggle_panel_menu.addAction(item) 

1527 item.setParent(self.toggle_panel_menu) 

1528 sort_actions(self.toggle_panel_menu) 

1529 

1530 def remove_panel_toggler(self, item): 

1531 self.toggle_panel_menu.removeAction(item) 

1532 

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

1534 cache_dir=None, force_cache=False): 

1535 

1536 if cache_dir is None: 

1537 cache_dir = pyrocko.config.config().cache_dir 

1538 if isinstance(paths, str): 

1539 paths = [paths] 

1540 

1541 fns = pyrocko.util.select_files( 

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

1543 

1544 if not fns: 

1545 return 

1546 

1547 cache = pyrocko.pile.get_cache(cache_dir) 

1548 

1549 t = [time.time()] 

1550 

1551 def update_bar(label, value): 

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

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

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

1555 else: 

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

1557 

1558 return pbs.set_status(label, value) 

1559 

1560 def update_progress(label, i, n): 

1561 abort = False 

1562 

1563 qw.qApp.processEvents() 

1564 if n != 0: 

1565 perc = i*100/n 

1566 else: 

1567 perc = 100 

1568 abort |= update_bar(label, perc) 

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

1570 

1571 tnow = time.time() 

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

1573 self.update() 

1574 t[0] = tnow 

1575 

1576 return abort 

1577 

1578 self.automatic_updates = False 

1579 

1580 self.pile.load_files( 

1581 sorted(fns), 

1582 filename_attributes=regex, 

1583 cache=cache, 

1584 fileformat=format, 

1585 show_progress=False, 

1586 update_progress=update_progress) 

1587 

1588 self.automatic_updates = True 

1589 self.update() 

1590 

1591 def load_queued(self): 

1592 if not self._paths_to_load: 

1593 return 

1594 paths = self._paths_to_load 

1595 self._paths_to_load = [] 

1596 self.load(paths) 

1597 

1598 def load_soon(self, paths): 

1599 self._paths_to_load.extend(paths) 

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

1601 

1602 def open_waveforms(self): 

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

1604 

1605 fns, _ = qw.QFileDialog.getOpenFileNames( 

1606 self, caption, options=qfiledialog_options) 

1607 

1608 if fns: 

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

1610 

1611 def open_waveform_directory(self): 

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

1613 

1614 dn = qw.QFileDialog.getExistingDirectory( 

1615 self, caption, options=qfiledialog_options) 

1616 

1617 if dn: 

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

1619 

1620 def open_stations(self, fns=None): 

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

1622 

1623 if not fns: 

1624 fns, _ = qw.QFileDialog.getOpenFileNames( 

1625 self, caption, options=qfiledialog_options) 

1626 

1627 try: 

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

1629 for stat in stations: 

1630 self.add_stations(stat) 

1631 

1632 except Exception as e: 

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

1634 

1635 def open_stations_xml(self, fns=None): 

1636 from pyrocko.io import stationxml 

1637 

1638 caption = 'Select one or more StationXML files' 

1639 if not fns: 

1640 fns, _ = qw.QFileDialog.getOpenFileNames( 

1641 self, caption, options=qfiledialog_options, 

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

1643 ';;All files (*)') 

1644 

1645 try: 

1646 stations = [ 

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

1648 for x in fns] 

1649 

1650 for stat in stations: 

1651 self.add_stations(stat) 

1652 

1653 except Exception as e: 

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

1655 

1656 def add_traces(self, traces): 

1657 if traces: 

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

1659 self.pile.add_file(mtf) 

1660 ticket = (self.pile, mtf) 

1661 return ticket 

1662 else: 

1663 return (None, None) 

1664 

1665 def release_data(self, tickets): 

1666 for ticket in tickets: 

1667 pile, mtf = ticket 

1668 if pile is not None: 

1669 pile.remove_file(mtf) 

1670 

1671 def periodical(self): 

1672 if self.menuitem_watch.isChecked(): 

1673 if self.pile.reload_modified(): 

1674 self.update() 

1675 

1676 def get_pile(self): 

1677 return self.pile 

1678 

1679 def pile_changed(self, what): 

1680 self.pile_has_changed = True 

1681 self.pile_has_changed_signal.emit() 

1682 if self.automatic_updates: 

1683 self.update() 

1684 

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

1686 

1687 if gather is None: 

1688 def gather_func(tr): 

1689 return tr.nslc_id 

1690 

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

1692 

1693 else: 

1694 def gather_func(tr): 

1695 return ( 

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

1697 

1698 if color is None: 

1699 def color(tr): 

1700 return tr.location 

1701 

1702 self.gather = gather_func 

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

1704 

1705 self.color_gather = color 

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

1707 previous_ntracks = self.ntracks 

1708 self.set_ntracks(len(keys)) 

1709 

1710 if self.shown_tracks_range is None or \ 

1711 previous_ntracks == 0 or \ 

1712 self.show_all: 

1713 

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

1715 key_at_top = None 

1716 n = high-low 

1717 

1718 else: 

1719 low, high = self.shown_tracks_range 

1720 key_at_top = self.track_keys[low] 

1721 n = high-low 

1722 

1723 self.track_keys = sorted(keys) 

1724 

1725 track_patterns = [] 

1726 for k in self.track_keys: 

1727 pat = ['*', '*', '*', '*'] 

1728 for i, j in enumerate(gather): 

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

1730 

1731 track_patterns.append(pat) 

1732 

1733 self.track_patterns = track_patterns 

1734 

1735 if key_at_top is not None: 

1736 try: 

1737 ind = self.track_keys.index(key_at_top) 

1738 low = ind 

1739 high = low+n 

1740 except Exception: 

1741 pass 

1742 

1743 self.set_tracks_range((low, high)) 

1744 

1745 self.key_to_row = dict( 

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

1747 

1748 def inrange(x, r): 

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

1750 

1751 def trace_selector(trace): 

1752 gt = self.gather(trace) 

1753 return ( 

1754 gt in self.key_to_row and 

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

1756 

1757 self.trace_selector = lambda x: \ 

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

1759 and trace_selector(x) 

1760 

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

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

1763 self.show_all: 

1764 

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

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

1767 tlen = (tmax - tmin) 

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

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

1770 

1771 def set_time_range(self, tmin, tmax): 

1772 if tmin is None: 

1773 tmin = initial_time_range[0] 

1774 

1775 if tmax is None: 

1776 tmax = initial_time_range[1] 

1777 

1778 if tmin > tmax: 

1779 tmin, tmax = tmax, tmin 

1780 

1781 if tmin == tmax: 

1782 tmin -= 1. 

1783 tmax += 1. 

1784 

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

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

1787 

1788 min_deltat = self.content_deltat_range()[0] 

1789 if (tmax - tmin < min_deltat): 

1790 m = (tmin + tmax) / 2. 

1791 tmin = m - min_deltat/2. 

1792 tmax = m + min_deltat/2. 

1793 

1794 self.time_projection.set_in_range(tmin, tmax) 

1795 self.tmin, self.tmax = tmin, tmax 

1796 

1797 def get_time_range(self): 

1798 return self.tmin, self.tmax 

1799 

1800 def ypart(self, y): 

1801 if y < self.ax_height: 

1802 return -1 

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

1804 return 1 

1805 else: 

1806 return 0 

1807 

1808 def time_fractional_digits(self): 

1809 min_deltat = self.content_deltat_range()[0] 

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

1811 

1812 def write_markers(self, fn=None): 

1813 caption = "Choose a file name to write 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.markers, fn, 

1821 fdigits=self.time_fractional_digits()) 

1822 

1823 except Exception as e: 

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

1825 

1826 def write_selected_markers(self, fn=None): 

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

1828 if not fn: 

1829 fn, _ = qw.QFileDialog.getSaveFileName( 

1830 self, caption, options=qfiledialog_options) 

1831 if fn: 

1832 try: 

1833 Marker.save_markers( 

1834 self.iter_selected_markers(), 

1835 fn, 

1836 fdigits=self.time_fractional_digits()) 

1837 

1838 except Exception as e: 

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

1840 

1841 def read_events(self, fn=None): 

1842 ''' 

1843 Open QFileDialog to open, read and add 

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

1845 representation to the pile viewer. 

1846 ''' 

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

1848 if not fn: 

1849 fn, _ = qw.QFileDialog.getOpenFileName( 

1850 self, caption, options=qfiledialog_options) 

1851 if fn: 

1852 try: 

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

1854 self.associate_phases_to_events() 

1855 

1856 except Exception as e: 

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

1858 

1859 def read_markers(self, fn=None): 

1860 ''' 

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

1862 ''' 

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

1864 if not fn: 

1865 fn, _ = qw.QFileDialog.getOpenFileName( 

1866 self, caption, options=qfiledialog_options) 

1867 if fn: 

1868 try: 

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

1870 self.associate_phases_to_events() 

1871 

1872 except Exception as e: 

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

1874 

1875 def associate_phases_to_events(self): 

1876 associate_phases_to_events(self.markers) 

1877 

1878 def add_marker(self, marker): 

1879 # need index to inform QAbstactTableModel about upcoming change, 

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

1881 self.markers.insert(marker) 

1882 i = self.markers.remove(marker) 

1883 

1884 self.begin_markers_add.emit(i, i) 

1885 self.markers.insert(marker) 

1886 self.end_markers_add.emit() 

1887 self.markers_deltat_max = max( 

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

1889 

1890 def add_markers(self, markers): 

1891 if not self.markers: 

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

1893 self.markers.insert_many(markers) 

1894 self.end_markers_add.emit() 

1895 self.update_markers_deltat_max() 

1896 else: 

1897 for marker in markers: 

1898 self.add_marker(marker) 

1899 

1900 def update_markers_deltat_max(self): 

1901 if self.markers: 

1902 self.markers_deltat_max = max( 

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

1904 

1905 def remove_marker(self, marker): 

1906 ''' 

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

1908 

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

1910 ''' 

1911 

1912 if marker is self.active_event_marker: 

1913 self.deactivate_event_marker() 

1914 

1915 try: 

1916 i = self.markers.index(marker) 

1917 self.begin_markers_remove.emit(i, i) 

1918 self.markers.remove_at(i) 

1919 self.end_markers_remove.emit() 

1920 except ValueError: 

1921 pass 

1922 

1923 def remove_markers(self, markers): 

1924 ''' 

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

1926 

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

1928 instances 

1929 ''' 

1930 

1931 if markers is self.markers: 

1932 markers = list(markers) 

1933 

1934 for marker in markers: 

1935 self.remove_marker(marker) 

1936 

1937 self.update_markers_deltat_max() 

1938 

1939 def remove_selected_markers(self): 

1940 def delete_segment(istart, iend): 

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

1942 for _ in range(iend - istart): 

1943 self.markers.remove_at(istart) 

1944 

1945 self.end_markers_remove.emit() 

1946 

1947 istart = None 

1948 ipos = 0 

1949 markers = self.markers 

1950 nmarkers = len(self.markers) 

1951 while ipos < nmarkers: 

1952 marker = markers[ipos] 

1953 if marker.is_selected(): 

1954 if marker is self.active_event_marker: 

1955 self.deactivate_event_marker() 

1956 

1957 if istart is None: 

1958 istart = ipos 

1959 else: 

1960 if istart is not None: 

1961 delete_segment(istart, ipos) 

1962 nmarkers -= ipos - istart 

1963 ipos = istart - 1 

1964 istart = None 

1965 

1966 ipos += 1 

1967 

1968 if istart is not None: 

1969 delete_segment(istart, ipos) 

1970 

1971 self.update_markers_deltat_max() 

1972 

1973 def selected_markers(self): 

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

1975 

1976 def iter_selected_markers(self): 

1977 for marker in self.markers: 

1978 if marker.is_selected(): 

1979 yield marker 

1980 

1981 def get_markers(self): 

1982 return self.markers 

1983 

1984 def mousePressEvent(self, mouse_ev): 

1985 self.show_all = False 

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

1987 

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

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

1990 if self.picking: 

1991 if self.picking_down is None: 

1992 self.picking_down = ( 

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

1994 mouse_ev.y()) 

1995 

1996 elif marker is not None: 

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

1998 self.deselect_all() 

1999 marker.selected = True 

2000 self.emit_selected_markers() 

2001 self.update() 

2002 else: 

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

2004 self.track_trange = self.tmin, self.tmax 

2005 

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

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

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

2009 self.update_status() 

2010 

2011 def mouseReleaseEvent(self, mouse_ev): 

2012 if self.ignore_releases: 

2013 self.ignore_releases -= 1 

2014 return 

2015 

2016 if self.picking: 

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

2018 self.emit_selected_markers() 

2019 

2020 if self.track_start: 

2021 self.update() 

2022 

2023 self.track_start = None 

2024 self.track_trange = None 

2025 self.update_status() 

2026 

2027 def mouseDoubleClickEvent(self, mouse_ev): 

2028 self.show_all = False 

2029 self.start_picking(None) 

2030 self.ignore_releases = 1 

2031 

2032 def mouseMoveEvent(self, mouse_ev): 

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

2034 

2035 if self.picking: 

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

2037 

2038 elif self.track_start is not None: 

2039 x0, y0 = self.track_start 

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

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

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

2043 dy = 0 

2044 

2045 tmin0, tmax0 = self.track_trange 

2046 

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

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

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

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

2051 

2052 self.interrupt_following() 

2053 self.set_time_range( 

2054 tmin0 - dt - dtr*frac, 

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

2056 

2057 self.update() 

2058 else: 

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

2060 

2061 self.update_status() 

2062 

2063 def nslc_ids_under_cursor(self, x, y): 

2064 ftrack = self.track_to_screen.rev(y) 

2065 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2066 return nslc_ids 

2067 

2068 def marker_under_cursor(self, x, y): 

2069 mouset = self.time_projection.rev(x) 

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

2071 relevant_nslc_ids = None 

2072 for marker in self.markers: 

2073 if marker.kind not in self.visible_marker_kinds: 

2074 continue 

2075 

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

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

2078 

2079 if relevant_nslc_ids is None: 

2080 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2081 

2082 marker_nslc_ids = marker.get_nslc_ids() 

2083 if not marker_nslc_ids: 

2084 return marker 

2085 

2086 for nslc_id in marker_nslc_ids: 

2087 if nslc_id in relevant_nslc_ids: 

2088 return marker 

2089 

2090 def hoovering(self, x, y): 

2091 mouset = self.time_projection.rev(x) 

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

2093 needupdate = False 

2094 haveone = False 

2095 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2096 for marker in self.markers: 

2097 if marker.kind not in self.visible_marker_kinds: 

2098 continue 

2099 

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

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

2102 

2103 if state: 

2104 xstate = False 

2105 

2106 marker_nslc_ids = marker.get_nslc_ids() 

2107 if not marker_nslc_ids: 

2108 xstate = True 

2109 

2110 for nslc in relevant_nslc_ids: 

2111 if marker.match_nslc(nslc): 

2112 xstate = True 

2113 

2114 state = xstate 

2115 

2116 if state: 

2117 haveone = True 

2118 oldstate = marker.is_alerted() 

2119 if oldstate != state: 

2120 needupdate = True 

2121 marker.set_alerted(state) 

2122 if state: 

2123 self.message = marker.hoover_message() 

2124 

2125 if not haveone: 

2126 self.message = None 

2127 

2128 if needupdate: 

2129 self.update() 

2130 

2131 def event(self, event): 

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

2133 self.keyPressEvent(event) 

2134 return True 

2135 else: 

2136 return base.event(self, event) 

2137 

2138 def keyPressEvent(self, key_event): 

2139 self.show_all = False 

2140 dt = self.tmax - self.tmin 

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

2142 

2143 key = key_event.key() 

2144 try: 

2145 keytext = str(key_event.text()) 

2146 except UnicodeEncodeError: 

2147 return 

2148 

2149 if key == qc.Qt.Key_Space: 

2150 self.interrupt_following() 

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

2152 

2153 elif key == qc.Qt.Key_Up: 

2154 for m in self.selected_markers(): 

2155 if isinstance(m, PhaseMarker): 

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

2157 p = 0 

2158 else: 

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

2160 m.set_polarity(p) 

2161 

2162 elif key == qc.Qt.Key_Down: 

2163 for m in self.selected_markers(): 

2164 if isinstance(m, PhaseMarker): 

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

2166 p = 0 

2167 else: 

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

2169 m.set_polarity(p) 

2170 

2171 elif key == qc.Qt.Key_B: 

2172 dt = self.tmax - self.tmin 

2173 self.interrupt_following() 

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

2175 

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

2177 self.interrupt_following() 

2178 

2179 tgo = None 

2180 

2181 class TraceDummy(object): 

2182 def __init__(self, marker): 

2183 self._marker = marker 

2184 

2185 @property 

2186 def nslc_id(self): 

2187 return self._marker.one_nslc() 

2188 

2189 def marker_to_itrack(marker): 

2190 try: 

2191 return self.key_to_row.get( 

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

2193 

2194 except MarkerOneNSLCRequired: 

2195 return -1 

2196 

2197 emarker, pmarkers = self.get_active_markers() 

2198 pmarkers = [ 

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

2200 pmarkers.sort(key=lambda m: ( 

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

2202 

2203 if key == qc.Qt.Key_Backtab: 

2204 pmarkers.reverse() 

2205 

2206 smarkers = self.selected_markers() 

2207 iselected = [] 

2208 for sm in smarkers: 

2209 try: 

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

2211 except ValueError: 

2212 pass 

2213 

2214 if iselected: 

2215 icurrent = max(iselected) + 1 

2216 else: 

2217 icurrent = 0 

2218 

2219 if icurrent < len(pmarkers): 

2220 self.deselect_all() 

2221 cmarker = pmarkers[icurrent] 

2222 cmarker.selected = True 

2223 tgo = cmarker.tmin 

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

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

2226 

2227 itrack = marker_to_itrack(cmarker) 

2228 if itrack != -1: 

2229 if itrack < self.shown_tracks_range[0]: 

2230 self.scroll_tracks( 

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

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

2233 self.scroll_tracks( 

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

2235 

2236 if itrack not in self.track_to_nslc_ids: 

2237 self.go_to_selection() 

2238 

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

2240 smarkers = self.selected_markers() 

2241 tgo = None 

2242 dir = str(keytext) 

2243 if smarkers: 

2244 tmid = smarkers[0].tmin 

2245 for smarker in smarkers: 

2246 if dir == 'n': 

2247 tmid = max(smarker.tmin, tmid) 

2248 else: 

2249 tmid = min(smarker.tmin, tmid) 

2250 

2251 tgo = tmid 

2252 

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

2254 for marker in sorted( 

2255 self.markers, 

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

2257 

2258 t = marker.tmin 

2259 if t > tmid and \ 

2260 marker.kind in self.visible_marker_kinds and \ 

2261 (dir == 'n' or 

2262 isinstance(marker, EventMarker)): 

2263 

2264 self.deselect_all() 

2265 marker.selected = True 

2266 tgo = t 

2267 break 

2268 else: 

2269 for marker in sorted( 

2270 self.markers, 

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

2272 reverse=True): 

2273 

2274 t = marker.tmin 

2275 if t < tmid and \ 

2276 marker.kind in self.visible_marker_kinds and \ 

2277 (dir == 'p' or 

2278 isinstance(marker, EventMarker)): 

2279 self.deselect_all() 

2280 marker.selected = True 

2281 tgo = t 

2282 break 

2283 

2284 if tgo is not None: 

2285 self.interrupt_following() 

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

2287 

2288 elif keytext == 'r': 

2289 if self.pile.reload_modified(): 

2290 self.reloaded = True 

2291 

2292 elif keytext == 'R': 

2293 self.setup_snufflings() 

2294 

2295 elif key == qc.Qt.Key_Backspace: 

2296 self.remove_selected_markers() 

2297 

2298 elif keytext == 'a': 

2299 for marker in self.markers: 

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

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

2302 marker.kind in self.visible_marker_kinds): 

2303 marker.selected = True 

2304 else: 

2305 marker.selected = False 

2306 

2307 elif keytext == 'A': 

2308 for marker in self.markers: 

2309 if marker.kind in self.visible_marker_kinds: 

2310 marker.selected = True 

2311 

2312 elif keytext == 'd': 

2313 self.deselect_all() 

2314 

2315 elif keytext == 'E': 

2316 self.deactivate_event_marker() 

2317 

2318 elif keytext == 'e': 

2319 markers = self.selected_markers() 

2320 event_markers_in_spe = [ 

2321 marker for marker in markers 

2322 if not isinstance(marker, PhaseMarker)] 

2323 

2324 phase_markers = [ 

2325 marker for marker in markers 

2326 if isinstance(marker, PhaseMarker)] 

2327 

2328 if len(event_markers_in_spe) == 1: 

2329 event_marker = event_markers_in_spe[0] 

2330 if not isinstance(event_marker, EventMarker): 

2331 nslcs = list(event_marker.nslc_ids) 

2332 lat, lon = 0.0, 0.0 

2333 old = self.get_active_event() 

2334 if len(nslcs) == 1: 

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

2336 elif old is not None: 

2337 lat, lon = old.lat, old.lon 

2338 

2339 event_marker.convert_to_event_marker(lat, lon) 

2340 

2341 self.set_active_event_marker(event_marker) 

2342 event = event_marker.get_event() 

2343 for marker in phase_markers: 

2344 marker.set_event(event) 

2345 

2346 else: 

2347 for marker in event_markers_in_spe: 

2348 marker.convert_to_event_marker() 

2349 

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

2351 for marker in self.selected_markers(): 

2352 marker.set_kind(int(keytext)) 

2353 self.emit_selected_markers() 

2354 

2355 elif key in fkey_map: 

2356 self.handle_fkeys(key) 

2357 

2358 elif key == qc.Qt.Key_Escape: 

2359 if self.picking: 

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

2361 

2362 elif key == qc.Qt.Key_PageDown: 

2363 self.scroll_tracks( 

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

2365 

2366 elif key == qc.Qt.Key_PageUp: 

2367 self.scroll_tracks( 

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

2369 

2370 elif key == qc.Qt.Key_Plus: 

2371 self.zoom_tracks(0., 1.) 

2372 

2373 elif key == qc.Qt.Key_Minus: 

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

2375 

2376 elif key == qc.Qt.Key_Equal: 

2377 ntracks_shown = self.shown_tracks_range[1] - \ 

2378 self.shown_tracks_range[0] 

2379 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2380 self.zoom_tracks(0., dtracks) 

2381 

2382 elif key == qc.Qt.Key_Colon: 

2383 self.want_input.emit() 

2384 

2385 elif keytext == 'f': 

2386 self.toggle_fullscreen() 

2387 

2388 elif keytext == 'g': 

2389 self.go_to_selection() 

2390 

2391 elif keytext == 'G': 

2392 self.go_to_selection(tight=True) 

2393 

2394 elif keytext == 'm': 

2395 self.toggle_marker_editor() 

2396 

2397 elif keytext == 'c': 

2398 self.toggle_main_controls() 

2399 

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

2401 dir = 1 

2402 amount = 1 

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

2404 dir = -1 

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

2406 amount = 10 

2407 self.nudge_selected_markers(dir*amount) 

2408 else: 

2409 super().keyPressEvent(key_event) 

2410 

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

2412 self.emit_selected_markers() 

2413 

2414 self.update() 

2415 self.update_status() 

2416 

2417 def handle_fkeys(self, key): 

2418 self.set_phase_kind( 

2419 self.selected_markers(), 

2420 fkey_map[key] + 1) 

2421 self.emit_selected_markers() 

2422 

2423 def emit_selected_markers(self): 

2424 ibounds = [] 

2425 last_selected = False 

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

2427 this_selected = marker.is_selected() 

2428 if this_selected != last_selected: 

2429 ibounds.append(imarker) 

2430 

2431 last_selected = this_selected 

2432 

2433 if last_selected: 

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

2435 

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

2437 self.n_selected_markers = sum( 

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

2439 self.marker_selection_changed.emit(chunks) 

2440 

2441 def toggle_marker_editor(self): 

2442 self.panel_parent.toggle_marker_editor() 

2443 

2444 def toggle_main_controls(self): 

2445 self.panel_parent.toggle_main_controls() 

2446 

2447 def nudge_selected_markers(self, npixels): 

2448 a, b = self.time_projection.ur 

2449 c, d = self.time_projection.xr 

2450 for marker in self.selected_markers(): 

2451 if not isinstance(marker, EventMarker): 

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

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

2454 

2455 def toggle_fullscreen(self): 

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

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

2458 self.window().showNormal() 

2459 else: 

2460 if is_macos: 

2461 self.window().showMaximized() 

2462 else: 

2463 self.window().showFullScreen() 

2464 

2465 def about(self): 

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

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

2468 txt = f.read() 

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

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

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

2472 

2473 def help(self): 

2474 class MyScrollArea(qw.QScrollArea): 

2475 

2476 def sizeHint(self): 

2477 s = qc.QSize() 

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

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

2480 return s 

2481 

2482 with open(pyrocko.util.data_file( 

2483 'snuffler_help.html')) as f: 

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

2485 

2486 with open(pyrocko.util.data_file( 

2487 'snuffler_help_epilog.html')) as f: 

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

2489 

2490 for h in [hcheat, hepilog]: 

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

2492 h.setWordWrap(True) 

2493 

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

2495 

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

2497 scroller = qw.QScrollArea() 

2498 frame = qw.QFrame(scroller) 

2499 frame.setLineWidth(0) 

2500 layout = qw.QVBoxLayout() 

2501 layout.setContentsMargins(0, 0, 0, 0) 

2502 layout.setSpacing(0) 

2503 frame.setLayout(layout) 

2504 scroller.setWidget(frame) 

2505 scroller.setWidgetResizable(True) 

2506 frame.setBackgroundRole(qg.QPalette.Base) 

2507 for h in labels: 

2508 h.setParent(frame) 

2509 h.setMargin(3) 

2510 h.setTextInteractionFlags( 

2511 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2512 h.setBackgroundRole(qg.QPalette.Base) 

2513 layout.addWidget(h) 

2514 h.linkActivated.connect( 

2515 self.open_link) 

2516 

2517 if self.panel_parent is not None: 

2518 if target == 'panel': 

2519 self.panel_parent.add_panel( 

2520 name, scroller, True, volatile=False) 

2521 else: 

2522 self.panel_parent.add_tab(name, scroller) 

2523 

2524 def open_link(self, link): 

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

2526 

2527 def wheelEvent(self, wheel_event): 

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

2529 

2530 n = self.wheel_pos // 120 

2531 self.wheel_pos = self.wheel_pos % 120 

2532 if n == 0: 

2533 return 

2534 

2535 amount = max( 

2536 1., 

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

2538 wdelta = amount * n 

2539 

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

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

2542 / (trmax-trmin) 

2543 

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

2545 self.zoom_tracks(anchor, wdelta) 

2546 else: 

2547 self.scroll_tracks(-wdelta) 

2548 

2549 def dragEnterEvent(self, event): 

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

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

2552 event.setDropAction(qc.Qt.LinkAction) 

2553 event.accept() 

2554 

2555 def dropEvent(self, event): 

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

2557 paths = list( 

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

2559 event.acceptProposedAction() 

2560 self.load(paths) 

2561 

2562 def get_phase_name(self, kind): 

2563 return self.config.get_phase_name(kind) 

2564 

2565 def set_phase_kind(self, markers, kind): 

2566 phasename = self.get_phase_name(kind) 

2567 

2568 for marker in markers: 

2569 if isinstance(marker, PhaseMarker): 

2570 if kind == 10: 

2571 marker.convert_to_marker() 

2572 else: 

2573 marker.set_phasename(phasename) 

2574 marker.set_event(self.get_active_event()) 

2575 

2576 elif isinstance(marker, EventMarker): 

2577 pass 

2578 

2579 else: 

2580 if kind != 10: 

2581 event = self.get_active_event() 

2582 marker.convert_to_phase_marker( 

2583 event, phasename, None, False) 

2584 

2585 def set_ntracks(self, ntracks): 

2586 if self.ntracks != ntracks: 

2587 self.ntracks = ntracks 

2588 if self.shown_tracks_range is not None: 

2589 l, h = self.shown_tracks_range 

2590 else: 

2591 l, h = 0, self.ntracks 

2592 

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

2594 

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

2596 

2597 low, high = range 

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

2599 high = min(self.ntracks, high) 

2600 low = max(0, low) 

2601 high = max(1, high) 

2602 

2603 if start is None: 

2604 start = float(low) 

2605 

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

2607 self.shown_tracks_range = low, high 

2608 self.shown_tracks_start = start 

2609 

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

2611 

2612 def scroll_tracks(self, shift): 

2613 shown = self.shown_tracks_range 

2614 shiftmin = -shown[0] 

2615 shiftmax = self.ntracks-shown[1] 

2616 shift = max(shiftmin, shift) 

2617 shift = min(shiftmax, shift) 

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

2619 

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

2621 

2622 self.update() 

2623 

2624 def zoom_tracks(self, anchor, delta): 

2625 ntracks_shown = self.shown_tracks_range[1] \ 

2626 - self.shown_tracks_range[0] 

2627 

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

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

2630 return 

2631 

2632 ntracks_shown += int(round(delta)) 

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

2634 

2635 u = self.shown_tracks_start 

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

2637 nv = nu + ntracks_shown 

2638 if nv > self.ntracks: 

2639 nu -= nv - self.ntracks 

2640 nv -= nv - self.ntracks 

2641 

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

2643 

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

2645 - self.shown_tracks_range[0] 

2646 

2647 self.update() 

2648 

2649 def content_time_range(self): 

2650 pile = self.get_pile() 

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

2652 if tmin is None: 

2653 tmin = initial_time_range[0] 

2654 if tmax is None: 

2655 tmax = initial_time_range[1] 

2656 

2657 return tmin, tmax 

2658 

2659 def content_deltat_range(self): 

2660 pile = self.get_pile() 

2661 

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

2663 

2664 if deltatmin is None: 

2665 deltatmin = 0.001 

2666 

2667 if deltatmax is None: 

2668 deltatmax = 1000.0 

2669 

2670 return deltatmin, deltatmax 

2671 

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

2673 if tmax < tmin: 

2674 tmin, tmax = tmax, tmin 

2675 

2676 deltatmin = self.content_deltat_range()[0] 

2677 dt = deltatmin * self.visible_length * 0.95 

2678 

2679 if dt == 0.0: 

2680 dt = 1.0 

2681 

2682 if tight: 

2683 if tmax != tmin: 

2684 dtm = tmax - tmin 

2685 tmin -= dtm*0.1 

2686 tmax += dtm*0.1 

2687 return tmin, tmax 

2688 else: 

2689 tcenter = (tmin + tmax) / 2. 

2690 tmin = tcenter - 0.5*dt 

2691 tmax = tcenter + 0.5*dt 

2692 return tmin, tmax 

2693 

2694 if tmax-tmin < dt: 

2695 vmin, vmax = self.get_time_range() 

2696 dt = min(vmax - vmin, dt) 

2697 

2698 tcenter = (tmin+tmax)/2. 

2699 etmin, etmax = tmin, tmax 

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

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

2702 dtm = tmax-tmin 

2703 if etmin == tmin: 

2704 tmin -= dtm*0.1 

2705 if etmax == tmax: 

2706 tmax += dtm*0.1 

2707 

2708 else: 

2709 dtm = tmax-tmin 

2710 tmin -= dtm*0.1 

2711 tmax += dtm*0.1 

2712 

2713 return tmin, tmax 

2714 

2715 def go_to_selection(self, tight=False): 

2716 markers = self.selected_markers() 

2717 if markers: 

2718 tmax, tmin = self.content_time_range() 

2719 for marker in markers: 

2720 tmin = min(tmin, marker.tmin) 

2721 tmax = max(tmax, marker.tmax) 

2722 

2723 else: 

2724 if tight: 

2725 vmin, vmax = self.get_time_range() 

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

2727 else: 

2728 tmin, tmax = self.content_time_range() 

2729 

2730 tmin, tmax = self.make_good_looking_time_range( 

2731 tmin, tmax, tight=tight) 

2732 

2733 self.interrupt_following() 

2734 self.set_time_range(tmin, tmax) 

2735 self.update() 

2736 

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

2738 tmax = t 

2739 if tlen is not None: 

2740 tmax = t+tlen 

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

2742 self.interrupt_following() 

2743 self.set_time_range(tmin, tmax) 

2744 self.update() 

2745 

2746 def go_to_event_by_name(self, name): 

2747 for marker in self.markers: 

2748 if isinstance(marker, EventMarker): 

2749 event = marker.get_event() 

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

2751 tmin, tmax = self.make_good_looking_time_range( 

2752 event.time, event.time) 

2753 

2754 self.interrupt_following() 

2755 self.set_time_range(tmin, tmax) 

2756 

2757 def printit(self): 

2758 from .qt_compat import qprint 

2759 printer = qprint.QPrinter() 

2760 printer.setOrientation(qprint.QPrinter.Landscape) 

2761 

2762 dialog = qprint.QPrintDialog(printer, self) 

2763 dialog.setWindowTitle('Print') 

2764 

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

2766 return 

2767 

2768 painter = qg.QPainter() 

2769 painter.begin(printer) 

2770 page = printer.pageRect() 

2771 self.drawit( 

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

2773 

2774 painter.end() 

2775 

2776 def savesvg(self, fn=None): 

2777 

2778 if not fn: 

2779 fn, _ = qw.QFileDialog.getSaveFileName( 

2780 self, 

2781 'Save as SVG|PNG', 

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

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

2784 options=qfiledialog_options) 

2785 

2786 if fn == '': 

2787 return 

2788 

2789 fn = str(fn) 

2790 

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

2792 try: 

2793 w, h = 842, 595 

2794 margin = 0.025 

2795 m = max(w, h)*margin 

2796 

2797 generator = qsvg.QSvgGenerator() 

2798 generator.setFileName(fn) 

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

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

2801 

2802 painter = qg.QPainter() 

2803 painter.begin(generator) 

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

2805 painter.end() 

2806 

2807 except Exception as e: 

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

2809 

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

2811 pixmap = self.grab() 

2812 

2813 try: 

2814 pixmap.save(fn) 

2815 

2816 except Exception as e: 

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

2818 

2819 else: 

2820 self.fail( 

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

2822 '".png".') 

2823 

2824 def paintEvent(self, paint_ev): 

2825 ''' 

2826 Called by QT whenever widget needs to be painted. 

2827 ''' 

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

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

2830 if self.in_paint_event: 

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

2832 return 

2833 

2834 self.in_paint_event = True 

2835 

2836 painter = qg.QPainter(self) 

2837 

2838 if self.menuitem_antialias.isChecked(): 

2839 painter.setRenderHint(qg.QPainter.Antialiasing) 

2840 

2841 self.drawit(painter) 

2842 

2843 logger.debug( 

2844 'Time spent drawing: ' 

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

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

2847 (self.timer_draw - self.timer_cutout)) 

2848 

2849 logger.debug( 

2850 'Time spent processing:' 

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

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

2853 self.timer_cutout.get()) 

2854 

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

2856 self.time_last_painted = time.time() 

2857 self.in_paint_event = False 

2858 

2859 def determine_box_styles(self): 

2860 

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

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

2863 istyle = 0 

2864 trace_styles = {} 

2865 for itr, tr in enumerate(traces): 

2866 if itr > 0: 

2867 other = traces[itr-1] 

2868 if not ( 

2869 other.nslc_id == tr.nslc_id 

2870 and other.deltat == tr.deltat 

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

2872 < gap_lap_tolerance*tr.deltat): 

2873 

2874 istyle += 1 

2875 

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

2877 

2878 self.trace_styles = trace_styles 

2879 

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

2881 

2882 for v_projection in track_projections.values(): 

2883 v_projection.set_in_range(0., 1.) 

2884 

2885 def selector(x): 

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

2887 

2888 if self.trace_filter is not None: 

2889 def tselector(x): 

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

2891 

2892 else: 

2893 tselector = selector 

2894 

2895 traces = list(self.pile.iter_traces( 

2896 group_selector=selector, trace_selector=tselector)) 

2897 

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

2899 

2900 def drawbox(itrack, istyle, traces): 

2901 v_projection = track_projections[itrack] 

2902 dvmin = v_projection(0.) 

2903 dvmax = v_projection(1.) 

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

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

2906 

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

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

2909 p.fillRect(rect, style.fill_brush) 

2910 p.setPen(style.frame_pen) 

2911 p.drawRect(rect) 

2912 

2913 traces_by_style = {} 

2914 for itr, tr in enumerate(traces): 

2915 gt = self.gather(tr) 

2916 if gt not in self.key_to_row: 

2917 continue 

2918 

2919 itrack = self.key_to_row[gt] 

2920 if itrack not in track_projections: 

2921 continue 

2922 

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

2924 

2925 if len(traces) < 500: 

2926 drawbox(itrack, istyle, [tr]) 

2927 else: 

2928 if (itrack, istyle) not in traces_by_style: 

2929 traces_by_style[itrack, istyle] = [] 

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

2931 

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

2933 drawbox(itrack, istyle, traces) 

2934 

2935 def draw_visible_markers( 

2936 self, p, vcenter_projection, primary_pen): 

2937 

2938 try: 

2939 markers = self.markers.with_key_in_limited( 

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

2941 

2942 except pyrocko.pile.TooMany: 

2943 tmin = self.markers[0].tmin 

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

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

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

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

2948 v0, _ = vcenter_projection.get_out_range() 

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

2950 

2951 p.save() 

2952 

2953 pen = qg.QPen(primary_pen) 

2954 pen.setWidth(2) 

2955 pen.setStyle(qc.Qt.DotLine) 

2956 # pat = [5., 3.] 

2957 # pen.setDashPattern(pat) 

2958 p.setPen(pen) 

2959 

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

2961 s_selected = ' (all selected)' 

2962 elif self.n_selected_markers > 0: 

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

2964 else: 

2965 s_selected = '' 

2966 

2967 draw_label( 

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

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

2970 label_bg, 'LB') 

2971 

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

2973 p.drawLine(line) 

2974 p.restore() 

2975 

2976 return 

2977 

2978 for marker in markers: 

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

2980 and marker.kind in self.visible_marker_kinds: 

2981 

2982 marker.draw( 

2983 p, self.time_projection, vcenter_projection, 

2984 with_label=True) 

2985 

2986 def get_squirrel(self): 

2987 try: 

2988 return self.pile._squirrel 

2989 except AttributeError: 

2990 return None 

2991 

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

2993 sq = self.get_squirrel() 

2994 if sq is None: 

2995 return 

2996 

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

2998 v_projection = track_projections[itrack] 

2999 dvmin = v_projection(0.) 

3000 dvmax = v_projection(1.) 

3001 dtmin = time_projection.clipped(tmin, 0) 

3002 dtmax = time_projection.clipped(tmax, 1) 

3003 

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

3005 p.fillRect(rect, style.fill_brush) 

3006 p.setPen(style.frame_pen) 

3007 p.drawRect(rect) 

3008 

3009 pattern_list = [] 

3010 pattern_to_itrack = {} 

3011 for key in self.track_keys: 

3012 itrack = self.key_to_row[key] 

3013 if itrack not in track_projections: 

3014 continue 

3015 

3016 pattern = self.track_patterns[itrack] 

3017 pattern_to_itrack[tuple(pattern)] = itrack 

3018 pattern_list.append(tuple(pattern)) 

3019 

3020 vmin, vmax = self.get_time_range() 

3021 

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

3023 for coverage in sq.get_coverage( 

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

3025 itrack = pattern_to_itrack[coverage.pattern.nslc] 

3026 

3027 if coverage.changes is None: 

3028 drawbox( 

3029 itrack, coverage.tmin, coverage.tmax, 

3030 box_styles_coverage[kind][0]) 

3031 else: 

3032 t = None 

3033 pcount = 0 

3034 for tb, count in coverage.changes: 

3035 if t is not None and tb > t: 

3036 if pcount > 0: 

3037 drawbox( 

3038 itrack, t, tb, 

3039 box_styles_coverage[kind][ 

3040 min(len(box_styles_coverage)-1, 

3041 pcount)]) 

3042 

3043 t = tb 

3044 pcount = count 

3045 

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

3047 ''' 

3048 This performs the actual drawing. 

3049 ''' 

3050 

3051 self.timer_draw.start() 

3052 show_boxes = self.menuitem_showboxes.isChecked() 

3053 sq = self.get_squirrel() 

3054 

3055 if self.gather is None: 

3056 self.set_gathering() 

3057 

3058 if self.pile_has_changed: 

3059 

3060 if not self.sortingmode_change_delayed(): 

3061 self.sortingmode_change() 

3062 

3063 if show_boxes and sq is None: 

3064 self.determine_box_styles() 

3065 

3066 self.pile_has_changed = False 

3067 

3068 if h is None: 

3069 h = float(self.height()) 

3070 if w is None: 

3071 w = float(self.width()) 

3072 

3073 if printmode: 

3074 primary_color = (0, 0, 0) 

3075 else: 

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

3077 

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

3079 

3080 ax_h = float(self.ax_height) 

3081 

3082 vbottom_ax_projection = Projection() 

3083 vtop_ax_projection = Projection() 

3084 vcenter_projection = Projection() 

3085 

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

3087 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3088 vtop_ax_projection.set_out_range(0., ax_h) 

3089 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3090 vcenter_projection.set_in_range(0., 1.) 

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

3092 

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

3094 track_projections = {} 

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

3096 proj = Projection() 

3097 proj.set_out_range( 

3098 self.track_to_screen(i+0.05), 

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

3100 

3101 track_projections[i] = proj 

3102 

3103 if self.tmin > self.tmax: 

3104 return 

3105 

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

3107 vbottom_ax_projection.set_in_range(0, ax_h) 

3108 

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

3110 

3111 yscaler = pyrocko.plot.AutoScaler() 

3112 

3113 p.setPen(primary_pen) 

3114 

3115 font = qg.QFont() 

3116 font.setBold(True) 

3117 

3118 axannotfont = qg.QFont() 

3119 axannotfont.setBold(True) 

3120 axannotfont.setPointSize(8) 

3121 

3122 processed_traces = self.prepare_cutout2( 

3123 self.tmin, self.tmax, 

3124 trace_selector=self.trace_selector, 

3125 degap=self.menuitem_degap.isChecked(), 

3126 demean=self.menuitem_demean.isChecked()) 

3127 

3128 if not printmode and show_boxes: 

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

3130 or (self.view_mode is ViewMode.Waterfall 

3131 and not processed_traces): 

3132 

3133 if sq is None: 

3134 self.draw_trace_boxes( 

3135 p, self.time_projection, track_projections) 

3136 

3137 else: 

3138 self.draw_coverage( 

3139 p, self.time_projection, track_projections) 

3140 

3141 p.setFont(font) 

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

3143 

3144 color_lookup = dict( 

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

3146 

3147 self.track_to_nslc_ids = {} 

3148 nticks = 0 

3149 annot_labels = [] 

3150 

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

3152 waterfall = self.waterfall 

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

3154 waterfall.set_traces(processed_traces) 

3155 waterfall.set_cmap(self.waterfall_cmap) 

3156 waterfall.set_integrate(self.waterfall_integrate) 

3157 waterfall.set_clip( 

3158 self.waterfall_clip_min, self.waterfall_clip_max) 

3159 waterfall.show_absolute_values( 

3160 self.waterfall_show_absolute) 

3161 

3162 rect = qc.QRectF( 

3163 0, self.ax_height, 

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

3165 ) 

3166 waterfall.draw_waterfall(p, rect=rect) 

3167 

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

3169 show_scales = self.menuitem_showscalerange.isChecked() \ 

3170 or self.menuitem_showscaleaxis.isChecked() 

3171 

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

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

3174 - self.track_to_screen(0.05) 

3175 

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

3177 

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

3179 if self.menuitem_showscaleaxis.isChecked() \ 

3180 else 15 

3181 

3182 yscaler = pyrocko.plot.AutoScaler( 

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

3184 snap=show_scales 

3185 and not self.menuitem_showscaleaxis.isChecked()) 

3186 

3187 data_ranges = pyrocko.trace.minmax( 

3188 processed_traces, 

3189 key=self.scaling_key, 

3190 mode=self.scaling_base[0], 

3191 outer_mode=self.scaling_base[1]) 

3192 

3193 if not self.menuitem_fixscalerange.isChecked(): 

3194 self.old_data_ranges = data_ranges 

3195 else: 

3196 data_ranges.update(self.old_data_ranges) 

3197 

3198 self.apply_scaling_hooks(data_ranges) 

3199 

3200 trace_to_itrack = {} 

3201 track_scaling_keys = {} 

3202 track_scaling_colors = {} 

3203 for trace in processed_traces: 

3204 gt = self.gather(trace) 

3205 if gt not in self.key_to_row: 

3206 continue 

3207 

3208 itrack = self.key_to_row[gt] 

3209 if itrack not in track_projections: 

3210 continue 

3211 

3212 trace_to_itrack[trace] = itrack 

3213 

3214 if itrack not in self.track_to_nslc_ids: 

3215 self.track_to_nslc_ids[itrack] = set() 

3216 

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

3218 

3219 if itrack not in track_scaling_keys: 

3220 track_scaling_keys[itrack] = set() 

3221 

3222 scaling_key = self.scaling_key(trace) 

3223 track_scaling_keys[itrack].add(scaling_key) 

3224 

3225 color = pyrocko.plot.color( 

3226 color_lookup[self.color_gather(trace)]) 

3227 

3228 k = itrack, scaling_key 

3229 if k not in track_scaling_colors \ 

3230 and self.menuitem_colortraces.isChecked(): 

3231 track_scaling_colors[k] = color 

3232 else: 

3233 track_scaling_colors[k] = primary_color 

3234 

3235 # y axes, zero lines 

3236 trace_projections = {} 

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

3238 if itrack not in track_scaling_keys: 

3239 continue 

3240 uoff = 0 

3241 for scaling_key in track_scaling_keys[itrack]: 

3242 data_range = data_ranges[scaling_key] 

3243 dymin, dymax = data_range 

3244 ymin, ymax, yinc = yscaler.make_scale( 

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

3246 iexp = yscaler.make_exp(yinc) 

3247 factor = 10**iexp 

3248 trace_projection = track_projections[itrack].copy() 

3249 trace_projection.set_in_range(ymax, ymin) 

3250 trace_projections[itrack, scaling_key] = \ 

3251 trace_projection 

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

3253 vmin, vmax = trace_projection.get_out_range() 

3254 umax_zeroline = umax 

3255 uoffnext = uoff 

3256 

3257 if show_scales: 

3258 pen = qg.QPen(primary_pen) 

3259 k = itrack, scaling_key 

3260 if k in track_scaling_colors: 

3261 c = qg.QColor(*track_scaling_colors[ 

3262 itrack, scaling_key]) 

3263 

3264 pen.setColor(c) 

3265 

3266 p.setPen(pen) 

3267 if nlinesavail > 3: 

3268 if self.menuitem_showscaleaxis.isChecked(): 

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

3270 ny_annot = int( 

3271 math.floor(ymax/yinc) 

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

3273 

3274 for iy_annot in range(ny_annot): 

3275 y = ymin_annot + iy_annot*yinc 

3276 v = trace_projection(y) 

3277 line = qc.QLineF( 

3278 umax-10-uoff, v, umax-uoff, v) 

3279 

3280 p.drawLine(line) 

3281 if iy_annot == ny_annot - 1 \ 

3282 and iexp != 0: 

3283 sexp = ' &times; ' \ 

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

3285 else: 

3286 sexp = '' 

3287 

3288 snum = num_to_html(y/factor) 

3289 lab = Label( 

3290 p, 

3291 umax-20-uoff, 

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

3293 label_bg=None, 

3294 anchor='MR', 

3295 font=axannotfont, 

3296 color=c) 

3297 

3298 uoffnext = max( 

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

3300 

3301 annot_labels.append(lab) 

3302 if y == 0.: 

3303 umax_zeroline = \ 

3304 umax - 20 \ 

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

3306 - uoff 

3307 else: 

3308 if not show_boxes: 

3309 qpoints = make_QPolygonF( 

3310 [umax-20-uoff, 

3311 umax-10-uoff, 

3312 umax-10-uoff, 

3313 umax-20-uoff], 

3314 [vmax, vmax, vmin, vmin]) 

3315 p.drawPolyline(qpoints) 

3316 

3317 snum = num_to_html(ymin) 

3318 labmin = Label( 

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

3320 label_bg=None, 

3321 anchor='BR', 

3322 font=axannotfont, 

3323 color=c) 

3324 

3325 annot_labels.append(labmin) 

3326 snum = num_to_html(ymax) 

3327 labmax = Label( 

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

3329 label_bg=None, 

3330 anchor='TR', 

3331 font=axannotfont, 

3332 color=c) 

3333 

3334 annot_labels.append(labmax) 

3335 

3336 for lab in (labmin, labmax): 

3337 uoffnext = max( 

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

3339 

3340 if self.menuitem_showzeroline.isChecked(): 

3341 v = trace_projection(0.) 

3342 if vmin <= v <= vmax: 

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

3344 p.drawLine(line) 

3345 

3346 uoff = uoffnext 

3347 

3348 p.setFont(font) 

3349 p.setPen(primary_pen) 

3350 for trace in processed_traces: 

3351 if self.view_mode is not ViewMode.Wiggle: 

3352 break 

3353 

3354 if trace not in trace_to_itrack: 

3355 continue 

3356 

3357 itrack = trace_to_itrack[trace] 

3358 scaling_key = self.scaling_key(trace) 

3359 trace_projection = trace_projections[ 

3360 itrack, scaling_key] 

3361 

3362 vdata = trace_projection(trace.get_ydata()) 

3363 

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

3365 udata_max = float(self.time_projection( 

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

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

3368 

3369 qpoints = make_QPolygonF(udata, vdata) 

3370 

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

3372 vmin, vmax = trace_projection.get_out_range() 

3373 

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

3375 

3376 if self.menuitem_cliptraces.isChecked(): 

3377 p.setClipRect(trackrect) 

3378 

3379 if self.menuitem_colortraces.isChecked(): 

3380 color = pyrocko.plot.color( 

3381 color_lookup[self.color_gather(trace)]) 

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

3383 p.setPen(pen) 

3384 

3385 p.drawPolyline(qpoints) 

3386 

3387 if self.floating_marker: 

3388 self.floating_marker.draw_trace( 

3389 self, p, trace, 

3390 self.time_projection, trace_projection, 1.0) 

3391 

3392 for marker in self.markers.with_key_in( 

3393 self.tmin - self.markers_deltat_max, 

3394 self.tmax): 

3395 

3396 if marker.tmin < self.tmax \ 

3397 and self.tmin < marker.tmax \ 

3398 and marker.kind \ 

3399 in self.visible_marker_kinds: 

3400 marker.draw_trace( 

3401 self, p, trace, self.time_projection, 

3402 trace_projection, 1.0) 

3403 

3404 p.setPen(primary_pen) 

3405 

3406 if self.menuitem_cliptraces.isChecked(): 

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

3408 

3409 if self.floating_marker: 

3410 self.floating_marker.draw( 

3411 p, self.time_projection, vcenter_projection) 

3412 

3413 self.draw_visible_markers( 

3414 p, vcenter_projection, primary_pen) 

3415 

3416 p.setPen(primary_pen) 

3417 while font.pointSize() > 2: 

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

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

3420 - self.track_to_screen(0.05) 

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

3422 if nlinesavail > 1: 

3423 break 

3424 

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

3426 

3427 p.setFont(font) 

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

3429 

3430 for key in self.track_keys: 

3431 itrack = self.key_to_row[key] 

3432 if itrack in track_projections: 

3433 plabel = ' '.join( 

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

3435 lx = 10 

3436 ly = self.track_to_screen(itrack+0.5) 

3437 

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

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

3440 continue 

3441 

3442 contains_cursor = \ 

3443 self.track_to_screen(itrack) \ 

3444 < mouse_pos.y() \ 

3445 < self.track_to_screen(itrack+1) 

3446 

3447 if not contains_cursor: 

3448 continue 

3449 

3450 font_large = p.font() 

3451 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3452 p.setFont(font_large) 

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

3454 p.setFont(font) 

3455 

3456 for lab in annot_labels: 

3457 lab.draw() 

3458 

3459 self.timer_draw.stop() 

3460 

3461 def see_data_params(self): 

3462 

3463 min_deltat = self.content_deltat_range()[0] 

3464 

3465 # determine padding and downampling requirements 

3466 if self.lowpass is not None: 

3467 deltat_target = 1./self.lowpass * 0.25 

3468 ndecimate = min( 

3469 50, 

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

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

3472 else: 

3473 ndecimate = 1 

3474 tpad = min_deltat*5. 

3475 

3476 if self.highpass is not None: 

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

3478 

3479 nsee_points_per_trace = 5000*10 

3480 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3481 

3482 return ndecimate, tpad, tsee 

3483 

3484 def clean_update(self): 

3485 self.cached_processed_traces = None 

3486 self.update() 

3487 

3488 def get_adequate_tpad(self): 

3489 tpad = 0. 

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

3491 if f is not None: 

3492 tpad = max(tpad, 1.0/f) 

3493 

3494 for snuffling in self.snufflings: 

3495 if snuffling._post_process_hook_enabled \ 

3496 or snuffling._pre_process_hook_enabled: 

3497 

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

3499 

3500 return tpad 

3501 

3502 def prepare_cutout2( 

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

3504 demean=True, nmax=6000): 

3505 

3506 if self.pile.is_empty(): 

3507 return [] 

3508 

3509 nmax = self.visible_length 

3510 

3511 self.timer_cutout.start() 

3512 

3513 tsee = tmax-tmin 

3514 min_deltat_wo_decimate = tsee/nmax 

3515 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3516 

3517 min_deltat_allow = min_deltat_wo_decimate 

3518 if self.lowpass is not None: 

3519 target_deltat_lp = 0.25/self.lowpass 

3520 if target_deltat_lp > min_deltat_wo_decimate: 

3521 min_deltat_allow = min_deltat_w_decimate 

3522 

3523 min_deltat_allow = math.exp( 

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

3525 

3526 tmin_ = tmin 

3527 tmax_ = tmax 

3528 

3529 # fetch more than needed? 

3530 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3534 

3535 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3536 lphp = self.menuitem_lphp.isChecked() 

3537 ads = self.menuitem_allowdownsampling.isChecked() 

3538 

3539 tpad = self.get_adequate_tpad() 

3540 tpad = max(tpad, tsee) 

3541 

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

3543 vec = ( 

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

3545 self.highpass, fft_filtering, lphp, 

3546 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3547 ads, self.pile.get_update_count()) 

3548 

3549 if (self.cached_vec 

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

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

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

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

3554 and self.cached_processed_traces is not None): 

3555 

3556 logger.debug('Using cached traces') 

3557 processed_traces = self.cached_processed_traces 

3558 

3559 else: 

3560 processed_traces = [] 

3561 if self.pile.deltatmax >= min_deltat_allow: 

3562 

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

3564 def group_selector(gr): 

3565 return gr.deltatmax >= min_deltat_allow 

3566 

3567 kwargs = dict(group_selector=group_selector) 

3568 else: 

3569 kwargs = {} 

3570 

3571 if trace_selector is not None: 

3572 def trace_selectorx(tr): 

3573 return tr.deltat >= min_deltat_allow \ 

3574 and trace_selector(tr) 

3575 else: 

3576 def trace_selectorx(tr): 

3577 return tr.deltat >= min_deltat_allow 

3578 

3579 for traces in self.pile.chopper( 

3580 tmin=tmin, tmax=tmax, tpad=tpad, 

3581 want_incomplete=True, 

3582 degap=degap, 

3583 maxgap=gap_lap_tolerance, 

3584 maxlap=gap_lap_tolerance, 

3585 keep_current_files_open=True, 

3586 trace_selector=trace_selectorx, 

3587 accessor_id=id(self), 

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

3589 include_last=True, **kwargs): 

3590 

3591 if demean: 

3592 for tr in traces: 

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

3594 continue 

3595 y = tr.get_ydata() 

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

3597 

3598 traces = self.pre_process_hooks(traces) 

3599 

3600 for trace in traces: 

3601 

3602 if not (trace.meta 

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

3604 

3605 if fft_filtering: 

3606 but = pyrocko.response.ButterworthResponse 

3607 multres = pyrocko.response.MultiplyResponse 

3608 if self.lowpass is not None \ 

3609 or self.highpass is not None: 

3610 

3611 it = num.arange( 

3612 trace.data_len(), dtype=float) 

3613 detr_data, m, b = detrend( 

3614 it, trace.get_ydata()) 

3615 

3616 trace.set_ydata(detr_data) 

3617 

3618 freqs, fdata = trace.spectrum( 

3619 pad_to_pow2=True, tfade=None) 

3620 

3621 nfreqs = fdata.size 

3622 

3623 key = (trace.deltat, nfreqs) 

3624 

3625 if key not in self.tf_cache: 

3626 resps = [] 

3627 if self.lowpass is not None: 

3628 resps.append(but( 

3629 order=4, 

3630 corner=self.lowpass, 

3631 type='low')) 

3632 

3633 if self.highpass is not None: 

3634 resps.append(but( 

3635 order=4, 

3636 corner=self.highpass, 

3637 type='high')) 

3638 

3639 resp = multres(resps) 

3640 self.tf_cache[key] = \ 

3641 resp.evaluate(freqs) 

3642 

3643 filtered_data = num.fft.irfft( 

3644 fdata*self.tf_cache[key] 

3645 )[:trace.data_len()] 

3646 

3647 retrended_data = retrend( 

3648 it, filtered_data, m, b) 

3649 

3650 trace.set_ydata(retrended_data) 

3651 

3652 else: 

3653 

3654 if ads and self.lowpass is not None: 

3655 while trace.deltat \ 

3656 < min_deltat_wo_decimate: 

3657 

3658 trace.downsample(2, demean=False) 

3659 

3660 fmax = 0.5/trace.deltat 

3661 if not lphp and ( 

3662 self.lowpass is not None 

3663 and self.highpass is not None 

3664 and self.lowpass < fmax 

3665 and self.highpass < fmax 

3666 and self.highpass < self.lowpass): 

3667 

3668 trace.bandpass( 

3669 2, self.highpass, self.lowpass) 

3670 else: 

3671 if self.lowpass is not None: 

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

3673 trace.lowpass( 

3674 4, self.lowpass, 

3675 demean=False) 

3676 

3677 if self.highpass is not None: 

3678 if self.lowpass is None \ 

3679 or self.highpass \ 

3680 < self.lowpass: 

3681 

3682 if self.highpass < \ 

3683 0.5/trace.deltat: 

3684 trace.highpass( 

3685 4, self.highpass, 

3686 demean=False) 

3687 

3688 processed_traces.append(trace) 

3689 

3690 if self.rotate != 0.0: 

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

3692 cphi = math.cos(phi) 

3693 sphi = math.sin(phi) 

3694 for a in processed_traces: 

3695 for b in processed_traces: 

3696 if (a.network == b.network 

3697 and a.station == b.station 

3698 and a.location == b.location 

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

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

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

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

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

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

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

3706 

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

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

3709 a.set_ydata(aydata) 

3710 b.set_ydata(bydata) 

3711 

3712 processed_traces = self.post_process_hooks(processed_traces) 

3713 

3714 self.cached_processed_traces = processed_traces 

3715 self.cached_vec = vec 

3716 

3717 chopped_traces = [] 

3718 for trace in processed_traces: 

3719 chop_tmin = tmin_ - trace.deltat*4 

3720 chop_tmax = tmax_ + trace.deltat*4 

3721 

3722 try: 

3723 ctrace = trace.chop( 

3724 chop_tmin, chop_tmax, 

3725 inplace=False) 

3726 

3727 except pyrocko.trace.NoData: 

3728 continue 

3729 

3730 if ctrace.data_len() < 2: 

3731 continue 

3732 

3733 chopped_traces.append(ctrace) 

3734 

3735 self.timer_cutout.stop() 

3736 return chopped_traces 

3737 

3738 def pre_process_hooks(self, traces): 

3739 for snuffling in self.snufflings: 

3740 if snuffling._pre_process_hook_enabled: 

3741 traces = snuffling.pre_process_hook(traces) 

3742 

3743 return traces 

3744 

3745 def post_process_hooks(self, traces): 

3746 for snuffling in self.snufflings: 

3747 if snuffling._post_process_hook_enabled: 

3748 traces = snuffling.post_process_hook(traces) 

3749 

3750 return traces 

3751 

3752 def visible_length_change(self, ignore=None): 

3753 for menuitem, vlen in self.menuitems_visible_length: 

3754 if menuitem.isChecked(): 

3755 self.visible_length = vlen 

3756 

3757 def scaling_base_change(self, ignore=None): 

3758 for menuitem, scaling_base in self.menuitems_scaling_base: 

3759 if menuitem.isChecked(): 

3760 self.scaling_base = scaling_base 

3761 

3762 def scalingmode_change(self, ignore=None): 

3763 for menuitem, scaling_key in self.menuitems_scaling: 

3764 if menuitem.isChecked(): 

3765 self.scaling_key = scaling_key 

3766 self.update() 

3767 

3768 def apply_scaling_hooks(self, data_ranges): 

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

3770 hook = self.scaling_hooks[k] 

3771 hook(data_ranges) 

3772 

3773 def viewmode_change(self, ignore=True): 

3774 for item, mode in self.menuitems_viewmode: 

3775 if item.isChecked(): 

3776 self.view_mode = mode 

3777 break 

3778 else: 

3779 raise AttributeError('unknown view mode') 

3780 

3781 items_waterfall_disabled = ( 

3782 self.menuitem_showscaleaxis, 

3783 self.menuitem_showscalerange, 

3784 self.menuitem_showzeroline, 

3785 self.menuitem_colortraces, 

3786 self.menuitem_cliptraces, 

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

3788 ) 

3789 

3790 if self.view_mode is ViewMode.Waterfall: 

3791 self.parent().show_colorbar_ctrl(True) 

3792 self.parent().show_gain_ctrl(False) 

3793 

3794 for item in items_waterfall_disabled: 

3795 item.setDisabled(True) 

3796 

3797 self.visible_length = 180. 

3798 else: 

3799 self.parent().show_colorbar_ctrl(False) 

3800 self.parent().show_gain_ctrl(True) 

3801 

3802 for item in items_waterfall_disabled: 

3803 item.setDisabled(False) 

3804 

3805 self.visible_length_change() 

3806 self.update() 

3807 

3808 def set_scaling_hook(self, k, hook): 

3809 self.scaling_hooks[k] = hook 

3810 

3811 def remove_scaling_hook(self, k): 

3812 del self.scaling_hooks[k] 

3813 

3814 def remove_scaling_hooks(self): 

3815 self.scaling_hooks = {} 

3816 

3817 def s_sortingmode_change(self, ignore=None): 

3818 for menuitem, valfunc in self.menuitems_ssorting: 

3819 if menuitem.isChecked(): 

3820 self._ssort = valfunc 

3821 

3822 self.sortingmode_change() 

3823 

3824 def sortingmode_change(self, ignore=None): 

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

3826 if menuitem.isChecked(): 

3827 self.set_gathering(gather, color) 

3828 

3829 self.sortingmode_change_time = time.time() 

3830 

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

3832 self.lowpass = value 

3833 self.passband_check() 

3834 self.tf_cache = {} 

3835 self.update() 

3836 

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

3838 self.highpass = value 

3839 self.passband_check() 

3840 self.tf_cache = {} 

3841 self.update() 

3842 

3843 def passband_check(self): 

3844 if self.highpass and self.lowpass \ 

3845 and self.highpass >= self.lowpass: 

3846 

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

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

3849 'deactivate the highpass.' 

3850 

3851 self.update_status() 

3852 else: 

3853 oldmess = self.message 

3854 self.message = None 

3855 if oldmess is not None: 

3856 self.update_status() 

3857 

3858 def gain_change(self, value, ignore): 

3859 self.gain = value 

3860 self.update() 

3861 

3862 def rot_change(self, value, ignore): 

3863 self.rotate = value 

3864 self.update() 

3865 

3866 def waterfall_cmap_change(self, cmap): 

3867 self.waterfall_cmap = cmap 

3868 self.update() 

3869 

3870 def waterfall_clip_change(self, clip_min, clip_max): 

3871 self.waterfall_clip_min = clip_min 

3872 self.waterfall_clip_max = clip_max 

3873 self.update() 

3874 

3875 def waterfall_show_absolute_change(self, toggle): 

3876 self.waterfall_show_absolute = toggle 

3877 self.update() 

3878 

3879 def waterfall_set_integrate(self, toggle): 

3880 self.waterfall_integrate = toggle 

3881 self.update() 

3882 

3883 def set_selected_markers(self, markers): 

3884 ''' 

3885 Set a list of markers selected 

3886 

3887 :param markers: list of markers 

3888 ''' 

3889 self.deselect_all() 

3890 for m in markers: 

3891 m.selected = True 

3892 

3893 self.update() 

3894 

3895 def deselect_all(self): 

3896 for marker in self.markers: 

3897 marker.selected = False 

3898 

3899 def animate_picking(self): 

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

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

3902 

3903 def get_nslc_ids_for_track(self, ftrack): 

3904 itrack = int(ftrack) 

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

3906 

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

3908 if self.picking: 

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

3910 self.picking = None 

3911 self.picking_down = None 

3912 self.picking_timer.stop() 

3913 self.picking_timer = None 

3914 if not abort: 

3915 self.add_marker(self.floating_marker) 

3916 self.floating_marker.selected = True 

3917 self.emit_selected_markers() 

3918 

3919 self.floating_marker = None 

3920 

3921 def start_picking(self, ignore): 

3922 

3923 if not self.picking: 

3924 self.deselect_all() 

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

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

3927 

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

3929 self.picking.setGeometry( 

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

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

3932 

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

3934 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3936 self.floating_marker.selected = True 

3937 

3938 self.picking_timer = qc.QTimer() 

3939 self.picking_timer.timeout.connect( 

3940 self.animate_picking) 

3941 

3942 self.picking_timer.setInterval(50) 

3943 self.picking_timer.start() 

3944 

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

3946 if self.picking: 

3947 mouset = self.time_projection.rev(x) 

3948 dt = 0.0 

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

3950 if mouset < self.tmin: 

3951 dt = -(self.tmin - mouset) 

3952 else: 

3953 dt = mouset - self.tmax 

3954 ddt = self.tmax-self.tmin 

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

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

3957 

3958 x0 = x 

3959 if self.picking_down is not None: 

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

3961 

3962 w = abs(x-x0) 

3963 x0 = min(x0, x) 

3964 

3965 tmin, tmax = ( 

3966 self.time_projection.rev(x0), 

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

3968 

3969 tmin, tmax = ( 

3970 max(working_system_time_range[0], tmin), 

3971 min(working_system_time_range[1], tmax)) 

3972 

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

3974 

3975 self.picking.setGeometry( 

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

3977 

3978 ftrack = self.track_to_screen.rev(y) 

3979 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3981 

3982 if dt != 0.0 and doshift: 

3983 self.interrupt_following() 

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

3985 

3986 self.update() 

3987 

3988 def update_status(self): 

3989 

3990 if self.message is None: 

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

3992 

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

3994 if not is_working_time(mouse_t): 

3995 return 

3996 

3997 if self.floating_marker: 

3998 tmi, tma = ( 

3999 self.floating_marker.tmin, 

4000 self.floating_marker.tmax) 

4001 

4002 tt, ms = gmtime_x(tmi) 

4003 

4004 if tmi == tma: 

4005 message = mystrftime( 

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

4007 tt=tt, milliseconds=ms) 

4008 else: 

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

4010 message = mystrftime( 

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

4012 tt=tt, milliseconds=ms) 

4013 else: 

4014 tt, ms = gmtime_x(mouse_t) 

4015 

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

4017 else: 

4018 message = self.message 

4019 

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

4021 sb.clearMessage() 

4022 sb.showMessage(message) 

4023 

4024 def set_sortingmode_change_delay_time(self, dt): 

4025 self.sortingmode_change_delay_time = dt 

4026 

4027 def sortingmode_change_delayed(self): 

4028 now = time.time() 

4029 return ( 

4030 self.sortingmode_change_delay_time is not None 

4031 and now - self.sortingmode_change_time 

4032 < self.sortingmode_change_delay_time) 

4033 

4034 def set_visible_marker_kinds(self, kinds): 

4035 self.deselect_all() 

4036 self.visible_marker_kinds = tuple(kinds) 

4037 self.emit_selected_markers() 

4038 

4039 def following(self): 

4040 return self.follow_timer is not None \ 

4041 and not self.following_interrupted() 

4042 

4043 def interrupt_following(self): 

4044 self.interactive_range_change_time = time.time() 

4045 

4046 def following_interrupted(self, now=None): 

4047 if now is None: 

4048 now = time.time() 

4049 return now - self.interactive_range_change_time \ 

4050 < self.interactive_range_change_delay_time 

4051 

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

4053 if tmax_start is None: 

4054 tmax_start = time.time() 

4055 self.show_all = False 

4056 self.follow_time = tlen 

4057 self.follow_timer = qc.QTimer(self) 

4058 self.follow_timer.timeout.connect( 

4059 self.follow_update) 

4060 self.follow_timer.setInterval(interval) 

4061 self.follow_timer.start() 

4062 self.follow_started = time.time() 

4063 self.follow_lapse = lapse 

4064 self.follow_tshift = self.follow_started - tmax_start 

4065 self.interactive_range_change_time = 0.0 

4066 

4067 def unfollow(self): 

4068 if self.follow_timer is not None: 

4069 self.follow_timer.stop() 

4070 self.follow_timer = None 

4071 self.interactive_range_change_time = 0.0 

4072 

4073 def follow_update(self): 

4074 rnow = time.time() 

4075 if self.follow_lapse is None: 

4076 now = rnow 

4077 else: 

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

4079 * self.follow_lapse 

4080 

4081 if self.following_interrupted(rnow): 

4082 return 

4083 self.set_time_range( 

4084 now-self.follow_time-self.follow_tshift, 

4085 now-self.follow_tshift) 

4086 

4087 self.update() 

4088 

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

4090 self.return_tag = return_tag 

4091 self.window().close() 

4092 

4093 def cleanup(self): 

4094 self.about_to_close.emit() 

4095 self.timer.stop() 

4096 if self.follow_timer is not None: 

4097 self.follow_timer.stop() 

4098 

4099 for snuffling in list(self.snufflings): 

4100 self.remove_snuffling(snuffling) 

4101 

4102 def set_error_message(self, key, value): 

4103 if value is None: 

4104 if key in self.error_messages: 

4105 del self.error_messages[key] 

4106 else: 

4107 self.error_messages[key] = value 

4108 

4109 def inputline_changed(self, text): 

4110 pass 

4111 

4112 def inputline_finished(self, text): 

4113 line = str(text) 

4114 

4115 toks = line.split() 

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

4117 if len(toks) >= 1: 

4118 command = toks[0].lower() 

4119 

4120 try: 

4121 quick_filter_commands = { 

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

4123 's': '*.%s.*.*', 

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

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

4126 

4127 if command in quick_filter_commands: 

4128 if len(toks) >= 2: 

4129 patterns = [ 

4130 quick_filter_commands[toks[0]] % pat 

4131 for pat in toks[1:]] 

4132 self.set_quick_filter_patterns(patterns, line) 

4133 else: 

4134 self.set_quick_filter_patterns(None) 

4135 

4136 self.update() 

4137 

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

4139 if len(toks) >= 2: 

4140 patterns = [] 

4141 if len(toks) == 2: 

4142 patterns = [toks[1]] 

4143 elif len(toks) >= 3: 

4144 x = { 

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

4146 's': '*.%s.*.*', 

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

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

4149 

4150 if toks[1] in x: 

4151 patterns.extend( 

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

4153 

4154 for pattern in patterns: 

4155 if command == 'hide': 

4156 self.add_blacklist_pattern(pattern) 

4157 else: 

4158 self.remove_blacklist_pattern(pattern) 

4159 

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

4161 self.clear_blacklist() 

4162 

4163 clearit = True 

4164 

4165 self.update() 

4166 

4167 elif command == 'markers': 

4168 if len(toks) == 2: 

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

4170 kinds = self.all_marker_kinds 

4171 else: 

4172 kinds = [] 

4173 for x in toks[1]: 

4174 try: 

4175 kinds.append(int(x)) 

4176 except Exception: 

4177 pass 

4178 

4179 self.set_visible_marker_kinds(kinds) 

4180 

4181 elif len(toks) == 1: 

4182 self.set_visible_marker_kinds(()) 

4183 

4184 self.update() 

4185 

4186 elif command == 'scaling': 

4187 if len(toks) == 2: 

4188 hideit = False 

4189 error = 'wrong number of arguments' 

4190 

4191 if len(toks) >= 3: 

4192 vmin, vmax = [ 

4193 pyrocko.model.float_or_none(x) 

4194 for x in toks[-2:]] 

4195 

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

4197 if k in d: 

4198 if vmin is not None: 

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

4200 if vmax is not None: 

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

4202 

4203 if len(toks) == 1: 

4204 self.remove_scaling_hooks() 

4205 

4206 elif len(toks) == 3: 

4207 def hook(data_ranges): 

4208 for k in data_ranges: 

4209 upd(data_ranges, k, vmin, vmax) 

4210 

4211 self.set_scaling_hook('_', hook) 

4212 

4213 elif len(toks) == 4: 

4214 pattern = toks[1] 

4215 

4216 def hook(data_ranges): 

4217 for k in pyrocko.util.match_nslcs( 

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

4219 

4220 upd(data_ranges, k, vmin, vmax) 

4221 

4222 self.set_scaling_hook(pattern, hook) 

4223 

4224 elif command == 'goto': 

4225 toks2 = line.split(None, 1) 

4226 if len(toks2) == 2: 

4227 arg = toks2[1] 

4228 m = re.match( 

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

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

4231 if m: 

4232 tlen = None 

4233 if not m.group(1): 

4234 tlen = 12*32*24*60*60 

4235 elif not m.group(2): 

4236 tlen = 32*24*60*60 

4237 elif not m.group(3): 

4238 tlen = 24*60*60 

4239 elif not m.group(4): 

4240 tlen = 60*60 

4241 elif not m.group(5): 

4242 tlen = 60 

4243 

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

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

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

4247 t = pyrocko.util.str_to_time(arg) 

4248 self.go_to_time(t, tlen=tlen) 

4249 

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

4251 supl = '00:00:00' 

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

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

4254 tmin, tmax = self.get_time_range() 

4255 sdate = pyrocko.util.time_to_str( 

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

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

4258 self.go_to_time(t) 

4259 

4260 elif arg == 'today': 

4261 self.go_to_time( 

4262 day_start( 

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

4264 

4265 elif arg == 'yesterday': 

4266 self.go_to_time( 

4267 day_start( 

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

4269 

4270 else: 

4271 self.go_to_event_by_name(arg) 

4272 

4273 else: 

4274 raise PileViewerMainException( 

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

4276 

4277 except PileViewerMainException as e: 

4278 error = str(e) 

4279 hideit = False 

4280 

4281 return clearit, hideit, error 

4282 

4283 return PileViewerMain 

4284 

4285 

4286PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4287GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget) 

4288 

4289 

4290class LineEditWithAbort(qw.QLineEdit): 

4291 

4292 aborted = qc.pyqtSignal() 

4293 history_down = qc.pyqtSignal() 

4294 history_up = qc.pyqtSignal() 

4295 

4296 def keyPressEvent(self, key_event): 

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

4298 self.aborted.emit() 

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

4300 self.history_down.emit() 

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

4302 self.history_up.emit() 

4303 else: 

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

4305 

4306 

4307class PileViewer(qw.QFrame): 

4308 ''' 

4309 PileViewerMain + Controls + Inputline 

4310 ''' 

4311 

4312 def __init__( 

4313 self, pile, 

4314 ntracks_shown_max=20, 

4315 marker_editor_sortable=True, 

4316 use_opengl=None, 

4317 panel_parent=None, 

4318 *args): 

4319 

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

4321 

4322 layout = qw.QGridLayout() 

4323 layout.setContentsMargins(0, 0, 0, 0) 

4324 layout.setSpacing(0) 

4325 

4326 self.menu = PileViewerMenuBar(self) 

4327 

4328 if use_opengl is None: 

4329 use_opengl = is_macos 

4330 

4331 if use_opengl: 

4332 self.viewer = GLPileViewerMain( 

4333 pile, 

4334 ntracks_shown_max=ntracks_shown_max, 

4335 panel_parent=panel_parent, 

4336 menu=self.menu) 

4337 else: 

4338 self.viewer = PileViewerMain( 

4339 pile, 

4340 ntracks_shown_max=ntracks_shown_max, 

4341 panel_parent=panel_parent, 

4342 menu=self.menu) 

4343 

4344 self.marker_editor_sortable = marker_editor_sortable 

4345 

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

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

4348 

4349 self.input_area = qw.QFrame(self) 

4350 ia_layout = qw.QGridLayout() 

4351 ia_layout.setContentsMargins(11, 11, 11, 11) 

4352 self.input_area.setLayout(ia_layout) 

4353 

4354 self.inputline = LineEditWithAbort(self.input_area) 

4355 self.inputline.returnPressed.connect( 

4356 self.inputline_returnpressed) 

4357 self.inputline.editingFinished.connect( 

4358 self.inputline_finished) 

4359 self.inputline.aborted.connect( 

4360 self.inputline_aborted) 

4361 

4362 self.inputline.history_down.connect( 

4363 lambda: self.step_through_history(1)) 

4364 self.inputline.history_up.connect( 

4365 lambda: self.step_through_history(-1)) 

4366 

4367 self.inputline.textEdited.connect( 

4368 self.inputline_changed) 

4369 

4370 self.inputline.setPlaceholderText( 

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

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

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

4374 self.input_area.hide() 

4375 self.history = None 

4376 

4377 self.inputline_error_str = None 

4378 

4379 self.inputline_error = qw.QLabel() 

4380 self.inputline_error.hide() 

4381 

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

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

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

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

4386 

4387 pb = Progressbars(self) 

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

4389 self.progressbars = pb 

4390 

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

4392 self.scrollbar = scrollbar 

4393 layout.addWidget(scrollbar, 1, 1) 

4394 self.scrollbar.valueChanged.connect( 

4395 self.scrollbar_changed) 

4396 

4397 self.block_scrollbar_changes = False 

4398 

4399 self.viewer.want_input.connect( 

4400 self.inputline_show) 

4401 self.viewer.tracks_range_changed.connect( 

4402 self.tracks_range_changed) 

4403 self.viewer.pile_has_changed_signal.connect( 

4404 self.adjust_controls) 

4405 self.viewer.about_to_close.connect( 

4406 self.save_inputline_history) 

4407 

4408 self.setLayout(layout) 

4409 

4410 def cleanup(self): 

4411 self.viewer.cleanup() 

4412 

4413 def get_progressbars(self): 

4414 return self.progressbars 

4415 

4416 def inputline_show(self): 

4417 if not self.history: 

4418 self.load_inputline_history() 

4419 

4420 self.input_area.show() 

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

4422 self.inputline.selectAll() 

4423 

4424 def inputline_set_error(self, string): 

4425 self.inputline_error_str = string 

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

4427 self.inputline.selectAll() 

4428 self.inputline_error.setText(string) 

4429 self.input_area.show() 

4430 self.inputline_error.show() 

4431 

4432 def inputline_clear_error(self): 

4433 if self.inputline_error_str: 

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

4435 self.inputline_error_str = None 

4436 self.inputline_error.clear() 

4437 self.inputline_error.hide() 

4438 

4439 def inputline_changed(self, line): 

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

4441 self.inputline_clear_error() 

4442 

4443 def inputline_returnpressed(self): 

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

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

4446 

4447 if error: 

4448 self.inputline_set_error(error) 

4449 

4450 line = line.strip() 

4451 

4452 if line != '' and not error: 

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

4454 self.history.append(line) 

4455 

4456 if clearit: 

4457 

4458 self.inputline.blockSignals(True) 

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

4460 if qpat is None: 

4461 self.inputline.clear() 

4462 else: 

4463 self.inputline.setText(qinp) 

4464 self.inputline.blockSignals(False) 

4465 

4466 if hideit and not error: 

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

4468 self.input_area.hide() 

4469 

4470 self.hist_ind = len(self.history) 

4471 

4472 def inputline_aborted(self): 

4473 ''' 

4474 Hide the input line. 

4475 ''' 

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

4477 self.hist_ind = len(self.history) 

4478 self.input_area.hide() 

4479 

4480 def save_inputline_history(self): 

4481 ''' 

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

4483 ''' 

4484 if not self.history: 

4485 return 

4486 

4487 conf = pyrocko.config 

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

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

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

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

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

4493 

4494 def load_inputline_history(self): 

4495 ''' 

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

4497 ''' 

4498 conf = pyrocko.config 

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

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

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

4502 f.write('\n') 

4503 

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

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

4506 

4507 self.hist_ind = len(self.history) 

4508 

4509 def step_through_history(self, ud=1): 

4510 ''' 

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

4512 ''' 

4513 n = len(self.history) 

4514 self.hist_ind += ud 

4515 self.hist_ind %= (n + 1) 

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

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

4518 else: 

4519 self.inputline.setText('') 

4520 

4521 def inputline_finished(self): 

4522 pass 

4523 

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

4525 if self.block_scrollbar_changes: 

4526 return 

4527 

4528 self.scrollbar.blockSignals(True) 

4529 self.scrollbar.setPageStep(ihi-ilo) 

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

4531 self.scrollbar.setRange(0, vmax) 

4532 self.scrollbar.setValue(ilo) 

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

4534 self.scrollbar.blockSignals(False) 

4535 

4536 def scrollbar_changed(self, value): 

4537 self.block_scrollbar_changes = True 

4538 ilo = value 

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

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

4541 self.block_scrollbar_changes = False 

4542 self.update_contents() 

4543 

4544 def controls(self): 

4545 frame = qw.QFrame(self) 

4546 layout = qw.QGridLayout() 

4547 frame.setLayout(layout) 

4548 

4549 minfreq = 0.001 

4550 maxfreq = 1000.0 

4551 self.lowpass_control = ValControl(high_is_none=True) 

4552 self.lowpass_control.setup( 

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

4554 self.highpass_control = ValControl(low_is_none=True) 

4555 self.highpass_control.setup( 

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

4557 self.gain_control = ValControl() 

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

4559 self.rot_control = LinValControl() 

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

4561 self.colorbar_control = ColorbarControl(self) 

4562 

4563 self.lowpass_control.valchange.connect( 

4564 self.viewer.lowpass_change) 

4565 self.highpass_control.valchange.connect( 

4566 self.viewer.highpass_change) 

4567 self.gain_control.valchange.connect( 

4568 self.viewer.gain_change) 

4569 self.rot_control.valchange.connect( 

4570 self.viewer.rot_change) 

4571 self.colorbar_control.cmap_changed.connect( 

4572 self.viewer.waterfall_cmap_change 

4573 ) 

4574 self.colorbar_control.clip_changed.connect( 

4575 self.viewer.waterfall_clip_change 

4576 ) 

4577 self.colorbar_control.show_absolute_toggled.connect( 

4578 self.viewer.waterfall_show_absolute_change 

4579 ) 

4580 self.colorbar_control.show_integrate_toggled.connect( 

4581 self.viewer.waterfall_set_integrate 

4582 ) 

4583 

4584 for icontrol, control in enumerate(( 

4585 self.highpass_control, 

4586 self.lowpass_control, 

4587 self.gain_control, 

4588 self.rot_control, 

4589 self.colorbar_control)): 

4590 

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

4592 layout.addWidget(widget, icontrol, iwidget) 

4593 

4594 spacer = qw.QSpacerItem( 

4595 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4597 

4598 self.adjust_controls() 

4599 self.viewer.viewmode_change(ViewMode.Wiggle) 

4600 return frame 

4601 

4602 def marker_editor(self): 

4603 editor = pyrocko.gui.marker_editor.MarkerEditor( 

4604 self, sortable=self.marker_editor_sortable) 

4605 

4606 editor.set_viewer(self.get_view()) 

4607 editor.get_marker_model().dataChanged.connect( 

4608 self.update_contents) 

4609 return editor 

4610 

4611 def adjust_controls(self): 

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

4613 maxfreq = 0.5/dtmin 

4614 minfreq = (0.5/dtmax)*0.0001 

4615 self.lowpass_control.set_range(minfreq, maxfreq) 

4616 self.highpass_control.set_range(minfreq, maxfreq) 

4617 

4618 def setup_snufflings(self): 

4619 self.viewer.setup_snufflings() 

4620 

4621 def get_view(self): 

4622 return self.viewer 

4623 

4624 def update_contents(self): 

4625 self.viewer.update() 

4626 

4627 def get_pile(self): 

4628 return self.viewer.get_pile() 

4629 

4630 def show_colorbar_ctrl(self, show): 

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

4632 w.setVisible(show) 

4633 

4634 def show_gain_ctrl(self, show): 

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

4636 w.setVisible(show)