Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/gui/snuffler/pile_viewer.py: 71%

2852 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2024-01-04 09:19 +0000

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 PileViewerMenuBarButton(qw.QPushButton): 

773 

774 def __init__(self, text, *args, **kwargs): 

775 qw.QPushButton.__init__(self, text, *args, **kwargs) 

776 self.setFlat(True) 

777 self.setSizePolicy( 

778 qw.QSizePolicy.Preferred, qw.QSizePolicy.Preferred) 

779 self.setContentsMargins(0, 0, 0, 0) 

780 s = self.fontMetrics().boundingRect(text) 

781 self.setMaximumHeight(s.height() + 3) 

782 self.setMaximumWidth(max(s.height() + 3, s.width() + 10)) 

783 

784 def sizeHint(self): 

785 s = qw.QPushButton.sizeHint(self) 

786 return qc.QSize(max(s.height(), s.width()), s.height()) 

787 

788 

789def MakePileViewerMainClass(base): 

790 

791 class PileViewerMain(base): 

792 

793 want_input = qc.pyqtSignal() 

794 toggle_input = qc.pyqtSignal() 

795 about_to_close = qc.pyqtSignal() 

796 pile_has_changed_signal = qc.pyqtSignal() 

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

798 

799 begin_markers_add = qc.pyqtSignal(int, int) 

800 end_markers_add = qc.pyqtSignal() 

801 begin_markers_remove = qc.pyqtSignal(int, int) 

802 end_markers_remove = qc.pyqtSignal() 

803 

804 marker_selection_changed = qc.pyqtSignal(list) 

805 active_event_marker_changed = qc.pyqtSignal() 

806 

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

808 menu=None): 

809 base.__init__(self, *args) 

810 

811 self.pile = pile 

812 self.ax_height = 80 

813 self.panel_parent = panel_parent 

814 

815 self.click_tolerance = 5 

816 

817 self.ntracks_shown_max = ntracks_shown_max 

818 self.initial_ntracks_shown_max = ntracks_shown_max 

819 self.ntracks = 0 

820 self.show_all = True 

821 self.shown_tracks_range = None 

822 self.track_start = None 

823 self.track_trange = None 

824 

825 self.lowpass = None 

826 self.highpass = None 

827 self.gain = 1.0 

828 self.rotate = 0.0 

829 self.picking_down = None 

830 self.picking = None 

831 self.floating_marker = None 

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

833 self.markers_deltat_max = 0. 

834 self.n_selected_markers = 0 

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

836 self.visible_marker_kinds = self.all_marker_kinds 

837 self.active_event_marker = None 

838 self.ignore_releases = 0 

839 self.reloaded = False 

840 self.pile_has_changed = False 

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

842 

843 self.tax = TimeAx() 

844 self.setBackgroundRole(qg.QPalette.Base) 

845 self.setAutoFillBackground(True) 

846 poli = qw.QSizePolicy( 

847 qw.QSizePolicy.Expanding, 

848 qw.QSizePolicy.Expanding) 

849 

850 self.setSizePolicy(poli) 

851 self.setMinimumSize(300, 200) 

852 self.setFocusPolicy(qc.Qt.ClickFocus) 

853 

854 self.menu = menu 

855 

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

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

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

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

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

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

862 

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

864 

865 self.snufflings_menu = self.toggle_panel_menu.addMenu( 

866 'Run Snuffling') 

867 self.toggle_panel_menu.addSeparator() 

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

869 help_menu.addSeparator() 

870 

871 file_menu.addAction( 

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

873 'Open waveform files...', 

874 self.open_waveforms, 

875 qg.QKeySequence.Open) 

876 

877 file_menu.addAction( 

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

879 'Open waveform directory...', 

880 self.open_waveform_directory) 

881 

882 file_menu.addAction( 

883 'Open station files...', 

884 self.open_stations) 

885 

886 file_menu.addAction( 

887 'Open StationXML files...', 

888 self.open_stations_xml) 

889 

890 file_menu.addAction( 

891 'Open event file...', 

892 self.read_events) 

893 

894 file_menu.addSeparator() 

895 file_menu.addAction( 

896 'Open marker file...', 

897 self.read_markers) 

898 

899 file_menu.addAction( 

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

901 'Save markers...', 

902 self.write_markers, 

903 qg.QKeySequence.Save) 

904 

905 file_menu.addAction( 

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

907 'Save selected markers...', 

908 self.write_selected_markers, 

909 qg.QKeySequence.SaveAs) 

910 

911 file_menu.addSeparator() 

912 file_menu.addAction( 

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

914 'Print', 

915 self.printit, 

916 qg.QKeySequence.Print) 

917 

918 file_menu.addAction( 

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

920 'Save as SVG or PNG', 

921 self.savesvg, 

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

923 

924 file_menu.addSeparator() 

925 close = file_menu.addAction( 

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

927 'Close', 

928 self.myclose) 

929 close.setShortcuts( 

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

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

932 

933 # Scale Menu 

934 menudef = [ 

935 ('Individual Scale', 

936 lambda tr: tr.nslc_id, 

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

938 ('Common Scale', 

939 lambda tr: None, 

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

941 ('Common Scale per Station', 

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

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

944 ('Common Scale per Station Location', 

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

946 ('Common Scale per Component', 

947 lambda tr: (tr.channel)), 

948 ] 

949 

950 self.menuitems_scaling = add_radiobuttongroup( 

951 scale_menu, menudef, self.scalingmode_change, 

952 default=self.config.trace_scale) 

953 scale_menu.addSeparator() 

954 

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

956 self.scaling_hooks = {} 

957 self.scalingmode_change() 

958 

959 menudef = [ 

960 ('Scaling based on Minimum and Maximum', 

961 ('minmax', 'minmax')), 

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

963 ('minmax', 'robust')), 

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

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

966 ] 

967 

968 self.menuitems_scaling_base = add_radiobuttongroup( 

969 scale_menu, menudef, self.scaling_base_change) 

970 

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

972 scale_menu.addSeparator() 

973 

974 self.menuitem_fixscalerange = scale_menu.addAction( 

975 'Fix Scale Ranges') 

976 self.menuitem_fixscalerange.setCheckable(True) 

977 

978 # Sort Menu 

979 def sector_dist(sta): 

980 if sta.dist_m is None: 

981 return None, None 

982 else: 

983 return ( 

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

985 m_float(sta.dist_m)) 

986 

987 menudef = [ 

988 ('Sort by Names', 

989 lambda tr: (), 

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

991 ('Sort by Distance', 

992 lambda tr: self.station_attrib( 

993 tr, 

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

995 lambda tr: (None,)), 

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

997 ('Sort by Azimuth', 

998 lambda tr: self.station_attrib( 

999 tr, 

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

1001 lambda tr: (None,))), 

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

1003 lambda tr: self.station_attrib( 

1004 tr, 

1005 sector_dist, 

1006 lambda tr: (None, None))), 

1007 ('Sort by Backazimuth', 

1008 lambda tr: self.station_attrib( 

1009 tr, 

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

1011 lambda tr: (None,))), 

1012 ] 

1013 self.menuitems_ssorting = add_radiobuttongroup( 

1014 sort_menu, menudef, self.s_sortingmode_change) 

1015 sort_menu.addSeparator() 

1016 

1017 self._ssort = lambda tr: () 

1018 

1019 self.menu.addSeparator() 

1020 

1021 menudef = [ 

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

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

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

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

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

1027 lambda tr: tr.channel)), 

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

1029 ((1, 0, 3, 2), 

1030 lambda tr: tr.channel)), 

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

1032 ((2, 0, 1, 3), 

1033 lambda tr: tr.channel)), 

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

1035 ((3, 0, 1, 2), 

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

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

1038 ((0, 1, 3), 

1039 lambda tr: tr.location)), 

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

1041 ((1, 0, 3), 

1042 lambda tr: tr.location)), 

1043 ] 

1044 

1045 self.menuitems_sorting = add_radiobuttongroup( 

1046 sort_menu, menudef, self.sortingmode_change) 

1047 

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

1049 self.config.visible_length_setting] 

1050 

1051 # View menu 

1052 self.menuitems_visible_length = add_radiobuttongroup( 

1053 view_menu, menudef, 

1054 self.visible_length_change) 

1055 view_menu.addSeparator() 

1056 

1057 view_modes = [ 

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

1059 ('Waterfall', ViewMode.Waterfall) 

1060 ] 

1061 

1062 self.menuitems_viewmode = add_radiobuttongroup( 

1063 view_menu, view_modes, 

1064 self.viewmode_change, default=ViewMode.Wiggle) 

1065 view_menu.addSeparator() 

1066 

1067 self.menuitem_cliptraces = view_menu.addAction( 

1068 'Clip Traces') 

1069 self.menuitem_cliptraces.setCheckable(True) 

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

1071 

1072 self.menuitem_showboxes = view_menu.addAction( 

1073 'Show Boxes') 

1074 self.menuitem_showboxes.setCheckable(True) 

1075 self.menuitem_showboxes.setChecked( 

1076 self.config.show_boxes) 

1077 

1078 self.menuitem_colortraces = view_menu.addAction( 

1079 'Color Traces') 

1080 self.menuitem_colortraces.setCheckable(True) 

1081 self.menuitem_antialias = view_menu.addAction( 

1082 'Antialiasing') 

1083 self.menuitem_antialias.setCheckable(True) 

1084 

1085 view_menu.addSeparator() 

1086 self.menuitem_showscalerange = view_menu.addAction( 

1087 'Show Scale Ranges') 

1088 self.menuitem_showscalerange.setCheckable(True) 

1089 self.menuitem_showscalerange.setChecked( 

1090 self.config.show_scale_ranges) 

1091 

1092 self.menuitem_showscaleaxis = view_menu.addAction( 

1093 'Show Scale Axes') 

1094 self.menuitem_showscaleaxis.setCheckable(True) 

1095 self.menuitem_showscaleaxis.setChecked( 

1096 self.config.show_scale_axes) 

1097 

1098 self.menuitem_showzeroline = view_menu.addAction( 

1099 'Show Zero Lines') 

1100 self.menuitem_showzeroline.setCheckable(True) 

1101 

1102 view_menu.addSeparator() 

1103 view_menu.addAction( 

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

1105 'Fullscreen', 

1106 self.toggle_fullscreen, 

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

1108 

1109 # Options Menu 

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

1111 self.menuitem_demean.setCheckable(True) 

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

1113 self.menuitem_demean.setShortcut( 

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

1115 

1116 self.menuitem_distances_3d = options_menu.addAction( 

1117 '3D distances', 

1118 self.distances_3d_changed) 

1119 self.menuitem_distances_3d.setCheckable(True) 

1120 

1121 self.menuitem_allowdownsampling = options_menu.addAction( 

1122 'Allow Downsampling') 

1123 self.menuitem_allowdownsampling.setCheckable(True) 

1124 self.menuitem_allowdownsampling.setChecked(True) 

1125 

1126 self.menuitem_degap = options_menu.addAction( 

1127 'Allow Degapping') 

1128 self.menuitem_degap.setCheckable(True) 

1129 self.menuitem_degap.setChecked(True) 

1130 

1131 options_menu.addSeparator() 

1132 

1133 self.menuitem_fft_filtering = options_menu.addAction( 

1134 'FFT Filtering') 

1135 self.menuitem_fft_filtering.setCheckable(True) 

1136 

1137 self.menuitem_lphp = options_menu.addAction( 

1138 'Bandpass is Low- + Highpass') 

1139 self.menuitem_lphp.setCheckable(True) 

1140 self.menuitem_lphp.setChecked(True) 

1141 

1142 options_menu.addSeparator() 

1143 self.menuitem_watch = options_menu.addAction( 

1144 'Watch Files') 

1145 self.menuitem_watch.setCheckable(True) 

1146 

1147 self.menuitem_liberal_fetch = options_menu.addAction( 

1148 'Liberal Fetch Optimization') 

1149 self.menuitem_liberal_fetch.setCheckable(True) 

1150 

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

1152 

1153 self.snufflings_menu.addAction( 

1154 'Reload Snufflings', 

1155 self.setup_snufflings) 

1156 

1157 # Disable ShadowPileTest 

1158 if False: 

1159 test_action = self.menu.addAction( 

1160 'Test', 

1161 self.toggletest) 

1162 test_action.setCheckable(True) 

1163 

1164 help_menu.addAction( 

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

1166 'Snuffler Controls', 

1167 self.help, 

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

1169 

1170 help_menu.addAction( 

1171 'About', 

1172 self.about) 

1173 

1174 toolbar = qw.QFrame(self.menu) 

1175 toolbar_layout = qw.QHBoxLayout() 

1176 toolbar_layout.setContentsMargins(1, 1, 1, 1) 

1177 toolbar.setLayout(toolbar_layout) 

1178 

1179 def tracks_plus(*args): 

1180 self.zoom_tracks(0., 1.) 

1181 

1182 button = PileViewerMenuBarButton('+') 

1183 button.clicked.connect(tracks_plus) 

1184 button.setToolTip('Show more traces.') 

1185 toolbar_layout.addWidget(button) 

1186 

1187 def tracks_minus(*args): 

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

1189 

1190 button = PileViewerMenuBarButton('-') 

1191 button.clicked.connect(tracks_minus) 

1192 button.setToolTip('Show fewer traces.') 

1193 toolbar_layout.addWidget(button) 

1194 

1195 def toggle_input(*args): 

1196 self.toggle_input.emit() 

1197 

1198 button = PileViewerMenuBarButton(':') 

1199 button.setToolTip('Show command line.') 

1200 button.clicked.connect(toggle_input) 

1201 toolbar_layout.addWidget(button) 

1202 

1203 self.menu.setCornerWidget(toolbar) 

1204 

1205 self.time_projection = Projection() 

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

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

1208 

1209 self.gather = None 

1210 

1211 self.trace_filter = None 

1212 self.quick_filter = None 

1213 self.quick_filter_patterns = None, None 

1214 self.blacklist = [] 

1215 

1216 self.track_to_screen = Projection() 

1217 self.track_to_nslc_ids = {} 

1218 

1219 self.cached_vec = None 

1220 self.cached_processed_traces = None 

1221 

1222 self.timer = qc.QTimer(self) 

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

1224 self.timer.setInterval(1000) 

1225 self.timer.start() 

1226 

1227 self._pile_changed = self.pile_changed # need to keep a strong ref 

1228 self.pile.add_listener(self._pile_changed) 

1229 

1230 self.trace_styles = {} 

1231 if self.get_squirrel() is None: 

1232 self.determine_box_styles() 

1233 

1234 self.setMouseTracking(True) 

1235 

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

1237 self.snuffling_modules = {} 

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

1239 self.default_snufflings = None 

1240 self.snufflings = [] 

1241 

1242 self.stations = {} 

1243 

1244 self.timer_draw = Timer() 

1245 self.timer_cutout = Timer() 

1246 self.time_spent_painting = 0.0 

1247 self.time_last_painted = time.time() 

1248 

1249 self.interactive_range_change_time = 0.0 

1250 self.interactive_range_change_delay_time = 10.0 

1251 self.follow_timer = None 

1252 

1253 self.sortingmode_change_time = 0.0 

1254 self.sortingmode_change_delay_time = None 

1255 

1256 self.old_data_ranges = {} 

1257 

1258 self.return_tag = None 

1259 self.wheel_pos = 60 

1260 

1261 self.setAcceptDrops(True) 

1262 self._paths_to_load = [] 

1263 

1264 self.tf_cache = {} 

1265 

1266 self.waterfall = TraceWaterfall() 

1267 self.waterfall_cmap = 'viridis' 

1268 self.waterfall_clip_min = 0. 

1269 self.waterfall_clip_max = 1. 

1270 self.waterfall_show_absolute = False 

1271 self.waterfall_integrate = False 

1272 self.view_mode = ViewMode.Wiggle 

1273 

1274 self.automatic_updates = True 

1275 

1276 self.closing = False 

1277 self.in_paint_event = False 

1278 

1279 def fail(self, reason): 

1280 box = qw.QMessageBox(self) 

1281 box.setText(reason) 

1282 box.exec_() 

1283 

1284 def set_trace_filter(self, filter_func): 

1285 self.trace_filter = filter_func 

1286 self.sortingmode_change() 

1287 

1288 def update_trace_filter(self): 

1289 if self.blacklist: 

1290 

1291 def blacklist_func(tr): 

1292 return not pyrocko.util.match_nslc( 

1293 self.blacklist, tr.nslc_id) 

1294 

1295 else: 

1296 blacklist_func = None 

1297 

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

1299 self.set_trace_filter(None) 

1300 elif self.quick_filter is None: 

1301 self.set_trace_filter(blacklist_func) 

1302 elif blacklist_func is None: 

1303 self.set_trace_filter(self.quick_filter) 

1304 else: 

1305 self.set_trace_filter( 

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

1307 

1308 def set_quick_filter(self, filter_func): 

1309 self.quick_filter = filter_func 

1310 self.update_trace_filter() 

1311 

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

1313 if patterns is not None: 

1314 self.set_quick_filter( 

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

1316 else: 

1317 self.set_quick_filter(None) 

1318 

1319 self.quick_filter_patterns = patterns, inputline 

1320 

1321 def get_quick_filter_patterns(self): 

1322 return self.quick_filter_patterns 

1323 

1324 def add_blacklist_pattern(self, pattern): 

1325 if pattern == 'empty': 

1326 keys = set(self.pile.nslc_ids) 

1327 trs = self.pile.all( 

1328 tmin=self.tmin, 

1329 tmax=self.tmax, 

1330 load_data=False, 

1331 degap=False) 

1332 

1333 for tr in trs: 

1334 if tr.nslc_id in keys: 

1335 keys.remove(tr.nslc_id) 

1336 

1337 for key in keys: 

1338 xpattern = '.'.join(key) 

1339 if xpattern not in self.blacklist: 

1340 self.blacklist.append(xpattern) 

1341 

1342 else: 

1343 if pattern in self.blacklist: 

1344 self.blacklist.remove(pattern) 

1345 

1346 self.blacklist.append(pattern) 

1347 

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

1349 self.update_trace_filter() 

1350 

1351 def remove_blacklist_pattern(self, pattern): 

1352 if pattern in self.blacklist: 

1353 self.blacklist.remove(pattern) 

1354 else: 

1355 raise PileViewerMainException( 

1356 'Pattern not found in blacklist.') 

1357 

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

1359 self.update_trace_filter() 

1360 

1361 def clear_blacklist(self): 

1362 self.blacklist = [] 

1363 self.update_trace_filter() 

1364 

1365 def ssort(self, tr): 

1366 return self._ssort(tr) 

1367 

1368 def station_key(self, x): 

1369 return x.network, x.station 

1370 

1371 def station_keys(self, x): 

1372 return [ 

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

1374 (x.network, x.station)] 

1375 

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

1377 for sk in self.station_keys(tr): 

1378 if sk in self.stations: 

1379 station = self.stations[sk] 

1380 return getter(station) 

1381 

1382 return default_getter(tr) 

1383 

1384 def get_station(self, sk): 

1385 return self.stations[sk] 

1386 

1387 def has_station(self, station): 

1388 for sk in self.station_keys(station): 

1389 if sk in self.stations: 

1390 return True 

1391 

1392 return False 

1393 

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

1395 return self.station_attrib( 

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

1397 

1398 def set_stations(self, stations): 

1399 self.stations = {} 

1400 self.add_stations(stations) 

1401 

1402 def add_stations(self, stations): 

1403 for station in stations: 

1404 for sk in self.station_keys(station): 

1405 self.stations[sk] = station 

1406 

1407 ev = self.get_active_event() 

1408 if ev: 

1409 self.set_origin(ev) 

1410 

1411 def add_event(self, event): 

1412 marker = EventMarker(event) 

1413 self.add_marker(marker) 

1414 

1415 def add_events(self, events): 

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

1417 self.add_markers(markers) 

1418 

1419 def set_event_marker_as_origin(self, ignore=None): 

1420 selected = self.selected_markers() 

1421 if not selected: 

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

1423 return 

1424 

1425 m = selected[0] 

1426 if not isinstance(m, EventMarker): 

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

1428 return 

1429 

1430 self.set_active_event_marker(m) 

1431 

1432 def deactivate_event_marker(self): 

1433 if self.active_event_marker: 

1434 self.active_event_marker.active = False 

1435 

1436 self.active_event_marker_changed.emit() 

1437 self.active_event_marker = None 

1438 

1439 def set_active_event_marker(self, event_marker): 

1440 if self.active_event_marker: 

1441 self.active_event_marker.active = False 

1442 

1443 self.active_event_marker = event_marker 

1444 event_marker.active = True 

1445 event = event_marker.get_event() 

1446 self.set_origin(event) 

1447 self.active_event_marker_changed.emit() 

1448 

1449 def set_active_event(self, event): 

1450 for marker in self.markers: 

1451 if isinstance(marker, EventMarker): 

1452 if marker.get_event() is event: 

1453 self.set_active_event_marker(marker) 

1454 

1455 def get_active_event_marker(self): 

1456 return self.active_event_marker 

1457 

1458 def get_active_event(self): 

1459 m = self.get_active_event_marker() 

1460 if m is not None: 

1461 return m.get_event() 

1462 else: 

1463 return None 

1464 

1465 def get_active_markers(self): 

1466 emarker = self.get_active_event_marker() 

1467 if emarker is None: 

1468 return None, [] 

1469 

1470 else: 

1471 ev = emarker.get_event() 

1472 pmarkers = [ 

1473 m for m in self.markers 

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

1475 

1476 return emarker, pmarkers 

1477 

1478 def set_origin(self, location): 

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

1480 station.set_event_relative_data( 

1481 location, 

1482 distance_3d=self.menuitem_distances_3d.isChecked()) 

1483 

1484 self.sortingmode_change() 

1485 

1486 def distances_3d_changed(self): 

1487 ignore = self.menuitem_distances_3d.isChecked() 

1488 self.set_event_marker_as_origin(ignore) 

1489 

1490 def iter_snuffling_modules(self): 

1491 pjoin = os.path.join 

1492 for path in self.snuffling_paths: 

1493 

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

1495 os.mkdir(path) 

1496 

1497 for entry in os.listdir(path): 

1498 directory = path 

1499 fn = entry 

1500 d = pjoin(path, entry) 

1501 if os.path.isdir(d): 

1502 directory = d 

1503 if os.path.isfile( 

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

1505 fn = 'snuffling.py' 

1506 

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

1508 continue 

1509 

1510 name = fn[:-3] 

1511 

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

1513 self.snuffling_modules[directory, name] = \ 

1514 pyrocko.gui.snuffler.snuffling.SnufflingModule( 

1515 directory, name, self) 

1516 

1517 yield self.snuffling_modules[directory, name] 

1518 

1519 def setup_snufflings(self): 

1520 # user snufflings 

1521 from pyrocko.gui.snuffler.snuffling import BrokenSnufflingModule 

1522 for mod in self.iter_snuffling_modules(): 

1523 try: 

1524 mod.load_if_needed() 

1525 except BrokenSnufflingModule as e: 

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

1527 

1528 # load the default snufflings on first run 

1529 if self.default_snufflings is None: 

1530 self.default_snufflings = pyrocko.gui.snuffler\ 

1531 .snufflings.__snufflings__() 

1532 for snuffling in self.default_snufflings: 

1533 self.add_snuffling(snuffling) 

1534 

1535 def set_panel_parent(self, panel_parent): 

1536 self.panel_parent = panel_parent 

1537 

1538 def get_panel_parent(self): 

1539 return self.panel_parent 

1540 

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

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

1543 snuffling.init_gui( 

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

1545 self.snufflings.append(snuffling) 

1546 self.update() 

1547 

1548 def remove_snuffling(self, snuffling): 

1549 snuffling.delete_gui() 

1550 self.update() 

1551 self.snufflings.remove(snuffling) 

1552 snuffling.pre_destroy() 

1553 

1554 def add_snuffling_menuitem(self, item): 

1555 self.snufflings_menu.addAction(item) 

1556 item.setParent(self.snufflings_menu) 

1557 sort_actions(self.snufflings_menu) 

1558 

1559 def remove_snuffling_menuitem(self, item): 

1560 self.snufflings_menu.removeAction(item) 

1561 

1562 def add_snuffling_help_menuitem(self, item): 

1563 self.snuffling_help.addAction(item) 

1564 item.setParent(self.snuffling_help) 

1565 sort_actions(self.snuffling_help) 

1566 

1567 def remove_snuffling_help_menuitem(self, item): 

1568 self.snuffling_help.removeAction(item) 

1569 

1570 def add_panel_toggler(self, item): 

1571 self.toggle_panel_menu.addAction(item) 

1572 item.setParent(self.toggle_panel_menu) 

1573 sort_actions(self.toggle_panel_menu) 

1574 

1575 def remove_panel_toggler(self, item): 

1576 self.toggle_panel_menu.removeAction(item) 

1577 

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

1579 cache_dir=None, force_cache=False): 

1580 

1581 if cache_dir is None: 

1582 cache_dir = pyrocko.config.config().cache_dir 

1583 if isinstance(paths, str): 

1584 paths = [paths] 

1585 

1586 fns = pyrocko.util.select_files( 

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

1588 

1589 if not fns: 

1590 return 

1591 

1592 cache = pyrocko.pile.get_cache(cache_dir) 

1593 

1594 t = [time.time()] 

1595 

1596 def update_bar(label, value): 

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

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

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

1600 else: 

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

1602 

1603 return pbs.set_status(label, value) 

1604 

1605 def update_progress(label, i, n): 

1606 abort = False 

1607 

1608 qw.qApp.processEvents() 

1609 if n != 0: 

1610 perc = i*100/n 

1611 else: 

1612 perc = 100 

1613 abort |= update_bar(label, perc) 

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

1615 

1616 tnow = time.time() 

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

1618 self.update() 

1619 t[0] = tnow 

1620 

1621 return abort 

1622 

1623 self.automatic_updates = False 

1624 

1625 self.pile.load_files( 

1626 sorted(fns), 

1627 filename_attributes=regex, 

1628 cache=cache, 

1629 fileformat=format, 

1630 show_progress=False, 

1631 update_progress=update_progress) 

1632 

1633 self.automatic_updates = True 

1634 self.update() 

1635 

1636 def load_queued(self): 

1637 if not self._paths_to_load: 

1638 return 

1639 paths = self._paths_to_load 

1640 self._paths_to_load = [] 

1641 self.load(paths) 

1642 

1643 def load_soon(self, paths): 

1644 self._paths_to_load.extend(paths) 

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

1646 

1647 def open_waveforms(self): 

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

1649 

1650 fns, _ = qw.QFileDialog.getOpenFileNames( 

1651 self, caption, options=qfiledialog_options) 

1652 

1653 if fns: 

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

1655 

1656 def open_waveform_directory(self): 

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

1658 

1659 dn = qw.QFileDialog.getExistingDirectory( 

1660 self, caption, options=qfiledialog_options) 

1661 

1662 if dn: 

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

1664 

1665 def open_stations(self, fns=None): 

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

1667 

1668 if not fns: 

1669 fns, _ = qw.QFileDialog.getOpenFileNames( 

1670 self, caption, options=qfiledialog_options) 

1671 

1672 try: 

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

1674 for stat in stations: 

1675 self.add_stations(stat) 

1676 

1677 except Exception as e: 

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

1679 

1680 def open_stations_xml(self, fns=None): 

1681 from pyrocko.io import stationxml 

1682 

1683 caption = 'Select one or more StationXML files' 

1684 if not fns: 

1685 fns, _ = qw.QFileDialog.getOpenFileNames( 

1686 self, caption, options=qfiledialog_options, 

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

1688 ';;All files (*)') 

1689 

1690 try: 

1691 stations = [ 

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

1693 for x in fns] 

1694 

1695 for stat in stations: 

1696 self.add_stations(stat) 

1697 

1698 except Exception as e: 

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

1700 

1701 def add_traces(self, traces): 

1702 if traces: 

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

1704 self.pile.add_file(mtf) 

1705 ticket = (self.pile, mtf) 

1706 return ticket 

1707 else: 

1708 return (None, None) 

1709 

1710 def release_data(self, tickets): 

1711 for ticket in tickets: 

1712 pile, mtf = ticket 

1713 if pile is not None: 

1714 pile.remove_file(mtf) 

1715 

1716 def periodical(self): 

1717 if self.menuitem_watch.isChecked(): 

1718 if self.pile.reload_modified(): 

1719 self.update() 

1720 

1721 def get_pile(self): 

1722 return self.pile 

1723 

1724 def pile_changed(self, what, content): 

1725 self.pile_has_changed = True 

1726 self.pile_has_changed_signal.emit() 

1727 if self.automatic_updates: 

1728 self.update() 

1729 

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

1731 

1732 if gather is None: 

1733 def gather_func(tr): 

1734 return tr.nslc_id 

1735 

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

1737 

1738 else: 

1739 def gather_func(tr): 

1740 return ( 

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

1742 

1743 if color is None: 

1744 def color(tr): 

1745 return tr.location 

1746 

1747 self.gather = gather_func 

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

1749 

1750 self.color_gather = color 

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

1752 previous_ntracks = self.ntracks 

1753 self.set_ntracks(len(keys)) 

1754 

1755 if self.shown_tracks_range is None or \ 

1756 previous_ntracks == 0 or \ 

1757 self.show_all: 

1758 

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

1760 key_at_top = None 

1761 n = high-low 

1762 

1763 else: 

1764 low, high = self.shown_tracks_range 

1765 key_at_top = self.track_keys[low] 

1766 n = high-low 

1767 

1768 self.track_keys = sorted(keys) 

1769 

1770 track_patterns = [] 

1771 for k in self.track_keys: 

1772 pat = ['*', '*', '*', '*'] 

1773 for i, j in enumerate(gather): 

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

1775 

1776 track_patterns.append(pat) 

1777 

1778 self.track_patterns = track_patterns 

1779 

1780 if key_at_top is not None: 

1781 try: 

1782 ind = self.track_keys.index(key_at_top) 

1783 low = ind 

1784 high = low+n 

1785 except Exception: 

1786 pass 

1787 

1788 self.set_tracks_range((low, high)) 

1789 

1790 self.key_to_row = dict( 

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

1792 

1793 def inrange(x, r): 

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

1795 

1796 def trace_selector(trace): 

1797 gt = self.gather(trace) 

1798 return ( 

1799 gt in self.key_to_row and 

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

1801 

1802 self.trace_selector = lambda x: \ 

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

1804 and trace_selector(x) 

1805 

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

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

1808 self.show_all: 

1809 

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

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

1812 tlen = (tmax - tmin) 

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

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

1815 

1816 def set_time_range(self, tmin, tmax): 

1817 if tmin is None: 

1818 tmin = initial_time_range[0] 

1819 

1820 if tmax is None: 

1821 tmax = initial_time_range[1] 

1822 

1823 if tmin > tmax: 

1824 tmin, tmax = tmax, tmin 

1825 

1826 if tmin == tmax: 

1827 tmin -= 1. 

1828 tmax += 1. 

1829 

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

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

1832 

1833 min_deltat = self.content_deltat_range()[0] 

1834 if (tmax - tmin < min_deltat): 

1835 m = (tmin + tmax) / 2. 

1836 tmin = m - min_deltat/2. 

1837 tmax = m + min_deltat/2. 

1838 

1839 self.time_projection.set_in_range(tmin, tmax) 

1840 self.tmin, self.tmax = tmin, tmax 

1841 

1842 def get_time_range(self): 

1843 return self.tmin, self.tmax 

1844 

1845 def ypart(self, y): 

1846 if y < self.ax_height: 

1847 return -1 

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

1849 return 1 

1850 else: 

1851 return 0 

1852 

1853 def time_fractional_digits(self): 

1854 min_deltat = self.content_deltat_range()[0] 

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

1856 

1857 def write_markers(self, fn=None): 

1858 caption = 'Choose a file name to write markers' 

1859 if not fn: 

1860 fn, _ = qw.QFileDialog.getSaveFileName( 

1861 self, caption, options=qfiledialog_options) 

1862 if fn: 

1863 try: 

1864 Marker.save_markers( 

1865 self.markers, fn, 

1866 fdigits=self.time_fractional_digits()) 

1867 

1868 except Exception as e: 

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

1870 

1871 def write_selected_markers(self, fn=None): 

1872 caption = 'Choose a file name to write selected markers' 

1873 if not fn: 

1874 fn, _ = qw.QFileDialog.getSaveFileName( 

1875 self, caption, options=qfiledialog_options) 

1876 if fn: 

1877 try: 

1878 Marker.save_markers( 

1879 self.iter_selected_markers(), 

1880 fn, 

1881 fdigits=self.time_fractional_digits()) 

1882 

1883 except Exception as e: 

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

1885 

1886 def read_events(self, fn=None): 

1887 ''' 

1888 Open QFileDialog to open, read and add 

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

1890 representation to the pile viewer. 

1891 ''' 

1892 caption = 'Selet one or more files to open' 

1893 if not fn: 

1894 fn, _ = qw.QFileDialog.getOpenFileName( 

1895 self, caption, options=qfiledialog_options) 

1896 if fn: 

1897 try: 

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

1899 self.associate_phases_to_events() 

1900 

1901 except Exception as e: 

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

1903 

1904 def read_markers(self, fn=None): 

1905 ''' 

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

1907 ''' 

1908 caption = 'Selet one or more marker files to open' 

1909 if not fn: 

1910 fn, _ = qw.QFileDialog.getOpenFileName( 

1911 self, caption, options=qfiledialog_options) 

1912 if fn: 

1913 try: 

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

1915 self.associate_phases_to_events() 

1916 

1917 except Exception as e: 

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

1919 

1920 def associate_phases_to_events(self): 

1921 associate_phases_to_events(self.markers) 

1922 

1923 def add_marker(self, marker): 

1924 # need index to inform QAbstactTableModel about upcoming change, 

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

1926 self.markers.insert(marker) 

1927 i = self.markers.remove(marker) 

1928 

1929 self.begin_markers_add.emit(i, i) 

1930 self.markers.insert(marker) 

1931 self.end_markers_add.emit() 

1932 self.markers_deltat_max = max( 

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

1934 

1935 def add_markers(self, markers): 

1936 if not self.markers: 

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

1938 self.markers.insert_many(markers) 

1939 self.end_markers_add.emit() 

1940 self.update_markers_deltat_max() 

1941 else: 

1942 for marker in markers: 

1943 self.add_marker(marker) 

1944 

1945 def update_markers_deltat_max(self): 

1946 if self.markers: 

1947 self.markers_deltat_max = max( 

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

1949 

1950 def remove_marker(self, marker): 

1951 ''' 

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

1953 

1954 :param marker: 

1955 :py:class:`~pyrocko.gui.snuffler.marker.Marker` (or subclass) 

1956 instance 

1957 ''' 

1958 

1959 if marker is self.active_event_marker: 

1960 self.deactivate_event_marker() 

1961 

1962 try: 

1963 i = self.markers.index(marker) 

1964 self.begin_markers_remove.emit(i, i) 

1965 self.markers.remove_at(i) 

1966 self.end_markers_remove.emit() 

1967 except ValueError: 

1968 pass 

1969 

1970 def remove_markers(self, markers): 

1971 ''' 

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

1973 

1974 :param markers: 

1975 list of :py:class:`~pyrocko.gui.snuffler.marker.Marker` (or 

1976 subclass) instances 

1977 ''' 

1978 

1979 if markers is self.markers: 

1980 markers = list(markers) 

1981 

1982 for marker in markers: 

1983 self.remove_marker(marker) 

1984 

1985 self.update_markers_deltat_max() 

1986 

1987 def remove_selected_markers(self): 

1988 def delete_segment(istart, iend): 

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

1990 for _ in range(iend - istart): 

1991 self.markers.remove_at(istart) 

1992 

1993 self.end_markers_remove.emit() 

1994 

1995 istart = None 

1996 ipos = 0 

1997 markers = self.markers 

1998 nmarkers = len(self.markers) 

1999 while ipos < nmarkers: 

2000 marker = markers[ipos] 

2001 if marker.is_selected(): 

2002 if marker is self.active_event_marker: 

2003 self.deactivate_event_marker() 

2004 

2005 if istart is None: 

2006 istart = ipos 

2007 else: 

2008 if istart is not None: 

2009 delete_segment(istart, ipos) 

2010 nmarkers -= ipos - istart 

2011 ipos = istart - 1 

2012 istart = None 

2013 

2014 ipos += 1 

2015 

2016 if istart is not None: 

2017 delete_segment(istart, ipos) 

2018 

2019 self.update_markers_deltat_max() 

2020 

2021 def selected_markers(self): 

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

2023 

2024 def iter_selected_markers(self): 

2025 for marker in self.markers: 

2026 if marker.is_selected(): 

2027 yield marker 

2028 

2029 def get_markers(self): 

2030 return self.markers 

2031 

2032 def mousePressEvent(self, mouse_ev): 

2033 '' 

2034 self.show_all = False 

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

2036 

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

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

2039 if self.picking: 

2040 if self.picking_down is None: 

2041 self.picking_down = ( 

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

2043 mouse_ev.y()) 

2044 

2045 elif marker is not None: 

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

2047 self.deselect_all() 

2048 marker.selected = True 

2049 self.emit_selected_markers() 

2050 self.update() 

2051 else: 

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

2053 self.track_trange = self.tmin, self.tmax 

2054 

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

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

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

2058 self.update_status() 

2059 

2060 def mouseReleaseEvent(self, mouse_ev): 

2061 '' 

2062 if self.ignore_releases: 

2063 self.ignore_releases -= 1 

2064 return 

2065 

2066 if self.picking: 

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

2068 self.emit_selected_markers() 

2069 

2070 if self.track_start: 

2071 self.update() 

2072 

2073 self.track_start = None 

2074 self.track_trange = None 

2075 self.update_status() 

2076 

2077 def mouseDoubleClickEvent(self, mouse_ev): 

2078 '' 

2079 self.show_all = False 

2080 self.start_picking(None) 

2081 self.ignore_releases = 1 

2082 

2083 def mouseMoveEvent(self, mouse_ev): 

2084 '' 

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

2086 

2087 if self.picking: 

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

2089 

2090 elif self.track_start is not None: 

2091 x0, y0 = self.track_start 

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

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

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

2095 dy = 0 

2096 

2097 tmin0, tmax0 = self.track_trange 

2098 

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

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

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

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

2103 

2104 self.interrupt_following() 

2105 self.set_time_range( 

2106 tmin0 - dt - dtr*frac, 

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

2108 

2109 self.update() 

2110 else: 

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

2112 

2113 self.update_status() 

2114 

2115 def nslc_ids_under_cursor(self, x, y): 

2116 ftrack = self.track_to_screen.rev(y) 

2117 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2118 return nslc_ids 

2119 

2120 def marker_under_cursor(self, x, y): 

2121 mouset = self.time_projection.rev(x) 

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

2123 relevant_nslc_ids = None 

2124 for marker in self.markers: 

2125 if marker.kind not in self.visible_marker_kinds: 

2126 continue 

2127 

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

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

2130 

2131 if relevant_nslc_ids is None: 

2132 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2133 

2134 marker_nslc_ids = marker.get_nslc_ids() 

2135 if not marker_nslc_ids: 

2136 return marker 

2137 

2138 for nslc_id in marker_nslc_ids: 

2139 if nslc_id in relevant_nslc_ids: 

2140 return marker 

2141 

2142 def hoovering(self, x, y): 

2143 mouset = self.time_projection.rev(x) 

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

2145 needupdate = False 

2146 haveone = False 

2147 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2148 for marker in self.markers: 

2149 if marker.kind not in self.visible_marker_kinds: 

2150 continue 

2151 

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

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

2154 

2155 if state: 

2156 xstate = False 

2157 

2158 marker_nslc_ids = marker.get_nslc_ids() 

2159 if not marker_nslc_ids: 

2160 xstate = True 

2161 

2162 for nslc in relevant_nslc_ids: 

2163 if marker.match_nslc(nslc): 

2164 xstate = True 

2165 

2166 state = xstate 

2167 

2168 if state: 

2169 haveone = True 

2170 oldstate = marker.is_alerted() 

2171 if oldstate != state: 

2172 needupdate = True 

2173 marker.set_alerted(state) 

2174 if state: 

2175 self.window().status_messages.set( 

2176 'marker', marker.hoover_message()) 

2177 

2178 if needupdate: 

2179 self.update() 

2180 

2181 self.update_status() 

2182 

2183 def event(self, event): 

2184 '' 

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

2186 self.keyPressEvent(event) 

2187 return True 

2188 else: 

2189 return base.event(self, event) 

2190 

2191 def keyPressEvent(self, key_event): 

2192 '' 

2193 self.show_all = False 

2194 dt = self.tmax - self.tmin 

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

2196 

2197 key = key_event.key() 

2198 try: 

2199 keytext = str(key_event.text()) 

2200 except UnicodeEncodeError: 

2201 return 

2202 

2203 if key == qc.Qt.Key_Space: 

2204 self.interrupt_following() 

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

2206 

2207 elif key == qc.Qt.Key_Up: 

2208 for m in self.selected_markers(): 

2209 if isinstance(m, PhaseMarker): 

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

2211 p = 0 

2212 else: 

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

2214 m.set_polarity(p) 

2215 

2216 elif key == qc.Qt.Key_Down: 

2217 for m in self.selected_markers(): 

2218 if isinstance(m, PhaseMarker): 

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

2220 p = 0 

2221 else: 

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

2223 m.set_polarity(p) 

2224 

2225 elif key == qc.Qt.Key_B: 

2226 dt = self.tmax - self.tmin 

2227 self.interrupt_following() 

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

2229 

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

2231 self.interrupt_following() 

2232 

2233 tgo = None 

2234 

2235 class TraceDummy(object): 

2236 def __init__(self, marker): 

2237 self._marker = marker 

2238 

2239 @property 

2240 def nslc_id(self): 

2241 return self._marker.one_nslc() 

2242 

2243 def marker_to_itrack(marker): 

2244 try: 

2245 return self.key_to_row.get( 

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

2247 

2248 except MarkerOneNSLCRequired: 

2249 return -1 

2250 

2251 emarker, pmarkers = self.get_active_markers() 

2252 pmarkers = [ 

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

2254 pmarkers.sort(key=lambda m: ( 

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

2256 

2257 if key == qc.Qt.Key_Backtab: 

2258 pmarkers.reverse() 

2259 

2260 smarkers = self.selected_markers() 

2261 iselected = [] 

2262 for sm in smarkers: 

2263 try: 

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

2265 except ValueError: 

2266 pass 

2267 

2268 if iselected: 

2269 icurrent = max(iselected) + 1 

2270 else: 

2271 icurrent = 0 

2272 

2273 if icurrent < len(pmarkers): 

2274 self.deselect_all() 

2275 cmarker = pmarkers[icurrent] 

2276 cmarker.selected = True 

2277 tgo = cmarker.tmin 

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

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

2280 

2281 itrack = marker_to_itrack(cmarker) 

2282 if itrack != -1: 

2283 if itrack < self.shown_tracks_range[0]: 

2284 self.scroll_tracks( 

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

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

2287 self.scroll_tracks( 

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

2289 

2290 if itrack not in self.track_to_nslc_ids: 

2291 self.go_to_selection() 

2292 

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

2294 smarkers = self.selected_markers() 

2295 tgo = None 

2296 dir = str(keytext) 

2297 if smarkers: 

2298 tmid = smarkers[0].tmin 

2299 for smarker in smarkers: 

2300 if dir == 'n': 

2301 tmid = max(smarker.tmin, tmid) 

2302 else: 

2303 tmid = min(smarker.tmin, tmid) 

2304 

2305 tgo = tmid 

2306 

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

2308 for marker in sorted( 

2309 self.markers, 

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

2311 

2312 t = marker.tmin 

2313 if t > tmid and \ 

2314 marker.kind in self.visible_marker_kinds and \ 

2315 (dir == 'n' or 

2316 isinstance(marker, EventMarker)): 

2317 

2318 self.deselect_all() 

2319 marker.selected = True 

2320 tgo = t 

2321 break 

2322 else: 

2323 for marker in sorted( 

2324 self.markers, 

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

2326 reverse=True): 

2327 

2328 t = marker.tmin 

2329 if t < tmid and \ 

2330 marker.kind in self.visible_marker_kinds and \ 

2331 (dir == 'p' or 

2332 isinstance(marker, EventMarker)): 

2333 self.deselect_all() 

2334 marker.selected = True 

2335 tgo = t 

2336 break 

2337 

2338 if tgo is not None: 

2339 self.interrupt_following() 

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

2341 

2342 elif keytext == 'r': 

2343 if self.pile.reload_modified(): 

2344 self.reloaded = True 

2345 

2346 elif keytext == 'R': 

2347 self.setup_snufflings() 

2348 

2349 elif key == qc.Qt.Key_Backspace: 

2350 self.remove_selected_markers() 

2351 

2352 elif keytext == 'a': 

2353 for marker in self.markers: 

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

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

2356 marker.kind in self.visible_marker_kinds): 

2357 marker.selected = True 

2358 else: 

2359 marker.selected = False 

2360 

2361 elif keytext == 'A': 

2362 for marker in self.markers: 

2363 if marker.kind in self.visible_marker_kinds: 

2364 marker.selected = True 

2365 

2366 elif keytext == 'd': 

2367 self.deselect_all() 

2368 

2369 elif keytext == 'E': 

2370 self.deactivate_event_marker() 

2371 

2372 elif keytext == 'e': 

2373 markers = self.selected_markers() 

2374 event_markers_in_spe = [ 

2375 marker for marker in markers 

2376 if not isinstance(marker, PhaseMarker)] 

2377 

2378 phase_markers = [ 

2379 marker for marker in markers 

2380 if isinstance(marker, PhaseMarker)] 

2381 

2382 if len(event_markers_in_spe) == 1: 

2383 event_marker = event_markers_in_spe[0] 

2384 if not isinstance(event_marker, EventMarker): 

2385 nslcs = list(event_marker.nslc_ids) 

2386 lat, lon = 0.0, 0.0 

2387 old = self.get_active_event() 

2388 if len(nslcs) == 1: 

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

2390 elif old is not None: 

2391 lat, lon = old.lat, old.lon 

2392 

2393 event_marker.convert_to_event_marker(lat, lon) 

2394 

2395 self.set_active_event_marker(event_marker) 

2396 event = event_marker.get_event() 

2397 for marker in phase_markers: 

2398 marker.set_event(event) 

2399 

2400 else: 

2401 for marker in event_markers_in_spe: 

2402 marker.convert_to_event_marker() 

2403 

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

2405 for marker in self.selected_markers(): 

2406 marker.set_kind(int(keytext)) 

2407 self.emit_selected_markers() 

2408 

2409 elif key in fkey_map: 

2410 self.handle_fkeys(key) 

2411 

2412 elif key == qc.Qt.Key_Escape: 

2413 if self.picking: 

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

2415 

2416 elif key == qc.Qt.Key_PageDown: 

2417 self.scroll_tracks( 

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

2419 

2420 elif key == qc.Qt.Key_PageUp: 

2421 self.scroll_tracks( 

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

2423 

2424 elif key == qc.Qt.Key_Plus: 

2425 self.zoom_tracks(0., 1.) 

2426 

2427 elif key == qc.Qt.Key_Minus: 

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

2429 

2430 elif key == qc.Qt.Key_Equal: 

2431 ntracks_shown = self.shown_tracks_range[1] - \ 

2432 self.shown_tracks_range[0] 

2433 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2434 self.zoom_tracks(0., dtracks) 

2435 

2436 elif key == qc.Qt.Key_Colon: 

2437 self.want_input.emit() 

2438 

2439 elif keytext == 'f': 

2440 self.toggle_fullscreen() 

2441 

2442 elif keytext == 'g': 

2443 self.go_to_selection() 

2444 

2445 elif keytext == 'G': 

2446 self.go_to_selection(tight=True) 

2447 

2448 elif keytext == 'm': 

2449 self.toggle_marker_editor() 

2450 

2451 elif keytext == 'c': 

2452 self.toggle_main_controls() 

2453 

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

2455 dir = 1 

2456 amount = 1 

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

2458 dir = -1 

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

2460 amount = 10 

2461 self.nudge_selected_markers(dir*amount) 

2462 else: 

2463 super().keyPressEvent(key_event) 

2464 

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

2466 self.emit_selected_markers() 

2467 

2468 self.update() 

2469 self.update_status() 

2470 

2471 def handle_fkeys(self, key): 

2472 self.set_phase_kind( 

2473 self.selected_markers(), 

2474 fkey_map[key] + 1) 

2475 self.emit_selected_markers() 

2476 

2477 def emit_selected_markers(self): 

2478 ibounds = [] 

2479 last_selected = False 

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

2481 this_selected = marker.is_selected() 

2482 if this_selected != last_selected: 

2483 ibounds.append(imarker) 

2484 

2485 last_selected = this_selected 

2486 

2487 if last_selected: 

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

2489 

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

2491 self.n_selected_markers = sum( 

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

2493 self.marker_selection_changed.emit(chunks) 

2494 

2495 def toggle_marker_editor(self): 

2496 self.panel_parent.toggle_marker_editor() 

2497 

2498 def toggle_main_controls(self): 

2499 self.panel_parent.toggle_main_controls() 

2500 

2501 def nudge_selected_markers(self, npixels): 

2502 a, b = self.time_projection.ur 

2503 c, d = self.time_projection.xr 

2504 for marker in self.selected_markers(): 

2505 if not isinstance(marker, EventMarker): 

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

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

2508 

2509 def toggle_fullscreen(self): 

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

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

2512 self.window().showNormal() 

2513 else: 

2514 if is_macos: 

2515 self.window().showMaximized() 

2516 else: 

2517 self.window().showFullScreen() 

2518 

2519 def about(self): 

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

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

2522 txt = f.read() 

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

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

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

2526 

2527 def help(self): 

2528 class MyScrollArea(qw.QScrollArea): 

2529 

2530 def sizeHint(self): 

2531 s = qc.QSize() 

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

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

2534 return s 

2535 

2536 with open(pyrocko.util.data_file( 

2537 'snuffler_help.html')) as f: 

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

2539 

2540 with open(pyrocko.util.data_file( 

2541 'snuffler_help_epilog.html')) as f: 

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

2543 

2544 for h in [hcheat, hepilog]: 

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

2546 h.setWordWrap(True) 

2547 

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

2549 

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

2551 scroller = qw.QScrollArea() 

2552 frame = qw.QFrame(scroller) 

2553 frame.setLineWidth(0) 

2554 layout = qw.QVBoxLayout() 

2555 layout.setContentsMargins(0, 0, 0, 0) 

2556 layout.setSpacing(0) 

2557 frame.setLayout(layout) 

2558 scroller.setWidget(frame) 

2559 scroller.setWidgetResizable(True) 

2560 frame.setBackgroundRole(qg.QPalette.Base) 

2561 for h in labels: 

2562 h.setParent(frame) 

2563 h.setMargin(3) 

2564 h.setTextInteractionFlags( 

2565 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2566 h.setBackgroundRole(qg.QPalette.Base) 

2567 layout.addWidget(h) 

2568 h.linkActivated.connect( 

2569 self.open_link) 

2570 

2571 if self.panel_parent is not None: 

2572 if target == 'panel': 

2573 self.panel_parent.add_panel( 

2574 name, scroller, True, volatile=False) 

2575 else: 

2576 self.panel_parent.add_tab(name, scroller) 

2577 

2578 def open_link(self, link): 

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

2580 

2581 def wheelEvent(self, wheel_event): 

2582 '' 

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

2584 

2585 n = self.wheel_pos // 120 

2586 self.wheel_pos = self.wheel_pos % 120 

2587 if n == 0: 

2588 return 

2589 

2590 amount = max( 

2591 1., 

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

2593 wdelta = amount * n 

2594 

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

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

2597 / (trmax-trmin) 

2598 

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

2600 self.zoom_tracks(anchor, wdelta) 

2601 else: 

2602 self.scroll_tracks(-wdelta) 

2603 

2604 def dragEnterEvent(self, event): 

2605 '' 

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

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

2608 event.setDropAction(qc.Qt.LinkAction) 

2609 event.accept() 

2610 

2611 def dropEvent(self, event): 

2612 '' 

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

2614 paths = list( 

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

2616 event.acceptProposedAction() 

2617 self.load(paths) 

2618 

2619 def get_phase_name(self, kind): 

2620 return self.config.get_phase_name(kind) 

2621 

2622 def set_phase_kind(self, markers, kind): 

2623 phasename = self.get_phase_name(kind) 

2624 

2625 for marker in markers: 

2626 if isinstance(marker, PhaseMarker): 

2627 if kind == 10: 

2628 marker.convert_to_marker() 

2629 else: 

2630 marker.set_phasename(phasename) 

2631 marker.set_event(self.get_active_event()) 

2632 

2633 elif isinstance(marker, EventMarker): 

2634 pass 

2635 

2636 else: 

2637 if kind != 10: 

2638 event = self.get_active_event() 

2639 marker.convert_to_phase_marker( 

2640 event, phasename, None, False) 

2641 

2642 def set_ntracks(self, ntracks): 

2643 if self.ntracks != ntracks: 

2644 self.ntracks = ntracks 

2645 if self.shown_tracks_range is not None: 

2646 low, high = self.shown_tracks_range 

2647 else: 

2648 low, high = 0, self.ntracks 

2649 

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

2651 

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

2653 

2654 low, high = range 

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

2656 high = min(self.ntracks, high) 

2657 low = max(0, low) 

2658 high = max(1, high) 

2659 

2660 if start is None: 

2661 start = float(low) 

2662 

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

2664 self.shown_tracks_range = low, high 

2665 self.shown_tracks_start = start 

2666 

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

2668 

2669 def scroll_tracks(self, shift): 

2670 shown = self.shown_tracks_range 

2671 shiftmin = -shown[0] 

2672 shiftmax = self.ntracks-shown[1] 

2673 shift = max(shiftmin, shift) 

2674 shift = min(shiftmax, shift) 

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

2676 

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

2678 

2679 self.update() 

2680 

2681 def zoom_tracks(self, anchor, delta): 

2682 ntracks_shown = self.shown_tracks_range[1] \ 

2683 - self.shown_tracks_range[0] 

2684 

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

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

2687 return 

2688 

2689 ntracks_shown += int(round(delta)) 

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

2691 

2692 u = self.shown_tracks_start 

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

2694 nv = nu + ntracks_shown 

2695 if nv > self.ntracks: 

2696 nu -= nv - self.ntracks 

2697 nv -= nv - self.ntracks 

2698 

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

2700 

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

2702 - self.shown_tracks_range[0] 

2703 

2704 self.update() 

2705 

2706 def content_time_range(self): 

2707 pile = self.get_pile() 

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

2709 if tmin is None: 

2710 tmin = initial_time_range[0] 

2711 if tmax is None: 

2712 tmax = initial_time_range[1] 

2713 

2714 return tmin, tmax 

2715 

2716 def content_deltat_range(self): 

2717 pile = self.get_pile() 

2718 

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

2720 

2721 if deltatmin is None: 

2722 deltatmin = 0.001 

2723 

2724 if deltatmax is None: 

2725 deltatmax = 1000.0 

2726 

2727 return deltatmin, deltatmax 

2728 

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

2730 if tmax < tmin: 

2731 tmin, tmax = tmax, tmin 

2732 

2733 deltatmin = self.content_deltat_range()[0] 

2734 dt = deltatmin * self.visible_length * 0.95 

2735 

2736 if dt == 0.0: 

2737 dt = 1.0 

2738 

2739 if tight: 

2740 if tmax != tmin: 

2741 dtm = tmax - tmin 

2742 tmin -= dtm*0.1 

2743 tmax += dtm*0.1 

2744 return tmin, tmax 

2745 else: 

2746 tcenter = (tmin + tmax) / 2. 

2747 tmin = tcenter - 0.5*dt 

2748 tmax = tcenter + 0.5*dt 

2749 return tmin, tmax 

2750 

2751 if tmax-tmin < dt: 

2752 vmin, vmax = self.get_time_range() 

2753 dt = min(vmax - vmin, dt) 

2754 

2755 tcenter = (tmin+tmax)/2. 

2756 etmin, etmax = tmin, tmax 

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

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

2759 dtm = tmax-tmin 

2760 if etmin == tmin: 

2761 tmin -= dtm*0.1 

2762 if etmax == tmax: 

2763 tmax += dtm*0.1 

2764 

2765 else: 

2766 dtm = tmax-tmin 

2767 tmin -= dtm*0.1 

2768 tmax += dtm*0.1 

2769 

2770 return tmin, tmax 

2771 

2772 def go_to_selection(self, tight=False): 

2773 markers = self.selected_markers() 

2774 if markers: 

2775 tmax, tmin = self.content_time_range() 

2776 for marker in markers: 

2777 tmin = min(tmin, marker.tmin) 

2778 tmax = max(tmax, marker.tmax) 

2779 

2780 else: 

2781 if tight: 

2782 vmin, vmax = self.get_time_range() 

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

2784 else: 

2785 tmin, tmax = self.content_time_range() 

2786 

2787 tmin, tmax = self.make_good_looking_time_range( 

2788 tmin, tmax, tight=tight) 

2789 

2790 self.interrupt_following() 

2791 self.set_time_range(tmin, tmax) 

2792 self.update() 

2793 

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

2795 tmax = t 

2796 if tlen is not None: 

2797 tmax = t+tlen 

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

2799 self.interrupt_following() 

2800 self.set_time_range(tmin, tmax) 

2801 self.update() 

2802 

2803 def go_to_event_by_name(self, name): 

2804 for marker in self.markers: 

2805 if isinstance(marker, EventMarker): 

2806 event = marker.get_event() 

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

2808 tmin, tmax = self.make_good_looking_time_range( 

2809 event.time, event.time) 

2810 

2811 self.interrupt_following() 

2812 self.set_time_range(tmin, tmax) 

2813 

2814 def printit(self): 

2815 from ..qt_compat import qprint 

2816 printer = qprint.QPrinter() 

2817 printer.setOrientation(qprint.QPrinter.Landscape) 

2818 

2819 dialog = qprint.QPrintDialog(printer, self) 

2820 dialog.setWindowTitle('Print') 

2821 

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

2823 return 

2824 

2825 painter = qg.QPainter() 

2826 painter.begin(printer) 

2827 page = printer.pageRect() 

2828 self.drawit( 

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

2830 

2831 painter.end() 

2832 

2833 def savesvg(self, fn=None): 

2834 

2835 if not fn: 

2836 fn, _ = qw.QFileDialog.getSaveFileName( 

2837 self, 

2838 'Save as SVG|PNG', 

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

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

2841 options=qfiledialog_options) 

2842 

2843 if fn == '': 

2844 return 

2845 

2846 fn = str(fn) 

2847 

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

2849 try: 

2850 w, h = 842, 595 

2851 margin = 0.025 

2852 m = max(w, h)*margin 

2853 

2854 generator = qsvg.QSvgGenerator() 

2855 generator.setFileName(fn) 

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

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

2858 

2859 painter = qg.QPainter() 

2860 painter.begin(generator) 

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

2862 painter.end() 

2863 

2864 except Exception as e: 

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

2866 

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

2868 pixmap = self.grab() 

2869 

2870 try: 

2871 pixmap.save(fn) 

2872 

2873 except Exception as e: 

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

2875 

2876 else: 

2877 self.fail( 

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

2879 '".png".') 

2880 

2881 def paintEvent(self, paint_ev): 

2882 ''' 

2883 Called by QT whenever widget needs to be painted. 

2884 ''' 

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

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

2887 if self.in_paint_event: 

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

2889 return 

2890 

2891 self.in_paint_event = True 

2892 

2893 painter = qg.QPainter(self) 

2894 

2895 if self.menuitem_antialias.isChecked(): 

2896 painter.setRenderHint(qg.QPainter.Antialiasing) 

2897 

2898 self.drawit(painter) 

2899 

2900 logger.debug( 

2901 'Time spent drawing: ' 

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

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

2904 (self.timer_draw - self.timer_cutout)) 

2905 

2906 logger.debug( 

2907 'Time spent processing:' 

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

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

2910 self.timer_cutout.get()) 

2911 

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

2913 self.time_last_painted = time.time() 

2914 self.in_paint_event = False 

2915 

2916 def determine_box_styles(self): 

2917 

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

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

2920 istyle = 0 

2921 trace_styles = {} 

2922 for itr, tr in enumerate(traces): 

2923 if itr > 0: 

2924 other = traces[itr-1] 

2925 if not ( 

2926 other.nslc_id == tr.nslc_id 

2927 and other.deltat == tr.deltat 

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

2929 < gap_lap_tolerance*tr.deltat): 

2930 

2931 istyle += 1 

2932 

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

2934 

2935 self.trace_styles = trace_styles 

2936 

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

2938 

2939 for v_projection in track_projections.values(): 

2940 v_projection.set_in_range(0., 1.) 

2941 

2942 def selector(x): 

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

2944 

2945 if self.trace_filter is not None: 

2946 def tselector(x): 

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

2948 

2949 else: 

2950 tselector = selector 

2951 

2952 traces = list(self.pile.iter_traces( 

2953 group_selector=selector, trace_selector=tselector)) 

2954 

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

2956 

2957 def drawbox(itrack, istyle, traces): 

2958 v_projection = track_projections[itrack] 

2959 dvmin = v_projection(0.) 

2960 dvmax = v_projection(1.) 

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

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

2963 

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

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

2966 p.fillRect(rect, style.fill_brush) 

2967 p.setPen(style.frame_pen) 

2968 p.drawRect(rect) 

2969 

2970 traces_by_style = {} 

2971 for itr, tr in enumerate(traces): 

2972 gt = self.gather(tr) 

2973 if gt not in self.key_to_row: 

2974 continue 

2975 

2976 itrack = self.key_to_row[gt] 

2977 if itrack not in track_projections: 

2978 continue 

2979 

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

2981 

2982 if len(traces) < 500: 

2983 drawbox(itrack, istyle, [tr]) 

2984 else: 

2985 if (itrack, istyle) not in traces_by_style: 

2986 traces_by_style[itrack, istyle] = [] 

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

2988 

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

2990 drawbox(itrack, istyle, traces) 

2991 

2992 def draw_visible_markers( 

2993 self, p, vcenter_projection, primary_pen): 

2994 

2995 try: 

2996 markers = self.markers.with_key_in_limited( 

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

2998 

2999 except pyrocko.pile.TooMany: 

3000 tmin = self.markers[0].tmin 

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

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

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

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

3005 v0, _ = vcenter_projection.get_out_range() 

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

3007 

3008 p.save() 

3009 

3010 pen = qg.QPen(primary_pen) 

3011 pen.setWidth(2) 

3012 pen.setStyle(qc.Qt.DotLine) 

3013 # pat = [5., 3.] 

3014 # pen.setDashPattern(pat) 

3015 p.setPen(pen) 

3016 

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

3018 s_selected = ' (all selected)' 

3019 elif self.n_selected_markers > 0: 

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

3021 else: 

3022 s_selected = '' 

3023 

3024 draw_label( 

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

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

3027 label_bg, 'LB') 

3028 

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

3030 p.drawLine(line) 

3031 p.restore() 

3032 

3033 return 

3034 

3035 for marker in markers: 

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

3037 and marker.kind in self.visible_marker_kinds: 

3038 

3039 marker.draw( 

3040 p, self.time_projection, vcenter_projection, 

3041 with_label=True) 

3042 

3043 def get_squirrel(self): 

3044 try: 

3045 return self.pile._squirrel 

3046 except AttributeError: 

3047 return None 

3048 

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

3050 sq = self.get_squirrel() 

3051 if sq is None: 

3052 return 

3053 

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

3055 v_projection = track_projections[itrack] 

3056 dvmin = v_projection(0.) 

3057 dvmax = v_projection(1.) 

3058 dtmin = time_projection.clipped(tmin, 0) 

3059 dtmax = time_projection.clipped(tmax, 1) 

3060 

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

3062 p.fillRect(rect, style.fill_brush) 

3063 p.setPen(style.frame_pen) 

3064 p.drawRect(rect) 

3065 

3066 pattern_list = [] 

3067 pattern_to_itrack = {} 

3068 for key in self.track_keys: 

3069 itrack = self.key_to_row[key] 

3070 if itrack not in track_projections: 

3071 continue 

3072 

3073 pattern = self.track_patterns[itrack] 

3074 pattern_to_itrack[tuple(pattern)] = itrack 

3075 pattern_list.append(tuple(pattern)) 

3076 

3077 vmin, vmax = self.get_time_range() 

3078 

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

3080 for coverage in sq.get_coverage( 

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

3082 itrack = pattern_to_itrack[coverage.pattern.nslc] 

3083 

3084 if coverage.changes is None: 

3085 drawbox( 

3086 itrack, coverage.tmin, coverage.tmax, 

3087 box_styles_coverage[kind][0]) 

3088 else: 

3089 t = None 

3090 pcount = 0 

3091 for tb, count in coverage.changes: 

3092 if t is not None and tb > t: 

3093 if pcount > 0: 

3094 drawbox( 

3095 itrack, t, tb, 

3096 box_styles_coverage[kind][ 

3097 min(len(box_styles_coverage)-1, 

3098 pcount)]) 

3099 

3100 t = tb 

3101 pcount = count 

3102 

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

3104 ''' 

3105 This performs the actual drawing. 

3106 ''' 

3107 

3108 self.timer_draw.start() 

3109 show_boxes = self.menuitem_showboxes.isChecked() 

3110 sq = self.get_squirrel() 

3111 

3112 if self.gather is None: 

3113 self.set_gathering() 

3114 

3115 if self.pile_has_changed: 

3116 

3117 if not self.sortingmode_change_delayed(): 

3118 self.sortingmode_change() 

3119 

3120 if show_boxes and sq is None: 

3121 self.determine_box_styles() 

3122 

3123 self.pile_has_changed = False 

3124 

3125 if h is None: 

3126 h = float(self.height()) 

3127 if w is None: 

3128 w = float(self.width()) 

3129 

3130 if printmode: 

3131 primary_color = (0, 0, 0) 

3132 else: 

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

3134 

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

3136 

3137 ax_h = float(self.ax_height) 

3138 

3139 vbottom_ax_projection = Projection() 

3140 vtop_ax_projection = Projection() 

3141 vcenter_projection = Projection() 

3142 

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

3144 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3145 vtop_ax_projection.set_out_range(0., ax_h) 

3146 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3147 vcenter_projection.set_in_range(0., 1.) 

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

3149 

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

3151 track_projections = {} 

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

3153 proj = Projection() 

3154 proj.set_out_range( 

3155 self.track_to_screen(i+0.05), 

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

3157 

3158 track_projections[i] = proj 

3159 

3160 if self.tmin > self.tmax: 

3161 return 

3162 

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

3164 vbottom_ax_projection.set_in_range(0, ax_h) 

3165 

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

3167 

3168 yscaler = pyrocko.plot.AutoScaler() 

3169 

3170 p.setPen(primary_pen) 

3171 

3172 font = qg.QFont() 

3173 font.setBold(True) 

3174 

3175 axannotfont = qg.QFont() 

3176 axannotfont.setBold(True) 

3177 axannotfont.setPointSize(8) 

3178 

3179 processed_traces = self.prepare_cutout2( 

3180 self.tmin, self.tmax, 

3181 trace_selector=self.trace_selector, 

3182 degap=self.menuitem_degap.isChecked(), 

3183 demean=self.menuitem_demean.isChecked()) 

3184 

3185 if not printmode and show_boxes: 

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

3187 or (self.view_mode is ViewMode.Waterfall 

3188 and not processed_traces): 

3189 

3190 if sq is None: 

3191 self.draw_trace_boxes( 

3192 p, self.time_projection, track_projections) 

3193 

3194 else: 

3195 self.draw_coverage( 

3196 p, self.time_projection, track_projections) 

3197 

3198 p.setFont(font) 

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

3200 

3201 color_lookup = dict( 

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

3203 

3204 self.track_to_nslc_ids = {} 

3205 nticks = 0 

3206 annot_labels = [] 

3207 

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

3209 waterfall = self.waterfall 

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

3211 waterfall.set_traces(processed_traces) 

3212 waterfall.set_cmap(self.waterfall_cmap) 

3213 waterfall.set_integrate(self.waterfall_integrate) 

3214 waterfall.set_clip( 

3215 self.waterfall_clip_min, self.waterfall_clip_max) 

3216 waterfall.show_absolute_values( 

3217 self.waterfall_show_absolute) 

3218 

3219 rect = qc.QRectF( 

3220 0, self.ax_height, 

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

3222 ) 

3223 waterfall.draw_waterfall(p, rect=rect) 

3224 

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

3226 show_scales = self.menuitem_showscalerange.isChecked() \ 

3227 or self.menuitem_showscaleaxis.isChecked() 

3228 

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

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

3231 - self.track_to_screen(0.05) 

3232 

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

3234 

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

3236 if self.menuitem_showscaleaxis.isChecked() \ 

3237 else 15 

3238 

3239 yscaler = pyrocko.plot.AutoScaler( 

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

3241 snap=show_scales 

3242 and not self.menuitem_showscaleaxis.isChecked()) 

3243 

3244 data_ranges = pyrocko.trace.minmax( 

3245 processed_traces, 

3246 key=self.scaling_key, 

3247 mode=self.scaling_base[0], 

3248 outer_mode=self.scaling_base[1]) 

3249 

3250 if not self.menuitem_fixscalerange.isChecked(): 

3251 self.old_data_ranges = data_ranges 

3252 else: 

3253 data_ranges.update(self.old_data_ranges) 

3254 

3255 self.apply_scaling_hooks(data_ranges) 

3256 

3257 trace_to_itrack = {} 

3258 track_scaling_keys = {} 

3259 track_scaling_colors = {} 

3260 for trace in processed_traces: 

3261 gt = self.gather(trace) 

3262 if gt not in self.key_to_row: 

3263 continue 

3264 

3265 itrack = self.key_to_row[gt] 

3266 if itrack not in track_projections: 

3267 continue 

3268 

3269 trace_to_itrack[trace] = itrack 

3270 

3271 if itrack not in self.track_to_nslc_ids: 

3272 self.track_to_nslc_ids[itrack] = set() 

3273 

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

3275 

3276 if itrack not in track_scaling_keys: 

3277 track_scaling_keys[itrack] = set() 

3278 

3279 scaling_key = self.scaling_key(trace) 

3280 track_scaling_keys[itrack].add(scaling_key) 

3281 

3282 color = pyrocko.plot.color( 

3283 color_lookup[self.color_gather(trace)]) 

3284 

3285 k = itrack, scaling_key 

3286 if k not in track_scaling_colors \ 

3287 and self.menuitem_colortraces.isChecked(): 

3288 track_scaling_colors[k] = color 

3289 else: 

3290 track_scaling_colors[k] = primary_color 

3291 

3292 # y axes, zero lines 

3293 trace_projections = {} 

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

3295 if itrack not in track_scaling_keys: 

3296 continue 

3297 uoff = 0 

3298 for scaling_key in track_scaling_keys[itrack]: 

3299 data_range = data_ranges[scaling_key] 

3300 dymin, dymax = data_range 

3301 ymin, ymax, yinc = yscaler.make_scale( 

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

3303 iexp = yscaler.make_exp(yinc) 

3304 factor = 10**iexp 

3305 trace_projection = track_projections[itrack].copy() 

3306 trace_projection.set_in_range(ymax, ymin) 

3307 trace_projections[itrack, scaling_key] = \ 

3308 trace_projection 

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

3310 vmin, vmax = trace_projection.get_out_range() 

3311 umax_zeroline = umax 

3312 uoffnext = uoff 

3313 

3314 if show_scales: 

3315 pen = qg.QPen(primary_pen) 

3316 k = itrack, scaling_key 

3317 if k in track_scaling_colors: 

3318 c = qg.QColor(*track_scaling_colors[ 

3319 itrack, scaling_key]) 

3320 

3321 pen.setColor(c) 

3322 

3323 p.setPen(pen) 

3324 if nlinesavail > 3: 

3325 if self.menuitem_showscaleaxis.isChecked(): 

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

3327 ny_annot = int( 

3328 math.floor(ymax/yinc) 

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

3330 

3331 for iy_annot in range(ny_annot): 

3332 y = ymin_annot + iy_annot*yinc 

3333 v = trace_projection(y) 

3334 line = qc.QLineF( 

3335 umax-10-uoff, v, umax-uoff, v) 

3336 

3337 p.drawLine(line) 

3338 if iy_annot == ny_annot - 1 \ 

3339 and iexp != 0: 

3340 sexp = ' &times; ' \ 

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

3342 else: 

3343 sexp = '' 

3344 

3345 snum = num_to_html(y/factor) 

3346 lab = Label( 

3347 p, 

3348 umax-20-uoff, 

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

3350 label_bg=None, 

3351 anchor='MR', 

3352 font=axannotfont, 

3353 color=c) 

3354 

3355 uoffnext = max( 

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

3357 

3358 annot_labels.append(lab) 

3359 if y == 0.: 

3360 umax_zeroline = \ 

3361 umax - 20 \ 

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

3363 - uoff 

3364 else: 

3365 if not show_boxes: 

3366 qpoints = make_QPolygonF( 

3367 [umax-20-uoff, 

3368 umax-10-uoff, 

3369 umax-10-uoff, 

3370 umax-20-uoff], 

3371 [vmax, vmax, vmin, vmin]) 

3372 p.drawPolyline(qpoints) 

3373 

3374 snum = num_to_html(ymin) 

3375 labmin = Label( 

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

3377 label_bg=None, 

3378 anchor='BR', 

3379 font=axannotfont, 

3380 color=c) 

3381 

3382 annot_labels.append(labmin) 

3383 snum = num_to_html(ymax) 

3384 labmax = Label( 

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

3386 label_bg=None, 

3387 anchor='TR', 

3388 font=axannotfont, 

3389 color=c) 

3390 

3391 annot_labels.append(labmax) 

3392 

3393 for lab in (labmin, labmax): 

3394 uoffnext = max( 

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

3396 

3397 if self.menuitem_showzeroline.isChecked(): 

3398 v = trace_projection(0.) 

3399 if vmin <= v <= vmax: 

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

3401 p.drawLine(line) 

3402 

3403 uoff = uoffnext 

3404 

3405 p.setFont(font) 

3406 p.setPen(primary_pen) 

3407 for trace in processed_traces: 

3408 if self.view_mode is not ViewMode.Wiggle: 

3409 break 

3410 

3411 if trace not in trace_to_itrack: 

3412 continue 

3413 

3414 itrack = trace_to_itrack[trace] 

3415 scaling_key = self.scaling_key(trace) 

3416 trace_projection = trace_projections[ 

3417 itrack, scaling_key] 

3418 

3419 vdata = trace_projection(trace.get_ydata()) 

3420 

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

3422 udata_max = float(self.time_projection( 

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

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

3425 

3426 qpoints = make_QPolygonF(udata, vdata) 

3427 

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

3429 vmin, vmax = trace_projection.get_out_range() 

3430 

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

3432 

3433 if self.menuitem_cliptraces.isChecked(): 

3434 p.setClipRect(trackrect) 

3435 

3436 if self.menuitem_colortraces.isChecked(): 

3437 color = pyrocko.plot.color( 

3438 color_lookup[self.color_gather(trace)]) 

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

3440 p.setPen(pen) 

3441 

3442 p.drawPolyline(qpoints) 

3443 

3444 if self.floating_marker: 

3445 self.floating_marker.draw_trace( 

3446 self, p, trace, 

3447 self.time_projection, trace_projection, 1.0) 

3448 

3449 for marker in self.markers.with_key_in( 

3450 self.tmin - self.markers_deltat_max, 

3451 self.tmax): 

3452 

3453 if marker.tmin < self.tmax \ 

3454 and self.tmin < marker.tmax \ 

3455 and marker.kind \ 

3456 in self.visible_marker_kinds: 

3457 marker.draw_trace( 

3458 self, p, trace, self.time_projection, 

3459 trace_projection, 1.0) 

3460 

3461 p.setPen(primary_pen) 

3462 

3463 if self.menuitem_cliptraces.isChecked(): 

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

3465 

3466 if self.floating_marker: 

3467 self.floating_marker.draw( 

3468 p, self.time_projection, vcenter_projection) 

3469 

3470 self.draw_visible_markers( 

3471 p, vcenter_projection, primary_pen) 

3472 

3473 p.setPen(primary_pen) 

3474 while font.pointSize() > 2: 

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

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

3477 - self.track_to_screen(0.05) 

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

3479 if nlinesavail > 1: 

3480 break 

3481 

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

3483 

3484 p.setFont(font) 

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

3486 

3487 for key in self.track_keys: 

3488 itrack = self.key_to_row[key] 

3489 if itrack in track_projections: 

3490 plabel = ' '.join( 

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

3492 lx = 10 

3493 ly = self.track_to_screen(itrack+0.5) 

3494 

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

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

3497 continue 

3498 

3499 contains_cursor = \ 

3500 self.track_to_screen(itrack) \ 

3501 < mouse_pos.y() \ 

3502 < self.track_to_screen(itrack+1) 

3503 

3504 if not contains_cursor: 

3505 continue 

3506 

3507 font_large = p.font() 

3508 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3509 p.setFont(font_large) 

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

3511 p.setFont(font) 

3512 

3513 for lab in annot_labels: 

3514 lab.draw() 

3515 

3516 self.timer_draw.stop() 

3517 

3518 def see_data_params(self): 

3519 

3520 min_deltat = self.content_deltat_range()[0] 

3521 

3522 # determine padding and downampling requirements 

3523 if self.lowpass is not None: 

3524 deltat_target = 1./self.lowpass * 0.25 

3525 ndecimate = min( 

3526 50, 

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

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

3529 else: 

3530 ndecimate = 1 

3531 tpad = min_deltat*5. 

3532 

3533 if self.highpass is not None: 

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

3535 

3536 nsee_points_per_trace = 5000*10 

3537 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3538 

3539 return ndecimate, tpad, tsee 

3540 

3541 def clean_update(self): 

3542 self.cached_processed_traces = None 

3543 self.update() 

3544 

3545 def get_adequate_tpad(self): 

3546 tpad = 0. 

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

3548 if f is not None: 

3549 tpad = max(tpad, 1.0/f) 

3550 

3551 for snuffling in self.snufflings: 

3552 if snuffling._post_process_hook_enabled \ 

3553 or snuffling._pre_process_hook_enabled: 

3554 

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

3556 

3557 return tpad 

3558 

3559 def prepare_cutout2( 

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

3561 demean=True, nmax=6000): 

3562 

3563 if self.pile.is_empty(): 

3564 return [] 

3565 

3566 nmax = self.visible_length 

3567 

3568 self.timer_cutout.start() 

3569 

3570 tsee = tmax-tmin 

3571 min_deltat_wo_decimate = tsee/nmax 

3572 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3573 

3574 min_deltat_allow = min_deltat_wo_decimate 

3575 if self.lowpass is not None: 

3576 target_deltat_lp = 0.25/self.lowpass 

3577 if target_deltat_lp > min_deltat_wo_decimate: 

3578 min_deltat_allow = min_deltat_w_decimate 

3579 

3580 min_deltat_allow = math.exp( 

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

3582 

3583 tmin_ = tmin 

3584 tmax_ = tmax 

3585 

3586 # fetch more than needed? 

3587 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3591 

3592 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3593 lphp = self.menuitem_lphp.isChecked() 

3594 ads = self.menuitem_allowdownsampling.isChecked() 

3595 

3596 tpad = self.get_adequate_tpad() 

3597 tpad = max(tpad, tsee) 

3598 

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

3600 vec = ( 

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

3602 self.highpass, fft_filtering, lphp, 

3603 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3604 ads, self.pile.get_update_count()) 

3605 

3606 if (self.cached_vec 

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

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

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

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

3611 and self.cached_processed_traces is not None): 

3612 

3613 logger.debug('Using cached traces') 

3614 processed_traces = self.cached_processed_traces 

3615 

3616 else: 

3617 processed_traces = [] 

3618 if self.pile.deltatmax >= min_deltat_allow: 

3619 

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

3621 def group_selector(gr): 

3622 return gr.deltatmax >= min_deltat_allow 

3623 

3624 kwargs = dict(group_selector=group_selector) 

3625 else: 

3626 kwargs = {} 

3627 

3628 if trace_selector is not None: 

3629 def trace_selectorx(tr): 

3630 return tr.deltat >= min_deltat_allow \ 

3631 and trace_selector(tr) 

3632 else: 

3633 def trace_selectorx(tr): 

3634 return tr.deltat >= min_deltat_allow 

3635 

3636 for traces in self.pile.chopper( 

3637 tmin=tmin, tmax=tmax, tpad=tpad, 

3638 want_incomplete=True, 

3639 degap=degap, 

3640 maxgap=gap_lap_tolerance, 

3641 maxlap=gap_lap_tolerance, 

3642 keep_current_files_open=True, 

3643 trace_selector=trace_selectorx, 

3644 accessor_id=id(self), 

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

3646 include_last=True, **kwargs): 

3647 

3648 if demean: 

3649 for tr in traces: 

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

3651 continue 

3652 y = tr.get_ydata() 

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

3654 

3655 traces = self.pre_process_hooks(traces) 

3656 

3657 for trace in traces: 

3658 

3659 if not (trace.meta 

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

3661 

3662 if fft_filtering: 

3663 but = pyrocko.response.ButterworthResponse 

3664 multres = pyrocko.response.MultiplyResponse 

3665 if self.lowpass is not None \ 

3666 or self.highpass is not None: 

3667 

3668 it = num.arange( 

3669 trace.data_len(), dtype=float) 

3670 detr_data, m, b = detrend( 

3671 it, trace.get_ydata()) 

3672 

3673 trace.set_ydata(detr_data) 

3674 

3675 freqs, fdata = trace.spectrum( 

3676 pad_to_pow2=True, tfade=None) 

3677 

3678 nfreqs = fdata.size 

3679 

3680 key = (trace.deltat, nfreqs) 

3681 

3682 if key not in self.tf_cache: 

3683 resps = [] 

3684 if self.lowpass is not None: 

3685 resps.append(but( 

3686 order=4, 

3687 corner=self.lowpass, 

3688 type='low')) 

3689 

3690 if self.highpass is not None: 

3691 resps.append(but( 

3692 order=4, 

3693 corner=self.highpass, 

3694 type='high')) 

3695 

3696 resp = multres(resps) 

3697 self.tf_cache[key] = \ 

3698 resp.evaluate(freqs) 

3699 

3700 filtered_data = num.fft.irfft( 

3701 fdata*self.tf_cache[key] 

3702 )[:trace.data_len()] 

3703 

3704 retrended_data = retrend( 

3705 it, filtered_data, m, b) 

3706 

3707 trace.set_ydata(retrended_data) 

3708 

3709 else: 

3710 

3711 if ads and self.lowpass is not None: 

3712 while trace.deltat \ 

3713 < min_deltat_wo_decimate: 

3714 

3715 trace.downsample(2, demean=False) 

3716 

3717 fmax = 0.5/trace.deltat 

3718 if not lphp and ( 

3719 self.lowpass is not None 

3720 and self.highpass is not None 

3721 and self.lowpass < fmax 

3722 and self.highpass < fmax 

3723 and self.highpass < self.lowpass): 

3724 

3725 trace.bandpass( 

3726 2, self.highpass, self.lowpass) 

3727 else: 

3728 if self.lowpass is not None: 

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

3730 trace.lowpass( 

3731 4, self.lowpass, 

3732 demean=False) 

3733 

3734 if self.highpass is not None: 

3735 if self.lowpass is None \ 

3736 or self.highpass \ 

3737 < self.lowpass: 

3738 

3739 if self.highpass < \ 

3740 0.5/trace.deltat: 

3741 trace.highpass( 

3742 4, self.highpass, 

3743 demean=False) 

3744 

3745 processed_traces.append(trace) 

3746 

3747 if self.rotate != 0.0: 

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

3749 cphi = math.cos(phi) 

3750 sphi = math.sin(phi) 

3751 for a in processed_traces: 

3752 for b in processed_traces: 

3753 if (a.network == b.network 

3754 and a.station == b.station 

3755 and a.location == b.location 

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

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

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

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

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

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

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

3763 

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

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

3766 a.set_ydata(aydata) 

3767 b.set_ydata(bydata) 

3768 

3769 processed_traces = self.post_process_hooks(processed_traces) 

3770 

3771 self.cached_processed_traces = processed_traces 

3772 self.cached_vec = vec 

3773 

3774 chopped_traces = [] 

3775 for trace in processed_traces: 

3776 chop_tmin = tmin_ - trace.deltat*4 

3777 chop_tmax = tmax_ + trace.deltat*4 

3778 

3779 try: 

3780 ctrace = trace.chop( 

3781 chop_tmin, chop_tmax, 

3782 inplace=False) 

3783 

3784 except pyrocko.trace.NoData: 

3785 continue 

3786 

3787 if ctrace.data_len() < 2: 

3788 continue 

3789 

3790 chopped_traces.append(ctrace) 

3791 

3792 self.timer_cutout.stop() 

3793 return chopped_traces 

3794 

3795 def pre_process_hooks(self, traces): 

3796 for snuffling in self.snufflings: 

3797 if snuffling._pre_process_hook_enabled: 

3798 traces = snuffling.pre_process_hook(traces) 

3799 

3800 return traces 

3801 

3802 def post_process_hooks(self, traces): 

3803 for snuffling in self.snufflings: 

3804 if snuffling._post_process_hook_enabled: 

3805 traces = snuffling.post_process_hook(traces) 

3806 

3807 return traces 

3808 

3809 def visible_length_change(self, ignore=None): 

3810 for menuitem, vlen in self.menuitems_visible_length: 

3811 if menuitem.isChecked(): 

3812 self.visible_length = vlen 

3813 

3814 def scaling_base_change(self, ignore=None): 

3815 for menuitem, scaling_base in self.menuitems_scaling_base: 

3816 if menuitem.isChecked(): 

3817 self.scaling_base = scaling_base 

3818 

3819 def scalingmode_change(self, ignore=None): 

3820 for menuitem, scaling_key in self.menuitems_scaling: 

3821 if menuitem.isChecked(): 

3822 self.scaling_key = scaling_key 

3823 self.update() 

3824 

3825 def apply_scaling_hooks(self, data_ranges): 

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

3827 hook = self.scaling_hooks[k] 

3828 hook(data_ranges) 

3829 

3830 def viewmode_change(self, ignore=True): 

3831 for item, mode in self.menuitems_viewmode: 

3832 if item.isChecked(): 

3833 self.view_mode = mode 

3834 break 

3835 else: 

3836 raise AttributeError('unknown view mode') 

3837 

3838 items_waterfall_disabled = ( 

3839 self.menuitem_showscaleaxis, 

3840 self.menuitem_showscalerange, 

3841 self.menuitem_showzeroline, 

3842 self.menuitem_colortraces, 

3843 self.menuitem_cliptraces, 

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

3845 ) 

3846 

3847 if self.view_mode is ViewMode.Waterfall: 

3848 self.parent().show_colorbar_ctrl(True) 

3849 self.parent().show_gain_ctrl(False) 

3850 

3851 for item in items_waterfall_disabled: 

3852 item.setDisabled(True) 

3853 

3854 self.visible_length = 180. 

3855 else: 

3856 self.parent().show_colorbar_ctrl(False) 

3857 self.parent().show_gain_ctrl(True) 

3858 

3859 for item in items_waterfall_disabled: 

3860 item.setDisabled(False) 

3861 

3862 self.visible_length_change() 

3863 self.update() 

3864 

3865 def set_scaling_hook(self, k, hook): 

3866 self.scaling_hooks[k] = hook 

3867 

3868 def remove_scaling_hook(self, k): 

3869 del self.scaling_hooks[k] 

3870 

3871 def remove_scaling_hooks(self): 

3872 self.scaling_hooks = {} 

3873 

3874 def s_sortingmode_change(self, ignore=None): 

3875 for menuitem, valfunc in self.menuitems_ssorting: 

3876 if menuitem.isChecked(): 

3877 self._ssort = valfunc 

3878 

3879 self.sortingmode_change() 

3880 

3881 def sortingmode_change(self, ignore=None): 

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

3883 if menuitem.isChecked(): 

3884 self.set_gathering(gather, color) 

3885 

3886 self.sortingmode_change_time = time.time() 

3887 

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

3889 self.lowpass = value 

3890 self.passband_check() 

3891 self.tf_cache = {} 

3892 self.update() 

3893 

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

3895 self.highpass = value 

3896 self.passband_check() 

3897 self.tf_cache = {} 

3898 self.update() 

3899 

3900 def passband_check(self): 

3901 if self.highpass and self.lowpass \ 

3902 and self.highpass >= self.lowpass: 

3903 

3904 self.window().status_messages.set( 

3905 'filter_error', 

3906 'Corner frequency of highpass greater than ' 

3907 'corner frequency of lowpass. Highpass deactivated.') 

3908 else: 

3909 self.window().status_messages.clear('filter_error') 

3910 

3911 def gain_change(self, value, ignore): 

3912 self.gain = value 

3913 self.update() 

3914 

3915 def rot_change(self, value, ignore): 

3916 self.rotate = value 

3917 self.update() 

3918 

3919 def waterfall_cmap_change(self, cmap): 

3920 self.waterfall_cmap = cmap 

3921 self.update() 

3922 

3923 def waterfall_clip_change(self, clip_min, clip_max): 

3924 self.waterfall_clip_min = clip_min 

3925 self.waterfall_clip_max = clip_max 

3926 self.update() 

3927 

3928 def waterfall_show_absolute_change(self, toggle): 

3929 self.waterfall_show_absolute = toggle 

3930 self.update() 

3931 

3932 def waterfall_set_integrate(self, toggle): 

3933 self.waterfall_integrate = toggle 

3934 self.update() 

3935 

3936 def set_selected_markers(self, markers): 

3937 ''' 

3938 Set a list of markers selected 

3939 

3940 :param markers: list of markers 

3941 ''' 

3942 self.deselect_all() 

3943 for m in markers: 

3944 m.selected = True 

3945 

3946 self.update() 

3947 

3948 def deselect_all(self): 

3949 for marker in self.markers: 

3950 marker.selected = False 

3951 

3952 def animate_picking(self): 

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

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

3955 

3956 def get_nslc_ids_for_track(self, ftrack): 

3957 itrack = int(ftrack) 

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

3959 

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

3961 if self.picking: 

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

3963 self.picking = None 

3964 self.picking_down = None 

3965 self.picking_timer.stop() 

3966 self.picking_timer = None 

3967 if not abort: 

3968 self.add_marker(self.floating_marker) 

3969 self.floating_marker.selected = True 

3970 self.emit_selected_markers() 

3971 

3972 self.floating_marker = None 

3973 

3974 def start_picking(self, ignore): 

3975 

3976 if not self.picking: 

3977 self.deselect_all() 

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

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

3980 

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

3982 self.picking.setGeometry( 

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

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

3985 

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

3987 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3989 self.floating_marker.selected = True 

3990 

3991 self.picking_timer = qc.QTimer() 

3992 self.picking_timer.timeout.connect( 

3993 self.animate_picking) 

3994 

3995 self.picking_timer.setInterval(50) 

3996 self.picking_timer.start() 

3997 

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

3999 if self.picking: 

4000 mouset = self.time_projection.rev(x) 

4001 dt = 0.0 

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

4003 if mouset < self.tmin: 

4004 dt = -(self.tmin - mouset) 

4005 else: 

4006 dt = mouset - self.tmax 

4007 ddt = self.tmax-self.tmin 

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

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

4010 

4011 x0 = x 

4012 if self.picking_down is not None: 

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

4014 

4015 w = abs(x-x0) 

4016 x0 = min(x0, x) 

4017 

4018 tmin, tmax = ( 

4019 self.time_projection.rev(x0), 

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

4021 

4022 tmin, tmax = ( 

4023 max(working_system_time_range[0], tmin), 

4024 min(working_system_time_range[1], tmax)) 

4025 

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

4027 

4028 self.picking.setGeometry( 

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

4030 

4031 ftrack = self.track_to_screen.rev(y) 

4032 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

4034 

4035 if dt != 0.0 and doshift: 

4036 self.interrupt_following() 

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

4038 

4039 self.update() 

4040 

4041 def update_status(self): 

4042 

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

4044 

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

4046 if not is_working_time(mouse_t): 

4047 return 

4048 

4049 if self.floating_marker: 

4050 tmi, tma = ( 

4051 self.floating_marker.tmin, 

4052 self.floating_marker.tmax) 

4053 

4054 tt, ms = gmtime_x(tmi) 

4055 

4056 if tmi == tma: 

4057 message = mystrftime( 

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

4059 tt=tt, milliseconds=ms) 

4060 else: 

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

4062 message = mystrftime( 

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

4064 tt=tt, milliseconds=ms) 

4065 else: 

4066 tt, ms = gmtime_x(mouse_t) 

4067 

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

4069 

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

4071 sb.clearMessage() 

4072 sb.showMessage(message) 

4073 

4074 def set_sortingmode_change_delay_time(self, dt): 

4075 self.sortingmode_change_delay_time = dt 

4076 

4077 def sortingmode_change_delayed(self): 

4078 now = time.time() 

4079 return ( 

4080 self.sortingmode_change_delay_time is not None 

4081 and now - self.sortingmode_change_time 

4082 < self.sortingmode_change_delay_time) 

4083 

4084 def set_visible_marker_kinds(self, kinds): 

4085 self.deselect_all() 

4086 self.visible_marker_kinds = tuple(kinds) 

4087 self.emit_selected_markers() 

4088 

4089 def following(self): 

4090 return self.follow_timer is not None \ 

4091 and not self.following_interrupted() 

4092 

4093 def interrupt_following(self): 

4094 self.interactive_range_change_time = time.time() 

4095 

4096 def following_interrupted(self, now=None): 

4097 if now is None: 

4098 now = time.time() 

4099 return now - self.interactive_range_change_time \ 

4100 < self.interactive_range_change_delay_time 

4101 

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

4103 if tmax_start is None: 

4104 tmax_start = time.time() 

4105 self.show_all = False 

4106 self.follow_time = tlen 

4107 self.follow_timer = qc.QTimer(self) 

4108 self.follow_timer.timeout.connect( 

4109 self.follow_update) 

4110 self.follow_timer.setInterval(interval) 

4111 self.follow_timer.start() 

4112 self.follow_started = time.time() 

4113 self.follow_lapse = lapse 

4114 self.follow_tshift = self.follow_started - tmax_start 

4115 self.interactive_range_change_time = 0.0 

4116 

4117 def unfollow(self): 

4118 if self.follow_timer is not None: 

4119 self.follow_timer.stop() 

4120 self.follow_timer = None 

4121 self.interactive_range_change_time = 0.0 

4122 

4123 def follow_update(self): 

4124 rnow = time.time() 

4125 if self.follow_lapse is None: 

4126 now = rnow 

4127 else: 

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

4129 * self.follow_lapse 

4130 

4131 if self.following_interrupted(rnow): 

4132 return 

4133 self.set_time_range( 

4134 now-self.follow_time-self.follow_tshift, 

4135 now-self.follow_tshift) 

4136 

4137 self.update() 

4138 

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

4140 self.return_tag = return_tag 

4141 self.window().close() 

4142 

4143 def cleanup(self): 

4144 self.about_to_close.emit() 

4145 self.timer.stop() 

4146 if self.follow_timer is not None: 

4147 self.follow_timer.stop() 

4148 

4149 for snuffling in list(self.snufflings): 

4150 self.remove_snuffling(snuffling) 

4151 

4152 def inputline_changed(self, text): 

4153 pass 

4154 

4155 def inputline_finished(self, text): 

4156 line = str(text) 

4157 

4158 toks = line.split() 

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

4160 if len(toks) >= 1: 

4161 command = toks[0].lower() 

4162 

4163 try: 

4164 quick_filter_commands = { 

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

4166 's': '*.%s.*.*', 

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

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

4169 

4170 if command in quick_filter_commands: 

4171 if len(toks) >= 2: 

4172 patterns = [ 

4173 quick_filter_commands[toks[0]] % pat 

4174 for pat in toks[1:]] 

4175 self.set_quick_filter_patterns(patterns, line) 

4176 else: 

4177 self.set_quick_filter_patterns(None) 

4178 

4179 self.update() 

4180 

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

4182 if len(toks) >= 2: 

4183 patterns = [] 

4184 if len(toks) == 2: 

4185 patterns = [toks[1]] 

4186 elif len(toks) >= 3: 

4187 x = { 

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

4189 's': '*.%s.*.*', 

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

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

4192 

4193 if toks[1] in x: 

4194 patterns.extend( 

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

4196 

4197 for pattern in patterns: 

4198 if command == 'hide': 

4199 self.add_blacklist_pattern(pattern) 

4200 else: 

4201 self.remove_blacklist_pattern(pattern) 

4202 

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

4204 self.clear_blacklist() 

4205 

4206 clearit = True 

4207 

4208 self.update() 

4209 

4210 elif command == 'markers': 

4211 if len(toks) == 2: 

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

4213 kinds = self.all_marker_kinds 

4214 else: 

4215 kinds = [] 

4216 for x in toks[1]: 

4217 try: 

4218 kinds.append(int(x)) 

4219 except Exception: 

4220 pass 

4221 

4222 self.set_visible_marker_kinds(kinds) 

4223 

4224 elif len(toks) == 1: 

4225 self.set_visible_marker_kinds(()) 

4226 

4227 self.update() 

4228 

4229 elif command == 'scaling': 

4230 if len(toks) == 2: 

4231 hideit = False 

4232 error = 'wrong number of arguments' 

4233 

4234 if len(toks) >= 3: 

4235 vmin, vmax = [ 

4236 pyrocko.model.float_or_none(x) 

4237 for x in toks[-2:]] 

4238 

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

4240 if k in d: 

4241 if vmin is not None: 

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

4243 if vmax is not None: 

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

4245 

4246 if len(toks) == 1: 

4247 self.remove_scaling_hooks() 

4248 

4249 elif len(toks) == 3: 

4250 def hook(data_ranges): 

4251 for k in data_ranges: 

4252 upd(data_ranges, k, vmin, vmax) 

4253 

4254 self.set_scaling_hook('_', hook) 

4255 

4256 elif len(toks) == 4: 

4257 pattern = toks[1] 

4258 

4259 def hook(data_ranges): 

4260 for k in pyrocko.util.match_nslcs( 

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

4262 

4263 upd(data_ranges, k, vmin, vmax) 

4264 

4265 self.set_scaling_hook(pattern, hook) 

4266 

4267 elif command == 'goto': 

4268 toks2 = line.split(None, 1) 

4269 if len(toks2) == 2: 

4270 arg = toks2[1] 

4271 m = re.match( 

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

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

4274 if m: 

4275 tlen = None 

4276 if not m.group(1): 

4277 tlen = 12*32*24*60*60 

4278 elif not m.group(2): 

4279 tlen = 32*24*60*60 

4280 elif not m.group(3): 

4281 tlen = 24*60*60 

4282 elif not m.group(4): 

4283 tlen = 60*60 

4284 elif not m.group(5): 

4285 tlen = 60 

4286 

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

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

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

4290 t = pyrocko.util.str_to_time(arg) 

4291 self.go_to_time(t, tlen=tlen) 

4292 

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

4294 supl = '00:00:00' 

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

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

4297 tmin, tmax = self.get_time_range() 

4298 sdate = pyrocko.util.time_to_str( 

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

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

4301 self.go_to_time(t) 

4302 

4303 elif arg == 'today': 

4304 self.go_to_time( 

4305 day_start( 

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

4307 

4308 elif arg == 'yesterday': 

4309 self.go_to_time( 

4310 day_start( 

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

4312 

4313 else: 

4314 self.go_to_event_by_name(arg) 

4315 

4316 else: 

4317 raise PileViewerMainException( 

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

4319 

4320 except PileViewerMainException as e: 

4321 error = str(e) 

4322 hideit = False 

4323 

4324 return clearit, hideit, error 

4325 

4326 return PileViewerMain 

4327 

4328 

4329PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4330GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget) 

4331 

4332 

4333class LineEditWithAbort(qw.QLineEdit): 

4334 

4335 aborted = qc.pyqtSignal() 

4336 history_down = qc.pyqtSignal() 

4337 history_up = qc.pyqtSignal() 

4338 

4339 def keyPressEvent(self, key_event): 

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

4341 self.aborted.emit() 

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

4343 self.history_down.emit() 

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

4345 self.history_up.emit() 

4346 else: 

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

4348 

4349 

4350class PileViewer(qw.QFrame): 

4351 ''' 

4352 PileViewerMain + Controls + Inputline 

4353 ''' 

4354 

4355 def __init__( 

4356 self, pile, 

4357 ntracks_shown_max=20, 

4358 marker_editor_sortable=True, 

4359 use_opengl=None, 

4360 panel_parent=None, 

4361 *args): 

4362 

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

4364 

4365 layout = qw.QGridLayout() 

4366 layout.setContentsMargins(0, 0, 0, 0) 

4367 layout.setSpacing(0) 

4368 

4369 self.menu = PileViewerMenuBar(self) 

4370 

4371 if use_opengl is None: 

4372 use_opengl = is_macos 

4373 

4374 if use_opengl: 

4375 self.viewer = GLPileViewerMain( 

4376 pile, 

4377 ntracks_shown_max=ntracks_shown_max, 

4378 panel_parent=panel_parent, 

4379 menu=self.menu) 

4380 else: 

4381 self.viewer = PileViewerMain( 

4382 pile, 

4383 ntracks_shown_max=ntracks_shown_max, 

4384 panel_parent=panel_parent, 

4385 menu=self.menu) 

4386 

4387 self.marker_editor_sortable = marker_editor_sortable 

4388 

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

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

4391 

4392 self.input_area = qw.QFrame(self) 

4393 ia_layout = qw.QGridLayout() 

4394 ia_layout.setContentsMargins(11, 11, 11, 11) 

4395 self.input_area.setLayout(ia_layout) 

4396 

4397 self.inputline = LineEditWithAbort(self.input_area) 

4398 self.inputline.returnPressed.connect( 

4399 self.inputline_returnpressed) 

4400 self.inputline.editingFinished.connect( 

4401 self.inputline_finished) 

4402 self.inputline.aborted.connect( 

4403 self.inputline_aborted) 

4404 

4405 self.inputline.history_down.connect( 

4406 lambda: self.step_through_history(1)) 

4407 self.inputline.history_up.connect( 

4408 lambda: self.step_through_history(-1)) 

4409 

4410 self.inputline.textEdited.connect( 

4411 self.inputline_changed) 

4412 

4413 self.inputline.setPlaceholderText( 

4414 u"Quick commands: e.g. 'c HH?' to select channels. " 

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

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

4417 self.input_area.hide() 

4418 self.history = None 

4419 

4420 self.inputline_error_str = None 

4421 

4422 self.inputline_error = qw.QLabel() 

4423 self.inputline_error.hide() 

4424 

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

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

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

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

4429 

4430 pb = Progressbars(self) 

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

4432 self.progressbars = pb 

4433 

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

4435 self.scrollbar = scrollbar 

4436 layout.addWidget(scrollbar, 1, 1) 

4437 self.scrollbar.valueChanged.connect( 

4438 self.scrollbar_changed) 

4439 

4440 self.block_scrollbar_changes = False 

4441 

4442 self.viewer.want_input.connect( 

4443 self.inputline_show) 

4444 self.viewer.toggle_input.connect( 

4445 self.inputline_toggle) 

4446 self.viewer.tracks_range_changed.connect( 

4447 self.tracks_range_changed) 

4448 self.viewer.pile_has_changed_signal.connect( 

4449 self.adjust_controls) 

4450 self.viewer.about_to_close.connect( 

4451 self.save_inputline_history) 

4452 

4453 self.setLayout(layout) 

4454 

4455 def cleanup(self): 

4456 self.viewer.cleanup() 

4457 

4458 def get_progressbars(self): 

4459 return self.progressbars 

4460 

4461 def inputline_show(self): 

4462 if not self.history: 

4463 self.load_inputline_history() 

4464 

4465 self.input_area.show() 

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

4467 self.inputline.selectAll() 

4468 

4469 def inputline_set_error(self, string): 

4470 self.inputline_error_str = string 

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

4472 self.inputline.selectAll() 

4473 self.inputline_error.setText(string) 

4474 self.input_area.show() 

4475 self.inputline_error.show() 

4476 

4477 def inputline_clear_error(self): 

4478 if self.inputline_error_str: 

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

4480 self.inputline_error_str = None 

4481 self.inputline_error.clear() 

4482 self.inputline_error.hide() 

4483 

4484 def inputline_changed(self, line): 

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

4486 self.inputline_clear_error() 

4487 

4488 def inputline_returnpressed(self): 

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

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

4491 

4492 if error: 

4493 self.inputline_set_error(error) 

4494 

4495 line = line.strip() 

4496 

4497 if line != '' and not error: 

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

4499 self.history.append(line) 

4500 

4501 if clearit: 

4502 

4503 self.inputline.blockSignals(True) 

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

4505 if qpat is None: 

4506 self.inputline.clear() 

4507 else: 

4508 self.inputline.setText(qinp) 

4509 self.inputline.blockSignals(False) 

4510 

4511 if hideit and not error: 

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

4513 self.input_area.hide() 

4514 

4515 self.hist_ind = len(self.history) 

4516 

4517 def inputline_aborted(self): 

4518 ''' 

4519 Hide the input line. 

4520 ''' 

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

4522 self.hist_ind = len(self.history) 

4523 self.input_area.hide() 

4524 

4525 def inputline_toggle(self): 

4526 if self.input_area.isVisible(): 

4527 self.inputline_aborted() 

4528 else: 

4529 self.inputline_show() 

4530 

4531 def save_inputline_history(self): 

4532 ''' 

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

4534 ''' 

4535 if not self.history: 

4536 return 

4537 

4538 conf = pyrocko.config 

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

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

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

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

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

4544 

4545 def load_inputline_history(self): 

4546 ''' 

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

4548 ''' 

4549 conf = pyrocko.config 

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

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

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

4553 f.write('\n') 

4554 

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

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

4557 

4558 self.hist_ind = len(self.history) 

4559 

4560 def step_through_history(self, ud=1): 

4561 ''' 

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

4563 ''' 

4564 n = len(self.history) 

4565 self.hist_ind += ud 

4566 self.hist_ind %= (n + 1) 

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

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

4569 else: 

4570 self.inputline.setText('') 

4571 

4572 def inputline_finished(self): 

4573 pass 

4574 

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

4576 if self.block_scrollbar_changes: 

4577 return 

4578 

4579 self.scrollbar.blockSignals(True) 

4580 self.scrollbar.setPageStep(ihi-ilo) 

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

4582 self.scrollbar.setRange(0, vmax) 

4583 self.scrollbar.setValue(ilo) 

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

4585 self.scrollbar.blockSignals(False) 

4586 

4587 def scrollbar_changed(self, value): 

4588 self.block_scrollbar_changes = True 

4589 ilo = value 

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

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

4592 self.block_scrollbar_changes = False 

4593 self.update_contents() 

4594 

4595 def controls(self): 

4596 frame = qw.QFrame(self) 

4597 layout = qw.QGridLayout() 

4598 frame.setLayout(layout) 

4599 

4600 minfreq = 0.001 

4601 maxfreq = 1000.0 

4602 self.lowpass_control = ValControl(high_is_none=True) 

4603 self.lowpass_control.setup( 

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

4605 self.highpass_control = ValControl(low_is_none=True) 

4606 self.highpass_control.setup( 

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

4608 self.gain_control = ValControl() 

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

4610 self.rot_control = LinValControl() 

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

4612 self.colorbar_control = ColorbarControl(self) 

4613 

4614 self.lowpass_control.valchange.connect( 

4615 self.viewer.lowpass_change) 

4616 self.highpass_control.valchange.connect( 

4617 self.viewer.highpass_change) 

4618 self.gain_control.valchange.connect( 

4619 self.viewer.gain_change) 

4620 self.rot_control.valchange.connect( 

4621 self.viewer.rot_change) 

4622 self.colorbar_control.cmap_changed.connect( 

4623 self.viewer.waterfall_cmap_change 

4624 ) 

4625 self.colorbar_control.clip_changed.connect( 

4626 self.viewer.waterfall_clip_change 

4627 ) 

4628 self.colorbar_control.show_absolute_toggled.connect( 

4629 self.viewer.waterfall_show_absolute_change 

4630 ) 

4631 self.colorbar_control.show_integrate_toggled.connect( 

4632 self.viewer.waterfall_set_integrate 

4633 ) 

4634 

4635 for icontrol, control in enumerate(( 

4636 self.highpass_control, 

4637 self.lowpass_control, 

4638 self.gain_control, 

4639 self.rot_control, 

4640 self.colorbar_control)): 

4641 

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

4643 layout.addWidget(widget, icontrol, iwidget) 

4644 

4645 spacer = qw.QSpacerItem( 

4646 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4648 

4649 self.adjust_controls() 

4650 self.viewer.viewmode_change(ViewMode.Wiggle) 

4651 return frame 

4652 

4653 def marker_editor(self): 

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

4655 self, sortable=self.marker_editor_sortable) 

4656 

4657 editor.set_viewer(self.get_view()) 

4658 editor.get_marker_model().dataChanged.connect( 

4659 self.update_contents) 

4660 return editor 

4661 

4662 def adjust_controls(self): 

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

4664 maxfreq = 0.5/dtmin 

4665 minfreq = (0.5/dtmax)*0.0001 

4666 self.lowpass_control.set_range(minfreq, maxfreq) 

4667 self.highpass_control.set_range(minfreq, maxfreq) 

4668 

4669 def setup_snufflings(self): 

4670 self.viewer.setup_snufflings() 

4671 

4672 def get_view(self): 

4673 return self.viewer 

4674 

4675 def update_contents(self): 

4676 self.viewer.update() 

4677 

4678 def get_pile(self): 

4679 return self.viewer.get_pile() 

4680 

4681 def show_colorbar_ctrl(self, show): 

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

4683 w.setVisible(show) 

4684 

4685 def show_gain_ctrl(self, show): 

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

4687 w.setVisible(show)