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

2866 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-10-11 11:01 +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.message = None 

840 self.reloaded = False 

841 self.pile_has_changed = False 

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

843 

844 self.tax = TimeAx() 

845 self.setBackgroundRole(qg.QPalette.Base) 

846 self.setAutoFillBackground(True) 

847 poli = qw.QSizePolicy( 

848 qw.QSizePolicy.Expanding, 

849 qw.QSizePolicy.Expanding) 

850 

851 self.setSizePolicy(poli) 

852 self.setMinimumSize(300, 200) 

853 self.setFocusPolicy(qc.Qt.ClickFocus) 

854 

855 self.menu = menu 

856 

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

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

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

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

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

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

863 

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

865 

866 self.snufflings_menu = self.toggle_panel_menu.addMenu( 

867 'Run Snuffling') 

868 self.toggle_panel_menu.addSeparator() 

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

870 help_menu.addSeparator() 

871 

872 file_menu.addAction( 

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

874 'Open waveform files...', 

875 self.open_waveforms, 

876 qg.QKeySequence.Open) 

877 

878 file_menu.addAction( 

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

880 'Open waveform directory...', 

881 self.open_waveform_directory) 

882 

883 file_menu.addAction( 

884 'Open station files...', 

885 self.open_stations) 

886 

887 file_menu.addAction( 

888 'Open StationXML files...', 

889 self.open_stations_xml) 

890 

891 file_menu.addAction( 

892 'Open event file...', 

893 self.read_events) 

894 

895 file_menu.addSeparator() 

896 file_menu.addAction( 

897 'Open marker file...', 

898 self.read_markers) 

899 

900 file_menu.addAction( 

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

902 'Save markers...', 

903 self.write_markers, 

904 qg.QKeySequence.Save) 

905 

906 file_menu.addAction( 

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

908 'Save selected markers...', 

909 self.write_selected_markers, 

910 qg.QKeySequence.SaveAs) 

911 

912 file_menu.addSeparator() 

913 file_menu.addAction( 

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

915 'Print', 

916 self.printit, 

917 qg.QKeySequence.Print) 

918 

919 file_menu.addAction( 

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

921 'Save as SVG or PNG', 

922 self.savesvg, 

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

924 

925 file_menu.addSeparator() 

926 close = file_menu.addAction( 

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

928 'Close', 

929 self.myclose) 

930 close.setShortcuts( 

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

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

933 

934 # Scale Menu 

935 menudef = [ 

936 ('Individual Scale', 

937 lambda tr: tr.nslc_id, 

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

939 ('Common Scale', 

940 lambda tr: None, 

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

942 ('Common Scale per Station', 

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

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

945 ('Common Scale per Station Location', 

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

947 ('Common Scale per Component', 

948 lambda tr: (tr.channel)), 

949 ] 

950 

951 self.menuitems_scaling = add_radiobuttongroup( 

952 scale_menu, menudef, self.scalingmode_change, 

953 default=self.config.trace_scale) 

954 scale_menu.addSeparator() 

955 

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

957 self.scaling_hooks = {} 

958 self.scalingmode_change() 

959 

960 menudef = [ 

961 ('Scaling based on Minimum and Maximum', 

962 ('minmax', 'minmax')), 

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

964 ('minmax', 'robust')), 

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

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

967 ] 

968 

969 self.menuitems_scaling_base = add_radiobuttongroup( 

970 scale_menu, menudef, self.scaling_base_change) 

971 

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

973 scale_menu.addSeparator() 

974 

975 self.menuitem_fixscalerange = scale_menu.addAction( 

976 'Fix Scale Ranges') 

977 self.menuitem_fixscalerange.setCheckable(True) 

978 

979 # Sort Menu 

980 def sector_dist(sta): 

981 if sta.dist_m is None: 

982 return None, None 

983 else: 

984 return ( 

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

986 m_float(sta.dist_m)) 

987 

988 menudef = [ 

989 ('Sort by Names', 

990 lambda tr: (), 

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

992 ('Sort by Distance', 

993 lambda tr: self.station_attrib( 

994 tr, 

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

996 lambda tr: (None,)), 

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

998 ('Sort by Azimuth', 

999 lambda tr: self.station_attrib( 

1000 tr, 

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

1002 lambda tr: (None,))), 

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

1004 lambda tr: self.station_attrib( 

1005 tr, 

1006 sector_dist, 

1007 lambda tr: (None, None))), 

1008 ('Sort by Backazimuth', 

1009 lambda tr: self.station_attrib( 

1010 tr, 

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

1012 lambda tr: (None,))), 

1013 ] 

1014 self.menuitems_ssorting = add_radiobuttongroup( 

1015 sort_menu, menudef, self.s_sortingmode_change) 

1016 sort_menu.addSeparator() 

1017 

1018 self._ssort = lambda tr: () 

1019 

1020 self.menu.addSeparator() 

1021 

1022 menudef = [ 

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

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

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

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

1027 ((0, 1, 3, 2), 

1028 lambda tr: tr.channel)), 

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

1030 ((1, 0, 3, 2), 

1031 lambda tr: tr.channel)), 

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

1033 ((2, 0, 1, 3), 

1034 lambda tr: tr.channel)), 

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

1036 ((3, 0, 1, 2), 

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

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

1039 ((0, 1, 3), 

1040 lambda tr: tr.location)), 

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

1042 ((1, 0, 3), 

1043 lambda tr: tr.location)), 

1044 ] 

1045 

1046 self.menuitems_sorting = add_radiobuttongroup( 

1047 sort_menu, menudef, self.sortingmode_change) 

1048 

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

1050 self.config.visible_length_setting] 

1051 

1052 # View menu 

1053 self.menuitems_visible_length = add_radiobuttongroup( 

1054 view_menu, menudef, 

1055 self.visible_length_change) 

1056 view_menu.addSeparator() 

1057 

1058 view_modes = [ 

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

1060 ('Waterfall', ViewMode.Waterfall) 

1061 ] 

1062 

1063 self.menuitems_viewmode = add_radiobuttongroup( 

1064 view_menu, view_modes, 

1065 self.viewmode_change, default=ViewMode.Wiggle) 

1066 view_menu.addSeparator() 

1067 

1068 self.menuitem_cliptraces = view_menu.addAction( 

1069 'Clip Traces') 

1070 self.menuitem_cliptraces.setCheckable(True) 

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

1072 

1073 self.menuitem_showboxes = view_menu.addAction( 

1074 'Show Boxes') 

1075 self.menuitem_showboxes.setCheckable(True) 

1076 self.menuitem_showboxes.setChecked( 

1077 self.config.show_boxes) 

1078 

1079 self.menuitem_colortraces = view_menu.addAction( 

1080 'Color Traces') 

1081 self.menuitem_colortraces.setCheckable(True) 

1082 self.menuitem_antialias = view_menu.addAction( 

1083 'Antialiasing') 

1084 self.menuitem_antialias.setCheckable(True) 

1085 

1086 view_menu.addSeparator() 

1087 self.menuitem_showscalerange = view_menu.addAction( 

1088 'Show Scale Ranges') 

1089 self.menuitem_showscalerange.setCheckable(True) 

1090 self.menuitem_showscalerange.setChecked( 

1091 self.config.show_scale_ranges) 

1092 

1093 self.menuitem_showscaleaxis = view_menu.addAction( 

1094 'Show Scale Axes') 

1095 self.menuitem_showscaleaxis.setCheckable(True) 

1096 self.menuitem_showscaleaxis.setChecked( 

1097 self.config.show_scale_axes) 

1098 

1099 self.menuitem_showzeroline = view_menu.addAction( 

1100 'Show Zero Lines') 

1101 self.menuitem_showzeroline.setCheckable(True) 

1102 

1103 view_menu.addSeparator() 

1104 view_menu.addAction( 

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

1106 'Fullscreen', 

1107 self.toggle_fullscreen, 

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

1109 

1110 # Options Menu 

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

1112 self.menuitem_demean.setCheckable(True) 

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

1114 self.menuitem_demean.setShortcut( 

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

1116 

1117 self.menuitem_distances_3d = options_menu.addAction( 

1118 '3D distances', 

1119 self.distances_3d_changed) 

1120 self.menuitem_distances_3d.setCheckable(True) 

1121 

1122 self.menuitem_allowdownsampling = options_menu.addAction( 

1123 'Allow Downsampling') 

1124 self.menuitem_allowdownsampling.setCheckable(True) 

1125 self.menuitem_allowdownsampling.setChecked(True) 

1126 

1127 self.menuitem_degap = options_menu.addAction( 

1128 'Allow Degapping') 

1129 self.menuitem_degap.setCheckable(True) 

1130 self.menuitem_degap.setChecked(True) 

1131 

1132 options_menu.addSeparator() 

1133 

1134 self.menuitem_fft_filtering = options_menu.addAction( 

1135 'FFT Filtering') 

1136 self.menuitem_fft_filtering.setCheckable(True) 

1137 

1138 self.menuitem_lphp = options_menu.addAction( 

1139 'Bandpass is Low- + Highpass') 

1140 self.menuitem_lphp.setCheckable(True) 

1141 self.menuitem_lphp.setChecked(True) 

1142 

1143 options_menu.addSeparator() 

1144 self.menuitem_watch = options_menu.addAction( 

1145 'Watch Files') 

1146 self.menuitem_watch.setCheckable(True) 

1147 

1148 self.menuitem_liberal_fetch = options_menu.addAction( 

1149 'Liberal Fetch Optimization') 

1150 self.menuitem_liberal_fetch.setCheckable(True) 

1151 

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

1153 

1154 self.snufflings_menu.addAction( 

1155 'Reload Snufflings', 

1156 self.setup_snufflings) 

1157 

1158 # Disable ShadowPileTest 

1159 if False: 

1160 test_action = self.menu.addAction( 

1161 'Test', 

1162 self.toggletest) 

1163 test_action.setCheckable(True) 

1164 

1165 help_menu.addAction( 

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

1167 'Snuffler Controls', 

1168 self.help, 

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

1170 

1171 help_menu.addAction( 

1172 'About', 

1173 self.about) 

1174 

1175 toolbar = qw.QFrame(self.menu) 

1176 toolbar_layout = qw.QHBoxLayout() 

1177 toolbar_layout.setContentsMargins(1, 1, 1, 1) 

1178 toolbar.setLayout(toolbar_layout) 

1179 

1180 def tracks_plus(*args): 

1181 self.zoom_tracks(0., 1.) 

1182 

1183 button = PileViewerMenuBarButton('+') 

1184 button.clicked.connect(tracks_plus) 

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

1186 toolbar_layout.addWidget(button) 

1187 

1188 def tracks_minus(*args): 

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

1190 

1191 button = PileViewerMenuBarButton('-') 

1192 button.clicked.connect(tracks_minus) 

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

1194 toolbar_layout.addWidget(button) 

1195 

1196 def toggle_input(*args): 

1197 self.toggle_input.emit() 

1198 

1199 button = PileViewerMenuBarButton(':') 

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

1201 button.clicked.connect(toggle_input) 

1202 toolbar_layout.addWidget(button) 

1203 

1204 self.menu.setCornerWidget(toolbar) 

1205 

1206 self.time_projection = Projection() 

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

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

1209 

1210 self.gather = None 

1211 

1212 self.trace_filter = None 

1213 self.quick_filter = None 

1214 self.quick_filter_patterns = None, None 

1215 self.blacklist = [] 

1216 

1217 self.track_to_screen = Projection() 

1218 self.track_to_nslc_ids = {} 

1219 

1220 self.cached_vec = None 

1221 self.cached_processed_traces = None 

1222 

1223 self.timer = qc.QTimer(self) 

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

1225 self.timer.setInterval(1000) 

1226 self.timer.start() 

1227 

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

1229 self.pile.add_listener(self._pile_changed) 

1230 

1231 self.trace_styles = {} 

1232 if self.get_squirrel() is None: 

1233 self.determine_box_styles() 

1234 

1235 self.setMouseTracking(True) 

1236 

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

1238 self.snuffling_modules = {} 

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

1240 self.default_snufflings = None 

1241 self.snufflings = [] 

1242 

1243 self.stations = {} 

1244 

1245 self.timer_draw = Timer() 

1246 self.timer_cutout = Timer() 

1247 self.time_spent_painting = 0.0 

1248 self.time_last_painted = time.time() 

1249 

1250 self.interactive_range_change_time = 0.0 

1251 self.interactive_range_change_delay_time = 10.0 

1252 self.follow_timer = None 

1253 

1254 self.sortingmode_change_time = 0.0 

1255 self.sortingmode_change_delay_time = None 

1256 

1257 self.old_data_ranges = {} 

1258 

1259 self.error_messages = {} 

1260 self.return_tag = None 

1261 self.wheel_pos = 60 

1262 

1263 self.setAcceptDrops(True) 

1264 self._paths_to_load = [] 

1265 

1266 self.tf_cache = {} 

1267 

1268 self.waterfall = TraceWaterfall() 

1269 self.waterfall_cmap = 'viridis' 

1270 self.waterfall_clip_min = 0. 

1271 self.waterfall_clip_max = 1. 

1272 self.waterfall_show_absolute = False 

1273 self.waterfall_integrate = False 

1274 self.view_mode = ViewMode.Wiggle 

1275 

1276 self.automatic_updates = True 

1277 

1278 self.closing = False 

1279 self.in_paint_event = False 

1280 

1281 def fail(self, reason): 

1282 box = qw.QMessageBox(self) 

1283 box.setText(reason) 

1284 box.exec_() 

1285 

1286 def set_trace_filter(self, filter_func): 

1287 self.trace_filter = filter_func 

1288 self.sortingmode_change() 

1289 

1290 def update_trace_filter(self): 

1291 if self.blacklist: 

1292 

1293 def blacklist_func(tr): 

1294 return not pyrocko.util.match_nslc( 

1295 self.blacklist, tr.nslc_id) 

1296 

1297 else: 

1298 blacklist_func = None 

1299 

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

1301 self.set_trace_filter(None) 

1302 elif self.quick_filter is None: 

1303 self.set_trace_filter(blacklist_func) 

1304 elif blacklist_func is None: 

1305 self.set_trace_filter(self.quick_filter) 

1306 else: 

1307 self.set_trace_filter( 

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

1309 

1310 def set_quick_filter(self, filter_func): 

1311 self.quick_filter = filter_func 

1312 self.update_trace_filter() 

1313 

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

1315 if patterns is not None: 

1316 self.set_quick_filter( 

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

1318 else: 

1319 self.set_quick_filter(None) 

1320 

1321 self.quick_filter_patterns = patterns, inputline 

1322 

1323 def get_quick_filter_patterns(self): 

1324 return self.quick_filter_patterns 

1325 

1326 def add_blacklist_pattern(self, pattern): 

1327 if pattern == 'empty': 

1328 keys = set(self.pile.nslc_ids) 

1329 trs = self.pile.all( 

1330 tmin=self.tmin, 

1331 tmax=self.tmax, 

1332 load_data=False, 

1333 degap=False) 

1334 

1335 for tr in trs: 

1336 if tr.nslc_id in keys: 

1337 keys.remove(tr.nslc_id) 

1338 

1339 for key in keys: 

1340 xpattern = '.'.join(key) 

1341 if xpattern not in self.blacklist: 

1342 self.blacklist.append(xpattern) 

1343 

1344 else: 

1345 if pattern in self.blacklist: 

1346 self.blacklist.remove(pattern) 

1347 

1348 self.blacklist.append(pattern) 

1349 

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

1351 self.update_trace_filter() 

1352 

1353 def remove_blacklist_pattern(self, pattern): 

1354 if pattern in self.blacklist: 

1355 self.blacklist.remove(pattern) 

1356 else: 

1357 raise PileViewerMainException( 

1358 'Pattern not found in blacklist.') 

1359 

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

1361 self.update_trace_filter() 

1362 

1363 def clear_blacklist(self): 

1364 self.blacklist = [] 

1365 self.update_trace_filter() 

1366 

1367 def ssort(self, tr): 

1368 return self._ssort(tr) 

1369 

1370 def station_key(self, x): 

1371 return x.network, x.station 

1372 

1373 def station_keys(self, x): 

1374 return [ 

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

1376 (x.network, x.station)] 

1377 

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

1379 for sk in self.station_keys(tr): 

1380 if sk in self.stations: 

1381 station = self.stations[sk] 

1382 return getter(station) 

1383 

1384 return default_getter(tr) 

1385 

1386 def get_station(self, sk): 

1387 return self.stations[sk] 

1388 

1389 def has_station(self, station): 

1390 for sk in self.station_keys(station): 

1391 if sk in self.stations: 

1392 return True 

1393 

1394 return False 

1395 

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

1397 return self.station_attrib( 

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

1399 

1400 def set_stations(self, stations): 

1401 self.stations = {} 

1402 self.add_stations(stations) 

1403 

1404 def add_stations(self, stations): 

1405 for station in stations: 

1406 for sk in self.station_keys(station): 

1407 self.stations[sk] = station 

1408 

1409 ev = self.get_active_event() 

1410 if ev: 

1411 self.set_origin(ev) 

1412 

1413 def add_event(self, event): 

1414 marker = EventMarker(event) 

1415 self.add_marker(marker) 

1416 

1417 def add_events(self, events): 

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

1419 self.add_markers(markers) 

1420 

1421 def set_event_marker_as_origin(self, ignore=None): 

1422 selected = self.selected_markers() 

1423 if not selected: 

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

1425 return 

1426 

1427 m = selected[0] 

1428 if not isinstance(m, EventMarker): 

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

1430 return 

1431 

1432 self.set_active_event_marker(m) 

1433 

1434 def deactivate_event_marker(self): 

1435 if self.active_event_marker: 

1436 self.active_event_marker.active = False 

1437 

1438 self.active_event_marker_changed.emit() 

1439 self.active_event_marker = None 

1440 

1441 def set_active_event_marker(self, event_marker): 

1442 if self.active_event_marker: 

1443 self.active_event_marker.active = False 

1444 

1445 self.active_event_marker = event_marker 

1446 event_marker.active = True 

1447 event = event_marker.get_event() 

1448 self.set_origin(event) 

1449 self.active_event_marker_changed.emit() 

1450 

1451 def set_active_event(self, event): 

1452 for marker in self.markers: 

1453 if isinstance(marker, EventMarker): 

1454 if marker.get_event() is event: 

1455 self.set_active_event_marker(marker) 

1456 

1457 def get_active_event_marker(self): 

1458 return self.active_event_marker 

1459 

1460 def get_active_event(self): 

1461 m = self.get_active_event_marker() 

1462 if m is not None: 

1463 return m.get_event() 

1464 else: 

1465 return None 

1466 

1467 def get_active_markers(self): 

1468 emarker = self.get_active_event_marker() 

1469 if emarker is None: 

1470 return None, [] 

1471 

1472 else: 

1473 ev = emarker.get_event() 

1474 pmarkers = [ 

1475 m for m in self.markers 

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

1477 

1478 return emarker, pmarkers 

1479 

1480 def set_origin(self, location): 

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

1482 station.set_event_relative_data( 

1483 location, 

1484 distance_3d=self.menuitem_distances_3d.isChecked()) 

1485 

1486 self.sortingmode_change() 

1487 

1488 def distances_3d_changed(self): 

1489 ignore = self.menuitem_distances_3d.isChecked() 

1490 self.set_event_marker_as_origin(ignore) 

1491 

1492 def iter_snuffling_modules(self): 

1493 pjoin = os.path.join 

1494 for path in self.snuffling_paths: 

1495 

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

1497 os.mkdir(path) 

1498 

1499 for entry in os.listdir(path): 

1500 directory = path 

1501 fn = entry 

1502 d = pjoin(path, entry) 

1503 if os.path.isdir(d): 

1504 directory = d 

1505 if os.path.isfile( 

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

1507 fn = 'snuffling.py' 

1508 

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

1510 continue 

1511 

1512 name = fn[:-3] 

1513 

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

1515 self.snuffling_modules[directory, name] = \ 

1516 pyrocko.gui.snuffler.snuffling.SnufflingModule( 

1517 directory, name, self) 

1518 

1519 yield self.snuffling_modules[directory, name] 

1520 

1521 def setup_snufflings(self): 

1522 # user snufflings 

1523 from pyrocko.gui.snuffler.snuffling import BrokenSnufflingModule 

1524 for mod in self.iter_snuffling_modules(): 

1525 try: 

1526 mod.load_if_needed() 

1527 except BrokenSnufflingModule as e: 

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

1529 

1530 # load the default snufflings on first run 

1531 if self.default_snufflings is None: 

1532 self.default_snufflings = pyrocko.gui.snuffler\ 

1533 .snufflings.__snufflings__() 

1534 for snuffling in self.default_snufflings: 

1535 self.add_snuffling(snuffling) 

1536 

1537 def set_panel_parent(self, panel_parent): 

1538 self.panel_parent = panel_parent 

1539 

1540 def get_panel_parent(self): 

1541 return self.panel_parent 

1542 

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

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

1545 snuffling.init_gui( 

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

1547 self.snufflings.append(snuffling) 

1548 self.update() 

1549 

1550 def remove_snuffling(self, snuffling): 

1551 snuffling.delete_gui() 

1552 self.update() 

1553 self.snufflings.remove(snuffling) 

1554 snuffling.pre_destroy() 

1555 

1556 def add_snuffling_menuitem(self, item): 

1557 self.snufflings_menu.addAction(item) 

1558 item.setParent(self.snufflings_menu) 

1559 sort_actions(self.snufflings_menu) 

1560 

1561 def remove_snuffling_menuitem(self, item): 

1562 self.snufflings_menu.removeAction(item) 

1563 

1564 def add_snuffling_help_menuitem(self, item): 

1565 self.snuffling_help.addAction(item) 

1566 item.setParent(self.snuffling_help) 

1567 sort_actions(self.snuffling_help) 

1568 

1569 def remove_snuffling_help_menuitem(self, item): 

1570 self.snuffling_help.removeAction(item) 

1571 

1572 def add_panel_toggler(self, item): 

1573 self.toggle_panel_menu.addAction(item) 

1574 item.setParent(self.toggle_panel_menu) 

1575 sort_actions(self.toggle_panel_menu) 

1576 

1577 def remove_panel_toggler(self, item): 

1578 self.toggle_panel_menu.removeAction(item) 

1579 

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

1581 cache_dir=None, force_cache=False): 

1582 

1583 if cache_dir is None: 

1584 cache_dir = pyrocko.config.config().cache_dir 

1585 if isinstance(paths, str): 

1586 paths = [paths] 

1587 

1588 fns = pyrocko.util.select_files( 

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

1590 

1591 if not fns: 

1592 return 

1593 

1594 cache = pyrocko.pile.get_cache(cache_dir) 

1595 

1596 t = [time.time()] 

1597 

1598 def update_bar(label, value): 

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

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

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

1602 else: 

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

1604 

1605 return pbs.set_status(label, value) 

1606 

1607 def update_progress(label, i, n): 

1608 abort = False 

1609 

1610 qw.qApp.processEvents() 

1611 if n != 0: 

1612 perc = i*100/n 

1613 else: 

1614 perc = 100 

1615 abort |= update_bar(label, perc) 

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

1617 

1618 tnow = time.time() 

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

1620 self.update() 

1621 t[0] = tnow 

1622 

1623 return abort 

1624 

1625 self.automatic_updates = False 

1626 

1627 self.pile.load_files( 

1628 sorted(fns), 

1629 filename_attributes=regex, 

1630 cache=cache, 

1631 fileformat=format, 

1632 show_progress=False, 

1633 update_progress=update_progress) 

1634 

1635 self.automatic_updates = True 

1636 self.update() 

1637 

1638 def load_queued(self): 

1639 if not self._paths_to_load: 

1640 return 

1641 paths = self._paths_to_load 

1642 self._paths_to_load = [] 

1643 self.load(paths) 

1644 

1645 def load_soon(self, paths): 

1646 self._paths_to_load.extend(paths) 

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

1648 

1649 def open_waveforms(self): 

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

1651 

1652 fns, _ = qw.QFileDialog.getOpenFileNames( 

1653 self, caption, options=qfiledialog_options) 

1654 

1655 if fns: 

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

1657 

1658 def open_waveform_directory(self): 

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

1660 

1661 dn = qw.QFileDialog.getExistingDirectory( 

1662 self, caption, options=qfiledialog_options) 

1663 

1664 if dn: 

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

1666 

1667 def open_stations(self, fns=None): 

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

1669 

1670 if not fns: 

1671 fns, _ = qw.QFileDialog.getOpenFileNames( 

1672 self, caption, options=qfiledialog_options) 

1673 

1674 try: 

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

1676 for stat in stations: 

1677 self.add_stations(stat) 

1678 

1679 except Exception as e: 

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

1681 

1682 def open_stations_xml(self, fns=None): 

1683 from pyrocko.io import stationxml 

1684 

1685 caption = 'Select one or more StationXML files' 

1686 if not fns: 

1687 fns, _ = qw.QFileDialog.getOpenFileNames( 

1688 self, caption, options=qfiledialog_options, 

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

1690 ';;All files (*)') 

1691 

1692 try: 

1693 stations = [ 

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

1695 for x in fns] 

1696 

1697 for stat in stations: 

1698 self.add_stations(stat) 

1699 

1700 except Exception as e: 

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

1702 

1703 def add_traces(self, traces): 

1704 if traces: 

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

1706 self.pile.add_file(mtf) 

1707 ticket = (self.pile, mtf) 

1708 return ticket 

1709 else: 

1710 return (None, None) 

1711 

1712 def release_data(self, tickets): 

1713 for ticket in tickets: 

1714 pile, mtf = ticket 

1715 if pile is not None: 

1716 pile.remove_file(mtf) 

1717 

1718 def periodical(self): 

1719 if self.menuitem_watch.isChecked(): 

1720 if self.pile.reload_modified(): 

1721 self.update() 

1722 

1723 def get_pile(self): 

1724 return self.pile 

1725 

1726 def pile_changed(self, what, content): 

1727 self.pile_has_changed = True 

1728 self.pile_has_changed_signal.emit() 

1729 if self.automatic_updates: 

1730 self.update() 

1731 

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

1733 

1734 if gather is None: 

1735 def gather_func(tr): 

1736 return tr.nslc_id 

1737 

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

1739 

1740 else: 

1741 def gather_func(tr): 

1742 return ( 

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

1744 

1745 if color is None: 

1746 def color(tr): 

1747 return tr.location 

1748 

1749 self.gather = gather_func 

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

1751 

1752 self.color_gather = color 

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

1754 previous_ntracks = self.ntracks 

1755 self.set_ntracks(len(keys)) 

1756 

1757 if self.shown_tracks_range is None or \ 

1758 previous_ntracks == 0 or \ 

1759 self.show_all: 

1760 

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

1762 key_at_top = None 

1763 n = high-low 

1764 

1765 else: 

1766 low, high = self.shown_tracks_range 

1767 key_at_top = self.track_keys[low] 

1768 n = high-low 

1769 

1770 self.track_keys = sorted(keys) 

1771 

1772 track_patterns = [] 

1773 for k in self.track_keys: 

1774 pat = ['*', '*', '*', '*'] 

1775 for i, j in enumerate(gather): 

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

1777 

1778 track_patterns.append(pat) 

1779 

1780 self.track_patterns = track_patterns 

1781 

1782 if key_at_top is not None: 

1783 try: 

1784 ind = self.track_keys.index(key_at_top) 

1785 low = ind 

1786 high = low+n 

1787 except Exception: 

1788 pass 

1789 

1790 self.set_tracks_range((low, high)) 

1791 

1792 self.key_to_row = dict( 

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

1794 

1795 def inrange(x, r): 

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

1797 

1798 def trace_selector(trace): 

1799 gt = self.gather(trace) 

1800 return ( 

1801 gt in self.key_to_row and 

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

1803 

1804 self.trace_selector = lambda x: \ 

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

1806 and trace_selector(x) 

1807 

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

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

1810 self.show_all: 

1811 

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

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

1814 tlen = (tmax - tmin) 

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

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

1817 

1818 def set_time_range(self, tmin, tmax): 

1819 if tmin is None: 

1820 tmin = initial_time_range[0] 

1821 

1822 if tmax is None: 

1823 tmax = initial_time_range[1] 

1824 

1825 if tmin > tmax: 

1826 tmin, tmax = tmax, tmin 

1827 

1828 if tmin == tmax: 

1829 tmin -= 1. 

1830 tmax += 1. 

1831 

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

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

1834 

1835 min_deltat = self.content_deltat_range()[0] 

1836 if (tmax - tmin < min_deltat): 

1837 m = (tmin + tmax) / 2. 

1838 tmin = m - min_deltat/2. 

1839 tmax = m + min_deltat/2. 

1840 

1841 self.time_projection.set_in_range(tmin, tmax) 

1842 self.tmin, self.tmax = tmin, tmax 

1843 

1844 def get_time_range(self): 

1845 return self.tmin, self.tmax 

1846 

1847 def ypart(self, y): 

1848 if y < self.ax_height: 

1849 return -1 

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

1851 return 1 

1852 else: 

1853 return 0 

1854 

1855 def time_fractional_digits(self): 

1856 min_deltat = self.content_deltat_range()[0] 

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

1858 

1859 def write_markers(self, fn=None): 

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

1861 if not fn: 

1862 fn, _ = qw.QFileDialog.getSaveFileName( 

1863 self, caption, options=qfiledialog_options) 

1864 if fn: 

1865 try: 

1866 Marker.save_markers( 

1867 self.markers, fn, 

1868 fdigits=self.time_fractional_digits()) 

1869 

1870 except Exception as e: 

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

1872 

1873 def write_selected_markers(self, fn=None): 

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

1875 if not fn: 

1876 fn, _ = qw.QFileDialog.getSaveFileName( 

1877 self, caption, options=qfiledialog_options) 

1878 if fn: 

1879 try: 

1880 Marker.save_markers( 

1881 self.iter_selected_markers(), 

1882 fn, 

1883 fdigits=self.time_fractional_digits()) 

1884 

1885 except Exception as e: 

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

1887 

1888 def read_events(self, fn=None): 

1889 ''' 

1890 Open QFileDialog to open, read and add 

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

1892 representation to the pile viewer. 

1893 ''' 

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

1895 if not fn: 

1896 fn, _ = qw.QFileDialog.getOpenFileName( 

1897 self, caption, options=qfiledialog_options) 

1898 if fn: 

1899 try: 

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

1901 self.associate_phases_to_events() 

1902 

1903 except Exception as e: 

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

1905 

1906 def read_markers(self, fn=None): 

1907 ''' 

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

1909 ''' 

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

1911 if not fn: 

1912 fn, _ = qw.QFileDialog.getOpenFileName( 

1913 self, caption, options=qfiledialog_options) 

1914 if fn: 

1915 try: 

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

1917 self.associate_phases_to_events() 

1918 

1919 except Exception as e: 

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

1921 

1922 def associate_phases_to_events(self): 

1923 associate_phases_to_events(self.markers) 

1924 

1925 def add_marker(self, marker): 

1926 # need index to inform QAbstactTableModel about upcoming change, 

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

1928 self.markers.insert(marker) 

1929 i = self.markers.remove(marker) 

1930 

1931 self.begin_markers_add.emit(i, i) 

1932 self.markers.insert(marker) 

1933 self.end_markers_add.emit() 

1934 self.markers_deltat_max = max( 

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

1936 

1937 def add_markers(self, markers): 

1938 if not self.markers: 

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

1940 self.markers.insert_many(markers) 

1941 self.end_markers_add.emit() 

1942 self.update_markers_deltat_max() 

1943 else: 

1944 for marker in markers: 

1945 self.add_marker(marker) 

1946 

1947 def update_markers_deltat_max(self): 

1948 if self.markers: 

1949 self.markers_deltat_max = max( 

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

1951 

1952 def remove_marker(self, marker): 

1953 ''' 

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

1955 

1956 :param marker: 

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

1958 instance 

1959 ''' 

1960 

1961 if marker is self.active_event_marker: 

1962 self.deactivate_event_marker() 

1963 

1964 try: 

1965 i = self.markers.index(marker) 

1966 self.begin_markers_remove.emit(i, i) 

1967 self.markers.remove_at(i) 

1968 self.end_markers_remove.emit() 

1969 except ValueError: 

1970 pass 

1971 

1972 def remove_markers(self, markers): 

1973 ''' 

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

1975 

1976 :param markers: 

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

1978 subclass) instances 

1979 ''' 

1980 

1981 if markers is self.markers: 

1982 markers = list(markers) 

1983 

1984 for marker in markers: 

1985 self.remove_marker(marker) 

1986 

1987 self.update_markers_deltat_max() 

1988 

1989 def remove_selected_markers(self): 

1990 def delete_segment(istart, iend): 

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

1992 for _ in range(iend - istart): 

1993 self.markers.remove_at(istart) 

1994 

1995 self.end_markers_remove.emit() 

1996 

1997 istart = None 

1998 ipos = 0 

1999 markers = self.markers 

2000 nmarkers = len(self.markers) 

2001 while ipos < nmarkers: 

2002 marker = markers[ipos] 

2003 if marker.is_selected(): 

2004 if marker is self.active_event_marker: 

2005 self.deactivate_event_marker() 

2006 

2007 if istart is None: 

2008 istart = ipos 

2009 else: 

2010 if istart is not None: 

2011 delete_segment(istart, ipos) 

2012 nmarkers -= ipos - istart 

2013 ipos = istart - 1 

2014 istart = None 

2015 

2016 ipos += 1 

2017 

2018 if istart is not None: 

2019 delete_segment(istart, ipos) 

2020 

2021 self.update_markers_deltat_max() 

2022 

2023 def selected_markers(self): 

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

2025 

2026 def iter_selected_markers(self): 

2027 for marker in self.markers: 

2028 if marker.is_selected(): 

2029 yield marker 

2030 

2031 def get_markers(self): 

2032 return self.markers 

2033 

2034 def mousePressEvent(self, mouse_ev): 

2035 '' 

2036 self.show_all = False 

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

2038 

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

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

2041 if self.picking: 

2042 if self.picking_down is None: 

2043 self.picking_down = ( 

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

2045 mouse_ev.y()) 

2046 

2047 elif marker is not None: 

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

2049 self.deselect_all() 

2050 marker.selected = True 

2051 self.emit_selected_markers() 

2052 self.update() 

2053 else: 

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

2055 self.track_trange = self.tmin, self.tmax 

2056 

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

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

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

2060 self.update_status() 

2061 

2062 def mouseReleaseEvent(self, mouse_ev): 

2063 '' 

2064 if self.ignore_releases: 

2065 self.ignore_releases -= 1 

2066 return 

2067 

2068 if self.picking: 

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

2070 self.emit_selected_markers() 

2071 

2072 if self.track_start: 

2073 self.update() 

2074 

2075 self.track_start = None 

2076 self.track_trange = None 

2077 self.update_status() 

2078 

2079 def mouseDoubleClickEvent(self, mouse_ev): 

2080 '' 

2081 self.show_all = False 

2082 self.start_picking(None) 

2083 self.ignore_releases = 1 

2084 

2085 def mouseMoveEvent(self, mouse_ev): 

2086 '' 

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

2088 

2089 if self.picking: 

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

2091 

2092 elif self.track_start is not None: 

2093 x0, y0 = self.track_start 

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

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

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

2097 dy = 0 

2098 

2099 tmin0, tmax0 = self.track_trange 

2100 

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

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

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

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

2105 

2106 self.interrupt_following() 

2107 self.set_time_range( 

2108 tmin0 - dt - dtr*frac, 

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

2110 

2111 self.update() 

2112 else: 

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

2114 

2115 self.update_status() 

2116 

2117 def nslc_ids_under_cursor(self, x, y): 

2118 ftrack = self.track_to_screen.rev(y) 

2119 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2120 return nslc_ids 

2121 

2122 def marker_under_cursor(self, x, y): 

2123 mouset = self.time_projection.rev(x) 

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

2125 relevant_nslc_ids = None 

2126 for marker in self.markers: 

2127 if marker.kind not in self.visible_marker_kinds: 

2128 continue 

2129 

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

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

2132 

2133 if relevant_nslc_ids is None: 

2134 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2135 

2136 marker_nslc_ids = marker.get_nslc_ids() 

2137 if not marker_nslc_ids: 

2138 return marker 

2139 

2140 for nslc_id in marker_nslc_ids: 

2141 if nslc_id in relevant_nslc_ids: 

2142 return marker 

2143 

2144 def hoovering(self, x, y): 

2145 mouset = self.time_projection.rev(x) 

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

2147 needupdate = False 

2148 haveone = False 

2149 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2150 for marker in self.markers: 

2151 if marker.kind not in self.visible_marker_kinds: 

2152 continue 

2153 

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

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

2156 

2157 if state: 

2158 xstate = False 

2159 

2160 marker_nslc_ids = marker.get_nslc_ids() 

2161 if not marker_nslc_ids: 

2162 xstate = True 

2163 

2164 for nslc in relevant_nslc_ids: 

2165 if marker.match_nslc(nslc): 

2166 xstate = True 

2167 

2168 state = xstate 

2169 

2170 if state: 

2171 haveone = True 

2172 oldstate = marker.is_alerted() 

2173 if oldstate != state: 

2174 needupdate = True 

2175 marker.set_alerted(state) 

2176 if state: 

2177 self.message = marker.hoover_message() 

2178 

2179 if not haveone: 

2180 self.message = None 

2181 

2182 if needupdate: 

2183 self.update() 

2184 

2185 def event(self, event): 

2186 '' 

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

2188 self.keyPressEvent(event) 

2189 return True 

2190 else: 

2191 return base.event(self, event) 

2192 

2193 def keyPressEvent(self, key_event): 

2194 '' 

2195 self.show_all = False 

2196 dt = self.tmax - self.tmin 

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

2198 

2199 key = key_event.key() 

2200 try: 

2201 keytext = str(key_event.text()) 

2202 except UnicodeEncodeError: 

2203 return 

2204 

2205 if key == qc.Qt.Key_Space: 

2206 self.interrupt_following() 

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

2208 

2209 elif key == qc.Qt.Key_Up: 

2210 for m in self.selected_markers(): 

2211 if isinstance(m, PhaseMarker): 

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

2213 p = 0 

2214 else: 

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

2216 m.set_polarity(p) 

2217 

2218 elif key == qc.Qt.Key_Down: 

2219 for m in self.selected_markers(): 

2220 if isinstance(m, PhaseMarker): 

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

2222 p = 0 

2223 else: 

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

2225 m.set_polarity(p) 

2226 

2227 elif key == qc.Qt.Key_B: 

2228 dt = self.tmax - self.tmin 

2229 self.interrupt_following() 

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

2231 

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

2233 self.interrupt_following() 

2234 

2235 tgo = None 

2236 

2237 class TraceDummy(object): 

2238 def __init__(self, marker): 

2239 self._marker = marker 

2240 

2241 @property 

2242 def nslc_id(self): 

2243 return self._marker.one_nslc() 

2244 

2245 def marker_to_itrack(marker): 

2246 try: 

2247 return self.key_to_row.get( 

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

2249 

2250 except MarkerOneNSLCRequired: 

2251 return -1 

2252 

2253 emarker, pmarkers = self.get_active_markers() 

2254 pmarkers = [ 

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

2256 pmarkers.sort(key=lambda m: ( 

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

2258 

2259 if key == qc.Qt.Key_Backtab: 

2260 pmarkers.reverse() 

2261 

2262 smarkers = self.selected_markers() 

2263 iselected = [] 

2264 for sm in smarkers: 

2265 try: 

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

2267 except ValueError: 

2268 pass 

2269 

2270 if iselected: 

2271 icurrent = max(iselected) + 1 

2272 else: 

2273 icurrent = 0 

2274 

2275 if icurrent < len(pmarkers): 

2276 self.deselect_all() 

2277 cmarker = pmarkers[icurrent] 

2278 cmarker.selected = True 

2279 tgo = cmarker.tmin 

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

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

2282 

2283 itrack = marker_to_itrack(cmarker) 

2284 if itrack != -1: 

2285 if itrack < self.shown_tracks_range[0]: 

2286 self.scroll_tracks( 

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

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

2289 self.scroll_tracks( 

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

2291 

2292 if itrack not in self.track_to_nslc_ids: 

2293 self.go_to_selection() 

2294 

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

2296 smarkers = self.selected_markers() 

2297 tgo = None 

2298 dir = str(keytext) 

2299 if smarkers: 

2300 tmid = smarkers[0].tmin 

2301 for smarker in smarkers: 

2302 if dir == 'n': 

2303 tmid = max(smarker.tmin, tmid) 

2304 else: 

2305 tmid = min(smarker.tmin, tmid) 

2306 

2307 tgo = tmid 

2308 

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

2310 for marker in sorted( 

2311 self.markers, 

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

2313 

2314 t = marker.tmin 

2315 if t > tmid and \ 

2316 marker.kind in self.visible_marker_kinds and \ 

2317 (dir == 'n' or 

2318 isinstance(marker, EventMarker)): 

2319 

2320 self.deselect_all() 

2321 marker.selected = True 

2322 tgo = t 

2323 break 

2324 else: 

2325 for marker in sorted( 

2326 self.markers, 

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

2328 reverse=True): 

2329 

2330 t = marker.tmin 

2331 if t < tmid and \ 

2332 marker.kind in self.visible_marker_kinds and \ 

2333 (dir == 'p' or 

2334 isinstance(marker, EventMarker)): 

2335 self.deselect_all() 

2336 marker.selected = True 

2337 tgo = t 

2338 break 

2339 

2340 if tgo is not None: 

2341 self.interrupt_following() 

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

2343 

2344 elif keytext == 'r': 

2345 if self.pile.reload_modified(): 

2346 self.reloaded = True 

2347 

2348 elif keytext == 'R': 

2349 self.setup_snufflings() 

2350 

2351 elif key == qc.Qt.Key_Backspace: 

2352 self.remove_selected_markers() 

2353 

2354 elif keytext == 'a': 

2355 for marker in self.markers: 

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

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

2358 marker.kind in self.visible_marker_kinds): 

2359 marker.selected = True 

2360 else: 

2361 marker.selected = False 

2362 

2363 elif keytext == 'A': 

2364 for marker in self.markers: 

2365 if marker.kind in self.visible_marker_kinds: 

2366 marker.selected = True 

2367 

2368 elif keytext == 'd': 

2369 self.deselect_all() 

2370 

2371 elif keytext == 'E': 

2372 self.deactivate_event_marker() 

2373 

2374 elif keytext == 'e': 

2375 markers = self.selected_markers() 

2376 event_markers_in_spe = [ 

2377 marker for marker in markers 

2378 if not isinstance(marker, PhaseMarker)] 

2379 

2380 phase_markers = [ 

2381 marker for marker in markers 

2382 if isinstance(marker, PhaseMarker)] 

2383 

2384 if len(event_markers_in_spe) == 1: 

2385 event_marker = event_markers_in_spe[0] 

2386 if not isinstance(event_marker, EventMarker): 

2387 nslcs = list(event_marker.nslc_ids) 

2388 lat, lon = 0.0, 0.0 

2389 old = self.get_active_event() 

2390 if len(nslcs) == 1: 

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

2392 elif old is not None: 

2393 lat, lon = old.lat, old.lon 

2394 

2395 event_marker.convert_to_event_marker(lat, lon) 

2396 

2397 self.set_active_event_marker(event_marker) 

2398 event = event_marker.get_event() 

2399 for marker in phase_markers: 

2400 marker.set_event(event) 

2401 

2402 else: 

2403 for marker in event_markers_in_spe: 

2404 marker.convert_to_event_marker() 

2405 

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

2407 for marker in self.selected_markers(): 

2408 marker.set_kind(int(keytext)) 

2409 self.emit_selected_markers() 

2410 

2411 elif key in fkey_map: 

2412 self.handle_fkeys(key) 

2413 

2414 elif key == qc.Qt.Key_Escape: 

2415 if self.picking: 

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

2417 

2418 elif key == qc.Qt.Key_PageDown: 

2419 self.scroll_tracks( 

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

2421 

2422 elif key == qc.Qt.Key_PageUp: 

2423 self.scroll_tracks( 

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

2425 

2426 elif key == qc.Qt.Key_Plus: 

2427 self.zoom_tracks(0., 1.) 

2428 

2429 elif key == qc.Qt.Key_Minus: 

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

2431 

2432 elif key == qc.Qt.Key_Equal: 

2433 ntracks_shown = self.shown_tracks_range[1] - \ 

2434 self.shown_tracks_range[0] 

2435 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2436 self.zoom_tracks(0., dtracks) 

2437 

2438 elif key == qc.Qt.Key_Colon: 

2439 self.want_input.emit() 

2440 

2441 elif keytext == 'f': 

2442 self.toggle_fullscreen() 

2443 

2444 elif keytext == 'g': 

2445 self.go_to_selection() 

2446 

2447 elif keytext == 'G': 

2448 self.go_to_selection(tight=True) 

2449 

2450 elif keytext == 'm': 

2451 self.toggle_marker_editor() 

2452 

2453 elif keytext == 'c': 

2454 self.toggle_main_controls() 

2455 

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

2457 dir = 1 

2458 amount = 1 

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

2460 dir = -1 

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

2462 amount = 10 

2463 self.nudge_selected_markers(dir*amount) 

2464 else: 

2465 super().keyPressEvent(key_event) 

2466 

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

2468 self.emit_selected_markers() 

2469 

2470 self.update() 

2471 self.update_status() 

2472 

2473 def handle_fkeys(self, key): 

2474 self.set_phase_kind( 

2475 self.selected_markers(), 

2476 fkey_map[key] + 1) 

2477 self.emit_selected_markers() 

2478 

2479 def emit_selected_markers(self): 

2480 ibounds = [] 

2481 last_selected = False 

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

2483 this_selected = marker.is_selected() 

2484 if this_selected != last_selected: 

2485 ibounds.append(imarker) 

2486 

2487 last_selected = this_selected 

2488 

2489 if last_selected: 

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

2491 

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

2493 self.n_selected_markers = sum( 

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

2495 self.marker_selection_changed.emit(chunks) 

2496 

2497 def toggle_marker_editor(self): 

2498 self.panel_parent.toggle_marker_editor() 

2499 

2500 def toggle_main_controls(self): 

2501 self.panel_parent.toggle_main_controls() 

2502 

2503 def nudge_selected_markers(self, npixels): 

2504 a, b = self.time_projection.ur 

2505 c, d = self.time_projection.xr 

2506 for marker in self.selected_markers(): 

2507 if not isinstance(marker, EventMarker): 

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

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

2510 

2511 def toggle_fullscreen(self): 

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

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

2514 self.window().showNormal() 

2515 else: 

2516 if is_macos: 

2517 self.window().showMaximized() 

2518 else: 

2519 self.window().showFullScreen() 

2520 

2521 def about(self): 

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

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

2524 txt = f.read() 

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

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

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

2528 

2529 def help(self): 

2530 class MyScrollArea(qw.QScrollArea): 

2531 

2532 def sizeHint(self): 

2533 s = qc.QSize() 

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

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

2536 return s 

2537 

2538 with open(pyrocko.util.data_file( 

2539 'snuffler_help.html')) as f: 

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

2541 

2542 with open(pyrocko.util.data_file( 

2543 'snuffler_help_epilog.html')) as f: 

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

2545 

2546 for h in [hcheat, hepilog]: 

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

2548 h.setWordWrap(True) 

2549 

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

2551 

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

2553 scroller = qw.QScrollArea() 

2554 frame = qw.QFrame(scroller) 

2555 frame.setLineWidth(0) 

2556 layout = qw.QVBoxLayout() 

2557 layout.setContentsMargins(0, 0, 0, 0) 

2558 layout.setSpacing(0) 

2559 frame.setLayout(layout) 

2560 scroller.setWidget(frame) 

2561 scroller.setWidgetResizable(True) 

2562 frame.setBackgroundRole(qg.QPalette.Base) 

2563 for h in labels: 

2564 h.setParent(frame) 

2565 h.setMargin(3) 

2566 h.setTextInteractionFlags( 

2567 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2568 h.setBackgroundRole(qg.QPalette.Base) 

2569 layout.addWidget(h) 

2570 h.linkActivated.connect( 

2571 self.open_link) 

2572 

2573 if self.panel_parent is not None: 

2574 if target == 'panel': 

2575 self.panel_parent.add_panel( 

2576 name, scroller, True, volatile=False) 

2577 else: 

2578 self.panel_parent.add_tab(name, scroller) 

2579 

2580 def open_link(self, link): 

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

2582 

2583 def wheelEvent(self, wheel_event): 

2584 '' 

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

2586 

2587 n = self.wheel_pos // 120 

2588 self.wheel_pos = self.wheel_pos % 120 

2589 if n == 0: 

2590 return 

2591 

2592 amount = max( 

2593 1., 

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

2595 wdelta = amount * n 

2596 

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

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

2599 / (trmax-trmin) 

2600 

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

2602 self.zoom_tracks(anchor, wdelta) 

2603 else: 

2604 self.scroll_tracks(-wdelta) 

2605 

2606 def dragEnterEvent(self, event): 

2607 '' 

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

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

2610 event.setDropAction(qc.Qt.LinkAction) 

2611 event.accept() 

2612 

2613 def dropEvent(self, event): 

2614 '' 

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

2616 paths = list( 

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

2618 event.acceptProposedAction() 

2619 self.load(paths) 

2620 

2621 def get_phase_name(self, kind): 

2622 return self.config.get_phase_name(kind) 

2623 

2624 def set_phase_kind(self, markers, kind): 

2625 phasename = self.get_phase_name(kind) 

2626 

2627 for marker in markers: 

2628 if isinstance(marker, PhaseMarker): 

2629 if kind == 10: 

2630 marker.convert_to_marker() 

2631 else: 

2632 marker.set_phasename(phasename) 

2633 marker.set_event(self.get_active_event()) 

2634 

2635 elif isinstance(marker, EventMarker): 

2636 pass 

2637 

2638 else: 

2639 if kind != 10: 

2640 event = self.get_active_event() 

2641 marker.convert_to_phase_marker( 

2642 event, phasename, None, False) 

2643 

2644 def set_ntracks(self, ntracks): 

2645 if self.ntracks != ntracks: 

2646 self.ntracks = ntracks 

2647 if self.shown_tracks_range is not None: 

2648 low, high = self.shown_tracks_range 

2649 else: 

2650 low, high = 0, self.ntracks 

2651 

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

2653 

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

2655 

2656 low, high = range 

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

2658 high = min(self.ntracks, high) 

2659 low = max(0, low) 

2660 high = max(1, high) 

2661 

2662 if start is None: 

2663 start = float(low) 

2664 

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

2666 self.shown_tracks_range = low, high 

2667 self.shown_tracks_start = start 

2668 

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

2670 

2671 def scroll_tracks(self, shift): 

2672 shown = self.shown_tracks_range 

2673 shiftmin = -shown[0] 

2674 shiftmax = self.ntracks-shown[1] 

2675 shift = max(shiftmin, shift) 

2676 shift = min(shiftmax, shift) 

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

2678 

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

2680 

2681 self.update() 

2682 

2683 def zoom_tracks(self, anchor, delta): 

2684 ntracks_shown = self.shown_tracks_range[1] \ 

2685 - self.shown_tracks_range[0] 

2686 

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

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

2689 return 

2690 

2691 ntracks_shown += int(round(delta)) 

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

2693 

2694 u = self.shown_tracks_start 

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

2696 nv = nu + ntracks_shown 

2697 if nv > self.ntracks: 

2698 nu -= nv - self.ntracks 

2699 nv -= nv - self.ntracks 

2700 

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

2702 

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

2704 - self.shown_tracks_range[0] 

2705 

2706 self.update() 

2707 

2708 def content_time_range(self): 

2709 pile = self.get_pile() 

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

2711 if tmin is None: 

2712 tmin = initial_time_range[0] 

2713 if tmax is None: 

2714 tmax = initial_time_range[1] 

2715 

2716 return tmin, tmax 

2717 

2718 def content_deltat_range(self): 

2719 pile = self.get_pile() 

2720 

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

2722 

2723 if deltatmin is None: 

2724 deltatmin = 0.001 

2725 

2726 if deltatmax is None: 

2727 deltatmax = 1000.0 

2728 

2729 return deltatmin, deltatmax 

2730 

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

2732 if tmax < tmin: 

2733 tmin, tmax = tmax, tmin 

2734 

2735 deltatmin = self.content_deltat_range()[0] 

2736 dt = deltatmin * self.visible_length * 0.95 

2737 

2738 if dt == 0.0: 

2739 dt = 1.0 

2740 

2741 if tight: 

2742 if tmax != tmin: 

2743 dtm = tmax - tmin 

2744 tmin -= dtm*0.1 

2745 tmax += dtm*0.1 

2746 return tmin, tmax 

2747 else: 

2748 tcenter = (tmin + tmax) / 2. 

2749 tmin = tcenter - 0.5*dt 

2750 tmax = tcenter + 0.5*dt 

2751 return tmin, tmax 

2752 

2753 if tmax-tmin < dt: 

2754 vmin, vmax = self.get_time_range() 

2755 dt = min(vmax - vmin, dt) 

2756 

2757 tcenter = (tmin+tmax)/2. 

2758 etmin, etmax = tmin, tmax 

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

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

2761 dtm = tmax-tmin 

2762 if etmin == tmin: 

2763 tmin -= dtm*0.1 

2764 if etmax == tmax: 

2765 tmax += dtm*0.1 

2766 

2767 else: 

2768 dtm = tmax-tmin 

2769 tmin -= dtm*0.1 

2770 tmax += dtm*0.1 

2771 

2772 return tmin, tmax 

2773 

2774 def go_to_selection(self, tight=False): 

2775 markers = self.selected_markers() 

2776 if markers: 

2777 tmax, tmin = self.content_time_range() 

2778 for marker in markers: 

2779 tmin = min(tmin, marker.tmin) 

2780 tmax = max(tmax, marker.tmax) 

2781 

2782 else: 

2783 if tight: 

2784 vmin, vmax = self.get_time_range() 

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

2786 else: 

2787 tmin, tmax = self.content_time_range() 

2788 

2789 tmin, tmax = self.make_good_looking_time_range( 

2790 tmin, tmax, tight=tight) 

2791 

2792 self.interrupt_following() 

2793 self.set_time_range(tmin, tmax) 

2794 self.update() 

2795 

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

2797 tmax = t 

2798 if tlen is not None: 

2799 tmax = t+tlen 

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

2801 self.interrupt_following() 

2802 self.set_time_range(tmin, tmax) 

2803 self.update() 

2804 

2805 def go_to_event_by_name(self, name): 

2806 for marker in self.markers: 

2807 if isinstance(marker, EventMarker): 

2808 event = marker.get_event() 

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

2810 tmin, tmax = self.make_good_looking_time_range( 

2811 event.time, event.time) 

2812 

2813 self.interrupt_following() 

2814 self.set_time_range(tmin, tmax) 

2815 

2816 def printit(self): 

2817 from ..qt_compat import qprint 

2818 printer = qprint.QPrinter() 

2819 printer.setOrientation(qprint.QPrinter.Landscape) 

2820 

2821 dialog = qprint.QPrintDialog(printer, self) 

2822 dialog.setWindowTitle('Print') 

2823 

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

2825 return 

2826 

2827 painter = qg.QPainter() 

2828 painter.begin(printer) 

2829 page = printer.pageRect() 

2830 self.drawit( 

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

2832 

2833 painter.end() 

2834 

2835 def savesvg(self, fn=None): 

2836 

2837 if not fn: 

2838 fn, _ = qw.QFileDialog.getSaveFileName( 

2839 self, 

2840 'Save as SVG|PNG', 

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

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

2843 options=qfiledialog_options) 

2844 

2845 if fn == '': 

2846 return 

2847 

2848 fn = str(fn) 

2849 

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

2851 try: 

2852 w, h = 842, 595 

2853 margin = 0.025 

2854 m = max(w, h)*margin 

2855 

2856 generator = qsvg.QSvgGenerator() 

2857 generator.setFileName(fn) 

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

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

2860 

2861 painter = qg.QPainter() 

2862 painter.begin(generator) 

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

2864 painter.end() 

2865 

2866 except Exception as e: 

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

2868 

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

2870 pixmap = self.grab() 

2871 

2872 try: 

2873 pixmap.save(fn) 

2874 

2875 except Exception as e: 

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

2877 

2878 else: 

2879 self.fail( 

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

2881 '".png".') 

2882 

2883 def paintEvent(self, paint_ev): 

2884 ''' 

2885 Called by QT whenever widget needs to be painted. 

2886 ''' 

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

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

2889 if self.in_paint_event: 

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

2891 return 

2892 

2893 self.in_paint_event = True 

2894 

2895 painter = qg.QPainter(self) 

2896 

2897 if self.menuitem_antialias.isChecked(): 

2898 painter.setRenderHint(qg.QPainter.Antialiasing) 

2899 

2900 self.drawit(painter) 

2901 

2902 logger.debug( 

2903 'Time spent drawing: ' 

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

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

2906 (self.timer_draw - self.timer_cutout)) 

2907 

2908 logger.debug( 

2909 'Time spent processing:' 

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

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

2912 self.timer_cutout.get()) 

2913 

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

2915 self.time_last_painted = time.time() 

2916 self.in_paint_event = False 

2917 

2918 def determine_box_styles(self): 

2919 

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

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

2922 istyle = 0 

2923 trace_styles = {} 

2924 for itr, tr in enumerate(traces): 

2925 if itr > 0: 

2926 other = traces[itr-1] 

2927 if not ( 

2928 other.nslc_id == tr.nslc_id 

2929 and other.deltat == tr.deltat 

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

2931 < gap_lap_tolerance*tr.deltat): 

2932 

2933 istyle += 1 

2934 

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

2936 

2937 self.trace_styles = trace_styles 

2938 

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

2940 

2941 for v_projection in track_projections.values(): 

2942 v_projection.set_in_range(0., 1.) 

2943 

2944 def selector(x): 

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

2946 

2947 if self.trace_filter is not None: 

2948 def tselector(x): 

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

2950 

2951 else: 

2952 tselector = selector 

2953 

2954 traces = list(self.pile.iter_traces( 

2955 group_selector=selector, trace_selector=tselector)) 

2956 

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

2958 

2959 def drawbox(itrack, istyle, traces): 

2960 v_projection = track_projections[itrack] 

2961 dvmin = v_projection(0.) 

2962 dvmax = v_projection(1.) 

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

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

2965 

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

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

2968 p.fillRect(rect, style.fill_brush) 

2969 p.setPen(style.frame_pen) 

2970 p.drawRect(rect) 

2971 

2972 traces_by_style = {} 

2973 for itr, tr in enumerate(traces): 

2974 gt = self.gather(tr) 

2975 if gt not in self.key_to_row: 

2976 continue 

2977 

2978 itrack = self.key_to_row[gt] 

2979 if itrack not in track_projections: 

2980 continue 

2981 

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

2983 

2984 if len(traces) < 500: 

2985 drawbox(itrack, istyle, [tr]) 

2986 else: 

2987 if (itrack, istyle) not in traces_by_style: 

2988 traces_by_style[itrack, istyle] = [] 

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

2990 

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

2992 drawbox(itrack, istyle, traces) 

2993 

2994 def draw_visible_markers( 

2995 self, p, vcenter_projection, primary_pen): 

2996 

2997 try: 

2998 markers = self.markers.with_key_in_limited( 

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

3000 

3001 except pyrocko.pile.TooMany: 

3002 tmin = self.markers[0].tmin 

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

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

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

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

3007 v0, _ = vcenter_projection.get_out_range() 

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

3009 

3010 p.save() 

3011 

3012 pen = qg.QPen(primary_pen) 

3013 pen.setWidth(2) 

3014 pen.setStyle(qc.Qt.DotLine) 

3015 # pat = [5., 3.] 

3016 # pen.setDashPattern(pat) 

3017 p.setPen(pen) 

3018 

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

3020 s_selected = ' (all selected)' 

3021 elif self.n_selected_markers > 0: 

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

3023 else: 

3024 s_selected = '' 

3025 

3026 draw_label( 

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

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

3029 label_bg, 'LB') 

3030 

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

3032 p.drawLine(line) 

3033 p.restore() 

3034 

3035 return 

3036 

3037 for marker in markers: 

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

3039 and marker.kind in self.visible_marker_kinds: 

3040 

3041 marker.draw( 

3042 p, self.time_projection, vcenter_projection, 

3043 with_label=True) 

3044 

3045 def get_squirrel(self): 

3046 try: 

3047 return self.pile._squirrel 

3048 except AttributeError: 

3049 return None 

3050 

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

3052 sq = self.get_squirrel() 

3053 if sq is None: 

3054 return 

3055 

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

3057 v_projection = track_projections[itrack] 

3058 dvmin = v_projection(0.) 

3059 dvmax = v_projection(1.) 

3060 dtmin = time_projection.clipped(tmin, 0) 

3061 dtmax = time_projection.clipped(tmax, 1) 

3062 

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

3064 p.fillRect(rect, style.fill_brush) 

3065 p.setPen(style.frame_pen) 

3066 p.drawRect(rect) 

3067 

3068 pattern_list = [] 

3069 pattern_to_itrack = {} 

3070 for key in self.track_keys: 

3071 itrack = self.key_to_row[key] 

3072 if itrack not in track_projections: 

3073 continue 

3074 

3075 pattern = self.track_patterns[itrack] 

3076 pattern_to_itrack[tuple(pattern)] = itrack 

3077 pattern_list.append(tuple(pattern)) 

3078 

3079 vmin, vmax = self.get_time_range() 

3080 

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

3082 for coverage in sq.get_coverage( 

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

3084 itrack = pattern_to_itrack[coverage.pattern.nslc] 

3085 

3086 if coverage.changes is None: 

3087 drawbox( 

3088 itrack, coverage.tmin, coverage.tmax, 

3089 box_styles_coverage[kind][0]) 

3090 else: 

3091 t = None 

3092 pcount = 0 

3093 for tb, count in coverage.changes: 

3094 if t is not None and tb > t: 

3095 if pcount > 0: 

3096 drawbox( 

3097 itrack, t, tb, 

3098 box_styles_coverage[kind][ 

3099 min(len(box_styles_coverage)-1, 

3100 pcount)]) 

3101 

3102 t = tb 

3103 pcount = count 

3104 

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

3106 ''' 

3107 This performs the actual drawing. 

3108 ''' 

3109 

3110 self.timer_draw.start() 

3111 show_boxes = self.menuitem_showboxes.isChecked() 

3112 sq = self.get_squirrel() 

3113 

3114 if self.gather is None: 

3115 self.set_gathering() 

3116 

3117 if self.pile_has_changed: 

3118 

3119 if not self.sortingmode_change_delayed(): 

3120 self.sortingmode_change() 

3121 

3122 if show_boxes and sq is None: 

3123 self.determine_box_styles() 

3124 

3125 self.pile_has_changed = False 

3126 

3127 if h is None: 

3128 h = float(self.height()) 

3129 if w is None: 

3130 w = float(self.width()) 

3131 

3132 if printmode: 

3133 primary_color = (0, 0, 0) 

3134 else: 

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

3136 

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

3138 

3139 ax_h = float(self.ax_height) 

3140 

3141 vbottom_ax_projection = Projection() 

3142 vtop_ax_projection = Projection() 

3143 vcenter_projection = Projection() 

3144 

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

3146 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3147 vtop_ax_projection.set_out_range(0., ax_h) 

3148 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3149 vcenter_projection.set_in_range(0., 1.) 

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

3151 

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

3153 track_projections = {} 

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

3155 proj = Projection() 

3156 proj.set_out_range( 

3157 self.track_to_screen(i+0.05), 

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

3159 

3160 track_projections[i] = proj 

3161 

3162 if self.tmin > self.tmax: 

3163 return 

3164 

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

3166 vbottom_ax_projection.set_in_range(0, ax_h) 

3167 

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

3169 

3170 yscaler = pyrocko.plot.AutoScaler() 

3171 

3172 p.setPen(primary_pen) 

3173 

3174 font = qg.QFont() 

3175 font.setBold(True) 

3176 

3177 axannotfont = qg.QFont() 

3178 axannotfont.setBold(True) 

3179 axannotfont.setPointSize(8) 

3180 

3181 processed_traces = self.prepare_cutout2( 

3182 self.tmin, self.tmax, 

3183 trace_selector=self.trace_selector, 

3184 degap=self.menuitem_degap.isChecked(), 

3185 demean=self.menuitem_demean.isChecked()) 

3186 

3187 if not printmode and show_boxes: 

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

3189 or (self.view_mode is ViewMode.Waterfall 

3190 and not processed_traces): 

3191 

3192 if sq is None: 

3193 self.draw_trace_boxes( 

3194 p, self.time_projection, track_projections) 

3195 

3196 else: 

3197 self.draw_coverage( 

3198 p, self.time_projection, track_projections) 

3199 

3200 p.setFont(font) 

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

3202 

3203 color_lookup = dict( 

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

3205 

3206 self.track_to_nslc_ids = {} 

3207 nticks = 0 

3208 annot_labels = [] 

3209 

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

3211 waterfall = self.waterfall 

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

3213 waterfall.set_traces(processed_traces) 

3214 waterfall.set_cmap(self.waterfall_cmap) 

3215 waterfall.set_integrate(self.waterfall_integrate) 

3216 waterfall.set_clip( 

3217 self.waterfall_clip_min, self.waterfall_clip_max) 

3218 waterfall.show_absolute_values( 

3219 self.waterfall_show_absolute) 

3220 

3221 rect = qc.QRectF( 

3222 0, self.ax_height, 

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

3224 ) 

3225 waterfall.draw_waterfall(p, rect=rect) 

3226 

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

3228 show_scales = self.menuitem_showscalerange.isChecked() \ 

3229 or self.menuitem_showscaleaxis.isChecked() 

3230 

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

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

3233 - self.track_to_screen(0.05) 

3234 

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

3236 

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

3238 if self.menuitem_showscaleaxis.isChecked() \ 

3239 else 15 

3240 

3241 yscaler = pyrocko.plot.AutoScaler( 

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

3243 snap=show_scales 

3244 and not self.menuitem_showscaleaxis.isChecked()) 

3245 

3246 data_ranges = pyrocko.trace.minmax( 

3247 processed_traces, 

3248 key=self.scaling_key, 

3249 mode=self.scaling_base[0], 

3250 outer_mode=self.scaling_base[1]) 

3251 

3252 if not self.menuitem_fixscalerange.isChecked(): 

3253 self.old_data_ranges = data_ranges 

3254 else: 

3255 data_ranges.update(self.old_data_ranges) 

3256 

3257 self.apply_scaling_hooks(data_ranges) 

3258 

3259 trace_to_itrack = {} 

3260 track_scaling_keys = {} 

3261 track_scaling_colors = {} 

3262 for trace in processed_traces: 

3263 gt = self.gather(trace) 

3264 if gt not in self.key_to_row: 

3265 continue 

3266 

3267 itrack = self.key_to_row[gt] 

3268 if itrack not in track_projections: 

3269 continue 

3270 

3271 trace_to_itrack[trace] = itrack 

3272 

3273 if itrack not in self.track_to_nslc_ids: 

3274 self.track_to_nslc_ids[itrack] = set() 

3275 

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

3277 

3278 if itrack not in track_scaling_keys: 

3279 track_scaling_keys[itrack] = set() 

3280 

3281 scaling_key = self.scaling_key(trace) 

3282 track_scaling_keys[itrack].add(scaling_key) 

3283 

3284 color = pyrocko.plot.color( 

3285 color_lookup[self.color_gather(trace)]) 

3286 

3287 k = itrack, scaling_key 

3288 if k not in track_scaling_colors \ 

3289 and self.menuitem_colortraces.isChecked(): 

3290 track_scaling_colors[k] = color 

3291 else: 

3292 track_scaling_colors[k] = primary_color 

3293 

3294 # y axes, zero lines 

3295 trace_projections = {} 

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

3297 if itrack not in track_scaling_keys: 

3298 continue 

3299 uoff = 0 

3300 for scaling_key in track_scaling_keys[itrack]: 

3301 data_range = data_ranges[scaling_key] 

3302 dymin, dymax = data_range 

3303 ymin, ymax, yinc = yscaler.make_scale( 

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

3305 iexp = yscaler.make_exp(yinc) 

3306 factor = 10**iexp 

3307 trace_projection = track_projections[itrack].copy() 

3308 trace_projection.set_in_range(ymax, ymin) 

3309 trace_projections[itrack, scaling_key] = \ 

3310 trace_projection 

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

3312 vmin, vmax = trace_projection.get_out_range() 

3313 umax_zeroline = umax 

3314 uoffnext = uoff 

3315 

3316 if show_scales: 

3317 pen = qg.QPen(primary_pen) 

3318 k = itrack, scaling_key 

3319 if k in track_scaling_colors: 

3320 c = qg.QColor(*track_scaling_colors[ 

3321 itrack, scaling_key]) 

3322 

3323 pen.setColor(c) 

3324 

3325 p.setPen(pen) 

3326 if nlinesavail > 3: 

3327 if self.menuitem_showscaleaxis.isChecked(): 

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

3329 ny_annot = int( 

3330 math.floor(ymax/yinc) 

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

3332 

3333 for iy_annot in range(ny_annot): 

3334 y = ymin_annot + iy_annot*yinc 

3335 v = trace_projection(y) 

3336 line = qc.QLineF( 

3337 umax-10-uoff, v, umax-uoff, v) 

3338 

3339 p.drawLine(line) 

3340 if iy_annot == ny_annot - 1 \ 

3341 and iexp != 0: 

3342 sexp = ' &times; ' \ 

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

3344 else: 

3345 sexp = '' 

3346 

3347 snum = num_to_html(y/factor) 

3348 lab = Label( 

3349 p, 

3350 umax-20-uoff, 

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

3352 label_bg=None, 

3353 anchor='MR', 

3354 font=axannotfont, 

3355 color=c) 

3356 

3357 uoffnext = max( 

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

3359 

3360 annot_labels.append(lab) 

3361 if y == 0.: 

3362 umax_zeroline = \ 

3363 umax - 20 \ 

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

3365 - uoff 

3366 else: 

3367 if not show_boxes: 

3368 qpoints = make_QPolygonF( 

3369 [umax-20-uoff, 

3370 umax-10-uoff, 

3371 umax-10-uoff, 

3372 umax-20-uoff], 

3373 [vmax, vmax, vmin, vmin]) 

3374 p.drawPolyline(qpoints) 

3375 

3376 snum = num_to_html(ymin) 

3377 labmin = Label( 

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

3379 label_bg=None, 

3380 anchor='BR', 

3381 font=axannotfont, 

3382 color=c) 

3383 

3384 annot_labels.append(labmin) 

3385 snum = num_to_html(ymax) 

3386 labmax = Label( 

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

3388 label_bg=None, 

3389 anchor='TR', 

3390 font=axannotfont, 

3391 color=c) 

3392 

3393 annot_labels.append(labmax) 

3394 

3395 for lab in (labmin, labmax): 

3396 uoffnext = max( 

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

3398 

3399 if self.menuitem_showzeroline.isChecked(): 

3400 v = trace_projection(0.) 

3401 if vmin <= v <= vmax: 

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

3403 p.drawLine(line) 

3404 

3405 uoff = uoffnext 

3406 

3407 p.setFont(font) 

3408 p.setPen(primary_pen) 

3409 for trace in processed_traces: 

3410 if self.view_mode is not ViewMode.Wiggle: 

3411 break 

3412 

3413 if trace not in trace_to_itrack: 

3414 continue 

3415 

3416 itrack = trace_to_itrack[trace] 

3417 scaling_key = self.scaling_key(trace) 

3418 trace_projection = trace_projections[ 

3419 itrack, scaling_key] 

3420 

3421 vdata = trace_projection(trace.get_ydata()) 

3422 

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

3424 udata_max = float(self.time_projection( 

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

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

3427 

3428 qpoints = make_QPolygonF(udata, vdata) 

3429 

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

3431 vmin, vmax = trace_projection.get_out_range() 

3432 

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

3434 

3435 if self.menuitem_cliptraces.isChecked(): 

3436 p.setClipRect(trackrect) 

3437 

3438 if self.menuitem_colortraces.isChecked(): 

3439 color = pyrocko.plot.color( 

3440 color_lookup[self.color_gather(trace)]) 

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

3442 p.setPen(pen) 

3443 

3444 p.drawPolyline(qpoints) 

3445 

3446 if self.floating_marker: 

3447 self.floating_marker.draw_trace( 

3448 self, p, trace, 

3449 self.time_projection, trace_projection, 1.0) 

3450 

3451 for marker in self.markers.with_key_in( 

3452 self.tmin - self.markers_deltat_max, 

3453 self.tmax): 

3454 

3455 if marker.tmin < self.tmax \ 

3456 and self.tmin < marker.tmax \ 

3457 and marker.kind \ 

3458 in self.visible_marker_kinds: 

3459 marker.draw_trace( 

3460 self, p, trace, self.time_projection, 

3461 trace_projection, 1.0) 

3462 

3463 p.setPen(primary_pen) 

3464 

3465 if self.menuitem_cliptraces.isChecked(): 

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

3467 

3468 if self.floating_marker: 

3469 self.floating_marker.draw( 

3470 p, self.time_projection, vcenter_projection) 

3471 

3472 self.draw_visible_markers( 

3473 p, vcenter_projection, primary_pen) 

3474 

3475 p.setPen(primary_pen) 

3476 while font.pointSize() > 2: 

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

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

3479 - self.track_to_screen(0.05) 

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

3481 if nlinesavail > 1: 

3482 break 

3483 

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

3485 

3486 p.setFont(font) 

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

3488 

3489 for key in self.track_keys: 

3490 itrack = self.key_to_row[key] 

3491 if itrack in track_projections: 

3492 plabel = ' '.join( 

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

3494 lx = 10 

3495 ly = self.track_to_screen(itrack+0.5) 

3496 

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

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

3499 continue 

3500 

3501 contains_cursor = \ 

3502 self.track_to_screen(itrack) \ 

3503 < mouse_pos.y() \ 

3504 < self.track_to_screen(itrack+1) 

3505 

3506 if not contains_cursor: 

3507 continue 

3508 

3509 font_large = p.font() 

3510 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3511 p.setFont(font_large) 

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

3513 p.setFont(font) 

3514 

3515 for lab in annot_labels: 

3516 lab.draw() 

3517 

3518 self.timer_draw.stop() 

3519 

3520 def see_data_params(self): 

3521 

3522 min_deltat = self.content_deltat_range()[0] 

3523 

3524 # determine padding and downampling requirements 

3525 if self.lowpass is not None: 

3526 deltat_target = 1./self.lowpass * 0.25 

3527 ndecimate = min( 

3528 50, 

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

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

3531 else: 

3532 ndecimate = 1 

3533 tpad = min_deltat*5. 

3534 

3535 if self.highpass is not None: 

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

3537 

3538 nsee_points_per_trace = 5000*10 

3539 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3540 

3541 return ndecimate, tpad, tsee 

3542 

3543 def clean_update(self): 

3544 self.cached_processed_traces = None 

3545 self.update() 

3546 

3547 def get_adequate_tpad(self): 

3548 tpad = 0. 

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

3550 if f is not None: 

3551 tpad = max(tpad, 1.0/f) 

3552 

3553 for snuffling in self.snufflings: 

3554 if snuffling._post_process_hook_enabled \ 

3555 or snuffling._pre_process_hook_enabled: 

3556 

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

3558 

3559 return tpad 

3560 

3561 def prepare_cutout2( 

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

3563 demean=True, nmax=6000): 

3564 

3565 if self.pile.is_empty(): 

3566 return [] 

3567 

3568 nmax = self.visible_length 

3569 

3570 self.timer_cutout.start() 

3571 

3572 tsee = tmax-tmin 

3573 min_deltat_wo_decimate = tsee/nmax 

3574 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3575 

3576 min_deltat_allow = min_deltat_wo_decimate 

3577 if self.lowpass is not None: 

3578 target_deltat_lp = 0.25/self.lowpass 

3579 if target_deltat_lp > min_deltat_wo_decimate: 

3580 min_deltat_allow = min_deltat_w_decimate 

3581 

3582 min_deltat_allow = math.exp( 

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

3584 

3585 tmin_ = tmin 

3586 tmax_ = tmax 

3587 

3588 # fetch more than needed? 

3589 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3593 

3594 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3595 lphp = self.menuitem_lphp.isChecked() 

3596 ads = self.menuitem_allowdownsampling.isChecked() 

3597 

3598 tpad = self.get_adequate_tpad() 

3599 tpad = max(tpad, tsee) 

3600 

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

3602 vec = ( 

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

3604 self.highpass, fft_filtering, lphp, 

3605 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3606 ads, self.pile.get_update_count()) 

3607 

3608 if (self.cached_vec 

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

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

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

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

3613 and self.cached_processed_traces is not None): 

3614 

3615 logger.debug('Using cached traces') 

3616 processed_traces = self.cached_processed_traces 

3617 

3618 else: 

3619 processed_traces = [] 

3620 if self.pile.deltatmax >= min_deltat_allow: 

3621 

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

3623 def group_selector(gr): 

3624 return gr.deltatmax >= min_deltat_allow 

3625 

3626 kwargs = dict(group_selector=group_selector) 

3627 else: 

3628 kwargs = {} 

3629 

3630 if trace_selector is not None: 

3631 def trace_selectorx(tr): 

3632 return tr.deltat >= min_deltat_allow \ 

3633 and trace_selector(tr) 

3634 else: 

3635 def trace_selectorx(tr): 

3636 return tr.deltat >= min_deltat_allow 

3637 

3638 for traces in self.pile.chopper( 

3639 tmin=tmin, tmax=tmax, tpad=tpad, 

3640 want_incomplete=True, 

3641 degap=degap, 

3642 maxgap=gap_lap_tolerance, 

3643 maxlap=gap_lap_tolerance, 

3644 keep_current_files_open=True, 

3645 trace_selector=trace_selectorx, 

3646 accessor_id=id(self), 

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

3648 include_last=True, **kwargs): 

3649 

3650 if demean: 

3651 for tr in traces: 

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

3653 continue 

3654 y = tr.get_ydata() 

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

3656 

3657 traces = self.pre_process_hooks(traces) 

3658 

3659 for trace in traces: 

3660 

3661 if not (trace.meta 

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

3663 

3664 if fft_filtering: 

3665 but = pyrocko.response.ButterworthResponse 

3666 multres = pyrocko.response.MultiplyResponse 

3667 if self.lowpass is not None \ 

3668 or self.highpass is not None: 

3669 

3670 it = num.arange( 

3671 trace.data_len(), dtype=float) 

3672 detr_data, m, b = detrend( 

3673 it, trace.get_ydata()) 

3674 

3675 trace.set_ydata(detr_data) 

3676 

3677 freqs, fdata = trace.spectrum( 

3678 pad_to_pow2=True, tfade=None) 

3679 

3680 nfreqs = fdata.size 

3681 

3682 key = (trace.deltat, nfreqs) 

3683 

3684 if key not in self.tf_cache: 

3685 resps = [] 

3686 if self.lowpass is not None: 

3687 resps.append(but( 

3688 order=4, 

3689 corner=self.lowpass, 

3690 type='low')) 

3691 

3692 if self.highpass is not None: 

3693 resps.append(but( 

3694 order=4, 

3695 corner=self.highpass, 

3696 type='high')) 

3697 

3698 resp = multres(resps) 

3699 self.tf_cache[key] = \ 

3700 resp.evaluate(freqs) 

3701 

3702 filtered_data = num.fft.irfft( 

3703 fdata*self.tf_cache[key] 

3704 )[:trace.data_len()] 

3705 

3706 retrended_data = retrend( 

3707 it, filtered_data, m, b) 

3708 

3709 trace.set_ydata(retrended_data) 

3710 

3711 else: 

3712 

3713 if ads and self.lowpass is not None: 

3714 while trace.deltat \ 

3715 < min_deltat_wo_decimate: 

3716 

3717 trace.downsample(2, demean=False) 

3718 

3719 fmax = 0.5/trace.deltat 

3720 if not lphp and ( 

3721 self.lowpass is not None 

3722 and self.highpass is not None 

3723 and self.lowpass < fmax 

3724 and self.highpass < fmax 

3725 and self.highpass < self.lowpass): 

3726 

3727 trace.bandpass( 

3728 2, self.highpass, self.lowpass) 

3729 else: 

3730 if self.lowpass is not None: 

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

3732 trace.lowpass( 

3733 4, self.lowpass, 

3734 demean=False) 

3735 

3736 if self.highpass is not None: 

3737 if self.lowpass is None \ 

3738 or self.highpass \ 

3739 < self.lowpass: 

3740 

3741 if self.highpass < \ 

3742 0.5/trace.deltat: 

3743 trace.highpass( 

3744 4, self.highpass, 

3745 demean=False) 

3746 

3747 processed_traces.append(trace) 

3748 

3749 if self.rotate != 0.0: 

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

3751 cphi = math.cos(phi) 

3752 sphi = math.sin(phi) 

3753 for a in processed_traces: 

3754 for b in processed_traces: 

3755 if (a.network == b.network 

3756 and a.station == b.station 

3757 and a.location == b.location 

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

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

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

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

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

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

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

3765 

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

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

3768 a.set_ydata(aydata) 

3769 b.set_ydata(bydata) 

3770 

3771 processed_traces = self.post_process_hooks(processed_traces) 

3772 

3773 self.cached_processed_traces = processed_traces 

3774 self.cached_vec = vec 

3775 

3776 chopped_traces = [] 

3777 for trace in processed_traces: 

3778 chop_tmin = tmin_ - trace.deltat*4 

3779 chop_tmax = tmax_ + trace.deltat*4 

3780 

3781 try: 

3782 ctrace = trace.chop( 

3783 chop_tmin, chop_tmax, 

3784 inplace=False) 

3785 

3786 except pyrocko.trace.NoData: 

3787 continue 

3788 

3789 if ctrace.data_len() < 2: 

3790 continue 

3791 

3792 chopped_traces.append(ctrace) 

3793 

3794 self.timer_cutout.stop() 

3795 return chopped_traces 

3796 

3797 def pre_process_hooks(self, traces): 

3798 for snuffling in self.snufflings: 

3799 if snuffling._pre_process_hook_enabled: 

3800 traces = snuffling.pre_process_hook(traces) 

3801 

3802 return traces 

3803 

3804 def post_process_hooks(self, traces): 

3805 for snuffling in self.snufflings: 

3806 if snuffling._post_process_hook_enabled: 

3807 traces = snuffling.post_process_hook(traces) 

3808 

3809 return traces 

3810 

3811 def visible_length_change(self, ignore=None): 

3812 for menuitem, vlen in self.menuitems_visible_length: 

3813 if menuitem.isChecked(): 

3814 self.visible_length = vlen 

3815 

3816 def scaling_base_change(self, ignore=None): 

3817 for menuitem, scaling_base in self.menuitems_scaling_base: 

3818 if menuitem.isChecked(): 

3819 self.scaling_base = scaling_base 

3820 

3821 def scalingmode_change(self, ignore=None): 

3822 for menuitem, scaling_key in self.menuitems_scaling: 

3823 if menuitem.isChecked(): 

3824 self.scaling_key = scaling_key 

3825 self.update() 

3826 

3827 def apply_scaling_hooks(self, data_ranges): 

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

3829 hook = self.scaling_hooks[k] 

3830 hook(data_ranges) 

3831 

3832 def viewmode_change(self, ignore=True): 

3833 for item, mode in self.menuitems_viewmode: 

3834 if item.isChecked(): 

3835 self.view_mode = mode 

3836 break 

3837 else: 

3838 raise AttributeError('unknown view mode') 

3839 

3840 items_waterfall_disabled = ( 

3841 self.menuitem_showscaleaxis, 

3842 self.menuitem_showscalerange, 

3843 self.menuitem_showzeroline, 

3844 self.menuitem_colortraces, 

3845 self.menuitem_cliptraces, 

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

3847 ) 

3848 

3849 if self.view_mode is ViewMode.Waterfall: 

3850 self.parent().show_colorbar_ctrl(True) 

3851 self.parent().show_gain_ctrl(False) 

3852 

3853 for item in items_waterfall_disabled: 

3854 item.setDisabled(True) 

3855 

3856 self.visible_length = 180. 

3857 else: 

3858 self.parent().show_colorbar_ctrl(False) 

3859 self.parent().show_gain_ctrl(True) 

3860 

3861 for item in items_waterfall_disabled: 

3862 item.setDisabled(False) 

3863 

3864 self.visible_length_change() 

3865 self.update() 

3866 

3867 def set_scaling_hook(self, k, hook): 

3868 self.scaling_hooks[k] = hook 

3869 

3870 def remove_scaling_hook(self, k): 

3871 del self.scaling_hooks[k] 

3872 

3873 def remove_scaling_hooks(self): 

3874 self.scaling_hooks = {} 

3875 

3876 def s_sortingmode_change(self, ignore=None): 

3877 for menuitem, valfunc in self.menuitems_ssorting: 

3878 if menuitem.isChecked(): 

3879 self._ssort = valfunc 

3880 

3881 self.sortingmode_change() 

3882 

3883 def sortingmode_change(self, ignore=None): 

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

3885 if menuitem.isChecked(): 

3886 self.set_gathering(gather, color) 

3887 

3888 self.sortingmode_change_time = time.time() 

3889 

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

3891 self.lowpass = value 

3892 self.passband_check() 

3893 self.tf_cache = {} 

3894 self.update() 

3895 

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

3897 self.highpass = value 

3898 self.passband_check() 

3899 self.tf_cache = {} 

3900 self.update() 

3901 

3902 def passband_check(self): 

3903 if self.highpass and self.lowpass \ 

3904 and self.highpass >= self.lowpass: 

3905 

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

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

3908 'deactivate the highpass.' 

3909 

3910 self.update_status() 

3911 else: 

3912 oldmess = self.message 

3913 self.message = None 

3914 if oldmess is not None: 

3915 self.update_status() 

3916 

3917 def gain_change(self, value, ignore): 

3918 self.gain = value 

3919 self.update() 

3920 

3921 def rot_change(self, value, ignore): 

3922 self.rotate = value 

3923 self.update() 

3924 

3925 def waterfall_cmap_change(self, cmap): 

3926 self.waterfall_cmap = cmap 

3927 self.update() 

3928 

3929 def waterfall_clip_change(self, clip_min, clip_max): 

3930 self.waterfall_clip_min = clip_min 

3931 self.waterfall_clip_max = clip_max 

3932 self.update() 

3933 

3934 def waterfall_show_absolute_change(self, toggle): 

3935 self.waterfall_show_absolute = toggle 

3936 self.update() 

3937 

3938 def waterfall_set_integrate(self, toggle): 

3939 self.waterfall_integrate = toggle 

3940 self.update() 

3941 

3942 def set_selected_markers(self, markers): 

3943 ''' 

3944 Set a list of markers selected 

3945 

3946 :param markers: list of markers 

3947 ''' 

3948 self.deselect_all() 

3949 for m in markers: 

3950 m.selected = True 

3951 

3952 self.update() 

3953 

3954 def deselect_all(self): 

3955 for marker in self.markers: 

3956 marker.selected = False 

3957 

3958 def animate_picking(self): 

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

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

3961 

3962 def get_nslc_ids_for_track(self, ftrack): 

3963 itrack = int(ftrack) 

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

3965 

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

3967 if self.picking: 

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

3969 self.picking = None 

3970 self.picking_down = None 

3971 self.picking_timer.stop() 

3972 self.picking_timer = None 

3973 if not abort: 

3974 self.add_marker(self.floating_marker) 

3975 self.floating_marker.selected = True 

3976 self.emit_selected_markers() 

3977 

3978 self.floating_marker = None 

3979 

3980 def start_picking(self, ignore): 

3981 

3982 if not self.picking: 

3983 self.deselect_all() 

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

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

3986 

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

3988 self.picking.setGeometry( 

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

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

3991 

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

3993 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3995 self.floating_marker.selected = True 

3996 

3997 self.picking_timer = qc.QTimer() 

3998 self.picking_timer.timeout.connect( 

3999 self.animate_picking) 

4000 

4001 self.picking_timer.setInterval(50) 

4002 self.picking_timer.start() 

4003 

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

4005 if self.picking: 

4006 mouset = self.time_projection.rev(x) 

4007 dt = 0.0 

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

4009 if mouset < self.tmin: 

4010 dt = -(self.tmin - mouset) 

4011 else: 

4012 dt = mouset - self.tmax 

4013 ddt = self.tmax-self.tmin 

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

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

4016 

4017 x0 = x 

4018 if self.picking_down is not None: 

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

4020 

4021 w = abs(x-x0) 

4022 x0 = min(x0, x) 

4023 

4024 tmin, tmax = ( 

4025 self.time_projection.rev(x0), 

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

4027 

4028 tmin, tmax = ( 

4029 max(working_system_time_range[0], tmin), 

4030 min(working_system_time_range[1], tmax)) 

4031 

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

4033 

4034 self.picking.setGeometry( 

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

4036 

4037 ftrack = self.track_to_screen.rev(y) 

4038 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

4040 

4041 if dt != 0.0 and doshift: 

4042 self.interrupt_following() 

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

4044 

4045 self.update() 

4046 

4047 def update_status(self): 

4048 

4049 if self.message is None: 

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

4051 

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

4053 if not is_working_time(mouse_t): 

4054 return 

4055 

4056 if self.floating_marker: 

4057 tmi, tma = ( 

4058 self.floating_marker.tmin, 

4059 self.floating_marker.tmax) 

4060 

4061 tt, ms = gmtime_x(tmi) 

4062 

4063 if tmi == tma: 

4064 message = mystrftime( 

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

4066 tt=tt, milliseconds=ms) 

4067 else: 

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

4069 message = mystrftime( 

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

4071 tt=tt, milliseconds=ms) 

4072 else: 

4073 tt, ms = gmtime_x(mouse_t) 

4074 

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

4076 else: 

4077 message = self.message 

4078 

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

4080 sb.clearMessage() 

4081 sb.showMessage(message) 

4082 

4083 def set_sortingmode_change_delay_time(self, dt): 

4084 self.sortingmode_change_delay_time = dt 

4085 

4086 def sortingmode_change_delayed(self): 

4087 now = time.time() 

4088 return ( 

4089 self.sortingmode_change_delay_time is not None 

4090 and now - self.sortingmode_change_time 

4091 < self.sortingmode_change_delay_time) 

4092 

4093 def set_visible_marker_kinds(self, kinds): 

4094 self.deselect_all() 

4095 self.visible_marker_kinds = tuple(kinds) 

4096 self.emit_selected_markers() 

4097 

4098 def following(self): 

4099 return self.follow_timer is not None \ 

4100 and not self.following_interrupted() 

4101 

4102 def interrupt_following(self): 

4103 self.interactive_range_change_time = time.time() 

4104 

4105 def following_interrupted(self, now=None): 

4106 if now is None: 

4107 now = time.time() 

4108 return now - self.interactive_range_change_time \ 

4109 < self.interactive_range_change_delay_time 

4110 

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

4112 if tmax_start is None: 

4113 tmax_start = time.time() 

4114 self.show_all = False 

4115 self.follow_time = tlen 

4116 self.follow_timer = qc.QTimer(self) 

4117 self.follow_timer.timeout.connect( 

4118 self.follow_update) 

4119 self.follow_timer.setInterval(interval) 

4120 self.follow_timer.start() 

4121 self.follow_started = time.time() 

4122 self.follow_lapse = lapse 

4123 self.follow_tshift = self.follow_started - tmax_start 

4124 self.interactive_range_change_time = 0.0 

4125 

4126 def unfollow(self): 

4127 if self.follow_timer is not None: 

4128 self.follow_timer.stop() 

4129 self.follow_timer = None 

4130 self.interactive_range_change_time = 0.0 

4131 

4132 def follow_update(self): 

4133 rnow = time.time() 

4134 if self.follow_lapse is None: 

4135 now = rnow 

4136 else: 

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

4138 * self.follow_lapse 

4139 

4140 if self.following_interrupted(rnow): 

4141 return 

4142 self.set_time_range( 

4143 now-self.follow_time-self.follow_tshift, 

4144 now-self.follow_tshift) 

4145 

4146 self.update() 

4147 

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

4149 self.return_tag = return_tag 

4150 self.window().close() 

4151 

4152 def cleanup(self): 

4153 self.about_to_close.emit() 

4154 self.timer.stop() 

4155 if self.follow_timer is not None: 

4156 self.follow_timer.stop() 

4157 

4158 for snuffling in list(self.snufflings): 

4159 self.remove_snuffling(snuffling) 

4160 

4161 def set_error_message(self, key, value): 

4162 if value is None: 

4163 if key in self.error_messages: 

4164 del self.error_messages[key] 

4165 else: 

4166 self.error_messages[key] = value 

4167 

4168 def inputline_changed(self, text): 

4169 pass 

4170 

4171 def inputline_finished(self, text): 

4172 line = str(text) 

4173 

4174 toks = line.split() 

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

4176 if len(toks) >= 1: 

4177 command = toks[0].lower() 

4178 

4179 try: 

4180 quick_filter_commands = { 

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

4182 's': '*.%s.*.*', 

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

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

4185 

4186 if command in quick_filter_commands: 

4187 if len(toks) >= 2: 

4188 patterns = [ 

4189 quick_filter_commands[toks[0]] % pat 

4190 for pat in toks[1:]] 

4191 self.set_quick_filter_patterns(patterns, line) 

4192 else: 

4193 self.set_quick_filter_patterns(None) 

4194 

4195 self.update() 

4196 

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

4198 if len(toks) >= 2: 

4199 patterns = [] 

4200 if len(toks) == 2: 

4201 patterns = [toks[1]] 

4202 elif len(toks) >= 3: 

4203 x = { 

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

4205 's': '*.%s.*.*', 

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

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

4208 

4209 if toks[1] in x: 

4210 patterns.extend( 

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

4212 

4213 for pattern in patterns: 

4214 if command == 'hide': 

4215 self.add_blacklist_pattern(pattern) 

4216 else: 

4217 self.remove_blacklist_pattern(pattern) 

4218 

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

4220 self.clear_blacklist() 

4221 

4222 clearit = True 

4223 

4224 self.update() 

4225 

4226 elif command == 'markers': 

4227 if len(toks) == 2: 

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

4229 kinds = self.all_marker_kinds 

4230 else: 

4231 kinds = [] 

4232 for x in toks[1]: 

4233 try: 

4234 kinds.append(int(x)) 

4235 except Exception: 

4236 pass 

4237 

4238 self.set_visible_marker_kinds(kinds) 

4239 

4240 elif len(toks) == 1: 

4241 self.set_visible_marker_kinds(()) 

4242 

4243 self.update() 

4244 

4245 elif command == 'scaling': 

4246 if len(toks) == 2: 

4247 hideit = False 

4248 error = 'wrong number of arguments' 

4249 

4250 if len(toks) >= 3: 

4251 vmin, vmax = [ 

4252 pyrocko.model.float_or_none(x) 

4253 for x in toks[-2:]] 

4254 

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

4256 if k in d: 

4257 if vmin is not None: 

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

4259 if vmax is not None: 

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

4261 

4262 if len(toks) == 1: 

4263 self.remove_scaling_hooks() 

4264 

4265 elif len(toks) == 3: 

4266 def hook(data_ranges): 

4267 for k in data_ranges: 

4268 upd(data_ranges, k, vmin, vmax) 

4269 

4270 self.set_scaling_hook('_', hook) 

4271 

4272 elif len(toks) == 4: 

4273 pattern = toks[1] 

4274 

4275 def hook(data_ranges): 

4276 for k in pyrocko.util.match_nslcs( 

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

4278 

4279 upd(data_ranges, k, vmin, vmax) 

4280 

4281 self.set_scaling_hook(pattern, hook) 

4282 

4283 elif command == 'goto': 

4284 toks2 = line.split(None, 1) 

4285 if len(toks2) == 2: 

4286 arg = toks2[1] 

4287 m = re.match( 

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

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

4290 if m: 

4291 tlen = None 

4292 if not m.group(1): 

4293 tlen = 12*32*24*60*60 

4294 elif not m.group(2): 

4295 tlen = 32*24*60*60 

4296 elif not m.group(3): 

4297 tlen = 24*60*60 

4298 elif not m.group(4): 

4299 tlen = 60*60 

4300 elif not m.group(5): 

4301 tlen = 60 

4302 

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

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

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

4306 t = pyrocko.util.str_to_time(arg) 

4307 self.go_to_time(t, tlen=tlen) 

4308 

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

4310 supl = '00:00:00' 

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

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

4313 tmin, tmax = self.get_time_range() 

4314 sdate = pyrocko.util.time_to_str( 

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

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

4317 self.go_to_time(t) 

4318 

4319 elif arg == 'today': 

4320 self.go_to_time( 

4321 day_start( 

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

4323 

4324 elif arg == 'yesterday': 

4325 self.go_to_time( 

4326 day_start( 

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

4328 

4329 else: 

4330 self.go_to_event_by_name(arg) 

4331 

4332 else: 

4333 raise PileViewerMainException( 

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

4335 

4336 except PileViewerMainException as e: 

4337 error = str(e) 

4338 hideit = False 

4339 

4340 return clearit, hideit, error 

4341 

4342 return PileViewerMain 

4343 

4344 

4345PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4346GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget) 

4347 

4348 

4349class LineEditWithAbort(qw.QLineEdit): 

4350 

4351 aborted = qc.pyqtSignal() 

4352 history_down = qc.pyqtSignal() 

4353 history_up = qc.pyqtSignal() 

4354 

4355 def keyPressEvent(self, key_event): 

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

4357 self.aborted.emit() 

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

4359 self.history_down.emit() 

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

4361 self.history_up.emit() 

4362 else: 

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

4364 

4365 

4366class PileViewer(qw.QFrame): 

4367 ''' 

4368 PileViewerMain + Controls + Inputline 

4369 ''' 

4370 

4371 def __init__( 

4372 self, pile, 

4373 ntracks_shown_max=20, 

4374 marker_editor_sortable=True, 

4375 use_opengl=None, 

4376 panel_parent=None, 

4377 *args): 

4378 

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

4380 

4381 layout = qw.QGridLayout() 

4382 layout.setContentsMargins(0, 0, 0, 0) 

4383 layout.setSpacing(0) 

4384 

4385 self.menu = PileViewerMenuBar(self) 

4386 

4387 if use_opengl is None: 

4388 use_opengl = is_macos 

4389 

4390 if use_opengl: 

4391 self.viewer = GLPileViewerMain( 

4392 pile, 

4393 ntracks_shown_max=ntracks_shown_max, 

4394 panel_parent=panel_parent, 

4395 menu=self.menu) 

4396 else: 

4397 self.viewer = PileViewerMain( 

4398 pile, 

4399 ntracks_shown_max=ntracks_shown_max, 

4400 panel_parent=panel_parent, 

4401 menu=self.menu) 

4402 

4403 self.marker_editor_sortable = marker_editor_sortable 

4404 

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

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

4407 

4408 self.input_area = qw.QFrame(self) 

4409 ia_layout = qw.QGridLayout() 

4410 ia_layout.setContentsMargins(11, 11, 11, 11) 

4411 self.input_area.setLayout(ia_layout) 

4412 

4413 self.inputline = LineEditWithAbort(self.input_area) 

4414 self.inputline.returnPressed.connect( 

4415 self.inputline_returnpressed) 

4416 self.inputline.editingFinished.connect( 

4417 self.inputline_finished) 

4418 self.inputline.aborted.connect( 

4419 self.inputline_aborted) 

4420 

4421 self.inputline.history_down.connect( 

4422 lambda: self.step_through_history(1)) 

4423 self.inputline.history_up.connect( 

4424 lambda: self.step_through_history(-1)) 

4425 

4426 self.inputline.textEdited.connect( 

4427 self.inputline_changed) 

4428 

4429 self.inputline.setPlaceholderText( 

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

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

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

4433 self.input_area.hide() 

4434 self.history = None 

4435 

4436 self.inputline_error_str = None 

4437 

4438 self.inputline_error = qw.QLabel() 

4439 self.inputline_error.hide() 

4440 

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

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

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

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

4445 

4446 pb = Progressbars(self) 

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

4448 self.progressbars = pb 

4449 

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

4451 self.scrollbar = scrollbar 

4452 layout.addWidget(scrollbar, 1, 1) 

4453 self.scrollbar.valueChanged.connect( 

4454 self.scrollbar_changed) 

4455 

4456 self.block_scrollbar_changes = False 

4457 

4458 self.viewer.want_input.connect( 

4459 self.inputline_show) 

4460 self.viewer.toggle_input.connect( 

4461 self.inputline_toggle) 

4462 self.viewer.tracks_range_changed.connect( 

4463 self.tracks_range_changed) 

4464 self.viewer.pile_has_changed_signal.connect( 

4465 self.adjust_controls) 

4466 self.viewer.about_to_close.connect( 

4467 self.save_inputline_history) 

4468 

4469 self.setLayout(layout) 

4470 

4471 def cleanup(self): 

4472 self.viewer.cleanup() 

4473 

4474 def get_progressbars(self): 

4475 return self.progressbars 

4476 

4477 def inputline_show(self): 

4478 if not self.history: 

4479 self.load_inputline_history() 

4480 

4481 self.input_area.show() 

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

4483 self.inputline.selectAll() 

4484 

4485 def inputline_set_error(self, string): 

4486 self.inputline_error_str = string 

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

4488 self.inputline.selectAll() 

4489 self.inputline_error.setText(string) 

4490 self.input_area.show() 

4491 self.inputline_error.show() 

4492 

4493 def inputline_clear_error(self): 

4494 if self.inputline_error_str: 

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

4496 self.inputline_error_str = None 

4497 self.inputline_error.clear() 

4498 self.inputline_error.hide() 

4499 

4500 def inputline_changed(self, line): 

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

4502 self.inputline_clear_error() 

4503 

4504 def inputline_returnpressed(self): 

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

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

4507 

4508 if error: 

4509 self.inputline_set_error(error) 

4510 

4511 line = line.strip() 

4512 

4513 if line != '' and not error: 

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

4515 self.history.append(line) 

4516 

4517 if clearit: 

4518 

4519 self.inputline.blockSignals(True) 

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

4521 if qpat is None: 

4522 self.inputline.clear() 

4523 else: 

4524 self.inputline.setText(qinp) 

4525 self.inputline.blockSignals(False) 

4526 

4527 if hideit and not error: 

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

4529 self.input_area.hide() 

4530 

4531 self.hist_ind = len(self.history) 

4532 

4533 def inputline_aborted(self): 

4534 ''' 

4535 Hide the input line. 

4536 ''' 

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

4538 self.hist_ind = len(self.history) 

4539 self.input_area.hide() 

4540 

4541 def inputline_toggle(self): 

4542 if self.input_area.isVisible(): 

4543 self.inputline_aborted() 

4544 else: 

4545 self.inputline_show() 

4546 

4547 def save_inputline_history(self): 

4548 ''' 

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

4550 ''' 

4551 if not self.history: 

4552 return 

4553 

4554 conf = pyrocko.config 

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

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

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

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

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

4560 

4561 def load_inputline_history(self): 

4562 ''' 

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

4564 ''' 

4565 conf = pyrocko.config 

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

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

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

4569 f.write('\n') 

4570 

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

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

4573 

4574 self.hist_ind = len(self.history) 

4575 

4576 def step_through_history(self, ud=1): 

4577 ''' 

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

4579 ''' 

4580 n = len(self.history) 

4581 self.hist_ind += ud 

4582 self.hist_ind %= (n + 1) 

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

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

4585 else: 

4586 self.inputline.setText('') 

4587 

4588 def inputline_finished(self): 

4589 pass 

4590 

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

4592 if self.block_scrollbar_changes: 

4593 return 

4594 

4595 self.scrollbar.blockSignals(True) 

4596 self.scrollbar.setPageStep(ihi-ilo) 

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

4598 self.scrollbar.setRange(0, vmax) 

4599 self.scrollbar.setValue(ilo) 

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

4601 self.scrollbar.blockSignals(False) 

4602 

4603 def scrollbar_changed(self, value): 

4604 self.block_scrollbar_changes = True 

4605 ilo = value 

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

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

4608 self.block_scrollbar_changes = False 

4609 self.update_contents() 

4610 

4611 def controls(self): 

4612 frame = qw.QFrame(self) 

4613 layout = qw.QGridLayout() 

4614 frame.setLayout(layout) 

4615 

4616 minfreq = 0.001 

4617 maxfreq = 1000.0 

4618 self.lowpass_control = ValControl(high_is_none=True) 

4619 self.lowpass_control.setup( 

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

4621 self.highpass_control = ValControl(low_is_none=True) 

4622 self.highpass_control.setup( 

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

4624 self.gain_control = ValControl() 

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

4626 self.rot_control = LinValControl() 

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

4628 self.colorbar_control = ColorbarControl(self) 

4629 

4630 self.lowpass_control.valchange.connect( 

4631 self.viewer.lowpass_change) 

4632 self.highpass_control.valchange.connect( 

4633 self.viewer.highpass_change) 

4634 self.gain_control.valchange.connect( 

4635 self.viewer.gain_change) 

4636 self.rot_control.valchange.connect( 

4637 self.viewer.rot_change) 

4638 self.colorbar_control.cmap_changed.connect( 

4639 self.viewer.waterfall_cmap_change 

4640 ) 

4641 self.colorbar_control.clip_changed.connect( 

4642 self.viewer.waterfall_clip_change 

4643 ) 

4644 self.colorbar_control.show_absolute_toggled.connect( 

4645 self.viewer.waterfall_show_absolute_change 

4646 ) 

4647 self.colorbar_control.show_integrate_toggled.connect( 

4648 self.viewer.waterfall_set_integrate 

4649 ) 

4650 

4651 for icontrol, control in enumerate(( 

4652 self.highpass_control, 

4653 self.lowpass_control, 

4654 self.gain_control, 

4655 self.rot_control, 

4656 self.colorbar_control)): 

4657 

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

4659 layout.addWidget(widget, icontrol, iwidget) 

4660 

4661 spacer = qw.QSpacerItem( 

4662 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4664 

4665 self.adjust_controls() 

4666 self.viewer.viewmode_change(ViewMode.Wiggle) 

4667 return frame 

4668 

4669 def marker_editor(self): 

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

4671 self, sortable=self.marker_editor_sortable) 

4672 

4673 editor.set_viewer(self.get_view()) 

4674 editor.get_marker_model().dataChanged.connect( 

4675 self.update_contents) 

4676 return editor 

4677 

4678 def adjust_controls(self): 

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

4680 maxfreq = 0.5/dtmin 

4681 minfreq = (0.5/dtmax)*0.0001 

4682 self.lowpass_control.set_range(minfreq, maxfreq) 

4683 self.highpass_control.set_range(minfreq, maxfreq) 

4684 

4685 def setup_snufflings(self): 

4686 self.viewer.setup_snufflings() 

4687 

4688 def get_view(self): 

4689 return self.viewer 

4690 

4691 def update_contents(self): 

4692 self.viewer.update() 

4693 

4694 def get_pile(self): 

4695 return self.viewer.get_pile() 

4696 

4697 def show_colorbar_ctrl(self, show): 

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

4699 w.setVisible(show) 

4700 

4701 def show_gain_ctrl(self, show): 

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

4703 w.setVisible(show)