1# https://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

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

5 

6import os 

7import time 

8import calendar 

9import datetime 

10import re 

11import math 

12import logging 

13import operator 

14import copy 

15import enum 

16from itertools import groupby 

17 

18import numpy as num 

19import pyrocko.model 

20import pyrocko.pile 

21import pyrocko.trace 

22import pyrocko.response 

23import pyrocko.util 

24import pyrocko.plot 

25import pyrocko.gui.snuffler.snuffling 

26import pyrocko.gui.snuffler.snufflings 

27import pyrocko.gui.snuffler.marker_editor 

28 

29from pyrocko.util import hpfloat, gmtime_x, mystrftime 

30 

31from .marker import associate_phases_to_events, MarkerOneNSLCRequired 

32 

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

34 PhaseMarker, make_QPolygonF, draw_label, Label, 

35 Progressbars, ColorbarControl) 

36 

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

38 

39from .pile_viewer_waterfall import TraceWaterfall 

40 

41import scipy.stats as sstats 

42import platform 

43 

44MIN_LABEL_SIZE_PT = 6 

45 

46qc.QString = str 

47 

48qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \ 

49 qw.QFileDialog.DontUseSheet 

50 

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

52 

53logger = logging.getLogger('pyrocko.gui.snuffler.pile_viewer') 

54 

55 

56def detrend(x, y): 

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

58 y_detrended = y - slope * x - offset 

59 return y_detrended, slope, offset 

60 

61 

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

63 return x * slope + y_detrended + offset 

64 

65 

66class Global(object): 

67 appOnDemand = None 

68 

69 

70class NSLC(object): 

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

72 self.network = n 

73 self.station = s 

74 self.location = l 

75 self.channel = c 

76 

77 

78class m_float(float): 

79 

80 def __str__(self): 

81 if abs(self) >= 10000.: 

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

83 elif abs(self) >= 1000.: 

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

85 else: 

86 return '%.5g m' % self 

87 

88 def __lt__(self, other): 

89 if other is None: 

90 return True 

91 return float(self) < float(other) 

92 

93 def __gt__(self, other): 

94 if other is None: 

95 return False 

96 return float(self) > float(other) 

97 

98 

99def m_float_or_none(x): 

100 if x is None: 

101 return None 

102 else: 

103 return m_float(x) 

104 

105 

106def make_chunks(items): 

107 ''' 

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

109 ''' 

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

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

112 

113 

114class deg_float(float): 

115 

116 def __str__(self): 

117 return '%4.0f' % self 

118 

119 def __lt__(self, other): 

120 if other is None: 

121 return True 

122 return float(self) < float(other) 

123 

124 def __gt__(self, other): 

125 if other is None: 

126 return False 

127 return float(self) > float(other) 

128 

129 

130def deg_float_or_none(x): 

131 if x is None: 

132 return None 

133 else: 

134 return deg_float(x) 

135 

136 

137class sector_int(int): 

138 

139 def __str__(self): 

140 return '[%i]' % self 

141 

142 def __lt__(self, other): 

143 if other is None: 

144 return True 

145 return int(self) < int(other) 

146 

147 def __gt__(self, other): 

148 if other is None: 

149 return False 

150 return int(self) > int(other) 

151 

152 

153def num_to_html(num): 

154 snum = '%g' % num 

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

156 if m: 

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

158 

159 return snum 

160 

161 

162gap_lap_tolerance = 5. 

163 

164 

165class ViewMode(enum.Enum): 

166 Wiggle = 1 

167 Waterfall = 2 

168 

169 

170class Timer(object): 

171 def __init__(self): 

172 self._start = None 

173 self._stop = None 

174 

175 def start(self): 

176 self._start = os.times() 

177 

178 def stop(self): 

179 self._stop = os.times() 

180 

181 def get(self): 

182 a = self._start 

183 b = self._stop 

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

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

186 else: 

187 return tuple([0.] * 5) 

188 

189 def __sub__(self, other): 

190 a = self.get() 

191 b = other.get() 

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

193 

194 

195class ObjectStyle(object): 

196 def __init__(self, frame_pen, fill_brush): 

197 self.frame_pen = frame_pen 

198 self.fill_brush = fill_brush 

199 

200 

201box_styles = [] 

202box_alpha = 100 

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

204 'scarletred'.split(): 

205 

206 box_styles.append(ObjectStyle( 

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

208 qg.QBrush(qg.QColor( 

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

210 )) 

211 

212box_styles_coverage = {} 

213 

214box_styles_coverage['waveform'] = [ 

215 ObjectStyle( 

216 qg.QPen( 

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

218 1, qc.Qt.DashLine), 

219 qg.QBrush(qg.QColor( 

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

221 ), 

222 ObjectStyle( 

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

224 qg.QBrush(qg.QColor( 

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

226 ), 

227 ObjectStyle( 

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

229 qg.QBrush(qg.QColor( 

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

231 )] 

232 

233box_styles_coverage['waveform_promise'] = [ 

234 ObjectStyle( 

235 qg.QPen( 

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

237 1, qc.Qt.DashLine), 

238 qg.QBrush(qg.QColor( 

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

240 ), 

241 ObjectStyle( 

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

243 qg.QBrush(qg.QColor( 

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

245 ), 

246 ObjectStyle( 

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

248 qg.QBrush(qg.QColor( 

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

250 )] 

251 

252sday = 60*60*24. # \ 

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

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

255 

256acceptable_tincs = num.array([ 

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

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

259 

260 

261working_system_time_range = \ 

262 pyrocko.util.working_system_time_range() 

263 

264initial_time_range = [] 

265 

266try: 

267 initial_time_range.append( 

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

269except Exception: 

270 initial_time_range.append(working_system_time_range[0]) 

271 

272try: 

273 initial_time_range.append( 

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

275except Exception: 

276 initial_time_range.append(working_system_time_range[1]) 

277 

278 

279def is_working_time(t): 

280 return working_system_time_range[0] <= t and \ 

281 t <= working_system_time_range[1] 

282 

283 

284def fancy_time_ax_format(inc): 

285 l0_fmt_brief = '' 

286 l2_fmt = '' 

287 l2_trig = 0 

288 if inc < 0.000001: 

289 l0_fmt = '.%n' 

290 l0_center = False 

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

292 l1_trig = 6 

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

294 l2_trig = 3 

295 elif inc < 0.001: 

296 l0_fmt = '.%u' 

297 l0_center = False 

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

299 l1_trig = 6 

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

301 l2_trig = 3 

302 elif inc < 1: 

303 l0_fmt = '.%r' 

304 l0_center = False 

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

306 l1_trig = 6 

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

308 l2_trig = 3 

309 elif inc < 60: 

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

311 l0_center = False 

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

313 l1_trig = 3 

314 elif inc < 3600: 

315 l0_fmt = '%H:%M' 

316 l0_center = False 

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

318 l1_trig = 3 

319 elif inc < sday: 

320 l0_fmt = '%H:%M' 

321 l0_center = False 

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

323 l1_trig = 3 

324 elif inc < smonth: 

325 l0_fmt = '%a %d' 

326 l0_fmt_brief = '%d' 

327 l0_center = True 

328 l1_fmt = '%b, %Y' 

329 l1_trig = 2 

330 elif inc < syear: 

331 l0_fmt = '%b' 

332 l0_center = True 

333 l1_fmt = '%Y' 

334 l1_trig = 1 

335 else: 

336 l0_fmt = '%Y' 

337 l0_center = False 

338 l1_fmt = '' 

339 l1_trig = 0 

340 

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

342 

343 

344def day_start(timestamp): 

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

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

347 return calendar.timegm(tts) 

348 

349 

350def month_start(timestamp): 

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

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

353 return calendar.timegm(tts) 

354 

355 

356def year_start(timestamp): 

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

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

359 return calendar.timegm(tts) 

360 

361 

362def time_nice_value(inc0): 

363 if inc0 < acceptable_tincs[0]: 

364 return pyrocko.plot.nice_value(inc0) 

365 elif inc0 > acceptable_tincs[-1]: 

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

367 else: 

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

369 return acceptable_tincs[i] 

370 

371 

372class TimeScaler(pyrocko.plot.AutoScaler): 

373 def __init__(self): 

374 pyrocko.plot.AutoScaler.__init__(self) 

375 self.mode = 'min-max' 

376 

377 def make_scale(self, data_range): 

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

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

380 

381 data_min = min(data_range) 

382 data_max = max(data_range) 

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

384 

385 mi, ma = data_min, data_max 

386 nmi = mi 

387 if self.mode != 'off': 

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

389 

390 nma = ma 

391 if self.mode != 'off': 

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

393 

394 mi, ma = nmi, nma 

395 

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

397 mi -= 1.0 

398 ma += 1.0 

399 

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

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

402 

403 # make nice tick increment 

404 if self.inc is not None: 

405 inc = self.inc 

406 else: 

407 if self.approx_ticks > 0.: 

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

409 else: 

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

411 

412 if inc == 0.0: 

413 inc = 1.0 

414 

415 if is_reverse: 

416 return ma, mi, -inc 

417 else: 

418 return mi, ma, inc 

419 

420 def make_ticks(self, data_range): 

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

422 

423 is_reverse = False 

424 if inc < 0: 

425 mi, ma, inc = ma, mi, -inc 

426 is_reverse = True 

427 

428 ticks = [] 

429 

430 if inc < sday: 

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

432 if inc < 0.001: 

433 mi_day = hpfloat(mi_day) 

434 

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

436 if inc < 0.001: 

437 base = hpfloat(base) 

438 

439 base_day = mi_day 

440 i = 0 

441 while True: 

442 tick = base+i*inc 

443 if tick > ma: 

444 break 

445 

446 tick_day = day_start(tick) 

447 if tick_day > base_day: 

448 base_day = tick_day 

449 base = base_day 

450 i = 0 

451 else: 

452 ticks.append(tick) 

453 i += 1 

454 

455 elif inc < smonth: 

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

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

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

459 if mi_day == mi: 

460 dt_base += delta 

461 i = 0 

462 while True: 

463 current = dt_base + i*delta 

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

465 if tick > ma: 

466 break 

467 ticks.append(tick) 

468 i += 1 

469 

470 elif inc < syear: 

471 mi_month = month_start(max( 

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

473 

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

475 while True: 

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

477 m += 1 

478 if m > 12: 

479 y, m = y+1, 1 

480 

481 if tick > ma: 

482 break 

483 

484 if tick >= mi: 

485 ticks.append(tick) 

486 

487 else: 

488 mi_year = year_start(max( 

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

490 

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

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

493 

494 while True: 

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

496 y += incy 

497 if tick > ma: 

498 break 

499 if tick >= mi: 

500 ticks.append(tick) 

501 

502 if is_reverse: 

503 ticks.reverse() 

504 

505 return ticks, inc 

506 

507 

508def need_l1_tick(tt, ms, l1_trig): 

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

510 

511 

512def tick_to_labels(tick, inc): 

513 tt, ms = gmtime_x(tick) 

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

515 fancy_time_ax_format(inc) 

516 

517 l0 = mystrftime(l0_fmt, tt, ms) 

518 l0_brief = mystrftime(l0_fmt_brief, tt, ms) 

519 l1, l2 = None, None 

520 if need_l1_tick(tt, ms, l1_trig): 

521 l1 = mystrftime(l1_fmt, tt, ms) 

522 if need_l1_tick(tt, ms, l2_trig): 

523 l2 = mystrftime(l2_fmt, tt, ms) 

524 

525 return l0, l0_brief, l0_center, l1, l2 

526 

527 

528def l1_l2_tick(tick, inc): 

529 tt, ms = gmtime_x(tick) 

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

531 fancy_time_ax_format(inc) 

532 

533 l1 = mystrftime(l1_fmt, tt, ms) 

534 l2 = mystrftime(l2_fmt, tt, ms) 

535 return l1, l2 

536 

537 

538class TimeAx(TimeScaler): 

539 def __init__(self, *args): 

540 TimeScaler.__init__(self, *args) 

541 

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

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

544 p.setPen(pen) 

545 font = qg.QFont() 

546 font.setBold(True) 

547 p.setFont(font) 

548 fm = p.fontMetrics() 

549 ticklen = 10 

550 pad = 10 

551 tmin, tmax = xprojection.get_in_range() 

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

553 l1_hits = 0 

554 l2_hits = 0 

555 

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

557 uumin, uumax = xprojection.get_out_range() 

558 first_tick_with_label = None 

559 

560 data = [] 

561 for tick in ticks: 

562 umin = xprojection(tick) 

563 

564 umin_approx_next = xprojection(tick+inc) 

565 umax = xprojection(tick) 

566 

567 pinc_approx = umin_approx_next - umin 

568 

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

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

571 

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

573 # hide year at epoch 

574 # (we assume that synthetic data is shown) 

575 if l2: 

576 l2 = None 

577 elif l1: 

578 l1 = None 

579 

580 if l0_center: 

581 ushift = (umin_approx_next-umin)/2. 

582 else: 

583 ushift = 0. 

584 

585 abbr_level = 0 

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

587 label0 = l0x 

588 rect0 = fm.boundingRect(label0) 

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

590 break 

591 

592 abbr_level += 1 

593 

594 data.append(( 

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

596 pinc_approx)) 

597 

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

599 pinc_approx) in data: 

600 

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

602 rect0 = fm.boundingRect(label0) 

603 

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

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

606 

607 if first_tick_with_label is None: 

608 first_tick_with_label = tick 

609 p.drawText(qc.QPointF( 

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

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

612 

613 if l1: 

614 label1 = l1 

615 rect1 = fm.boundingRect(label1) 

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

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

618 

619 p.drawText(qc.QPointF( 

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

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

622 label1) 

623 

624 l1_hits += 1 

625 

626 if l2: 

627 label2 = l2 

628 rect2 = fm.boundingRect(label2) 

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

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

631 

632 p.drawText(qc.QPointF( 

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

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

635 ticklen), label2) 

636 

637 l2_hits += 1 

638 

639 if first_tick_with_label is None: 

640 first_tick_with_label = tmin 

641 

642 l1, l2 = l1_l2_tick(first_tick_with_label, inc) 

643 

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

645 tmax - tmin < 3600*24: 

646 

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

648 if l2: 

649 l2 = None 

650 elif l1: 

651 l1 = None 

652 

653 if l1_hits == 0 and l1: 

654 label1 = l1 

655 rect1 = fm.boundingRect(label1) 

656 p.drawText(qc.QPointF( 

657 uumin+pad, 

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

659 label1) 

660 

661 l1_hits += 1 

662 

663 if l2_hits == 0 and l2: 

664 label2 = l2 

665 rect2 = fm.boundingRect(label2) 

666 p.drawText(qc.QPointF( 

667 uumin+pad, 

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

669 label2) 

670 

671 v = yprojection(0) 

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

673 

674 

675class Projection(object): 

676 def __init__(self): 

677 self.xr = 0., 1. 

678 self.ur = 0., 1. 

679 

680 def set_in_range(self, xmin, xmax): 

681 if xmax == xmin: 

682 xmax = xmin + 1. 

683 

684 self.xr = xmin, xmax 

685 

686 def get_in_range(self): 

687 return self.xr 

688 

689 def set_out_range(self, umin, umax): 

690 if umax == umin: 

691 umax = umin + 1. 

692 

693 self.ur = umin, umax 

694 

695 def get_out_range(self): 

696 return self.ur 

697 

698 def __call__(self, x): 

699 umin, umax = self.ur 

700 xmin, xmax = self.xr 

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

702 

703 def clipped(self, x, umax_pad): 

704 umin, umax = self.ur 

705 xmin, xmax = self.xr 

706 return min( 

707 umax-umax_pad, 

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

709 

710 def rev(self, u): 

711 umin, umax = self.ur 

712 xmin, xmax = self.xr 

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

714 

715 def copy(self): 

716 return copy.copy(self) 

717 

718 

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

720 group = qw.QActionGroup(menu) 

721 group.setExclusive(True) 

722 menuitems = [] 

723 

724 for name, value, *shortcut in menudef: 

725 action = menu.addAction(name) 

726 action.setCheckable(True) 

727 action.setActionGroup(group) 

728 if shortcut: 

729 action.setShortcut(shortcut[0]) 

730 

731 menuitems.append((action, value)) 

732 if default is not None and ( 

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

734 value == default): 

735 action.setChecked(True) 

736 

737 group.triggered.connect(target) 

738 

739 if default is None: 

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

741 

742 return menuitems 

743 

744 

745def sort_actions(menu): 

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

747 for action in actions: 

748 menu.removeAction(action) 

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

750 

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

752 if help_action: 

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

754 for action in actions: 

755 menu.addAction(action) 

756 

757 

758fkey_map = dict(zip( 

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

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

761 range(10))) 

762 

763 

764class PileViewerMainException(Exception): 

765 pass 

766 

767 

768class PileViewerMenuBar(qw.QMenuBar): 

769 ... 

770 

771 

772class PileViewerMenu(qw.QMenu): 

773 ... 

774 

775 

776def MakePileViewerMainClass(base): 

777 

778 class PileViewerMain(base): 

779 

780 want_input = qc.pyqtSignal() 

781 about_to_close = qc.pyqtSignal() 

782 pile_has_changed_signal = qc.pyqtSignal() 

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

784 

785 begin_markers_add = qc.pyqtSignal(int, int) 

786 end_markers_add = qc.pyqtSignal() 

787 begin_markers_remove = qc.pyqtSignal(int, int) 

788 end_markers_remove = qc.pyqtSignal() 

789 

790 marker_selection_changed = qc.pyqtSignal(list) 

791 active_event_marker_changed = qc.pyqtSignal() 

792 

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

794 menu=None): 

795 base.__init__(self, *args) 

796 

797 self.pile = pile 

798 self.ax_height = 80 

799 self.panel_parent = panel_parent 

800 

801 self.click_tolerance = 5 

802 

803 self.ntracks_shown_max = ntracks_shown_max 

804 self.initial_ntracks_shown_max = ntracks_shown_max 

805 self.ntracks = 0 

806 self.show_all = True 

807 self.shown_tracks_range = None 

808 self.track_start = None 

809 self.track_trange = None 

810 

811 self.lowpass = None 

812 self.highpass = None 

813 self.gain = 1.0 

814 self.rotate = 0.0 

815 self.picking_down = None 

816 self.picking = None 

817 self.floating_marker = None 

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

819 self.markers_deltat_max = 0. 

820 self.n_selected_markers = 0 

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

822 self.visible_marker_kinds = self.all_marker_kinds 

823 self.active_event_marker = None 

824 self.ignore_releases = 0 

825 self.message = None 

826 self.reloaded = False 

827 self.pile_has_changed = False 

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

829 

830 self.tax = TimeAx() 

831 self.setBackgroundRole(qg.QPalette.Base) 

832 self.setAutoFillBackground(True) 

833 poli = qw.QSizePolicy( 

834 qw.QSizePolicy.Expanding, 

835 qw.QSizePolicy.Expanding) 

836 

837 self.setSizePolicy(poli) 

838 self.setMinimumSize(300, 200) 

839 self.setFocusPolicy(qc.Qt.ClickFocus) 

840 

841 self.menu = menu or PileViewerMenu(self) 

842 

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

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

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

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

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

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

849 

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

851 

852 self.snufflings_menu = self.toggle_panel_menu.addMenu( 

853 'Run Snuffling') 

854 self.toggle_panel_menu.addSeparator() 

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

856 help_menu.addSeparator() 

857 

858 file_menu.addAction( 

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

860 'Open waveform files...', 

861 self.open_waveforms, 

862 qg.QKeySequence.Open) 

863 

864 file_menu.addAction( 

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

866 'Open waveform directory...', 

867 self.open_waveform_directory) 

868 

869 file_menu.addAction( 

870 'Open station files...', 

871 self.open_stations) 

872 

873 file_menu.addAction( 

874 'Open StationXML files...', 

875 self.open_stations_xml) 

876 

877 file_menu.addAction( 

878 'Open event file...', 

879 self.read_events) 

880 

881 file_menu.addSeparator() 

882 file_menu.addAction( 

883 'Open marker file...', 

884 self.read_markers) 

885 

886 file_menu.addAction( 

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

888 'Save markers...', 

889 self.write_markers, 

890 qg.QKeySequence.Save) 

891 

892 file_menu.addAction( 

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

894 'Save selected markers...', 

895 self.write_selected_markers, 

896 qg.QKeySequence.SaveAs) 

897 

898 file_menu.addSeparator() 

899 file_menu.addAction( 

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

901 'Print', 

902 self.printit, 

903 qg.QKeySequence.Print) 

904 

905 file_menu.addAction( 

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

907 'Save as SVG or PNG', 

908 self.savesvg, 

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

910 

911 file_menu.addSeparator() 

912 close = file_menu.addAction( 

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

914 'Close', 

915 self.myclose) 

916 close.setShortcuts( 

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

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

919 

920 # Scale Menu 

921 menudef = [ 

922 ('Individual Scale', 

923 lambda tr: tr.nslc_id, 

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

925 ('Common Scale', 

926 lambda tr: None, 

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

928 ('Common Scale per Station', 

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

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

931 ('Common Scale per Station Location', 

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

933 ('Common Scale per Component', 

934 lambda tr: (tr.channel)), 

935 ] 

936 

937 self.menuitems_scaling = add_radiobuttongroup( 

938 scale_menu, menudef, self.scalingmode_change, 

939 default=self.config.trace_scale) 

940 scale_menu.addSeparator() 

941 

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

943 self.scaling_hooks = {} 

944 self.scalingmode_change() 

945 

946 menudef = [ 

947 ('Scaling based on Minimum and Maximum', 

948 ('minmax', 'minmax')), 

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

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

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

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

953 ] 

954 

955 self.menuitems_scaling_base = add_radiobuttongroup( 

956 scale_menu, menudef, self.scaling_base_change) 

957 

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

959 scale_menu.addSeparator() 

960 

961 self.menuitem_fixscalerange = scale_menu.addAction( 

962 'Fix Scale Ranges') 

963 self.menuitem_fixscalerange.setCheckable(True) 

964 

965 # Sort Menu 

966 def sector_dist(sta): 

967 if sta.dist_m is None: 

968 return None, None 

969 else: 

970 return ( 

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

972 m_float(sta.dist_m)) 

973 

974 menudef = [ 

975 ('Sort by Names', 

976 lambda tr: (), 

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

978 ('Sort by Distance', 

979 lambda tr: self.station_attrib( 

980 tr, 

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

982 lambda tr: (None,)), 

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

984 ('Sort by Azimuth', 

985 lambda tr: self.station_attrib( 

986 tr, 

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

988 lambda tr: (None,))), 

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

990 lambda tr: self.station_attrib( 

991 tr, 

992 sector_dist, 

993 lambda tr: (None, None))), 

994 ('Sort by Backazimuth', 

995 lambda tr: self.station_attrib( 

996 tr, 

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

998 lambda tr: (None,))), 

999 ] 

1000 self.menuitems_ssorting = add_radiobuttongroup( 

1001 sort_menu, menudef, self.s_sortingmode_change) 

1002 sort_menu.addSeparator() 

1003 

1004 self._ssort = lambda tr: () 

1005 

1006 self.menu.addSeparator() 

1007 

1008 menudef = [ 

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

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

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

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

1013 ((0, 1, 3, 2), 

1014 lambda tr: tr.channel)), 

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

1016 ((1, 0, 3, 2), 

1017 lambda tr: tr.channel)), 

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

1019 ((2, 0, 1, 3), 

1020 lambda tr: tr.channel)), 

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

1022 ((3, 0, 1, 2), 

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

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

1025 ((0, 1, 3), 

1026 lambda tr: tr.location)), 

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

1028 ((1, 0, 3), 

1029 lambda tr: tr.location)), 

1030 ] 

1031 

1032 self.menuitems_sorting = add_radiobuttongroup( 

1033 sort_menu, menudef, self.sortingmode_change) 

1034 

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

1036 self.config.visible_length_setting] 

1037 

1038 # View menu 

1039 self.menuitems_visible_length = add_radiobuttongroup( 

1040 view_menu, menudef, 

1041 self.visible_length_change) 

1042 view_menu.addSeparator() 

1043 

1044 view_modes = [ 

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

1046 ('Waterfall', ViewMode.Waterfall) 

1047 ] 

1048 

1049 self.menuitems_viewmode = add_radiobuttongroup( 

1050 view_menu, view_modes, 

1051 self.viewmode_change, default=ViewMode.Wiggle) 

1052 view_menu.addSeparator() 

1053 

1054 self.menuitem_cliptraces = view_menu.addAction( 

1055 'Clip Traces') 

1056 self.menuitem_cliptraces.setCheckable(True) 

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

1058 

1059 self.menuitem_showboxes = view_menu.addAction( 

1060 'Show Boxes') 

1061 self.menuitem_showboxes.setCheckable(True) 

1062 self.menuitem_showboxes.setChecked( 

1063 self.config.show_boxes) 

1064 

1065 self.menuitem_colortraces = view_menu.addAction( 

1066 'Color Traces') 

1067 self.menuitem_colortraces.setCheckable(True) 

1068 self.menuitem_antialias = view_menu.addAction( 

1069 'Antialiasing') 

1070 self.menuitem_antialias.setCheckable(True) 

1071 

1072 view_menu.addSeparator() 

1073 self.menuitem_showscalerange = view_menu.addAction( 

1074 'Show Scale Ranges') 

1075 self.menuitem_showscalerange.setCheckable(True) 

1076 self.menuitem_showscalerange.setChecked( 

1077 self.config.show_scale_ranges) 

1078 

1079 self.menuitem_showscaleaxis = view_menu.addAction( 

1080 'Show Scale Axes') 

1081 self.menuitem_showscaleaxis.setCheckable(True) 

1082 self.menuitem_showscaleaxis.setChecked( 

1083 self.config.show_scale_axes) 

1084 

1085 self.menuitem_showzeroline = view_menu.addAction( 

1086 'Show Zero Lines') 

1087 self.menuitem_showzeroline.setCheckable(True) 

1088 

1089 view_menu.addSeparator() 

1090 view_menu.addAction( 

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

1092 'Fullscreen', 

1093 self.toggle_fullscreen, 

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

1095 

1096 # Options Menu 

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

1098 self.menuitem_demean.setCheckable(True) 

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

1100 self.menuitem_demean.setShortcut( 

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

1102 

1103 self.menuitem_distances_3d = options_menu.addAction( 

1104 '3D distances', 

1105 self.distances_3d_changed) 

1106 self.menuitem_distances_3d.setCheckable(True) 

1107 

1108 self.menuitem_allowdownsampling = options_menu.addAction( 

1109 'Allow Downsampling') 

1110 self.menuitem_allowdownsampling.setCheckable(True) 

1111 self.menuitem_allowdownsampling.setChecked(True) 

1112 

1113 self.menuitem_degap = options_menu.addAction( 

1114 'Allow Degapping') 

1115 self.menuitem_degap.setCheckable(True) 

1116 self.menuitem_degap.setChecked(True) 

1117 

1118 options_menu.addSeparator() 

1119 

1120 self.menuitem_fft_filtering = options_menu.addAction( 

1121 'FFT Filtering') 

1122 self.menuitem_fft_filtering.setCheckable(True) 

1123 

1124 self.menuitem_lphp = options_menu.addAction( 

1125 'Bandpass is Low- + Highpass') 

1126 self.menuitem_lphp.setCheckable(True) 

1127 self.menuitem_lphp.setChecked(True) 

1128 

1129 options_menu.addSeparator() 

1130 self.menuitem_watch = options_menu.addAction( 

1131 'Watch Files') 

1132 self.menuitem_watch.setCheckable(True) 

1133 

1134 self.menuitem_liberal_fetch = options_menu.addAction( 

1135 'Liberal Fetch Optimization') 

1136 self.menuitem_liberal_fetch.setCheckable(True) 

1137 

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

1139 

1140 self.snufflings_menu.addAction( 

1141 'Reload Snufflings', 

1142 self.setup_snufflings) 

1143 

1144 # Disable ShadowPileTest 

1145 if False: 

1146 test_action = self.menu.addAction( 

1147 'Test', 

1148 self.toggletest) 

1149 test_action.setCheckable(True) 

1150 

1151 help_menu.addAction( 

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

1153 'Snuffler Controls', 

1154 self.help, 

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

1156 

1157 help_menu.addAction( 

1158 'About', 

1159 self.about) 

1160 

1161 self.time_projection = Projection() 

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

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

1164 

1165 self.gather = None 

1166 

1167 self.trace_filter = None 

1168 self.quick_filter = None 

1169 self.quick_filter_patterns = None, None 

1170 self.blacklist = [] 

1171 

1172 self.track_to_screen = Projection() 

1173 self.track_to_nslc_ids = {} 

1174 

1175 self.cached_vec = None 

1176 self.cached_processed_traces = None 

1177 

1178 self.timer = qc.QTimer(self) 

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

1180 self.timer.setInterval(1000) 

1181 self.timer.start() 

1182 self.pile.add_listener(self) 

1183 self.trace_styles = {} 

1184 if self.get_squirrel() is None: 

1185 self.determine_box_styles() 

1186 

1187 self.setMouseTracking(True) 

1188 

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

1190 self.snuffling_modules = {} 

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

1192 self.default_snufflings = None 

1193 self.snufflings = [] 

1194 

1195 self.stations = {} 

1196 

1197 self.timer_draw = Timer() 

1198 self.timer_cutout = Timer() 

1199 self.time_spent_painting = 0.0 

1200 self.time_last_painted = time.time() 

1201 

1202 self.interactive_range_change_time = 0.0 

1203 self.interactive_range_change_delay_time = 10.0 

1204 self.follow_timer = None 

1205 

1206 self.sortingmode_change_time = 0.0 

1207 self.sortingmode_change_delay_time = None 

1208 

1209 self.old_data_ranges = {} 

1210 

1211 self.error_messages = {} 

1212 self.return_tag = None 

1213 self.wheel_pos = 60 

1214 

1215 self.setAcceptDrops(True) 

1216 self._paths_to_load = [] 

1217 

1218 self.tf_cache = {} 

1219 

1220 self.waterfall = TraceWaterfall() 

1221 self.waterfall_cmap = 'viridis' 

1222 self.waterfall_clip_min = 0. 

1223 self.waterfall_clip_max = 1. 

1224 self.waterfall_show_absolute = False 

1225 self.waterfall_integrate = False 

1226 self.view_mode = ViewMode.Wiggle 

1227 

1228 self.automatic_updates = True 

1229 

1230 self.closing = False 

1231 self.in_paint_event = False 

1232 

1233 def fail(self, reason): 

1234 box = qw.QMessageBox(self) 

1235 box.setText(reason) 

1236 box.exec_() 

1237 

1238 def set_trace_filter(self, filter_func): 

1239 self.trace_filter = filter_func 

1240 self.sortingmode_change() 

1241 

1242 def update_trace_filter(self): 

1243 if self.blacklist: 

1244 

1245 def blacklist_func(tr): 

1246 return not pyrocko.util.match_nslc( 

1247 self.blacklist, tr.nslc_id) 

1248 

1249 else: 

1250 blacklist_func = None 

1251 

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

1253 self.set_trace_filter(None) 

1254 elif self.quick_filter is None: 

1255 self.set_trace_filter(blacklist_func) 

1256 elif blacklist_func is None: 

1257 self.set_trace_filter(self.quick_filter) 

1258 else: 

1259 self.set_trace_filter( 

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

1261 

1262 def set_quick_filter(self, filter_func): 

1263 self.quick_filter = filter_func 

1264 self.update_trace_filter() 

1265 

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

1267 if patterns is not None: 

1268 self.set_quick_filter( 

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

1270 else: 

1271 self.set_quick_filter(None) 

1272 

1273 self.quick_filter_patterns = patterns, inputline 

1274 

1275 def get_quick_filter_patterns(self): 

1276 return self.quick_filter_patterns 

1277 

1278 def add_blacklist_pattern(self, pattern): 

1279 if pattern == 'empty': 

1280 keys = set(self.pile.nslc_ids) 

1281 trs = self.pile.all( 

1282 tmin=self.tmin, 

1283 tmax=self.tmax, 

1284 load_data=False, 

1285 degap=False) 

1286 

1287 for tr in trs: 

1288 if tr.nslc_id in keys: 

1289 keys.remove(tr.nslc_id) 

1290 

1291 for key in keys: 

1292 xpattern = '.'.join(key) 

1293 if xpattern not in self.blacklist: 

1294 self.blacklist.append(xpattern) 

1295 

1296 else: 

1297 if pattern in self.blacklist: 

1298 self.blacklist.remove(pattern) 

1299 

1300 self.blacklist.append(pattern) 

1301 

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

1303 self.update_trace_filter() 

1304 

1305 def remove_blacklist_pattern(self, pattern): 

1306 if pattern in self.blacklist: 

1307 self.blacklist.remove(pattern) 

1308 else: 

1309 raise PileViewerMainException( 

1310 'Pattern not found in blacklist.') 

1311 

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

1313 self.update_trace_filter() 

1314 

1315 def clear_blacklist(self): 

1316 self.blacklist = [] 

1317 self.update_trace_filter() 

1318 

1319 def ssort(self, tr): 

1320 return self._ssort(tr) 

1321 

1322 def station_key(self, x): 

1323 return x.network, x.station 

1324 

1325 def station_keys(self, x): 

1326 return [ 

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

1328 (x.network, x.station)] 

1329 

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

1331 for sk in self.station_keys(tr): 

1332 if sk in self.stations: 

1333 station = self.stations[sk] 

1334 return getter(station) 

1335 

1336 return default_getter(tr) 

1337 

1338 def get_station(self, sk): 

1339 return self.stations[sk] 

1340 

1341 def has_station(self, station): 

1342 for sk in self.station_keys(station): 

1343 if sk in self.stations: 

1344 return True 

1345 

1346 return False 

1347 

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

1349 return self.station_attrib( 

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

1351 

1352 def set_stations(self, stations): 

1353 self.stations = {} 

1354 self.add_stations(stations) 

1355 

1356 def add_stations(self, stations): 

1357 for station in stations: 

1358 for sk in self.station_keys(station): 

1359 self.stations[sk] = station 

1360 

1361 ev = self.get_active_event() 

1362 if ev: 

1363 self.set_origin(ev) 

1364 

1365 def add_event(self, event): 

1366 marker = EventMarker(event) 

1367 self.add_marker(marker) 

1368 

1369 def add_events(self, events): 

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

1371 self.add_markers(markers) 

1372 

1373 def set_event_marker_as_origin(self, ignore=None): 

1374 selected = self.selected_markers() 

1375 if not selected: 

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

1377 return 

1378 

1379 m = selected[0] 

1380 if not isinstance(m, EventMarker): 

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

1382 return 

1383 

1384 self.set_active_event_marker(m) 

1385 

1386 def deactivate_event_marker(self): 

1387 if self.active_event_marker: 

1388 self.active_event_marker.active = False 

1389 

1390 self.active_event_marker_changed.emit() 

1391 self.active_event_marker = None 

1392 

1393 def set_active_event_marker(self, event_marker): 

1394 if self.active_event_marker: 

1395 self.active_event_marker.active = False 

1396 

1397 self.active_event_marker = event_marker 

1398 event_marker.active = True 

1399 event = event_marker.get_event() 

1400 self.set_origin(event) 

1401 self.active_event_marker_changed.emit() 

1402 

1403 def set_active_event(self, event): 

1404 for marker in self.markers: 

1405 if isinstance(marker, EventMarker): 

1406 if marker.get_event() is event: 

1407 self.set_active_event_marker(marker) 

1408 

1409 def get_active_event_marker(self): 

1410 return self.active_event_marker 

1411 

1412 def get_active_event(self): 

1413 m = self.get_active_event_marker() 

1414 if m is not None: 

1415 return m.get_event() 

1416 else: 

1417 return None 

1418 

1419 def get_active_markers(self): 

1420 emarker = self.get_active_event_marker() 

1421 if emarker is None: 

1422 return None, [] 

1423 

1424 else: 

1425 ev = emarker.get_event() 

1426 pmarkers = [ 

1427 m for m in self.markers 

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

1429 

1430 return emarker, pmarkers 

1431 

1432 def set_origin(self, location): 

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

1434 station.set_event_relative_data( 

1435 location, 

1436 distance_3d=self.menuitem_distances_3d.isChecked()) 

1437 

1438 self.sortingmode_change() 

1439 

1440 def distances_3d_changed(self): 

1441 ignore = self.menuitem_distances_3d.isChecked() 

1442 self.set_event_marker_as_origin(ignore) 

1443 

1444 def iter_snuffling_modules(self): 

1445 pjoin = os.path.join 

1446 for path in self.snuffling_paths: 

1447 

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

1449 os.mkdir(path) 

1450 

1451 for entry in os.listdir(path): 

1452 directory = path 

1453 fn = entry 

1454 d = pjoin(path, entry) 

1455 if os.path.isdir(d): 

1456 directory = d 

1457 if os.path.isfile( 

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

1459 fn = 'snuffling.py' 

1460 

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

1462 continue 

1463 

1464 name = fn[:-3] 

1465 

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

1467 self.snuffling_modules[directory, name] = \ 

1468 pyrocko.gui.snuffler.snuffling.SnufflingModule( 

1469 directory, name, self) 

1470 

1471 yield self.snuffling_modules[directory, name] 

1472 

1473 def setup_snufflings(self): 

1474 # user snufflings 

1475 from pyrocko.gui.snuffler.snuffling import BrokenSnufflingModule 

1476 for mod in self.iter_snuffling_modules(): 

1477 try: 

1478 mod.load_if_needed() 

1479 except BrokenSnufflingModule as e: 

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

1481 

1482 # load the default snufflings on first run 

1483 if self.default_snufflings is None: 

1484 self.default_snufflings = pyrocko.gui.snuffler\ 

1485 .snufflings.__snufflings__() 

1486 for snuffling in self.default_snufflings: 

1487 self.add_snuffling(snuffling) 

1488 

1489 def set_panel_parent(self, panel_parent): 

1490 self.panel_parent = panel_parent 

1491 

1492 def get_panel_parent(self): 

1493 return self.panel_parent 

1494 

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

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

1497 snuffling.init_gui( 

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

1499 self.snufflings.append(snuffling) 

1500 self.update() 

1501 

1502 def remove_snuffling(self, snuffling): 

1503 snuffling.delete_gui() 

1504 self.update() 

1505 self.snufflings.remove(snuffling) 

1506 snuffling.pre_destroy() 

1507 

1508 def add_snuffling_menuitem(self, item): 

1509 self.snufflings_menu.addAction(item) 

1510 item.setParent(self.snufflings_menu) 

1511 sort_actions(self.snufflings_menu) 

1512 

1513 def remove_snuffling_menuitem(self, item): 

1514 self.snufflings_menu.removeAction(item) 

1515 

1516 def add_snuffling_help_menuitem(self, item): 

1517 self.snuffling_help.addAction(item) 

1518 item.setParent(self.snuffling_help) 

1519 sort_actions(self.snuffling_help) 

1520 

1521 def remove_snuffling_help_menuitem(self, item): 

1522 self.snuffling_help.removeAction(item) 

1523 

1524 def add_panel_toggler(self, item): 

1525 self.toggle_panel_menu.addAction(item) 

1526 item.setParent(self.toggle_panel_menu) 

1527 sort_actions(self.toggle_panel_menu) 

1528 

1529 def remove_panel_toggler(self, item): 

1530 self.toggle_panel_menu.removeAction(item) 

1531 

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

1533 cache_dir=None, force_cache=False): 

1534 

1535 if cache_dir is None: 

1536 cache_dir = pyrocko.config.config().cache_dir 

1537 if isinstance(paths, str): 

1538 paths = [paths] 

1539 

1540 fns = pyrocko.util.select_files( 

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

1542 

1543 if not fns: 

1544 return 

1545 

1546 cache = pyrocko.pile.get_cache(cache_dir) 

1547 

1548 t = [time.time()] 

1549 

1550 def update_bar(label, value): 

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

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

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

1554 else: 

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

1556 

1557 return pbs.set_status(label, value) 

1558 

1559 def update_progress(label, i, n): 

1560 abort = False 

1561 

1562 qw.qApp.processEvents() 

1563 if n != 0: 

1564 perc = i*100/n 

1565 else: 

1566 perc = 100 

1567 abort |= update_bar(label, perc) 

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

1569 

1570 tnow = time.time() 

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

1572 self.update() 

1573 t[0] = tnow 

1574 

1575 return abort 

1576 

1577 self.automatic_updates = False 

1578 

1579 self.pile.load_files( 

1580 sorted(fns), 

1581 filename_attributes=regex, 

1582 cache=cache, 

1583 fileformat=format, 

1584 show_progress=False, 

1585 update_progress=update_progress) 

1586 

1587 self.automatic_updates = True 

1588 self.update() 

1589 

1590 def load_queued(self): 

1591 if not self._paths_to_load: 

1592 return 

1593 paths = self._paths_to_load 

1594 self._paths_to_load = [] 

1595 self.load(paths) 

1596 

1597 def load_soon(self, paths): 

1598 self._paths_to_load.extend(paths) 

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

1600 

1601 def open_waveforms(self): 

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

1603 

1604 fns, _ = qw.QFileDialog.getOpenFileNames( 

1605 self, caption, options=qfiledialog_options) 

1606 

1607 if fns: 

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

1609 

1610 def open_waveform_directory(self): 

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

1612 

1613 dn = qw.QFileDialog.getExistingDirectory( 

1614 self, caption, options=qfiledialog_options) 

1615 

1616 if dn: 

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

1618 

1619 def open_stations(self, fns=None): 

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

1621 

1622 if not fns: 

1623 fns, _ = qw.QFileDialog.getOpenFileNames( 

1624 self, caption, options=qfiledialog_options) 

1625 

1626 try: 

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

1628 for stat in stations: 

1629 self.add_stations(stat) 

1630 

1631 except Exception as e: 

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

1633 

1634 def open_stations_xml(self, fns=None): 

1635 from pyrocko.io import stationxml 

1636 

1637 caption = 'Select one or more StationXML files' 

1638 if not fns: 

1639 fns, _ = qw.QFileDialog.getOpenFileNames( 

1640 self, caption, options=qfiledialog_options, 

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

1642 ';;All files (*)') 

1643 

1644 try: 

1645 stations = [ 

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

1647 for x in fns] 

1648 

1649 for stat in stations: 

1650 self.add_stations(stat) 

1651 

1652 except Exception as e: 

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

1654 

1655 def add_traces(self, traces): 

1656 if traces: 

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

1658 self.pile.add_file(mtf) 

1659 ticket = (self.pile, mtf) 

1660 return ticket 

1661 else: 

1662 return (None, None) 

1663 

1664 def release_data(self, tickets): 

1665 for ticket in tickets: 

1666 pile, mtf = ticket 

1667 if pile is not None: 

1668 pile.remove_file(mtf) 

1669 

1670 def periodical(self): 

1671 if self.menuitem_watch.isChecked(): 

1672 if self.pile.reload_modified(): 

1673 self.update() 

1674 

1675 def get_pile(self): 

1676 return self.pile 

1677 

1678 def pile_changed(self, what): 

1679 self.pile_has_changed = True 

1680 self.pile_has_changed_signal.emit() 

1681 if self.automatic_updates: 

1682 self.update() 

1683 

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

1685 

1686 if gather is None: 

1687 def gather_func(tr): 

1688 return tr.nslc_id 

1689 

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

1691 

1692 else: 

1693 def gather_func(tr): 

1694 return ( 

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

1696 

1697 if color is None: 

1698 def color(tr): 

1699 return tr.location 

1700 

1701 self.gather = gather_func 

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

1703 

1704 self.color_gather = color 

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

1706 previous_ntracks = self.ntracks 

1707 self.set_ntracks(len(keys)) 

1708 

1709 if self.shown_tracks_range is None or \ 

1710 previous_ntracks == 0 or \ 

1711 self.show_all: 

1712 

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

1714 key_at_top = None 

1715 n = high-low 

1716 

1717 else: 

1718 low, high = self.shown_tracks_range 

1719 key_at_top = self.track_keys[low] 

1720 n = high-low 

1721 

1722 self.track_keys = sorted(keys) 

1723 

1724 track_patterns = [] 

1725 for k in self.track_keys: 

1726 pat = ['*', '*', '*', '*'] 

1727 for i, j in enumerate(gather): 

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

1729 

1730 track_patterns.append(pat) 

1731 

1732 self.track_patterns = track_patterns 

1733 

1734 if key_at_top is not None: 

1735 try: 

1736 ind = self.track_keys.index(key_at_top) 

1737 low = ind 

1738 high = low+n 

1739 except Exception: 

1740 pass 

1741 

1742 self.set_tracks_range((low, high)) 

1743 

1744 self.key_to_row = dict( 

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

1746 

1747 def inrange(x, r): 

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

1749 

1750 def trace_selector(trace): 

1751 gt = self.gather(trace) 

1752 return ( 

1753 gt in self.key_to_row and 

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

1755 

1756 self.trace_selector = lambda x: \ 

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

1758 and trace_selector(x) 

1759 

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

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

1762 self.show_all: 

1763 

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

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

1766 tlen = (tmax - tmin) 

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

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

1769 

1770 def set_time_range(self, tmin, tmax): 

1771 if tmin is None: 

1772 tmin = initial_time_range[0] 

1773 

1774 if tmax is None: 

1775 tmax = initial_time_range[1] 

1776 

1777 if tmin > tmax: 

1778 tmin, tmax = tmax, tmin 

1779 

1780 if tmin == tmax: 

1781 tmin -= 1. 

1782 tmax += 1. 

1783 

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

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

1786 

1787 min_deltat = self.content_deltat_range()[0] 

1788 if (tmax - tmin < min_deltat): 

1789 m = (tmin + tmax) / 2. 

1790 tmin = m - min_deltat/2. 

1791 tmax = m + min_deltat/2. 

1792 

1793 self.time_projection.set_in_range(tmin, tmax) 

1794 self.tmin, self.tmax = tmin, tmax 

1795 

1796 def get_time_range(self): 

1797 return self.tmin, self.tmax 

1798 

1799 def ypart(self, y): 

1800 if y < self.ax_height: 

1801 return -1 

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

1803 return 1 

1804 else: 

1805 return 0 

1806 

1807 def time_fractional_digits(self): 

1808 min_deltat = self.content_deltat_range()[0] 

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

1810 

1811 def write_markers(self, fn=None): 

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

1813 if not fn: 

1814 fn, _ = qw.QFileDialog.getSaveFileName( 

1815 self, caption, options=qfiledialog_options) 

1816 if fn: 

1817 try: 

1818 Marker.save_markers( 

1819 self.markers, fn, 

1820 fdigits=self.time_fractional_digits()) 

1821 

1822 except Exception as e: 

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

1824 

1825 def write_selected_markers(self, fn=None): 

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

1827 if not fn: 

1828 fn, _ = qw.QFileDialog.getSaveFileName( 

1829 self, caption, options=qfiledialog_options) 

1830 if fn: 

1831 try: 

1832 Marker.save_markers( 

1833 self.iter_selected_markers(), 

1834 fn, 

1835 fdigits=self.time_fractional_digits()) 

1836 

1837 except Exception as e: 

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

1839 

1840 def read_events(self, fn=None): 

1841 ''' 

1842 Open QFileDialog to open, read and add 

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

1844 representation to the pile viewer. 

1845 ''' 

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

1847 if not fn: 

1848 fn, _ = qw.QFileDialog.getOpenFileName( 

1849 self, caption, options=qfiledialog_options) 

1850 if fn: 

1851 try: 

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

1853 self.associate_phases_to_events() 

1854 

1855 except Exception as e: 

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

1857 

1858 def read_markers(self, fn=None): 

1859 ''' 

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

1861 ''' 

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

1863 if not fn: 

1864 fn, _ = qw.QFileDialog.getOpenFileName( 

1865 self, caption, options=qfiledialog_options) 

1866 if fn: 

1867 try: 

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

1869 self.associate_phases_to_events() 

1870 

1871 except Exception as e: 

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

1873 

1874 def associate_phases_to_events(self): 

1875 associate_phases_to_events(self.markers) 

1876 

1877 def add_marker(self, marker): 

1878 # need index to inform QAbstactTableModel about upcoming change, 

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

1880 self.markers.insert(marker) 

1881 i = self.markers.remove(marker) 

1882 

1883 self.begin_markers_add.emit(i, i) 

1884 self.markers.insert(marker) 

1885 self.end_markers_add.emit() 

1886 self.markers_deltat_max = max( 

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

1888 

1889 def add_markers(self, markers): 

1890 if not self.markers: 

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

1892 self.markers.insert_many(markers) 

1893 self.end_markers_add.emit() 

1894 self.update_markers_deltat_max() 

1895 else: 

1896 for marker in markers: 

1897 self.add_marker(marker) 

1898 

1899 def update_markers_deltat_max(self): 

1900 if self.markers: 

1901 self.markers_deltat_max = max( 

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

1903 

1904 def remove_marker(self, marker): 

1905 ''' 

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

1907 

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

1909 ''' 

1910 

1911 if marker is self.active_event_marker: 

1912 self.deactivate_event_marker() 

1913 

1914 try: 

1915 i = self.markers.index(marker) 

1916 self.begin_markers_remove.emit(i, i) 

1917 self.markers.remove_at(i) 

1918 self.end_markers_remove.emit() 

1919 except ValueError: 

1920 pass 

1921 

1922 def remove_markers(self, markers): 

1923 ''' 

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

1925 

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

1927 instances 

1928 ''' 

1929 

1930 if markers is self.markers: 

1931 markers = list(markers) 

1932 

1933 for marker in markers: 

1934 self.remove_marker(marker) 

1935 

1936 self.update_markers_deltat_max() 

1937 

1938 def remove_selected_markers(self): 

1939 def delete_segment(istart, iend): 

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

1941 for _ in range(iend - istart): 

1942 self.markers.remove_at(istart) 

1943 

1944 self.end_markers_remove.emit() 

1945 

1946 istart = None 

1947 ipos = 0 

1948 markers = self.markers 

1949 nmarkers = len(self.markers) 

1950 while ipos < nmarkers: 

1951 marker = markers[ipos] 

1952 if marker.is_selected(): 

1953 if marker is self.active_event_marker: 

1954 self.deactivate_event_marker() 

1955 

1956 if istart is None: 

1957 istart = ipos 

1958 else: 

1959 if istart is not None: 

1960 delete_segment(istart, ipos) 

1961 nmarkers -= ipos - istart 

1962 ipos = istart - 1 

1963 istart = None 

1964 

1965 ipos += 1 

1966 

1967 if istart is not None: 

1968 delete_segment(istart, ipos) 

1969 

1970 self.update_markers_deltat_max() 

1971 

1972 def selected_markers(self): 

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

1974 

1975 def iter_selected_markers(self): 

1976 for marker in self.markers: 

1977 if marker.is_selected(): 

1978 yield marker 

1979 

1980 def get_markers(self): 

1981 return self.markers 

1982 

1983 def mousePressEvent(self, mouse_ev): 

1984 self.show_all = False 

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

1986 

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

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

1989 if self.picking: 

1990 if self.picking_down is None: 

1991 self.picking_down = ( 

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

1993 mouse_ev.y()) 

1994 

1995 elif marker is not None: 

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

1997 self.deselect_all() 

1998 marker.selected = True 

1999 self.emit_selected_markers() 

2000 self.update() 

2001 else: 

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

2003 self.track_trange = self.tmin, self.tmax 

2004 

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

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

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

2008 self.update_status() 

2009 

2010 def mouseReleaseEvent(self, mouse_ev): 

2011 if self.ignore_releases: 

2012 self.ignore_releases -= 1 

2013 return 

2014 

2015 if self.picking: 

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

2017 self.emit_selected_markers() 

2018 

2019 if self.track_start: 

2020 self.update() 

2021 

2022 self.track_start = None 

2023 self.track_trange = None 

2024 self.update_status() 

2025 

2026 def mouseDoubleClickEvent(self, mouse_ev): 

2027 self.show_all = False 

2028 self.start_picking(None) 

2029 self.ignore_releases = 1 

2030 

2031 def mouseMoveEvent(self, mouse_ev): 

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

2033 

2034 if self.picking: 

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

2036 

2037 elif self.track_start is not None: 

2038 x0, y0 = self.track_start 

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

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

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

2042 dy = 0 

2043 

2044 tmin0, tmax0 = self.track_trange 

2045 

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

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

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

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

2050 

2051 self.interrupt_following() 

2052 self.set_time_range( 

2053 tmin0 - dt - dtr*frac, 

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

2055 

2056 self.update() 

2057 else: 

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

2059 

2060 self.update_status() 

2061 

2062 def nslc_ids_under_cursor(self, x, y): 

2063 ftrack = self.track_to_screen.rev(y) 

2064 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2065 return nslc_ids 

2066 

2067 def marker_under_cursor(self, x, y): 

2068 mouset = self.time_projection.rev(x) 

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

2070 relevant_nslc_ids = None 

2071 for marker in self.markers: 

2072 if marker.kind not in self.visible_marker_kinds: 

2073 continue 

2074 

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

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

2077 

2078 if relevant_nslc_ids is None: 

2079 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2080 

2081 marker_nslc_ids = marker.get_nslc_ids() 

2082 if not marker_nslc_ids: 

2083 return marker 

2084 

2085 for nslc_id in marker_nslc_ids: 

2086 if nslc_id in relevant_nslc_ids: 

2087 return marker 

2088 

2089 def hoovering(self, x, y): 

2090 mouset = self.time_projection.rev(x) 

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

2092 needupdate = False 

2093 haveone = False 

2094 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2095 for marker in self.markers: 

2096 if marker.kind not in self.visible_marker_kinds: 

2097 continue 

2098 

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

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

2101 

2102 if state: 

2103 xstate = False 

2104 

2105 marker_nslc_ids = marker.get_nslc_ids() 

2106 if not marker_nslc_ids: 

2107 xstate = True 

2108 

2109 for nslc in relevant_nslc_ids: 

2110 if marker.match_nslc(nslc): 

2111 xstate = True 

2112 

2113 state = xstate 

2114 

2115 if state: 

2116 haveone = True 

2117 oldstate = marker.is_alerted() 

2118 if oldstate != state: 

2119 needupdate = True 

2120 marker.set_alerted(state) 

2121 if state: 

2122 self.message = marker.hoover_message() 

2123 

2124 if not haveone: 

2125 self.message = None 

2126 

2127 if needupdate: 

2128 self.update() 

2129 

2130 def event(self, event): 

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

2132 self.keyPressEvent(event) 

2133 return True 

2134 else: 

2135 return base.event(self, event) 

2136 

2137 def keyPressEvent(self, key_event): 

2138 self.show_all = False 

2139 dt = self.tmax - self.tmin 

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

2141 

2142 key = key_event.key() 

2143 try: 

2144 keytext = str(key_event.text()) 

2145 except UnicodeEncodeError: 

2146 return 

2147 

2148 if key == qc.Qt.Key_Space: 

2149 self.interrupt_following() 

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

2151 

2152 elif key == qc.Qt.Key_Up: 

2153 for m in self.selected_markers(): 

2154 if isinstance(m, PhaseMarker): 

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

2156 p = 0 

2157 else: 

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

2159 m.set_polarity(p) 

2160 

2161 elif key == qc.Qt.Key_Down: 

2162 for m in self.selected_markers(): 

2163 if isinstance(m, PhaseMarker): 

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

2165 p = 0 

2166 else: 

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

2168 m.set_polarity(p) 

2169 

2170 elif key == qc.Qt.Key_B: 

2171 dt = self.tmax - self.tmin 

2172 self.interrupt_following() 

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

2174 

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

2176 self.interrupt_following() 

2177 

2178 tgo = None 

2179 

2180 class TraceDummy(object): 

2181 def __init__(self, marker): 

2182 self._marker = marker 

2183 

2184 @property 

2185 def nslc_id(self): 

2186 return self._marker.one_nslc() 

2187 

2188 def marker_to_itrack(marker): 

2189 try: 

2190 return self.key_to_row.get( 

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

2192 

2193 except MarkerOneNSLCRequired: 

2194 return -1 

2195 

2196 emarker, pmarkers = self.get_active_markers() 

2197 pmarkers = [ 

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

2199 pmarkers.sort(key=lambda m: ( 

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

2201 

2202 if key == qc.Qt.Key_Backtab: 

2203 pmarkers.reverse() 

2204 

2205 smarkers = self.selected_markers() 

2206 iselected = [] 

2207 for sm in smarkers: 

2208 try: 

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

2210 except ValueError: 

2211 pass 

2212 

2213 if iselected: 

2214 icurrent = max(iselected) + 1 

2215 else: 

2216 icurrent = 0 

2217 

2218 if icurrent < len(pmarkers): 

2219 self.deselect_all() 

2220 cmarker = pmarkers[icurrent] 

2221 cmarker.selected = True 

2222 tgo = cmarker.tmin 

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

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

2225 

2226 itrack = marker_to_itrack(cmarker) 

2227 if itrack != -1: 

2228 if itrack < self.shown_tracks_range[0]: 

2229 self.scroll_tracks( 

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

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

2232 self.scroll_tracks( 

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

2234 

2235 if itrack not in self.track_to_nslc_ids: 

2236 self.go_to_selection() 

2237 

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

2239 smarkers = self.selected_markers() 

2240 tgo = None 

2241 dir = str(keytext) 

2242 if smarkers: 

2243 tmid = smarkers[0].tmin 

2244 for smarker in smarkers: 

2245 if dir == 'n': 

2246 tmid = max(smarker.tmin, tmid) 

2247 else: 

2248 tmid = min(smarker.tmin, tmid) 

2249 

2250 tgo = tmid 

2251 

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

2253 for marker in sorted( 

2254 self.markers, 

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

2256 

2257 t = marker.tmin 

2258 if t > tmid and \ 

2259 marker.kind in self.visible_marker_kinds and \ 

2260 (dir == 'n' or 

2261 isinstance(marker, EventMarker)): 

2262 

2263 self.deselect_all() 

2264 marker.selected = True 

2265 tgo = t 

2266 break 

2267 else: 

2268 for marker in sorted( 

2269 self.markers, 

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

2271 reverse=True): 

2272 

2273 t = marker.tmin 

2274 if t < tmid and \ 

2275 marker.kind in self.visible_marker_kinds and \ 

2276 (dir == 'p' or 

2277 isinstance(marker, EventMarker)): 

2278 self.deselect_all() 

2279 marker.selected = True 

2280 tgo = t 

2281 break 

2282 

2283 if tgo is not None: 

2284 self.interrupt_following() 

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

2286 

2287 elif keytext == 'r': 

2288 if self.pile.reload_modified(): 

2289 self.reloaded = True 

2290 

2291 elif keytext == 'R': 

2292 self.setup_snufflings() 

2293 

2294 elif key == qc.Qt.Key_Backspace: 

2295 self.remove_selected_markers() 

2296 

2297 elif keytext == 'a': 

2298 for marker in self.markers: 

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

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

2301 marker.kind in self.visible_marker_kinds): 

2302 marker.selected = True 

2303 else: 

2304 marker.selected = False 

2305 

2306 elif keytext == 'A': 

2307 for marker in self.markers: 

2308 if marker.kind in self.visible_marker_kinds: 

2309 marker.selected = True 

2310 

2311 elif keytext == 'd': 

2312 self.deselect_all() 

2313 

2314 elif keytext == 'E': 

2315 self.deactivate_event_marker() 

2316 

2317 elif keytext == 'e': 

2318 markers = self.selected_markers() 

2319 event_markers_in_spe = [ 

2320 marker for marker in markers 

2321 if not isinstance(marker, PhaseMarker)] 

2322 

2323 phase_markers = [ 

2324 marker for marker in markers 

2325 if isinstance(marker, PhaseMarker)] 

2326 

2327 if len(event_markers_in_spe) == 1: 

2328 event_marker = event_markers_in_spe[0] 

2329 if not isinstance(event_marker, EventMarker): 

2330 nslcs = list(event_marker.nslc_ids) 

2331 lat, lon = 0.0, 0.0 

2332 old = self.get_active_event() 

2333 if len(nslcs) == 1: 

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

2335 elif old is not None: 

2336 lat, lon = old.lat, old.lon 

2337 

2338 event_marker.convert_to_event_marker(lat, lon) 

2339 

2340 self.set_active_event_marker(event_marker) 

2341 event = event_marker.get_event() 

2342 for marker in phase_markers: 

2343 marker.set_event(event) 

2344 

2345 else: 

2346 for marker in event_markers_in_spe: 

2347 marker.convert_to_event_marker() 

2348 

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

2350 for marker in self.selected_markers(): 

2351 marker.set_kind(int(keytext)) 

2352 self.emit_selected_markers() 

2353 

2354 elif key in fkey_map: 

2355 self.handle_fkeys(key) 

2356 

2357 elif key == qc.Qt.Key_Escape: 

2358 if self.picking: 

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

2360 

2361 elif key == qc.Qt.Key_PageDown: 

2362 self.scroll_tracks( 

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

2364 

2365 elif key == qc.Qt.Key_PageUp: 

2366 self.scroll_tracks( 

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

2368 

2369 elif key == qc.Qt.Key_Plus: 

2370 self.zoom_tracks(0., 1.) 

2371 

2372 elif key == qc.Qt.Key_Minus: 

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

2374 

2375 elif key == qc.Qt.Key_Equal: 

2376 ntracks_shown = self.shown_tracks_range[1] - \ 

2377 self.shown_tracks_range[0] 

2378 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2379 self.zoom_tracks(0., dtracks) 

2380 

2381 elif key == qc.Qt.Key_Colon: 

2382 self.want_input.emit() 

2383 

2384 elif keytext == 'f': 

2385 self.toggle_fullscreen() 

2386 

2387 elif keytext == 'g': 

2388 self.go_to_selection() 

2389 

2390 elif keytext == 'G': 

2391 self.go_to_selection(tight=True) 

2392 

2393 elif keytext == 'm': 

2394 self.toggle_marker_editor() 

2395 

2396 elif keytext == 'c': 

2397 self.toggle_main_controls() 

2398 

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

2400 dir = 1 

2401 amount = 1 

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

2403 dir = -1 

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

2405 amount = 10 

2406 self.nudge_selected_markers(dir*amount) 

2407 else: 

2408 super().keyPressEvent(key_event) 

2409 

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

2411 self.emit_selected_markers() 

2412 

2413 self.update() 

2414 self.update_status() 

2415 

2416 def handle_fkeys(self, key): 

2417 self.set_phase_kind( 

2418 self.selected_markers(), 

2419 fkey_map[key] + 1) 

2420 self.emit_selected_markers() 

2421 

2422 def emit_selected_markers(self): 

2423 ibounds = [] 

2424 last_selected = False 

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

2426 this_selected = marker.is_selected() 

2427 if this_selected != last_selected: 

2428 ibounds.append(imarker) 

2429 

2430 last_selected = this_selected 

2431 

2432 if last_selected: 

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

2434 

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

2436 self.n_selected_markers = sum( 

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

2438 self.marker_selection_changed.emit(chunks) 

2439 

2440 def toggle_marker_editor(self): 

2441 self.panel_parent.toggle_marker_editor() 

2442 

2443 def toggle_main_controls(self): 

2444 self.panel_parent.toggle_main_controls() 

2445 

2446 def nudge_selected_markers(self, npixels): 

2447 a, b = self.time_projection.ur 

2448 c, d = self.time_projection.xr 

2449 for marker in self.selected_markers(): 

2450 if not isinstance(marker, EventMarker): 

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

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

2453 

2454 def toggle_fullscreen(self): 

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

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

2457 self.window().showNormal() 

2458 else: 

2459 if is_macos: 

2460 self.window().showMaximized() 

2461 else: 

2462 self.window().showFullScreen() 

2463 

2464 def about(self): 

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

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

2467 txt = f.read() 

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

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

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

2471 

2472 def help(self): 

2473 class MyScrollArea(qw.QScrollArea): 

2474 

2475 def sizeHint(self): 

2476 s = qc.QSize() 

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

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

2479 return s 

2480 

2481 with open(pyrocko.util.data_file( 

2482 'snuffler_help.html')) as f: 

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

2484 

2485 with open(pyrocko.util.data_file( 

2486 'snuffler_help_epilog.html')) as f: 

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

2488 

2489 for h in [hcheat, hepilog]: 

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

2491 h.setWordWrap(True) 

2492 

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

2494 

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

2496 scroller = qw.QScrollArea() 

2497 frame = qw.QFrame(scroller) 

2498 frame.setLineWidth(0) 

2499 layout = qw.QVBoxLayout() 

2500 layout.setContentsMargins(0, 0, 0, 0) 

2501 layout.setSpacing(0) 

2502 frame.setLayout(layout) 

2503 scroller.setWidget(frame) 

2504 scroller.setWidgetResizable(True) 

2505 frame.setBackgroundRole(qg.QPalette.Base) 

2506 for h in labels: 

2507 h.setParent(frame) 

2508 h.setMargin(3) 

2509 h.setTextInteractionFlags( 

2510 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2511 h.setBackgroundRole(qg.QPalette.Base) 

2512 layout.addWidget(h) 

2513 h.linkActivated.connect( 

2514 self.open_link) 

2515 

2516 if self.panel_parent is not None: 

2517 if target == 'panel': 

2518 self.panel_parent.add_panel( 

2519 name, scroller, True, volatile=False) 

2520 else: 

2521 self.panel_parent.add_tab(name, scroller) 

2522 

2523 def open_link(self, link): 

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

2525 

2526 def wheelEvent(self, wheel_event): 

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

2528 

2529 n = self.wheel_pos // 120 

2530 self.wheel_pos = self.wheel_pos % 120 

2531 if n == 0: 

2532 return 

2533 

2534 amount = max( 

2535 1., 

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

2537 wdelta = amount * n 

2538 

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

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

2541 / (trmax-trmin) 

2542 

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

2544 self.zoom_tracks(anchor, wdelta) 

2545 else: 

2546 self.scroll_tracks(-wdelta) 

2547 

2548 def dragEnterEvent(self, event): 

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

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

2551 event.setDropAction(qc.Qt.LinkAction) 

2552 event.accept() 

2553 

2554 def dropEvent(self, event): 

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

2556 paths = list( 

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

2558 event.acceptProposedAction() 

2559 self.load(paths) 

2560 

2561 def get_phase_name(self, kind): 

2562 return self.config.get_phase_name(kind) 

2563 

2564 def set_phase_kind(self, markers, kind): 

2565 phasename = self.get_phase_name(kind) 

2566 

2567 for marker in markers: 

2568 if isinstance(marker, PhaseMarker): 

2569 if kind == 10: 

2570 marker.convert_to_marker() 

2571 else: 

2572 marker.set_phasename(phasename) 

2573 marker.set_event(self.get_active_event()) 

2574 

2575 elif isinstance(marker, EventMarker): 

2576 pass 

2577 

2578 else: 

2579 if kind != 10: 

2580 event = self.get_active_event() 

2581 marker.convert_to_phase_marker( 

2582 event, phasename, None, False) 

2583 

2584 def set_ntracks(self, ntracks): 

2585 if self.ntracks != ntracks: 

2586 self.ntracks = ntracks 

2587 if self.shown_tracks_range is not None: 

2588 l, h = self.shown_tracks_range 

2589 else: 

2590 l, h = 0, self.ntracks 

2591 

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

2593 

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

2595 

2596 low, high = range 

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

2598 high = min(self.ntracks, high) 

2599 low = max(0, low) 

2600 high = max(1, high) 

2601 

2602 if start is None: 

2603 start = float(low) 

2604 

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

2606 self.shown_tracks_range = low, high 

2607 self.shown_tracks_start = start 

2608 

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

2610 

2611 def scroll_tracks(self, shift): 

2612 shown = self.shown_tracks_range 

2613 shiftmin = -shown[0] 

2614 shiftmax = self.ntracks-shown[1] 

2615 shift = max(shiftmin, shift) 

2616 shift = min(shiftmax, shift) 

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

2618 

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

2620 

2621 self.update() 

2622 

2623 def zoom_tracks(self, anchor, delta): 

2624 ntracks_shown = self.shown_tracks_range[1] \ 

2625 - self.shown_tracks_range[0] 

2626 

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

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

2629 return 

2630 

2631 ntracks_shown += int(round(delta)) 

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

2633 

2634 u = self.shown_tracks_start 

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

2636 nv = nu + ntracks_shown 

2637 if nv > self.ntracks: 

2638 nu -= nv - self.ntracks 

2639 nv -= nv - self.ntracks 

2640 

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

2642 

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

2644 - self.shown_tracks_range[0] 

2645 

2646 self.update() 

2647 

2648 def content_time_range(self): 

2649 pile = self.get_pile() 

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

2651 if tmin is None: 

2652 tmin = initial_time_range[0] 

2653 if tmax is None: 

2654 tmax = initial_time_range[1] 

2655 

2656 return tmin, tmax 

2657 

2658 def content_deltat_range(self): 

2659 pile = self.get_pile() 

2660 

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

2662 

2663 if deltatmin is None: 

2664 deltatmin = 0.001 

2665 

2666 if deltatmax is None: 

2667 deltatmax = 1000.0 

2668 

2669 return deltatmin, deltatmax 

2670 

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

2672 if tmax < tmin: 

2673 tmin, tmax = tmax, tmin 

2674 

2675 deltatmin = self.content_deltat_range()[0] 

2676 dt = deltatmin * self.visible_length * 0.95 

2677 

2678 if dt == 0.0: 

2679 dt = 1.0 

2680 

2681 if tight: 

2682 if tmax != tmin: 

2683 dtm = tmax - tmin 

2684 tmin -= dtm*0.1 

2685 tmax += dtm*0.1 

2686 return tmin, tmax 

2687 else: 

2688 tcenter = (tmin + tmax) / 2. 

2689 tmin = tcenter - 0.5*dt 

2690 tmax = tcenter + 0.5*dt 

2691 return tmin, tmax 

2692 

2693 if tmax-tmin < dt: 

2694 vmin, vmax = self.get_time_range() 

2695 dt = min(vmax - vmin, dt) 

2696 

2697 tcenter = (tmin+tmax)/2. 

2698 etmin, etmax = tmin, tmax 

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

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

2701 dtm = tmax-tmin 

2702 if etmin == tmin: 

2703 tmin -= dtm*0.1 

2704 if etmax == tmax: 

2705 tmax += dtm*0.1 

2706 

2707 else: 

2708 dtm = tmax-tmin 

2709 tmin -= dtm*0.1 

2710 tmax += dtm*0.1 

2711 

2712 return tmin, tmax 

2713 

2714 def go_to_selection(self, tight=False): 

2715 markers = self.selected_markers() 

2716 if markers: 

2717 tmax, tmin = self.content_time_range() 

2718 for marker in markers: 

2719 tmin = min(tmin, marker.tmin) 

2720 tmax = max(tmax, marker.tmax) 

2721 

2722 else: 

2723 if tight: 

2724 vmin, vmax = self.get_time_range() 

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

2726 else: 

2727 tmin, tmax = self.content_time_range() 

2728 

2729 tmin, tmax = self.make_good_looking_time_range( 

2730 tmin, tmax, tight=tight) 

2731 

2732 self.interrupt_following() 

2733 self.set_time_range(tmin, tmax) 

2734 self.update() 

2735 

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

2737 tmax = t 

2738 if tlen is not None: 

2739 tmax = t+tlen 

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

2741 self.interrupt_following() 

2742 self.set_time_range(tmin, tmax) 

2743 self.update() 

2744 

2745 def go_to_event_by_name(self, name): 

2746 for marker in self.markers: 

2747 if isinstance(marker, EventMarker): 

2748 event = marker.get_event() 

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

2750 tmin, tmax = self.make_good_looking_time_range( 

2751 event.time, event.time) 

2752 

2753 self.interrupt_following() 

2754 self.set_time_range(tmin, tmax) 

2755 

2756 def printit(self): 

2757 from ..qt_compat import qprint 

2758 printer = qprint.QPrinter() 

2759 printer.setOrientation(qprint.QPrinter.Landscape) 

2760 

2761 dialog = qprint.QPrintDialog(printer, self) 

2762 dialog.setWindowTitle('Print') 

2763 

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

2765 return 

2766 

2767 painter = qg.QPainter() 

2768 painter.begin(printer) 

2769 page = printer.pageRect() 

2770 self.drawit( 

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

2772 

2773 painter.end() 

2774 

2775 def savesvg(self, fn=None): 

2776 

2777 if not fn: 

2778 fn, _ = qw.QFileDialog.getSaveFileName( 

2779 self, 

2780 'Save as SVG|PNG', 

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

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

2783 options=qfiledialog_options) 

2784 

2785 if fn == '': 

2786 return 

2787 

2788 fn = str(fn) 

2789 

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

2791 try: 

2792 w, h = 842, 595 

2793 margin = 0.025 

2794 m = max(w, h)*margin 

2795 

2796 generator = qsvg.QSvgGenerator() 

2797 generator.setFileName(fn) 

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

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

2800 

2801 painter = qg.QPainter() 

2802 painter.begin(generator) 

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

2804 painter.end() 

2805 

2806 except Exception as e: 

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

2808 

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

2810 pixmap = self.grab() 

2811 

2812 try: 

2813 pixmap.save(fn) 

2814 

2815 except Exception as e: 

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

2817 

2818 else: 

2819 self.fail( 

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

2821 '".png".') 

2822 

2823 def paintEvent(self, paint_ev): 

2824 ''' 

2825 Called by QT whenever widget needs to be painted. 

2826 ''' 

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

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

2829 if self.in_paint_event: 

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

2831 return 

2832 

2833 self.in_paint_event = True 

2834 

2835 painter = qg.QPainter(self) 

2836 

2837 if self.menuitem_antialias.isChecked(): 

2838 painter.setRenderHint(qg.QPainter.Antialiasing) 

2839 

2840 self.drawit(painter) 

2841 

2842 logger.debug( 

2843 'Time spent drawing: ' 

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

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

2846 (self.timer_draw - self.timer_cutout)) 

2847 

2848 logger.debug( 

2849 'Time spent processing:' 

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

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

2852 self.timer_cutout.get()) 

2853 

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

2855 self.time_last_painted = time.time() 

2856 self.in_paint_event = False 

2857 

2858 def determine_box_styles(self): 

2859 

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

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

2862 istyle = 0 

2863 trace_styles = {} 

2864 for itr, tr in enumerate(traces): 

2865 if itr > 0: 

2866 other = traces[itr-1] 

2867 if not ( 

2868 other.nslc_id == tr.nslc_id 

2869 and other.deltat == tr.deltat 

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

2871 < gap_lap_tolerance*tr.deltat): 

2872 

2873 istyle += 1 

2874 

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

2876 

2877 self.trace_styles = trace_styles 

2878 

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

2880 

2881 for v_projection in track_projections.values(): 

2882 v_projection.set_in_range(0., 1.) 

2883 

2884 def selector(x): 

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

2886 

2887 if self.trace_filter is not None: 

2888 def tselector(x): 

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

2890 

2891 else: 

2892 tselector = selector 

2893 

2894 traces = list(self.pile.iter_traces( 

2895 group_selector=selector, trace_selector=tselector)) 

2896 

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

2898 

2899 def drawbox(itrack, istyle, traces): 

2900 v_projection = track_projections[itrack] 

2901 dvmin = v_projection(0.) 

2902 dvmax = v_projection(1.) 

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

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

2905 

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

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

2908 p.fillRect(rect, style.fill_brush) 

2909 p.setPen(style.frame_pen) 

2910 p.drawRect(rect) 

2911 

2912 traces_by_style = {} 

2913 for itr, tr in enumerate(traces): 

2914 gt = self.gather(tr) 

2915 if gt not in self.key_to_row: 

2916 continue 

2917 

2918 itrack = self.key_to_row[gt] 

2919 if itrack not in track_projections: 

2920 continue 

2921 

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

2923 

2924 if len(traces) < 500: 

2925 drawbox(itrack, istyle, [tr]) 

2926 else: 

2927 if (itrack, istyle) not in traces_by_style: 

2928 traces_by_style[itrack, istyle] = [] 

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

2930 

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

2932 drawbox(itrack, istyle, traces) 

2933 

2934 def draw_visible_markers( 

2935 self, p, vcenter_projection, primary_pen): 

2936 

2937 try: 

2938 markers = self.markers.with_key_in_limited( 

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

2940 

2941 except pyrocko.pile.TooMany: 

2942 tmin = self.markers[0].tmin 

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

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

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

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

2947 v0, _ = vcenter_projection.get_out_range() 

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

2949 

2950 p.save() 

2951 

2952 pen = qg.QPen(primary_pen) 

2953 pen.setWidth(2) 

2954 pen.setStyle(qc.Qt.DotLine) 

2955 # pat = [5., 3.] 

2956 # pen.setDashPattern(pat) 

2957 p.setPen(pen) 

2958 

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

2960 s_selected = ' (all selected)' 

2961 elif self.n_selected_markers > 0: 

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

2963 else: 

2964 s_selected = '' 

2965 

2966 draw_label( 

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

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

2969 label_bg, 'LB') 

2970 

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

2972 p.drawLine(line) 

2973 p.restore() 

2974 

2975 return 

2976 

2977 for marker in markers: 

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

2979 and marker.kind in self.visible_marker_kinds: 

2980 

2981 marker.draw( 

2982 p, self.time_projection, vcenter_projection, 

2983 with_label=True) 

2984 

2985 def get_squirrel(self): 

2986 try: 

2987 return self.pile._squirrel 

2988 except AttributeError: 

2989 return None 

2990 

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

2992 sq = self.get_squirrel() 

2993 if sq is None: 

2994 return 

2995 

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

2997 v_projection = track_projections[itrack] 

2998 dvmin = v_projection(0.) 

2999 dvmax = v_projection(1.) 

3000 dtmin = time_projection.clipped(tmin, 0) 

3001 dtmax = time_projection.clipped(tmax, 1) 

3002 

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

3004 p.fillRect(rect, style.fill_brush) 

3005 p.setPen(style.frame_pen) 

3006 p.drawRect(rect) 

3007 

3008 pattern_list = [] 

3009 pattern_to_itrack = {} 

3010 for key in self.track_keys: 

3011 itrack = self.key_to_row[key] 

3012 if itrack not in track_projections: 

3013 continue 

3014 

3015 pattern = self.track_patterns[itrack] 

3016 pattern_to_itrack[tuple(pattern)] = itrack 

3017 pattern_list.append(tuple(pattern)) 

3018 

3019 vmin, vmax = self.get_time_range() 

3020 

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

3022 for coverage in sq.get_coverage( 

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

3024 itrack = pattern_to_itrack[coverage.pattern.nslc] 

3025 

3026 if coverage.changes is None: 

3027 drawbox( 

3028 itrack, coverage.tmin, coverage.tmax, 

3029 box_styles_coverage[kind][0]) 

3030 else: 

3031 t = None 

3032 pcount = 0 

3033 for tb, count in coverage.changes: 

3034 if t is not None and tb > t: 

3035 if pcount > 0: 

3036 drawbox( 

3037 itrack, t, tb, 

3038 box_styles_coverage[kind][ 

3039 min(len(box_styles_coverage)-1, 

3040 pcount)]) 

3041 

3042 t = tb 

3043 pcount = count 

3044 

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

3046 ''' 

3047 This performs the actual drawing. 

3048 ''' 

3049 

3050 self.timer_draw.start() 

3051 show_boxes = self.menuitem_showboxes.isChecked() 

3052 sq = self.get_squirrel() 

3053 

3054 if self.gather is None: 

3055 self.set_gathering() 

3056 

3057 if self.pile_has_changed: 

3058 

3059 if not self.sortingmode_change_delayed(): 

3060 self.sortingmode_change() 

3061 

3062 if show_boxes and sq is None: 

3063 self.determine_box_styles() 

3064 

3065 self.pile_has_changed = False 

3066 

3067 if h is None: 

3068 h = float(self.height()) 

3069 if w is None: 

3070 w = float(self.width()) 

3071 

3072 if printmode: 

3073 primary_color = (0, 0, 0) 

3074 else: 

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

3076 

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

3078 

3079 ax_h = float(self.ax_height) 

3080 

3081 vbottom_ax_projection = Projection() 

3082 vtop_ax_projection = Projection() 

3083 vcenter_projection = Projection() 

3084 

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

3086 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3087 vtop_ax_projection.set_out_range(0., ax_h) 

3088 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3089 vcenter_projection.set_in_range(0., 1.) 

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

3091 

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

3093 track_projections = {} 

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

3095 proj = Projection() 

3096 proj.set_out_range( 

3097 self.track_to_screen(i+0.05), 

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

3099 

3100 track_projections[i] = proj 

3101 

3102 if self.tmin > self.tmax: 

3103 return 

3104 

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

3106 vbottom_ax_projection.set_in_range(0, ax_h) 

3107 

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

3109 

3110 yscaler = pyrocko.plot.AutoScaler() 

3111 

3112 p.setPen(primary_pen) 

3113 

3114 font = qg.QFont() 

3115 font.setBold(True) 

3116 

3117 axannotfont = qg.QFont() 

3118 axannotfont.setBold(True) 

3119 axannotfont.setPointSize(8) 

3120 

3121 processed_traces = self.prepare_cutout2( 

3122 self.tmin, self.tmax, 

3123 trace_selector=self.trace_selector, 

3124 degap=self.menuitem_degap.isChecked(), 

3125 demean=self.menuitem_demean.isChecked()) 

3126 

3127 if not printmode and show_boxes: 

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

3129 or (self.view_mode is ViewMode.Waterfall 

3130 and not processed_traces): 

3131 

3132 if sq is None: 

3133 self.draw_trace_boxes( 

3134 p, self.time_projection, track_projections) 

3135 

3136 else: 

3137 self.draw_coverage( 

3138 p, self.time_projection, track_projections) 

3139 

3140 p.setFont(font) 

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

3142 

3143 color_lookup = dict( 

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

3145 

3146 self.track_to_nslc_ids = {} 

3147 nticks = 0 

3148 annot_labels = [] 

3149 

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

3151 waterfall = self.waterfall 

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

3153 waterfall.set_traces(processed_traces) 

3154 waterfall.set_cmap(self.waterfall_cmap) 

3155 waterfall.set_integrate(self.waterfall_integrate) 

3156 waterfall.set_clip( 

3157 self.waterfall_clip_min, self.waterfall_clip_max) 

3158 waterfall.show_absolute_values( 

3159 self.waterfall_show_absolute) 

3160 

3161 rect = qc.QRectF( 

3162 0, self.ax_height, 

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

3164 ) 

3165 waterfall.draw_waterfall(p, rect=rect) 

3166 

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

3168 show_scales = self.menuitem_showscalerange.isChecked() \ 

3169 or self.menuitem_showscaleaxis.isChecked() 

3170 

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

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

3173 - self.track_to_screen(0.05) 

3174 

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

3176 

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

3178 if self.menuitem_showscaleaxis.isChecked() \ 

3179 else 15 

3180 

3181 yscaler = pyrocko.plot.AutoScaler( 

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

3183 snap=show_scales 

3184 and not self.menuitem_showscaleaxis.isChecked()) 

3185 

3186 data_ranges = pyrocko.trace.minmax( 

3187 processed_traces, 

3188 key=self.scaling_key, 

3189 mode=self.scaling_base[0], 

3190 outer_mode=self.scaling_base[1]) 

3191 

3192 if not self.menuitem_fixscalerange.isChecked(): 

3193 self.old_data_ranges = data_ranges 

3194 else: 

3195 data_ranges.update(self.old_data_ranges) 

3196 

3197 self.apply_scaling_hooks(data_ranges) 

3198 

3199 trace_to_itrack = {} 

3200 track_scaling_keys = {} 

3201 track_scaling_colors = {} 

3202 for trace in processed_traces: 

3203 gt = self.gather(trace) 

3204 if gt not in self.key_to_row: 

3205 continue 

3206 

3207 itrack = self.key_to_row[gt] 

3208 if itrack not in track_projections: 

3209 continue 

3210 

3211 trace_to_itrack[trace] = itrack 

3212 

3213 if itrack not in self.track_to_nslc_ids: 

3214 self.track_to_nslc_ids[itrack] = set() 

3215 

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

3217 

3218 if itrack not in track_scaling_keys: 

3219 track_scaling_keys[itrack] = set() 

3220 

3221 scaling_key = self.scaling_key(trace) 

3222 track_scaling_keys[itrack].add(scaling_key) 

3223 

3224 color = pyrocko.plot.color( 

3225 color_lookup[self.color_gather(trace)]) 

3226 

3227 k = itrack, scaling_key 

3228 if k not in track_scaling_colors \ 

3229 and self.menuitem_colortraces.isChecked(): 

3230 track_scaling_colors[k] = color 

3231 else: 

3232 track_scaling_colors[k] = primary_color 

3233 

3234 # y axes, zero lines 

3235 trace_projections = {} 

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

3237 if itrack not in track_scaling_keys: 

3238 continue 

3239 uoff = 0 

3240 for scaling_key in track_scaling_keys[itrack]: 

3241 data_range = data_ranges[scaling_key] 

3242 dymin, dymax = data_range 

3243 ymin, ymax, yinc = yscaler.make_scale( 

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

3245 iexp = yscaler.make_exp(yinc) 

3246 factor = 10**iexp 

3247 trace_projection = track_projections[itrack].copy() 

3248 trace_projection.set_in_range(ymax, ymin) 

3249 trace_projections[itrack, scaling_key] = \ 

3250 trace_projection 

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

3252 vmin, vmax = trace_projection.get_out_range() 

3253 umax_zeroline = umax 

3254 uoffnext = uoff 

3255 

3256 if show_scales: 

3257 pen = qg.QPen(primary_pen) 

3258 k = itrack, scaling_key 

3259 if k in track_scaling_colors: 

3260 c = qg.QColor(*track_scaling_colors[ 

3261 itrack, scaling_key]) 

3262 

3263 pen.setColor(c) 

3264 

3265 p.setPen(pen) 

3266 if nlinesavail > 3: 

3267 if self.menuitem_showscaleaxis.isChecked(): 

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

3269 ny_annot = int( 

3270 math.floor(ymax/yinc) 

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

3272 

3273 for iy_annot in range(ny_annot): 

3274 y = ymin_annot + iy_annot*yinc 

3275 v = trace_projection(y) 

3276 line = qc.QLineF( 

3277 umax-10-uoff, v, umax-uoff, v) 

3278 

3279 p.drawLine(line) 

3280 if iy_annot == ny_annot - 1 \ 

3281 and iexp != 0: 

3282 sexp = ' &times; ' \ 

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

3284 else: 

3285 sexp = '' 

3286 

3287 snum = num_to_html(y/factor) 

3288 lab = Label( 

3289 p, 

3290 umax-20-uoff, 

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

3292 label_bg=None, 

3293 anchor='MR', 

3294 font=axannotfont, 

3295 color=c) 

3296 

3297 uoffnext = max( 

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

3299 

3300 annot_labels.append(lab) 

3301 if y == 0.: 

3302 umax_zeroline = \ 

3303 umax - 20 \ 

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

3305 - uoff 

3306 else: 

3307 if not show_boxes: 

3308 qpoints = make_QPolygonF( 

3309 [umax-20-uoff, 

3310 umax-10-uoff, 

3311 umax-10-uoff, 

3312 umax-20-uoff], 

3313 [vmax, vmax, vmin, vmin]) 

3314 p.drawPolyline(qpoints) 

3315 

3316 snum = num_to_html(ymin) 

3317 labmin = Label( 

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

3319 label_bg=None, 

3320 anchor='BR', 

3321 font=axannotfont, 

3322 color=c) 

3323 

3324 annot_labels.append(labmin) 

3325 snum = num_to_html(ymax) 

3326 labmax = Label( 

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

3328 label_bg=None, 

3329 anchor='TR', 

3330 font=axannotfont, 

3331 color=c) 

3332 

3333 annot_labels.append(labmax) 

3334 

3335 for lab in (labmin, labmax): 

3336 uoffnext = max( 

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

3338 

3339 if self.menuitem_showzeroline.isChecked(): 

3340 v = trace_projection(0.) 

3341 if vmin <= v <= vmax: 

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

3343 p.drawLine(line) 

3344 

3345 uoff = uoffnext 

3346 

3347 p.setFont(font) 

3348 p.setPen(primary_pen) 

3349 for trace in processed_traces: 

3350 if self.view_mode is not ViewMode.Wiggle: 

3351 break 

3352 

3353 if trace not in trace_to_itrack: 

3354 continue 

3355 

3356 itrack = trace_to_itrack[trace] 

3357 scaling_key = self.scaling_key(trace) 

3358 trace_projection = trace_projections[ 

3359 itrack, scaling_key] 

3360 

3361 vdata = trace_projection(trace.get_ydata()) 

3362 

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

3364 udata_max = float(self.time_projection( 

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

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

3367 

3368 qpoints = make_QPolygonF(udata, vdata) 

3369 

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

3371 vmin, vmax = trace_projection.get_out_range() 

3372 

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

3374 

3375 if self.menuitem_cliptraces.isChecked(): 

3376 p.setClipRect(trackrect) 

3377 

3378 if self.menuitem_colortraces.isChecked(): 

3379 color = pyrocko.plot.color( 

3380 color_lookup[self.color_gather(trace)]) 

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

3382 p.setPen(pen) 

3383 

3384 p.drawPolyline(qpoints) 

3385 

3386 if self.floating_marker: 

3387 self.floating_marker.draw_trace( 

3388 self, p, trace, 

3389 self.time_projection, trace_projection, 1.0) 

3390 

3391 for marker in self.markers.with_key_in( 

3392 self.tmin - self.markers_deltat_max, 

3393 self.tmax): 

3394 

3395 if marker.tmin < self.tmax \ 

3396 and self.tmin < marker.tmax \ 

3397 and marker.kind \ 

3398 in self.visible_marker_kinds: 

3399 marker.draw_trace( 

3400 self, p, trace, self.time_projection, 

3401 trace_projection, 1.0) 

3402 

3403 p.setPen(primary_pen) 

3404 

3405 if self.menuitem_cliptraces.isChecked(): 

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

3407 

3408 if self.floating_marker: 

3409 self.floating_marker.draw( 

3410 p, self.time_projection, vcenter_projection) 

3411 

3412 self.draw_visible_markers( 

3413 p, vcenter_projection, primary_pen) 

3414 

3415 p.setPen(primary_pen) 

3416 while font.pointSize() > 2: 

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

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

3419 - self.track_to_screen(0.05) 

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

3421 if nlinesavail > 1: 

3422 break 

3423 

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

3425 

3426 p.setFont(font) 

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

3428 

3429 for key in self.track_keys: 

3430 itrack = self.key_to_row[key] 

3431 if itrack in track_projections: 

3432 plabel = ' '.join( 

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

3434 lx = 10 

3435 ly = self.track_to_screen(itrack+0.5) 

3436 

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

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

3439 continue 

3440 

3441 contains_cursor = \ 

3442 self.track_to_screen(itrack) \ 

3443 < mouse_pos.y() \ 

3444 < self.track_to_screen(itrack+1) 

3445 

3446 if not contains_cursor: 

3447 continue 

3448 

3449 font_large = p.font() 

3450 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3451 p.setFont(font_large) 

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

3453 p.setFont(font) 

3454 

3455 for lab in annot_labels: 

3456 lab.draw() 

3457 

3458 self.timer_draw.stop() 

3459 

3460 def see_data_params(self): 

3461 

3462 min_deltat = self.content_deltat_range()[0] 

3463 

3464 # determine padding and downampling requirements 

3465 if self.lowpass is not None: 

3466 deltat_target = 1./self.lowpass * 0.25 

3467 ndecimate = min( 

3468 50, 

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

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

3471 else: 

3472 ndecimate = 1 

3473 tpad = min_deltat*5. 

3474 

3475 if self.highpass is not None: 

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

3477 

3478 nsee_points_per_trace = 5000*10 

3479 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3480 

3481 return ndecimate, tpad, tsee 

3482 

3483 def clean_update(self): 

3484 self.cached_processed_traces = None 

3485 self.update() 

3486 

3487 def get_adequate_tpad(self): 

3488 tpad = 0. 

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

3490 if f is not None: 

3491 tpad = max(tpad, 1.0/f) 

3492 

3493 for snuffling in self.snufflings: 

3494 if snuffling._post_process_hook_enabled \ 

3495 or snuffling._pre_process_hook_enabled: 

3496 

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

3498 

3499 return tpad 

3500 

3501 def prepare_cutout2( 

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

3503 demean=True, nmax=6000): 

3504 

3505 if self.pile.is_empty(): 

3506 return [] 

3507 

3508 nmax = self.visible_length 

3509 

3510 self.timer_cutout.start() 

3511 

3512 tsee = tmax-tmin 

3513 min_deltat_wo_decimate = tsee/nmax 

3514 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3515 

3516 min_deltat_allow = min_deltat_wo_decimate 

3517 if self.lowpass is not None: 

3518 target_deltat_lp = 0.25/self.lowpass 

3519 if target_deltat_lp > min_deltat_wo_decimate: 

3520 min_deltat_allow = min_deltat_w_decimate 

3521 

3522 min_deltat_allow = math.exp( 

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

3524 

3525 tmin_ = tmin 

3526 tmax_ = tmax 

3527 

3528 # fetch more than needed? 

3529 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3533 

3534 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3535 lphp = self.menuitem_lphp.isChecked() 

3536 ads = self.menuitem_allowdownsampling.isChecked() 

3537 

3538 tpad = self.get_adequate_tpad() 

3539 tpad = max(tpad, tsee) 

3540 

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

3542 vec = ( 

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

3544 self.highpass, fft_filtering, lphp, 

3545 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3546 ads, self.pile.get_update_count()) 

3547 

3548 if (self.cached_vec 

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

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

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

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

3553 and self.cached_processed_traces is not None): 

3554 

3555 logger.debug('Using cached traces') 

3556 processed_traces = self.cached_processed_traces 

3557 

3558 else: 

3559 processed_traces = [] 

3560 if self.pile.deltatmax >= min_deltat_allow: 

3561 

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

3563 def group_selector(gr): 

3564 return gr.deltatmax >= min_deltat_allow 

3565 

3566 kwargs = dict(group_selector=group_selector) 

3567 else: 

3568 kwargs = {} 

3569 

3570 if trace_selector is not None: 

3571 def trace_selectorx(tr): 

3572 return tr.deltat >= min_deltat_allow \ 

3573 and trace_selector(tr) 

3574 else: 

3575 def trace_selectorx(tr): 

3576 return tr.deltat >= min_deltat_allow 

3577 

3578 for traces in self.pile.chopper( 

3579 tmin=tmin, tmax=tmax, tpad=tpad, 

3580 want_incomplete=True, 

3581 degap=degap, 

3582 maxgap=gap_lap_tolerance, 

3583 maxlap=gap_lap_tolerance, 

3584 keep_current_files_open=True, 

3585 trace_selector=trace_selectorx, 

3586 accessor_id=id(self), 

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

3588 include_last=True, **kwargs): 

3589 

3590 if demean: 

3591 for tr in traces: 

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

3593 continue 

3594 y = tr.get_ydata() 

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

3596 

3597 traces = self.pre_process_hooks(traces) 

3598 

3599 for trace in traces: 

3600 

3601 if not (trace.meta 

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

3603 

3604 if fft_filtering: 

3605 but = pyrocko.response.ButterworthResponse 

3606 multres = pyrocko.response.MultiplyResponse 

3607 if self.lowpass is not None \ 

3608 or self.highpass is not None: 

3609 

3610 it = num.arange( 

3611 trace.data_len(), dtype=float) 

3612 detr_data, m, b = detrend( 

3613 it, trace.get_ydata()) 

3614 

3615 trace.set_ydata(detr_data) 

3616 

3617 freqs, fdata = trace.spectrum( 

3618 pad_to_pow2=True, tfade=None) 

3619 

3620 nfreqs = fdata.size 

3621 

3622 key = (trace.deltat, nfreqs) 

3623 

3624 if key not in self.tf_cache: 

3625 resps = [] 

3626 if self.lowpass is not None: 

3627 resps.append(but( 

3628 order=4, 

3629 corner=self.lowpass, 

3630 type='low')) 

3631 

3632 if self.highpass is not None: 

3633 resps.append(but( 

3634 order=4, 

3635 corner=self.highpass, 

3636 type='high')) 

3637 

3638 resp = multres(resps) 

3639 self.tf_cache[key] = \ 

3640 resp.evaluate(freqs) 

3641 

3642 filtered_data = num.fft.irfft( 

3643 fdata*self.tf_cache[key] 

3644 )[:trace.data_len()] 

3645 

3646 retrended_data = retrend( 

3647 it, filtered_data, m, b) 

3648 

3649 trace.set_ydata(retrended_data) 

3650 

3651 else: 

3652 

3653 if ads and self.lowpass is not None: 

3654 while trace.deltat \ 

3655 < min_deltat_wo_decimate: 

3656 

3657 trace.downsample(2, demean=False) 

3658 

3659 fmax = 0.5/trace.deltat 

3660 if not lphp and ( 

3661 self.lowpass is not None 

3662 and self.highpass is not None 

3663 and self.lowpass < fmax 

3664 and self.highpass < fmax 

3665 and self.highpass < self.lowpass): 

3666 

3667 trace.bandpass( 

3668 2, self.highpass, self.lowpass) 

3669 else: 

3670 if self.lowpass is not None: 

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

3672 trace.lowpass( 

3673 4, self.lowpass, 

3674 demean=False) 

3675 

3676 if self.highpass is not None: 

3677 if self.lowpass is None \ 

3678 or self.highpass \ 

3679 < self.lowpass: 

3680 

3681 if self.highpass < \ 

3682 0.5/trace.deltat: 

3683 trace.highpass( 

3684 4, self.highpass, 

3685 demean=False) 

3686 

3687 processed_traces.append(trace) 

3688 

3689 if self.rotate != 0.0: 

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

3691 cphi = math.cos(phi) 

3692 sphi = math.sin(phi) 

3693 for a in processed_traces: 

3694 for b in processed_traces: 

3695 if (a.network == b.network 

3696 and a.station == b.station 

3697 and a.location == b.location 

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

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

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

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

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

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

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

3705 

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

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

3708 a.set_ydata(aydata) 

3709 b.set_ydata(bydata) 

3710 

3711 processed_traces = self.post_process_hooks(processed_traces) 

3712 

3713 self.cached_processed_traces = processed_traces 

3714 self.cached_vec = vec 

3715 

3716 chopped_traces = [] 

3717 for trace in processed_traces: 

3718 chop_tmin = tmin_ - trace.deltat*4 

3719 chop_tmax = tmax_ + trace.deltat*4 

3720 

3721 try: 

3722 ctrace = trace.chop( 

3723 chop_tmin, chop_tmax, 

3724 inplace=False) 

3725 

3726 except pyrocko.trace.NoData: 

3727 continue 

3728 

3729 if ctrace.data_len() < 2: 

3730 continue 

3731 

3732 chopped_traces.append(ctrace) 

3733 

3734 self.timer_cutout.stop() 

3735 return chopped_traces 

3736 

3737 def pre_process_hooks(self, traces): 

3738 for snuffling in self.snufflings: 

3739 if snuffling._pre_process_hook_enabled: 

3740 traces = snuffling.pre_process_hook(traces) 

3741 

3742 return traces 

3743 

3744 def post_process_hooks(self, traces): 

3745 for snuffling in self.snufflings: 

3746 if snuffling._post_process_hook_enabled: 

3747 traces = snuffling.post_process_hook(traces) 

3748 

3749 return traces 

3750 

3751 def visible_length_change(self, ignore=None): 

3752 for menuitem, vlen in self.menuitems_visible_length: 

3753 if menuitem.isChecked(): 

3754 self.visible_length = vlen 

3755 

3756 def scaling_base_change(self, ignore=None): 

3757 for menuitem, scaling_base in self.menuitems_scaling_base: 

3758 if menuitem.isChecked(): 

3759 self.scaling_base = scaling_base 

3760 

3761 def scalingmode_change(self, ignore=None): 

3762 for menuitem, scaling_key in self.menuitems_scaling: 

3763 if menuitem.isChecked(): 

3764 self.scaling_key = scaling_key 

3765 self.update() 

3766 

3767 def apply_scaling_hooks(self, data_ranges): 

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

3769 hook = self.scaling_hooks[k] 

3770 hook(data_ranges) 

3771 

3772 def viewmode_change(self, ignore=True): 

3773 for item, mode in self.menuitems_viewmode: 

3774 if item.isChecked(): 

3775 self.view_mode = mode 

3776 break 

3777 else: 

3778 raise AttributeError('unknown view mode') 

3779 

3780 items_waterfall_disabled = ( 

3781 self.menuitem_showscaleaxis, 

3782 self.menuitem_showscalerange, 

3783 self.menuitem_showzeroline, 

3784 self.menuitem_colortraces, 

3785 self.menuitem_cliptraces, 

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

3787 ) 

3788 

3789 if self.view_mode is ViewMode.Waterfall: 

3790 self.parent().show_colorbar_ctrl(True) 

3791 self.parent().show_gain_ctrl(False) 

3792 

3793 for item in items_waterfall_disabled: 

3794 item.setDisabled(True) 

3795 

3796 self.visible_length = 180. 

3797 else: 

3798 self.parent().show_colorbar_ctrl(False) 

3799 self.parent().show_gain_ctrl(True) 

3800 

3801 for item in items_waterfall_disabled: 

3802 item.setDisabled(False) 

3803 

3804 self.visible_length_change() 

3805 self.update() 

3806 

3807 def set_scaling_hook(self, k, hook): 

3808 self.scaling_hooks[k] = hook 

3809 

3810 def remove_scaling_hook(self, k): 

3811 del self.scaling_hooks[k] 

3812 

3813 def remove_scaling_hooks(self): 

3814 self.scaling_hooks = {} 

3815 

3816 def s_sortingmode_change(self, ignore=None): 

3817 for menuitem, valfunc in self.menuitems_ssorting: 

3818 if menuitem.isChecked(): 

3819 self._ssort = valfunc 

3820 

3821 self.sortingmode_change() 

3822 

3823 def sortingmode_change(self, ignore=None): 

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

3825 if menuitem.isChecked(): 

3826 self.set_gathering(gather, color) 

3827 

3828 self.sortingmode_change_time = time.time() 

3829 

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

3831 self.lowpass = value 

3832 self.passband_check() 

3833 self.tf_cache = {} 

3834 self.update() 

3835 

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

3837 self.highpass = value 

3838 self.passband_check() 

3839 self.tf_cache = {} 

3840 self.update() 

3841 

3842 def passband_check(self): 

3843 if self.highpass and self.lowpass \ 

3844 and self.highpass >= self.lowpass: 

3845 

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

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

3848 'deactivate the highpass.' 

3849 

3850 self.update_status() 

3851 else: 

3852 oldmess = self.message 

3853 self.message = None 

3854 if oldmess is not None: 

3855 self.update_status() 

3856 

3857 def gain_change(self, value, ignore): 

3858 self.gain = value 

3859 self.update() 

3860 

3861 def rot_change(self, value, ignore): 

3862 self.rotate = value 

3863 self.update() 

3864 

3865 def waterfall_cmap_change(self, cmap): 

3866 self.waterfall_cmap = cmap 

3867 self.update() 

3868 

3869 def waterfall_clip_change(self, clip_min, clip_max): 

3870 self.waterfall_clip_min = clip_min 

3871 self.waterfall_clip_max = clip_max 

3872 self.update() 

3873 

3874 def waterfall_show_absolute_change(self, toggle): 

3875 self.waterfall_show_absolute = toggle 

3876 self.update() 

3877 

3878 def waterfall_set_integrate(self, toggle): 

3879 self.waterfall_integrate = toggle 

3880 self.update() 

3881 

3882 def set_selected_markers(self, markers): 

3883 ''' 

3884 Set a list of markers selected 

3885 

3886 :param markers: list of markers 

3887 ''' 

3888 self.deselect_all() 

3889 for m in markers: 

3890 m.selected = True 

3891 

3892 self.update() 

3893 

3894 def deselect_all(self): 

3895 for marker in self.markers: 

3896 marker.selected = False 

3897 

3898 def animate_picking(self): 

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

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

3901 

3902 def get_nslc_ids_for_track(self, ftrack): 

3903 itrack = int(ftrack) 

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

3905 

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

3907 if self.picking: 

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

3909 self.picking = None 

3910 self.picking_down = None 

3911 self.picking_timer.stop() 

3912 self.picking_timer = None 

3913 if not abort: 

3914 self.add_marker(self.floating_marker) 

3915 self.floating_marker.selected = True 

3916 self.emit_selected_markers() 

3917 

3918 self.floating_marker = None 

3919 

3920 def start_picking(self, ignore): 

3921 

3922 if not self.picking: 

3923 self.deselect_all() 

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

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

3926 

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

3928 self.picking.setGeometry( 

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

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

3931 

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

3933 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3935 self.floating_marker.selected = True 

3936 

3937 self.picking_timer = qc.QTimer() 

3938 self.picking_timer.timeout.connect( 

3939 self.animate_picking) 

3940 

3941 self.picking_timer.setInterval(50) 

3942 self.picking_timer.start() 

3943 

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

3945 if self.picking: 

3946 mouset = self.time_projection.rev(x) 

3947 dt = 0.0 

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

3949 if mouset < self.tmin: 

3950 dt = -(self.tmin - mouset) 

3951 else: 

3952 dt = mouset - self.tmax 

3953 ddt = self.tmax-self.tmin 

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

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

3956 

3957 x0 = x 

3958 if self.picking_down is not None: 

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

3960 

3961 w = abs(x-x0) 

3962 x0 = min(x0, x) 

3963 

3964 tmin, tmax = ( 

3965 self.time_projection.rev(x0), 

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

3967 

3968 tmin, tmax = ( 

3969 max(working_system_time_range[0], tmin), 

3970 min(working_system_time_range[1], tmax)) 

3971 

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

3973 

3974 self.picking.setGeometry( 

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

3976 

3977 ftrack = self.track_to_screen.rev(y) 

3978 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3980 

3981 if dt != 0.0 and doshift: 

3982 self.interrupt_following() 

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

3984 

3985 self.update() 

3986 

3987 def update_status(self): 

3988 

3989 if self.message is None: 

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

3991 

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

3993 if not is_working_time(mouse_t): 

3994 return 

3995 

3996 if self.floating_marker: 

3997 tmi, tma = ( 

3998 self.floating_marker.tmin, 

3999 self.floating_marker.tmax) 

4000 

4001 tt, ms = gmtime_x(tmi) 

4002 

4003 if tmi == tma: 

4004 message = mystrftime( 

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

4006 tt=tt, milliseconds=ms) 

4007 else: 

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

4009 message = mystrftime( 

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

4011 tt=tt, milliseconds=ms) 

4012 else: 

4013 tt, ms = gmtime_x(mouse_t) 

4014 

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

4016 else: 

4017 message = self.message 

4018 

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

4020 sb.clearMessage() 

4021 sb.showMessage(message) 

4022 

4023 def set_sortingmode_change_delay_time(self, dt): 

4024 self.sortingmode_change_delay_time = dt 

4025 

4026 def sortingmode_change_delayed(self): 

4027 now = time.time() 

4028 return ( 

4029 self.sortingmode_change_delay_time is not None 

4030 and now - self.sortingmode_change_time 

4031 < self.sortingmode_change_delay_time) 

4032 

4033 def set_visible_marker_kinds(self, kinds): 

4034 self.deselect_all() 

4035 self.visible_marker_kinds = tuple(kinds) 

4036 self.emit_selected_markers() 

4037 

4038 def following(self): 

4039 return self.follow_timer is not None \ 

4040 and not self.following_interrupted() 

4041 

4042 def interrupt_following(self): 

4043 self.interactive_range_change_time = time.time() 

4044 

4045 def following_interrupted(self, now=None): 

4046 if now is None: 

4047 now = time.time() 

4048 return now - self.interactive_range_change_time \ 

4049 < self.interactive_range_change_delay_time 

4050 

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

4052 if tmax_start is None: 

4053 tmax_start = time.time() 

4054 self.show_all = False 

4055 self.follow_time = tlen 

4056 self.follow_timer = qc.QTimer(self) 

4057 self.follow_timer.timeout.connect( 

4058 self.follow_update) 

4059 self.follow_timer.setInterval(interval) 

4060 self.follow_timer.start() 

4061 self.follow_started = time.time() 

4062 self.follow_lapse = lapse 

4063 self.follow_tshift = self.follow_started - tmax_start 

4064 self.interactive_range_change_time = 0.0 

4065 

4066 def unfollow(self): 

4067 if self.follow_timer is not None: 

4068 self.follow_timer.stop() 

4069 self.follow_timer = None 

4070 self.interactive_range_change_time = 0.0 

4071 

4072 def follow_update(self): 

4073 rnow = time.time() 

4074 if self.follow_lapse is None: 

4075 now = rnow 

4076 else: 

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

4078 * self.follow_lapse 

4079 

4080 if self.following_interrupted(rnow): 

4081 return 

4082 self.set_time_range( 

4083 now-self.follow_time-self.follow_tshift, 

4084 now-self.follow_tshift) 

4085 

4086 self.update() 

4087 

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

4089 self.return_tag = return_tag 

4090 self.window().close() 

4091 

4092 def cleanup(self): 

4093 self.about_to_close.emit() 

4094 self.timer.stop() 

4095 if self.follow_timer is not None: 

4096 self.follow_timer.stop() 

4097 

4098 for snuffling in list(self.snufflings): 

4099 self.remove_snuffling(snuffling) 

4100 

4101 def set_error_message(self, key, value): 

4102 if value is None: 

4103 if key in self.error_messages: 

4104 del self.error_messages[key] 

4105 else: 

4106 self.error_messages[key] = value 

4107 

4108 def inputline_changed(self, text): 

4109 pass 

4110 

4111 def inputline_finished(self, text): 

4112 line = str(text) 

4113 

4114 toks = line.split() 

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

4116 if len(toks) >= 1: 

4117 command = toks[0].lower() 

4118 

4119 try: 

4120 quick_filter_commands = { 

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

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

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

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

4125 

4126 if command in quick_filter_commands: 

4127 if len(toks) >= 2: 

4128 patterns = [ 

4129 quick_filter_commands[toks[0]] % pat 

4130 for pat in toks[1:]] 

4131 self.set_quick_filter_patterns(patterns, line) 

4132 else: 

4133 self.set_quick_filter_patterns(None) 

4134 

4135 self.update() 

4136 

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

4138 if len(toks) >= 2: 

4139 patterns = [] 

4140 if len(toks) == 2: 

4141 patterns = [toks[1]] 

4142 elif len(toks) >= 3: 

4143 x = { 

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

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

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

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

4148 

4149 if toks[1] in x: 

4150 patterns.extend( 

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

4152 

4153 for pattern in patterns: 

4154 if command == 'hide': 

4155 self.add_blacklist_pattern(pattern) 

4156 else: 

4157 self.remove_blacklist_pattern(pattern) 

4158 

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

4160 self.clear_blacklist() 

4161 

4162 clearit = True 

4163 

4164 self.update() 

4165 

4166 elif command == 'markers': 

4167 if len(toks) == 2: 

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

4169 kinds = self.all_marker_kinds 

4170 else: 

4171 kinds = [] 

4172 for x in toks[1]: 

4173 try: 

4174 kinds.append(int(x)) 

4175 except Exception: 

4176 pass 

4177 

4178 self.set_visible_marker_kinds(kinds) 

4179 

4180 elif len(toks) == 1: 

4181 self.set_visible_marker_kinds(()) 

4182 

4183 self.update() 

4184 

4185 elif command == 'scaling': 

4186 if len(toks) == 2: 

4187 hideit = False 

4188 error = 'wrong number of arguments' 

4189 

4190 if len(toks) >= 3: 

4191 vmin, vmax = [ 

4192 pyrocko.model.float_or_none(x) 

4193 for x in toks[-2:]] 

4194 

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

4196 if k in d: 

4197 if vmin is not None: 

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

4199 if vmax is not None: 

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

4201 

4202 if len(toks) == 1: 

4203 self.remove_scaling_hooks() 

4204 

4205 elif len(toks) == 3: 

4206 def hook(data_ranges): 

4207 for k in data_ranges: 

4208 upd(data_ranges, k, vmin, vmax) 

4209 

4210 self.set_scaling_hook('_', hook) 

4211 

4212 elif len(toks) == 4: 

4213 pattern = toks[1] 

4214 

4215 def hook(data_ranges): 

4216 for k in pyrocko.util.match_nslcs( 

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

4218 

4219 upd(data_ranges, k, vmin, vmax) 

4220 

4221 self.set_scaling_hook(pattern, hook) 

4222 

4223 elif command == 'goto': 

4224 toks2 = line.split(None, 1) 

4225 if len(toks2) == 2: 

4226 arg = toks2[1] 

4227 m = re.match( 

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

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

4230 if m: 

4231 tlen = None 

4232 if not m.group(1): 

4233 tlen = 12*32*24*60*60 

4234 elif not m.group(2): 

4235 tlen = 32*24*60*60 

4236 elif not m.group(3): 

4237 tlen = 24*60*60 

4238 elif not m.group(4): 

4239 tlen = 60*60 

4240 elif not m.group(5): 

4241 tlen = 60 

4242 

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

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

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

4246 t = pyrocko.util.str_to_time(arg) 

4247 self.go_to_time(t, tlen=tlen) 

4248 

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

4250 supl = '00:00:00' 

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

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

4253 tmin, tmax = self.get_time_range() 

4254 sdate = pyrocko.util.time_to_str( 

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

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

4257 self.go_to_time(t) 

4258 

4259 elif arg == 'today': 

4260 self.go_to_time( 

4261 day_start( 

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

4263 

4264 elif arg == 'yesterday': 

4265 self.go_to_time( 

4266 day_start( 

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

4268 

4269 else: 

4270 self.go_to_event_by_name(arg) 

4271 

4272 else: 

4273 raise PileViewerMainException( 

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

4275 

4276 except PileViewerMainException as e: 

4277 error = str(e) 

4278 hideit = False 

4279 

4280 return clearit, hideit, error 

4281 

4282 return PileViewerMain 

4283 

4284 

4285PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4286GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget) 

4287 

4288 

4289class LineEditWithAbort(qw.QLineEdit): 

4290 

4291 aborted = qc.pyqtSignal() 

4292 history_down = qc.pyqtSignal() 

4293 history_up = qc.pyqtSignal() 

4294 

4295 def keyPressEvent(self, key_event): 

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

4297 self.aborted.emit() 

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

4299 self.history_down.emit() 

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

4301 self.history_up.emit() 

4302 else: 

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

4304 

4305 

4306class PileViewer(qw.QFrame): 

4307 ''' 

4308 PileViewerMain + Controls + Inputline 

4309 ''' 

4310 

4311 def __init__( 

4312 self, pile, 

4313 ntracks_shown_max=20, 

4314 marker_editor_sortable=True, 

4315 use_opengl=None, 

4316 panel_parent=None, 

4317 *args): 

4318 

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

4320 

4321 layout = qw.QGridLayout() 

4322 layout.setContentsMargins(0, 0, 0, 0) 

4323 layout.setSpacing(0) 

4324 

4325 self.menu = PileViewerMenuBar(self) 

4326 

4327 if use_opengl is None: 

4328 use_opengl = is_macos 

4329 

4330 if use_opengl: 

4331 self.viewer = GLPileViewerMain( 

4332 pile, 

4333 ntracks_shown_max=ntracks_shown_max, 

4334 panel_parent=panel_parent, 

4335 menu=self.menu) 

4336 else: 

4337 self.viewer = PileViewerMain( 

4338 pile, 

4339 ntracks_shown_max=ntracks_shown_max, 

4340 panel_parent=panel_parent, 

4341 menu=self.menu) 

4342 

4343 self.marker_editor_sortable = marker_editor_sortable 

4344 

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

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

4347 

4348 self.input_area = qw.QFrame(self) 

4349 ia_layout = qw.QGridLayout() 

4350 ia_layout.setContentsMargins(11, 11, 11, 11) 

4351 self.input_area.setLayout(ia_layout) 

4352 

4353 self.inputline = LineEditWithAbort(self.input_area) 

4354 self.inputline.returnPressed.connect( 

4355 self.inputline_returnpressed) 

4356 self.inputline.editingFinished.connect( 

4357 self.inputline_finished) 

4358 self.inputline.aborted.connect( 

4359 self.inputline_aborted) 

4360 

4361 self.inputline.history_down.connect( 

4362 lambda: self.step_through_history(1)) 

4363 self.inputline.history_up.connect( 

4364 lambda: self.step_through_history(-1)) 

4365 

4366 self.inputline.textEdited.connect( 

4367 self.inputline_changed) 

4368 

4369 self.inputline.setPlaceholderText( 

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

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

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

4373 self.input_area.hide() 

4374 self.history = None 

4375 

4376 self.inputline_error_str = None 

4377 

4378 self.inputline_error = qw.QLabel() 

4379 self.inputline_error.hide() 

4380 

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

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

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

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

4385 

4386 pb = Progressbars(self) 

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

4388 self.progressbars = pb 

4389 

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

4391 self.scrollbar = scrollbar 

4392 layout.addWidget(scrollbar, 1, 1) 

4393 self.scrollbar.valueChanged.connect( 

4394 self.scrollbar_changed) 

4395 

4396 self.block_scrollbar_changes = False 

4397 

4398 self.viewer.want_input.connect( 

4399 self.inputline_show) 

4400 self.viewer.tracks_range_changed.connect( 

4401 self.tracks_range_changed) 

4402 self.viewer.pile_has_changed_signal.connect( 

4403 self.adjust_controls) 

4404 self.viewer.about_to_close.connect( 

4405 self.save_inputline_history) 

4406 

4407 self.setLayout(layout) 

4408 

4409 def cleanup(self): 

4410 self.viewer.cleanup() 

4411 

4412 def get_progressbars(self): 

4413 return self.progressbars 

4414 

4415 def inputline_show(self): 

4416 if not self.history: 

4417 self.load_inputline_history() 

4418 

4419 self.input_area.show() 

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

4421 self.inputline.selectAll() 

4422 

4423 def inputline_set_error(self, string): 

4424 self.inputline_error_str = string 

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

4426 self.inputline.selectAll() 

4427 self.inputline_error.setText(string) 

4428 self.input_area.show() 

4429 self.inputline_error.show() 

4430 

4431 def inputline_clear_error(self): 

4432 if self.inputline_error_str: 

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

4434 self.inputline_error_str = None 

4435 self.inputline_error.clear() 

4436 self.inputline_error.hide() 

4437 

4438 def inputline_changed(self, line): 

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

4440 self.inputline_clear_error() 

4441 

4442 def inputline_returnpressed(self): 

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

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

4445 

4446 if error: 

4447 self.inputline_set_error(error) 

4448 

4449 line = line.strip() 

4450 

4451 if line != '' and not error: 

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

4453 self.history.append(line) 

4454 

4455 if clearit: 

4456 

4457 self.inputline.blockSignals(True) 

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

4459 if qpat is None: 

4460 self.inputline.clear() 

4461 else: 

4462 self.inputline.setText(qinp) 

4463 self.inputline.blockSignals(False) 

4464 

4465 if hideit and not error: 

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

4467 self.input_area.hide() 

4468 

4469 self.hist_ind = len(self.history) 

4470 

4471 def inputline_aborted(self): 

4472 ''' 

4473 Hide the input line. 

4474 ''' 

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

4476 self.hist_ind = len(self.history) 

4477 self.input_area.hide() 

4478 

4479 def save_inputline_history(self): 

4480 ''' 

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

4482 ''' 

4483 if not self.history: 

4484 return 

4485 

4486 conf = pyrocko.config 

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

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

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

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

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

4492 

4493 def load_inputline_history(self): 

4494 ''' 

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

4496 ''' 

4497 conf = pyrocko.config 

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

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

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

4501 f.write('\n') 

4502 

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

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

4505 

4506 self.hist_ind = len(self.history) 

4507 

4508 def step_through_history(self, ud=1): 

4509 ''' 

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

4511 ''' 

4512 n = len(self.history) 

4513 self.hist_ind += ud 

4514 self.hist_ind %= (n + 1) 

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

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

4517 else: 

4518 self.inputline.setText('') 

4519 

4520 def inputline_finished(self): 

4521 pass 

4522 

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

4524 if self.block_scrollbar_changes: 

4525 return 

4526 

4527 self.scrollbar.blockSignals(True) 

4528 self.scrollbar.setPageStep(ihi-ilo) 

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

4530 self.scrollbar.setRange(0, vmax) 

4531 self.scrollbar.setValue(ilo) 

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

4533 self.scrollbar.blockSignals(False) 

4534 

4535 def scrollbar_changed(self, value): 

4536 self.block_scrollbar_changes = True 

4537 ilo = value 

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

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

4540 self.block_scrollbar_changes = False 

4541 self.update_contents() 

4542 

4543 def controls(self): 

4544 frame = qw.QFrame(self) 

4545 layout = qw.QGridLayout() 

4546 frame.setLayout(layout) 

4547 

4548 minfreq = 0.001 

4549 maxfreq = 1000.0 

4550 self.lowpass_control = ValControl(high_is_none=True) 

4551 self.lowpass_control.setup( 

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

4553 self.highpass_control = ValControl(low_is_none=True) 

4554 self.highpass_control.setup( 

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

4556 self.gain_control = ValControl() 

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

4558 self.rot_control = LinValControl() 

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

4560 self.colorbar_control = ColorbarControl(self) 

4561 

4562 self.lowpass_control.valchange.connect( 

4563 self.viewer.lowpass_change) 

4564 self.highpass_control.valchange.connect( 

4565 self.viewer.highpass_change) 

4566 self.gain_control.valchange.connect( 

4567 self.viewer.gain_change) 

4568 self.rot_control.valchange.connect( 

4569 self.viewer.rot_change) 

4570 self.colorbar_control.cmap_changed.connect( 

4571 self.viewer.waterfall_cmap_change 

4572 ) 

4573 self.colorbar_control.clip_changed.connect( 

4574 self.viewer.waterfall_clip_change 

4575 ) 

4576 self.colorbar_control.show_absolute_toggled.connect( 

4577 self.viewer.waterfall_show_absolute_change 

4578 ) 

4579 self.colorbar_control.show_integrate_toggled.connect( 

4580 self.viewer.waterfall_set_integrate 

4581 ) 

4582 

4583 for icontrol, control in enumerate(( 

4584 self.highpass_control, 

4585 self.lowpass_control, 

4586 self.gain_control, 

4587 self.rot_control, 

4588 self.colorbar_control)): 

4589 

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

4591 layout.addWidget(widget, icontrol, iwidget) 

4592 

4593 spacer = qw.QSpacerItem( 

4594 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4596 

4597 self.adjust_controls() 

4598 self.viewer.viewmode_change(ViewMode.Wiggle) 

4599 return frame 

4600 

4601 def marker_editor(self): 

4602 editor = pyrocko.gui.snuffler.marker_editor.MarkerEditor( 

4603 self, sortable=self.marker_editor_sortable) 

4604 

4605 editor.set_viewer(self.get_view()) 

4606 editor.get_marker_model().dataChanged.connect( 

4607 self.update_contents) 

4608 return editor 

4609 

4610 def adjust_controls(self): 

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

4612 maxfreq = 0.5/dtmin 

4613 minfreq = (0.5/dtmax)*0.0001 

4614 self.lowpass_control.set_range(minfreq, maxfreq) 

4615 self.highpass_control.set_range(minfreq, maxfreq) 

4616 

4617 def setup_snufflings(self): 

4618 self.viewer.setup_snufflings() 

4619 

4620 def get_view(self): 

4621 return self.viewer 

4622 

4623 def update_contents(self): 

4624 self.viewer.update() 

4625 

4626 def get_pile(self): 

4627 return self.viewer.get_pile() 

4628 

4629 def show_colorbar_ctrl(self, show): 

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

4631 w.setVisible(show) 

4632 

4633 def show_gain_ctrl(self, show): 

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

4635 w.setVisible(show)