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

2845 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-10-10 09:02 +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 about_to_close = qc.pyqtSignal() 

795 pile_has_changed_signal = qc.pyqtSignal() 

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

797 

798 begin_markers_add = qc.pyqtSignal(int, int) 

799 end_markers_add = qc.pyqtSignal() 

800 begin_markers_remove = qc.pyqtSignal(int, int) 

801 end_markers_remove = qc.pyqtSignal() 

802 

803 marker_selection_changed = qc.pyqtSignal(list) 

804 active_event_marker_changed = qc.pyqtSignal() 

805 

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

807 menu=None): 

808 base.__init__(self, *args) 

809 

810 self.pile = pile 

811 self.ax_height = 80 

812 self.panel_parent = panel_parent 

813 

814 self.click_tolerance = 5 

815 

816 self.ntracks_shown_max = ntracks_shown_max 

817 self.initial_ntracks_shown_max = ntracks_shown_max 

818 self.ntracks = 0 

819 self.show_all = True 

820 self.shown_tracks_range = None 

821 self.track_start = None 

822 self.track_trange = None 

823 

824 self.lowpass = None 

825 self.highpass = None 

826 self.gain = 1.0 

827 self.rotate = 0.0 

828 self.picking_down = None 

829 self.picking = None 

830 self.floating_marker = None 

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

832 self.markers_deltat_max = 0. 

833 self.n_selected_markers = 0 

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

835 self.visible_marker_kinds = self.all_marker_kinds 

836 self.active_event_marker = None 

837 self.ignore_releases = 0 

838 self.message = None 

839 self.reloaded = False 

840 self.pile_has_changed = False 

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

842 

843 self.tax = TimeAx() 

844 self.setBackgroundRole(qg.QPalette.Base) 

845 self.setAutoFillBackground(True) 

846 poli = qw.QSizePolicy( 

847 qw.QSizePolicy.Expanding, 

848 qw.QSizePolicy.Expanding) 

849 

850 self.setSizePolicy(poli) 

851 self.setMinimumSize(300, 200) 

852 self.setFocusPolicy(qc.Qt.ClickFocus) 

853 

854 self.menu = menu 

855 

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

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

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

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

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

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

862 

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

864 

865 self.snufflings_menu = self.toggle_panel_menu.addMenu( 

866 'Run Snuffling') 

867 self.toggle_panel_menu.addSeparator() 

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

869 help_menu.addSeparator() 

870 

871 file_menu.addAction( 

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

873 'Open waveform files...', 

874 self.open_waveforms, 

875 qg.QKeySequence.Open) 

876 

877 file_menu.addAction( 

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

879 'Open waveform directory...', 

880 self.open_waveform_directory) 

881 

882 file_menu.addAction( 

883 'Open station files...', 

884 self.open_stations) 

885 

886 file_menu.addAction( 

887 'Open StationXML files...', 

888 self.open_stations_xml) 

889 

890 file_menu.addAction( 

891 'Open event file...', 

892 self.read_events) 

893 

894 file_menu.addSeparator() 

895 file_menu.addAction( 

896 'Open marker file...', 

897 self.read_markers) 

898 

899 file_menu.addAction( 

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

901 'Save markers...', 

902 self.write_markers, 

903 qg.QKeySequence.Save) 

904 

905 file_menu.addAction( 

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

907 'Save selected markers...', 

908 self.write_selected_markers, 

909 qg.QKeySequence.SaveAs) 

910 

911 file_menu.addSeparator() 

912 file_menu.addAction( 

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

914 'Print', 

915 self.printit, 

916 qg.QKeySequence.Print) 

917 

918 file_menu.addAction( 

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

920 'Save as SVG or PNG', 

921 self.savesvg, 

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

923 

924 file_menu.addSeparator() 

925 close = file_menu.addAction( 

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

927 'Close', 

928 self.myclose) 

929 close.setShortcuts( 

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

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

932 

933 # Scale Menu 

934 menudef = [ 

935 ('Individual Scale', 

936 lambda tr: tr.nslc_id, 

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

938 ('Common Scale', 

939 lambda tr: None, 

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

941 ('Common Scale per Station', 

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

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

944 ('Common Scale per Station Location', 

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

946 ('Common Scale per Component', 

947 lambda tr: (tr.channel)), 

948 ] 

949 

950 self.menuitems_scaling = add_radiobuttongroup( 

951 scale_menu, menudef, self.scalingmode_change, 

952 default=self.config.trace_scale) 

953 scale_menu.addSeparator() 

954 

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

956 self.scaling_hooks = {} 

957 self.scalingmode_change() 

958 

959 menudef = [ 

960 ('Scaling based on Minimum and Maximum', 

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

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

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

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

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

966 ] 

967 

968 self.menuitems_scaling_base = add_radiobuttongroup( 

969 scale_menu, menudef, self.scaling_base_change) 

970 

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

972 scale_menu.addSeparator() 

973 

974 self.menuitem_fixscalerange = scale_menu.addAction( 

975 'Fix Scale Ranges') 

976 self.menuitem_fixscalerange.setCheckable(True) 

977 

978 # Sort Menu 

979 def sector_dist(sta): 

980 if sta.dist_m is None: 

981 return None, None 

982 else: 

983 return ( 

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

985 m_float(sta.dist_m)) 

986 

987 menudef = [ 

988 ('Sort by Names', 

989 lambda tr: (), 

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

991 ('Sort by Distance', 

992 lambda tr: self.station_attrib( 

993 tr, 

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

995 lambda tr: (None,)), 

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

997 ('Sort by Azimuth', 

998 lambda tr: self.station_attrib( 

999 tr, 

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

1001 lambda tr: (None,))), 

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

1003 lambda tr: self.station_attrib( 

1004 tr, 

1005 sector_dist, 

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

1007 ('Sort by Backazimuth', 

1008 lambda tr: self.station_attrib( 

1009 tr, 

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

1011 lambda tr: (None,))), 

1012 ] 

1013 self.menuitems_ssorting = add_radiobuttongroup( 

1014 sort_menu, menudef, self.s_sortingmode_change) 

1015 sort_menu.addSeparator() 

1016 

1017 self._ssort = lambda tr: () 

1018 

1019 self.menu.addSeparator() 

1020 

1021 menudef = [ 

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

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

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

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

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

1027 lambda tr: tr.channel)), 

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

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

1030 lambda tr: tr.channel)), 

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

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

1033 lambda tr: tr.channel)), 

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

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

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

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

1038 ((0, 1, 3), 

1039 lambda tr: tr.location)), 

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

1041 ((1, 0, 3), 

1042 lambda tr: tr.location)), 

1043 ] 

1044 

1045 self.menuitems_sorting = add_radiobuttongroup( 

1046 sort_menu, menudef, self.sortingmode_change) 

1047 

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

1049 self.config.visible_length_setting] 

1050 

1051 # View menu 

1052 self.menuitems_visible_length = add_radiobuttongroup( 

1053 view_menu, menudef, 

1054 self.visible_length_change) 

1055 view_menu.addSeparator() 

1056 

1057 view_modes = [ 

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

1059 ('Waterfall', ViewMode.Waterfall) 

1060 ] 

1061 

1062 self.menuitems_viewmode = add_radiobuttongroup( 

1063 view_menu, view_modes, 

1064 self.viewmode_change, default=ViewMode.Wiggle) 

1065 view_menu.addSeparator() 

1066 

1067 self.menuitem_cliptraces = view_menu.addAction( 

1068 'Clip Traces') 

1069 self.menuitem_cliptraces.setCheckable(True) 

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

1071 

1072 self.menuitem_showboxes = view_menu.addAction( 

1073 'Show Boxes') 

1074 self.menuitem_showboxes.setCheckable(True) 

1075 self.menuitem_showboxes.setChecked( 

1076 self.config.show_boxes) 

1077 

1078 self.menuitem_colortraces = view_menu.addAction( 

1079 'Color Traces') 

1080 self.menuitem_colortraces.setCheckable(True) 

1081 self.menuitem_antialias = view_menu.addAction( 

1082 'Antialiasing') 

1083 self.menuitem_antialias.setCheckable(True) 

1084 

1085 view_menu.addSeparator() 

1086 self.menuitem_showscalerange = view_menu.addAction( 

1087 'Show Scale Ranges') 

1088 self.menuitem_showscalerange.setCheckable(True) 

1089 self.menuitem_showscalerange.setChecked( 

1090 self.config.show_scale_ranges) 

1091 

1092 self.menuitem_showscaleaxis = view_menu.addAction( 

1093 'Show Scale Axes') 

1094 self.menuitem_showscaleaxis.setCheckable(True) 

1095 self.menuitem_showscaleaxis.setChecked( 

1096 self.config.show_scale_axes) 

1097 

1098 self.menuitem_showzeroline = view_menu.addAction( 

1099 'Show Zero Lines') 

1100 self.menuitem_showzeroline.setCheckable(True) 

1101 

1102 view_menu.addSeparator() 

1103 view_menu.addAction( 

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

1105 'Fullscreen', 

1106 self.toggle_fullscreen, 

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

1108 

1109 # Options Menu 

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

1111 self.menuitem_demean.setCheckable(True) 

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

1113 self.menuitem_demean.setShortcut( 

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

1115 

1116 self.menuitem_distances_3d = options_menu.addAction( 

1117 '3D distances', 

1118 self.distances_3d_changed) 

1119 self.menuitem_distances_3d.setCheckable(True) 

1120 

1121 self.menuitem_allowdownsampling = options_menu.addAction( 

1122 'Allow Downsampling') 

1123 self.menuitem_allowdownsampling.setCheckable(True) 

1124 self.menuitem_allowdownsampling.setChecked(True) 

1125 

1126 self.menuitem_degap = options_menu.addAction( 

1127 'Allow Degapping') 

1128 self.menuitem_degap.setCheckable(True) 

1129 self.menuitem_degap.setChecked(True) 

1130 

1131 options_menu.addSeparator() 

1132 

1133 self.menuitem_fft_filtering = options_menu.addAction( 

1134 'FFT Filtering') 

1135 self.menuitem_fft_filtering.setCheckable(True) 

1136 

1137 self.menuitem_lphp = options_menu.addAction( 

1138 'Bandpass is Low- + Highpass') 

1139 self.menuitem_lphp.setCheckable(True) 

1140 self.menuitem_lphp.setChecked(True) 

1141 

1142 options_menu.addSeparator() 

1143 self.menuitem_watch = options_menu.addAction( 

1144 'Watch Files') 

1145 self.menuitem_watch.setCheckable(True) 

1146 

1147 self.menuitem_liberal_fetch = options_menu.addAction( 

1148 'Liberal Fetch Optimization') 

1149 self.menuitem_liberal_fetch.setCheckable(True) 

1150 

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

1152 

1153 self.snufflings_menu.addAction( 

1154 'Reload Snufflings', 

1155 self.setup_snufflings) 

1156 

1157 # Disable ShadowPileTest 

1158 if False: 

1159 test_action = self.menu.addAction( 

1160 'Test', 

1161 self.toggletest) 

1162 test_action.setCheckable(True) 

1163 

1164 help_menu.addAction( 

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

1166 'Snuffler Controls', 

1167 self.help, 

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

1169 

1170 help_menu.addAction( 

1171 'About', 

1172 self.about) 

1173 

1174 toolbar = qw.QFrame(self.menu) 

1175 toolbar_layout = qw.QHBoxLayout() 

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

1177 toolbar.setLayout(toolbar_layout) 

1178 toolbar_layout.addWidget(PileViewerMenuBarButton('+')) 

1179 toolbar_layout.addWidget(PileViewerMenuBarButton('-')) 

1180 toolbar_layout.addWidget(PileViewerMenuBarButton('>>')) 

1181 

1182 self.menu.setCornerWidget(toolbar) 

1183 

1184 self.time_projection = Projection() 

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

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

1187 

1188 self.gather = None 

1189 

1190 self.trace_filter = None 

1191 self.quick_filter = None 

1192 self.quick_filter_patterns = None, None 

1193 self.blacklist = [] 

1194 

1195 self.track_to_screen = Projection() 

1196 self.track_to_nslc_ids = {} 

1197 

1198 self.cached_vec = None 

1199 self.cached_processed_traces = None 

1200 

1201 self.timer = qc.QTimer(self) 

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

1203 self.timer.setInterval(1000) 

1204 self.timer.start() 

1205 

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

1207 self.pile.add_listener(self._pile_changed) 

1208 

1209 self.trace_styles = {} 

1210 if self.get_squirrel() is None: 

1211 self.determine_box_styles() 

1212 

1213 self.setMouseTracking(True) 

1214 

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

1216 self.snuffling_modules = {} 

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

1218 self.default_snufflings = None 

1219 self.snufflings = [] 

1220 

1221 self.stations = {} 

1222 

1223 self.timer_draw = Timer() 

1224 self.timer_cutout = Timer() 

1225 self.time_spent_painting = 0.0 

1226 self.time_last_painted = time.time() 

1227 

1228 self.interactive_range_change_time = 0.0 

1229 self.interactive_range_change_delay_time = 10.0 

1230 self.follow_timer = None 

1231 

1232 self.sortingmode_change_time = 0.0 

1233 self.sortingmode_change_delay_time = None 

1234 

1235 self.old_data_ranges = {} 

1236 

1237 self.error_messages = {} 

1238 self.return_tag = None 

1239 self.wheel_pos = 60 

1240 

1241 self.setAcceptDrops(True) 

1242 self._paths_to_load = [] 

1243 

1244 self.tf_cache = {} 

1245 

1246 self.waterfall = TraceWaterfall() 

1247 self.waterfall_cmap = 'viridis' 

1248 self.waterfall_clip_min = 0. 

1249 self.waterfall_clip_max = 1. 

1250 self.waterfall_show_absolute = False 

1251 self.waterfall_integrate = False 

1252 self.view_mode = ViewMode.Wiggle 

1253 

1254 self.automatic_updates = True 

1255 

1256 self.closing = False 

1257 self.in_paint_event = False 

1258 

1259 def fail(self, reason): 

1260 box = qw.QMessageBox(self) 

1261 box.setText(reason) 

1262 box.exec_() 

1263 

1264 def set_trace_filter(self, filter_func): 

1265 self.trace_filter = filter_func 

1266 self.sortingmode_change() 

1267 

1268 def update_trace_filter(self): 

1269 if self.blacklist: 

1270 

1271 def blacklist_func(tr): 

1272 return not pyrocko.util.match_nslc( 

1273 self.blacklist, tr.nslc_id) 

1274 

1275 else: 

1276 blacklist_func = None 

1277 

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

1279 self.set_trace_filter(None) 

1280 elif self.quick_filter is None: 

1281 self.set_trace_filter(blacklist_func) 

1282 elif blacklist_func is None: 

1283 self.set_trace_filter(self.quick_filter) 

1284 else: 

1285 self.set_trace_filter( 

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

1287 

1288 def set_quick_filter(self, filter_func): 

1289 self.quick_filter = filter_func 

1290 self.update_trace_filter() 

1291 

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

1293 if patterns is not None: 

1294 self.set_quick_filter( 

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

1296 else: 

1297 self.set_quick_filter(None) 

1298 

1299 self.quick_filter_patterns = patterns, inputline 

1300 

1301 def get_quick_filter_patterns(self): 

1302 return self.quick_filter_patterns 

1303 

1304 def add_blacklist_pattern(self, pattern): 

1305 if pattern == 'empty': 

1306 keys = set(self.pile.nslc_ids) 

1307 trs = self.pile.all( 

1308 tmin=self.tmin, 

1309 tmax=self.tmax, 

1310 load_data=False, 

1311 degap=False) 

1312 

1313 for tr in trs: 

1314 if tr.nslc_id in keys: 

1315 keys.remove(tr.nslc_id) 

1316 

1317 for key in keys: 

1318 xpattern = '.'.join(key) 

1319 if xpattern not in self.blacklist: 

1320 self.blacklist.append(xpattern) 

1321 

1322 else: 

1323 if pattern in self.blacklist: 

1324 self.blacklist.remove(pattern) 

1325 

1326 self.blacklist.append(pattern) 

1327 

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

1329 self.update_trace_filter() 

1330 

1331 def remove_blacklist_pattern(self, pattern): 

1332 if pattern in self.blacklist: 

1333 self.blacklist.remove(pattern) 

1334 else: 

1335 raise PileViewerMainException( 

1336 'Pattern not found in blacklist.') 

1337 

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

1339 self.update_trace_filter() 

1340 

1341 def clear_blacklist(self): 

1342 self.blacklist = [] 

1343 self.update_trace_filter() 

1344 

1345 def ssort(self, tr): 

1346 return self._ssort(tr) 

1347 

1348 def station_key(self, x): 

1349 return x.network, x.station 

1350 

1351 def station_keys(self, x): 

1352 return [ 

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

1354 (x.network, x.station)] 

1355 

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

1357 for sk in self.station_keys(tr): 

1358 if sk in self.stations: 

1359 station = self.stations[sk] 

1360 return getter(station) 

1361 

1362 return default_getter(tr) 

1363 

1364 def get_station(self, sk): 

1365 return self.stations[sk] 

1366 

1367 def has_station(self, station): 

1368 for sk in self.station_keys(station): 

1369 if sk in self.stations: 

1370 return True 

1371 

1372 return False 

1373 

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

1375 return self.station_attrib( 

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

1377 

1378 def set_stations(self, stations): 

1379 self.stations = {} 

1380 self.add_stations(stations) 

1381 

1382 def add_stations(self, stations): 

1383 for station in stations: 

1384 for sk in self.station_keys(station): 

1385 self.stations[sk] = station 

1386 

1387 ev = self.get_active_event() 

1388 if ev: 

1389 self.set_origin(ev) 

1390 

1391 def add_event(self, event): 

1392 marker = EventMarker(event) 

1393 self.add_marker(marker) 

1394 

1395 def add_events(self, events): 

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

1397 self.add_markers(markers) 

1398 

1399 def set_event_marker_as_origin(self, ignore=None): 

1400 selected = self.selected_markers() 

1401 if not selected: 

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

1403 return 

1404 

1405 m = selected[0] 

1406 if not isinstance(m, EventMarker): 

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

1408 return 

1409 

1410 self.set_active_event_marker(m) 

1411 

1412 def deactivate_event_marker(self): 

1413 if self.active_event_marker: 

1414 self.active_event_marker.active = False 

1415 

1416 self.active_event_marker_changed.emit() 

1417 self.active_event_marker = None 

1418 

1419 def set_active_event_marker(self, event_marker): 

1420 if self.active_event_marker: 

1421 self.active_event_marker.active = False 

1422 

1423 self.active_event_marker = event_marker 

1424 event_marker.active = True 

1425 event = event_marker.get_event() 

1426 self.set_origin(event) 

1427 self.active_event_marker_changed.emit() 

1428 

1429 def set_active_event(self, event): 

1430 for marker in self.markers: 

1431 if isinstance(marker, EventMarker): 

1432 if marker.get_event() is event: 

1433 self.set_active_event_marker(marker) 

1434 

1435 def get_active_event_marker(self): 

1436 return self.active_event_marker 

1437 

1438 def get_active_event(self): 

1439 m = self.get_active_event_marker() 

1440 if m is not None: 

1441 return m.get_event() 

1442 else: 

1443 return None 

1444 

1445 def get_active_markers(self): 

1446 emarker = self.get_active_event_marker() 

1447 if emarker is None: 

1448 return None, [] 

1449 

1450 else: 

1451 ev = emarker.get_event() 

1452 pmarkers = [ 

1453 m for m in self.markers 

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

1455 

1456 return emarker, pmarkers 

1457 

1458 def set_origin(self, location): 

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

1460 station.set_event_relative_data( 

1461 location, 

1462 distance_3d=self.menuitem_distances_3d.isChecked()) 

1463 

1464 self.sortingmode_change() 

1465 

1466 def distances_3d_changed(self): 

1467 ignore = self.menuitem_distances_3d.isChecked() 

1468 self.set_event_marker_as_origin(ignore) 

1469 

1470 def iter_snuffling_modules(self): 

1471 pjoin = os.path.join 

1472 for path in self.snuffling_paths: 

1473 

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

1475 os.mkdir(path) 

1476 

1477 for entry in os.listdir(path): 

1478 directory = path 

1479 fn = entry 

1480 d = pjoin(path, entry) 

1481 if os.path.isdir(d): 

1482 directory = d 

1483 if os.path.isfile( 

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

1485 fn = 'snuffling.py' 

1486 

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

1488 continue 

1489 

1490 name = fn[:-3] 

1491 

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

1493 self.snuffling_modules[directory, name] = \ 

1494 pyrocko.gui.snuffler.snuffling.SnufflingModule( 

1495 directory, name, self) 

1496 

1497 yield self.snuffling_modules[directory, name] 

1498 

1499 def setup_snufflings(self): 

1500 # user snufflings 

1501 from pyrocko.gui.snuffler.snuffling import BrokenSnufflingModule 

1502 for mod in self.iter_snuffling_modules(): 

1503 try: 

1504 mod.load_if_needed() 

1505 except BrokenSnufflingModule as e: 

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

1507 

1508 # load the default snufflings on first run 

1509 if self.default_snufflings is None: 

1510 self.default_snufflings = pyrocko.gui.snuffler\ 

1511 .snufflings.__snufflings__() 

1512 for snuffling in self.default_snufflings: 

1513 self.add_snuffling(snuffling) 

1514 

1515 def set_panel_parent(self, panel_parent): 

1516 self.panel_parent = panel_parent 

1517 

1518 def get_panel_parent(self): 

1519 return self.panel_parent 

1520 

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

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

1523 snuffling.init_gui( 

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

1525 self.snufflings.append(snuffling) 

1526 self.update() 

1527 

1528 def remove_snuffling(self, snuffling): 

1529 snuffling.delete_gui() 

1530 self.update() 

1531 self.snufflings.remove(snuffling) 

1532 snuffling.pre_destroy() 

1533 

1534 def add_snuffling_menuitem(self, item): 

1535 self.snufflings_menu.addAction(item) 

1536 item.setParent(self.snufflings_menu) 

1537 sort_actions(self.snufflings_menu) 

1538 

1539 def remove_snuffling_menuitem(self, item): 

1540 self.snufflings_menu.removeAction(item) 

1541 

1542 def add_snuffling_help_menuitem(self, item): 

1543 self.snuffling_help.addAction(item) 

1544 item.setParent(self.snuffling_help) 

1545 sort_actions(self.snuffling_help) 

1546 

1547 def remove_snuffling_help_menuitem(self, item): 

1548 self.snuffling_help.removeAction(item) 

1549 

1550 def add_panel_toggler(self, item): 

1551 self.toggle_panel_menu.addAction(item) 

1552 item.setParent(self.toggle_panel_menu) 

1553 sort_actions(self.toggle_panel_menu) 

1554 

1555 def remove_panel_toggler(self, item): 

1556 self.toggle_panel_menu.removeAction(item) 

1557 

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

1559 cache_dir=None, force_cache=False): 

1560 

1561 if cache_dir is None: 

1562 cache_dir = pyrocko.config.config().cache_dir 

1563 if isinstance(paths, str): 

1564 paths = [paths] 

1565 

1566 fns = pyrocko.util.select_files( 

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

1568 

1569 if not fns: 

1570 return 

1571 

1572 cache = pyrocko.pile.get_cache(cache_dir) 

1573 

1574 t = [time.time()] 

1575 

1576 def update_bar(label, value): 

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

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

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

1580 else: 

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

1582 

1583 return pbs.set_status(label, value) 

1584 

1585 def update_progress(label, i, n): 

1586 abort = False 

1587 

1588 qw.qApp.processEvents() 

1589 if n != 0: 

1590 perc = i*100/n 

1591 else: 

1592 perc = 100 

1593 abort |= update_bar(label, perc) 

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

1595 

1596 tnow = time.time() 

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

1598 self.update() 

1599 t[0] = tnow 

1600 

1601 return abort 

1602 

1603 self.automatic_updates = False 

1604 

1605 self.pile.load_files( 

1606 sorted(fns), 

1607 filename_attributes=regex, 

1608 cache=cache, 

1609 fileformat=format, 

1610 show_progress=False, 

1611 update_progress=update_progress) 

1612 

1613 self.automatic_updates = True 

1614 self.update() 

1615 

1616 def load_queued(self): 

1617 if not self._paths_to_load: 

1618 return 

1619 paths = self._paths_to_load 

1620 self._paths_to_load = [] 

1621 self.load(paths) 

1622 

1623 def load_soon(self, paths): 

1624 self._paths_to_load.extend(paths) 

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

1626 

1627 def open_waveforms(self): 

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

1629 

1630 fns, _ = qw.QFileDialog.getOpenFileNames( 

1631 self, caption, options=qfiledialog_options) 

1632 

1633 if fns: 

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

1635 

1636 def open_waveform_directory(self): 

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

1638 

1639 dn = qw.QFileDialog.getExistingDirectory( 

1640 self, caption, options=qfiledialog_options) 

1641 

1642 if dn: 

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

1644 

1645 def open_stations(self, fns=None): 

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

1647 

1648 if not fns: 

1649 fns, _ = qw.QFileDialog.getOpenFileNames( 

1650 self, caption, options=qfiledialog_options) 

1651 

1652 try: 

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

1654 for stat in stations: 

1655 self.add_stations(stat) 

1656 

1657 except Exception as e: 

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

1659 

1660 def open_stations_xml(self, fns=None): 

1661 from pyrocko.io import stationxml 

1662 

1663 caption = 'Select one or more StationXML files' 

1664 if not fns: 

1665 fns, _ = qw.QFileDialog.getOpenFileNames( 

1666 self, caption, options=qfiledialog_options, 

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

1668 ';;All files (*)') 

1669 

1670 try: 

1671 stations = [ 

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

1673 for x in fns] 

1674 

1675 for stat in stations: 

1676 self.add_stations(stat) 

1677 

1678 except Exception as e: 

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

1680 

1681 def add_traces(self, traces): 

1682 if traces: 

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

1684 self.pile.add_file(mtf) 

1685 ticket = (self.pile, mtf) 

1686 return ticket 

1687 else: 

1688 return (None, None) 

1689 

1690 def release_data(self, tickets): 

1691 for ticket in tickets: 

1692 pile, mtf = ticket 

1693 if pile is not None: 

1694 pile.remove_file(mtf) 

1695 

1696 def periodical(self): 

1697 if self.menuitem_watch.isChecked(): 

1698 if self.pile.reload_modified(): 

1699 self.update() 

1700 

1701 def get_pile(self): 

1702 return self.pile 

1703 

1704 def pile_changed(self, what, content): 

1705 self.pile_has_changed = True 

1706 self.pile_has_changed_signal.emit() 

1707 if self.automatic_updates: 

1708 self.update() 

1709 

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

1711 

1712 if gather is None: 

1713 def gather_func(tr): 

1714 return tr.nslc_id 

1715 

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

1717 

1718 else: 

1719 def gather_func(tr): 

1720 return ( 

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

1722 

1723 if color is None: 

1724 def color(tr): 

1725 return tr.location 

1726 

1727 self.gather = gather_func 

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

1729 

1730 self.color_gather = color 

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

1732 previous_ntracks = self.ntracks 

1733 self.set_ntracks(len(keys)) 

1734 

1735 if self.shown_tracks_range is None or \ 

1736 previous_ntracks == 0 or \ 

1737 self.show_all: 

1738 

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

1740 key_at_top = None 

1741 n = high-low 

1742 

1743 else: 

1744 low, high = self.shown_tracks_range 

1745 key_at_top = self.track_keys[low] 

1746 n = high-low 

1747 

1748 self.track_keys = sorted(keys) 

1749 

1750 track_patterns = [] 

1751 for k in self.track_keys: 

1752 pat = ['*', '*', '*', '*'] 

1753 for i, j in enumerate(gather): 

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

1755 

1756 track_patterns.append(pat) 

1757 

1758 self.track_patterns = track_patterns 

1759 

1760 if key_at_top is not None: 

1761 try: 

1762 ind = self.track_keys.index(key_at_top) 

1763 low = ind 

1764 high = low+n 

1765 except Exception: 

1766 pass 

1767 

1768 self.set_tracks_range((low, high)) 

1769 

1770 self.key_to_row = dict( 

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

1772 

1773 def inrange(x, r): 

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

1775 

1776 def trace_selector(trace): 

1777 gt = self.gather(trace) 

1778 return ( 

1779 gt in self.key_to_row and 

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

1781 

1782 self.trace_selector = lambda x: \ 

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

1784 and trace_selector(x) 

1785 

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

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

1788 self.show_all: 

1789 

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

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

1792 tlen = (tmax - tmin) 

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

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

1795 

1796 def set_time_range(self, tmin, tmax): 

1797 if tmin is None: 

1798 tmin = initial_time_range[0] 

1799 

1800 if tmax is None: 

1801 tmax = initial_time_range[1] 

1802 

1803 if tmin > tmax: 

1804 tmin, tmax = tmax, tmin 

1805 

1806 if tmin == tmax: 

1807 tmin -= 1. 

1808 tmax += 1. 

1809 

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

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

1812 

1813 min_deltat = self.content_deltat_range()[0] 

1814 if (tmax - tmin < min_deltat): 

1815 m = (tmin + tmax) / 2. 

1816 tmin = m - min_deltat/2. 

1817 tmax = m + min_deltat/2. 

1818 

1819 self.time_projection.set_in_range(tmin, tmax) 

1820 self.tmin, self.tmax = tmin, tmax 

1821 

1822 def get_time_range(self): 

1823 return self.tmin, self.tmax 

1824 

1825 def ypart(self, y): 

1826 if y < self.ax_height: 

1827 return -1 

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

1829 return 1 

1830 else: 

1831 return 0 

1832 

1833 def time_fractional_digits(self): 

1834 min_deltat = self.content_deltat_range()[0] 

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

1836 

1837 def write_markers(self, fn=None): 

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

1839 if not fn: 

1840 fn, _ = qw.QFileDialog.getSaveFileName( 

1841 self, caption, options=qfiledialog_options) 

1842 if fn: 

1843 try: 

1844 Marker.save_markers( 

1845 self.markers, fn, 

1846 fdigits=self.time_fractional_digits()) 

1847 

1848 except Exception as e: 

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

1850 

1851 def write_selected_markers(self, fn=None): 

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

1853 if not fn: 

1854 fn, _ = qw.QFileDialog.getSaveFileName( 

1855 self, caption, options=qfiledialog_options) 

1856 if fn: 

1857 try: 

1858 Marker.save_markers( 

1859 self.iter_selected_markers(), 

1860 fn, 

1861 fdigits=self.time_fractional_digits()) 

1862 

1863 except Exception as e: 

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

1865 

1866 def read_events(self, fn=None): 

1867 ''' 

1868 Open QFileDialog to open, read and add 

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

1870 representation to the pile viewer. 

1871 ''' 

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

1873 if not fn: 

1874 fn, _ = qw.QFileDialog.getOpenFileName( 

1875 self, caption, options=qfiledialog_options) 

1876 if fn: 

1877 try: 

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

1879 self.associate_phases_to_events() 

1880 

1881 except Exception as e: 

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

1883 

1884 def read_markers(self, fn=None): 

1885 ''' 

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

1887 ''' 

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

1889 if not fn: 

1890 fn, _ = qw.QFileDialog.getOpenFileName( 

1891 self, caption, options=qfiledialog_options) 

1892 if fn: 

1893 try: 

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

1895 self.associate_phases_to_events() 

1896 

1897 except Exception as e: 

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

1899 

1900 def associate_phases_to_events(self): 

1901 associate_phases_to_events(self.markers) 

1902 

1903 def add_marker(self, marker): 

1904 # need index to inform QAbstactTableModel about upcoming change, 

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

1906 self.markers.insert(marker) 

1907 i = self.markers.remove(marker) 

1908 

1909 self.begin_markers_add.emit(i, i) 

1910 self.markers.insert(marker) 

1911 self.end_markers_add.emit() 

1912 self.markers_deltat_max = max( 

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

1914 

1915 def add_markers(self, markers): 

1916 if not self.markers: 

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

1918 self.markers.insert_many(markers) 

1919 self.end_markers_add.emit() 

1920 self.update_markers_deltat_max() 

1921 else: 

1922 for marker in markers: 

1923 self.add_marker(marker) 

1924 

1925 def update_markers_deltat_max(self): 

1926 if self.markers: 

1927 self.markers_deltat_max = max( 

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

1929 

1930 def remove_marker(self, marker): 

1931 ''' 

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

1933 

1934 :param marker: 

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

1936 instance 

1937 ''' 

1938 

1939 if marker is self.active_event_marker: 

1940 self.deactivate_event_marker() 

1941 

1942 try: 

1943 i = self.markers.index(marker) 

1944 self.begin_markers_remove.emit(i, i) 

1945 self.markers.remove_at(i) 

1946 self.end_markers_remove.emit() 

1947 except ValueError: 

1948 pass 

1949 

1950 def remove_markers(self, markers): 

1951 ''' 

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

1953 

1954 :param markers: 

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

1956 subclass) instances 

1957 ''' 

1958 

1959 if markers is self.markers: 

1960 markers = list(markers) 

1961 

1962 for marker in markers: 

1963 self.remove_marker(marker) 

1964 

1965 self.update_markers_deltat_max() 

1966 

1967 def remove_selected_markers(self): 

1968 def delete_segment(istart, iend): 

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

1970 for _ in range(iend - istart): 

1971 self.markers.remove_at(istart) 

1972 

1973 self.end_markers_remove.emit() 

1974 

1975 istart = None 

1976 ipos = 0 

1977 markers = self.markers 

1978 nmarkers = len(self.markers) 

1979 while ipos < nmarkers: 

1980 marker = markers[ipos] 

1981 if marker.is_selected(): 

1982 if marker is self.active_event_marker: 

1983 self.deactivate_event_marker() 

1984 

1985 if istart is None: 

1986 istart = ipos 

1987 else: 

1988 if istart is not None: 

1989 delete_segment(istart, ipos) 

1990 nmarkers -= ipos - istart 

1991 ipos = istart - 1 

1992 istart = None 

1993 

1994 ipos += 1 

1995 

1996 if istart is not None: 

1997 delete_segment(istart, ipos) 

1998 

1999 self.update_markers_deltat_max() 

2000 

2001 def selected_markers(self): 

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

2003 

2004 def iter_selected_markers(self): 

2005 for marker in self.markers: 

2006 if marker.is_selected(): 

2007 yield marker 

2008 

2009 def get_markers(self): 

2010 return self.markers 

2011 

2012 def mousePressEvent(self, mouse_ev): 

2013 '' 

2014 self.show_all = False 

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

2016 

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

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

2019 if self.picking: 

2020 if self.picking_down is None: 

2021 self.picking_down = ( 

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

2023 mouse_ev.y()) 

2024 

2025 elif marker is not None: 

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

2027 self.deselect_all() 

2028 marker.selected = True 

2029 self.emit_selected_markers() 

2030 self.update() 

2031 else: 

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

2033 self.track_trange = self.tmin, self.tmax 

2034 

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

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

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

2038 self.update_status() 

2039 

2040 def mouseReleaseEvent(self, mouse_ev): 

2041 '' 

2042 if self.ignore_releases: 

2043 self.ignore_releases -= 1 

2044 return 

2045 

2046 if self.picking: 

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

2048 self.emit_selected_markers() 

2049 

2050 if self.track_start: 

2051 self.update() 

2052 

2053 self.track_start = None 

2054 self.track_trange = None 

2055 self.update_status() 

2056 

2057 def mouseDoubleClickEvent(self, mouse_ev): 

2058 '' 

2059 self.show_all = False 

2060 self.start_picking(None) 

2061 self.ignore_releases = 1 

2062 

2063 def mouseMoveEvent(self, mouse_ev): 

2064 '' 

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

2066 

2067 if self.picking: 

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

2069 

2070 elif self.track_start is not None: 

2071 x0, y0 = self.track_start 

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

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

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

2075 dy = 0 

2076 

2077 tmin0, tmax0 = self.track_trange 

2078 

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

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

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

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

2083 

2084 self.interrupt_following() 

2085 self.set_time_range( 

2086 tmin0 - dt - dtr*frac, 

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

2088 

2089 self.update() 

2090 else: 

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

2092 

2093 self.update_status() 

2094 

2095 def nslc_ids_under_cursor(self, x, y): 

2096 ftrack = self.track_to_screen.rev(y) 

2097 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2098 return nslc_ids 

2099 

2100 def marker_under_cursor(self, x, y): 

2101 mouset = self.time_projection.rev(x) 

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

2103 relevant_nslc_ids = None 

2104 for marker in self.markers: 

2105 if marker.kind not in self.visible_marker_kinds: 

2106 continue 

2107 

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

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

2110 

2111 if relevant_nslc_ids is None: 

2112 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2113 

2114 marker_nslc_ids = marker.get_nslc_ids() 

2115 if not marker_nslc_ids: 

2116 return marker 

2117 

2118 for nslc_id in marker_nslc_ids: 

2119 if nslc_id in relevant_nslc_ids: 

2120 return marker 

2121 

2122 def hoovering(self, x, y): 

2123 mouset = self.time_projection.rev(x) 

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

2125 needupdate = False 

2126 haveone = False 

2127 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2128 for marker in self.markers: 

2129 if marker.kind not in self.visible_marker_kinds: 

2130 continue 

2131 

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

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

2134 

2135 if state: 

2136 xstate = False 

2137 

2138 marker_nslc_ids = marker.get_nslc_ids() 

2139 if not marker_nslc_ids: 

2140 xstate = True 

2141 

2142 for nslc in relevant_nslc_ids: 

2143 if marker.match_nslc(nslc): 

2144 xstate = True 

2145 

2146 state = xstate 

2147 

2148 if state: 

2149 haveone = True 

2150 oldstate = marker.is_alerted() 

2151 if oldstate != state: 

2152 needupdate = True 

2153 marker.set_alerted(state) 

2154 if state: 

2155 self.message = marker.hoover_message() 

2156 

2157 if not haveone: 

2158 self.message = None 

2159 

2160 if needupdate: 

2161 self.update() 

2162 

2163 def event(self, event): 

2164 '' 

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

2166 self.keyPressEvent(event) 

2167 return True 

2168 else: 

2169 return base.event(self, event) 

2170 

2171 def keyPressEvent(self, key_event): 

2172 '' 

2173 self.show_all = False 

2174 dt = self.tmax - self.tmin 

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

2176 

2177 key = key_event.key() 

2178 try: 

2179 keytext = str(key_event.text()) 

2180 except UnicodeEncodeError: 

2181 return 

2182 

2183 if key == qc.Qt.Key_Space: 

2184 self.interrupt_following() 

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

2186 

2187 elif key == qc.Qt.Key_Up: 

2188 for m in self.selected_markers(): 

2189 if isinstance(m, PhaseMarker): 

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

2191 p = 0 

2192 else: 

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

2194 m.set_polarity(p) 

2195 

2196 elif key == qc.Qt.Key_Down: 

2197 for m in self.selected_markers(): 

2198 if isinstance(m, PhaseMarker): 

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

2200 p = 0 

2201 else: 

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

2203 m.set_polarity(p) 

2204 

2205 elif key == qc.Qt.Key_B: 

2206 dt = self.tmax - self.tmin 

2207 self.interrupt_following() 

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

2209 

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

2211 self.interrupt_following() 

2212 

2213 tgo = None 

2214 

2215 class TraceDummy(object): 

2216 def __init__(self, marker): 

2217 self._marker = marker 

2218 

2219 @property 

2220 def nslc_id(self): 

2221 return self._marker.one_nslc() 

2222 

2223 def marker_to_itrack(marker): 

2224 try: 

2225 return self.key_to_row.get( 

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

2227 

2228 except MarkerOneNSLCRequired: 

2229 return -1 

2230 

2231 emarker, pmarkers = self.get_active_markers() 

2232 pmarkers = [ 

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

2234 pmarkers.sort(key=lambda m: ( 

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

2236 

2237 if key == qc.Qt.Key_Backtab: 

2238 pmarkers.reverse() 

2239 

2240 smarkers = self.selected_markers() 

2241 iselected = [] 

2242 for sm in smarkers: 

2243 try: 

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

2245 except ValueError: 

2246 pass 

2247 

2248 if iselected: 

2249 icurrent = max(iselected) + 1 

2250 else: 

2251 icurrent = 0 

2252 

2253 if icurrent < len(pmarkers): 

2254 self.deselect_all() 

2255 cmarker = pmarkers[icurrent] 

2256 cmarker.selected = True 

2257 tgo = cmarker.tmin 

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

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

2260 

2261 itrack = marker_to_itrack(cmarker) 

2262 if itrack != -1: 

2263 if itrack < self.shown_tracks_range[0]: 

2264 self.scroll_tracks( 

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

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

2267 self.scroll_tracks( 

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

2269 

2270 if itrack not in self.track_to_nslc_ids: 

2271 self.go_to_selection() 

2272 

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

2274 smarkers = self.selected_markers() 

2275 tgo = None 

2276 dir = str(keytext) 

2277 if smarkers: 

2278 tmid = smarkers[0].tmin 

2279 for smarker in smarkers: 

2280 if dir == 'n': 

2281 tmid = max(smarker.tmin, tmid) 

2282 else: 

2283 tmid = min(smarker.tmin, tmid) 

2284 

2285 tgo = tmid 

2286 

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

2288 for marker in sorted( 

2289 self.markers, 

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

2291 

2292 t = marker.tmin 

2293 if t > tmid and \ 

2294 marker.kind in self.visible_marker_kinds and \ 

2295 (dir == 'n' or 

2296 isinstance(marker, EventMarker)): 

2297 

2298 self.deselect_all() 

2299 marker.selected = True 

2300 tgo = t 

2301 break 

2302 else: 

2303 for marker in sorted( 

2304 self.markers, 

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

2306 reverse=True): 

2307 

2308 t = marker.tmin 

2309 if t < tmid and \ 

2310 marker.kind in self.visible_marker_kinds and \ 

2311 (dir == 'p' or 

2312 isinstance(marker, EventMarker)): 

2313 self.deselect_all() 

2314 marker.selected = True 

2315 tgo = t 

2316 break 

2317 

2318 if tgo is not None: 

2319 self.interrupt_following() 

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

2321 

2322 elif keytext == 'r': 

2323 if self.pile.reload_modified(): 

2324 self.reloaded = True 

2325 

2326 elif keytext == 'R': 

2327 self.setup_snufflings() 

2328 

2329 elif key == qc.Qt.Key_Backspace: 

2330 self.remove_selected_markers() 

2331 

2332 elif keytext == 'a': 

2333 for marker in self.markers: 

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

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

2336 marker.kind in self.visible_marker_kinds): 

2337 marker.selected = True 

2338 else: 

2339 marker.selected = False 

2340 

2341 elif keytext == 'A': 

2342 for marker in self.markers: 

2343 if marker.kind in self.visible_marker_kinds: 

2344 marker.selected = True 

2345 

2346 elif keytext == 'd': 

2347 self.deselect_all() 

2348 

2349 elif keytext == 'E': 

2350 self.deactivate_event_marker() 

2351 

2352 elif keytext == 'e': 

2353 markers = self.selected_markers() 

2354 event_markers_in_spe = [ 

2355 marker for marker in markers 

2356 if not isinstance(marker, PhaseMarker)] 

2357 

2358 phase_markers = [ 

2359 marker for marker in markers 

2360 if isinstance(marker, PhaseMarker)] 

2361 

2362 if len(event_markers_in_spe) == 1: 

2363 event_marker = event_markers_in_spe[0] 

2364 if not isinstance(event_marker, EventMarker): 

2365 nslcs = list(event_marker.nslc_ids) 

2366 lat, lon = 0.0, 0.0 

2367 old = self.get_active_event() 

2368 if len(nslcs) == 1: 

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

2370 elif old is not None: 

2371 lat, lon = old.lat, old.lon 

2372 

2373 event_marker.convert_to_event_marker(lat, lon) 

2374 

2375 self.set_active_event_marker(event_marker) 

2376 event = event_marker.get_event() 

2377 for marker in phase_markers: 

2378 marker.set_event(event) 

2379 

2380 else: 

2381 for marker in event_markers_in_spe: 

2382 marker.convert_to_event_marker() 

2383 

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

2385 for marker in self.selected_markers(): 

2386 marker.set_kind(int(keytext)) 

2387 self.emit_selected_markers() 

2388 

2389 elif key in fkey_map: 

2390 self.handle_fkeys(key) 

2391 

2392 elif key == qc.Qt.Key_Escape: 

2393 if self.picking: 

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

2395 

2396 elif key == qc.Qt.Key_PageDown: 

2397 self.scroll_tracks( 

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

2399 

2400 elif key == qc.Qt.Key_PageUp: 

2401 self.scroll_tracks( 

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

2403 

2404 elif key == qc.Qt.Key_Plus: 

2405 self.zoom_tracks(0., 1.) 

2406 

2407 elif key == qc.Qt.Key_Minus: 

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

2409 

2410 elif key == qc.Qt.Key_Equal: 

2411 ntracks_shown = self.shown_tracks_range[1] - \ 

2412 self.shown_tracks_range[0] 

2413 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2414 self.zoom_tracks(0., dtracks) 

2415 

2416 elif key == qc.Qt.Key_Colon: 

2417 self.want_input.emit() 

2418 

2419 elif keytext == 'f': 

2420 self.toggle_fullscreen() 

2421 

2422 elif keytext == 'g': 

2423 self.go_to_selection() 

2424 

2425 elif keytext == 'G': 

2426 self.go_to_selection(tight=True) 

2427 

2428 elif keytext == 'm': 

2429 self.toggle_marker_editor() 

2430 

2431 elif keytext == 'c': 

2432 self.toggle_main_controls() 

2433 

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

2435 dir = 1 

2436 amount = 1 

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

2438 dir = -1 

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

2440 amount = 10 

2441 self.nudge_selected_markers(dir*amount) 

2442 else: 

2443 super().keyPressEvent(key_event) 

2444 

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

2446 self.emit_selected_markers() 

2447 

2448 self.update() 

2449 self.update_status() 

2450 

2451 def handle_fkeys(self, key): 

2452 self.set_phase_kind( 

2453 self.selected_markers(), 

2454 fkey_map[key] + 1) 

2455 self.emit_selected_markers() 

2456 

2457 def emit_selected_markers(self): 

2458 ibounds = [] 

2459 last_selected = False 

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

2461 this_selected = marker.is_selected() 

2462 if this_selected != last_selected: 

2463 ibounds.append(imarker) 

2464 

2465 last_selected = this_selected 

2466 

2467 if last_selected: 

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

2469 

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

2471 self.n_selected_markers = sum( 

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

2473 self.marker_selection_changed.emit(chunks) 

2474 

2475 def toggle_marker_editor(self): 

2476 self.panel_parent.toggle_marker_editor() 

2477 

2478 def toggle_main_controls(self): 

2479 self.panel_parent.toggle_main_controls() 

2480 

2481 def nudge_selected_markers(self, npixels): 

2482 a, b = self.time_projection.ur 

2483 c, d = self.time_projection.xr 

2484 for marker in self.selected_markers(): 

2485 if not isinstance(marker, EventMarker): 

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

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

2488 

2489 def toggle_fullscreen(self): 

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

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

2492 self.window().showNormal() 

2493 else: 

2494 if is_macos: 

2495 self.window().showMaximized() 

2496 else: 

2497 self.window().showFullScreen() 

2498 

2499 def about(self): 

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

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

2502 txt = f.read() 

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

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

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

2506 

2507 def help(self): 

2508 class MyScrollArea(qw.QScrollArea): 

2509 

2510 def sizeHint(self): 

2511 s = qc.QSize() 

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

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

2514 return s 

2515 

2516 with open(pyrocko.util.data_file( 

2517 'snuffler_help.html')) as f: 

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

2519 

2520 with open(pyrocko.util.data_file( 

2521 'snuffler_help_epilog.html')) as f: 

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

2523 

2524 for h in [hcheat, hepilog]: 

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

2526 h.setWordWrap(True) 

2527 

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

2529 

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

2531 scroller = qw.QScrollArea() 

2532 frame = qw.QFrame(scroller) 

2533 frame.setLineWidth(0) 

2534 layout = qw.QVBoxLayout() 

2535 layout.setContentsMargins(0, 0, 0, 0) 

2536 layout.setSpacing(0) 

2537 frame.setLayout(layout) 

2538 scroller.setWidget(frame) 

2539 scroller.setWidgetResizable(True) 

2540 frame.setBackgroundRole(qg.QPalette.Base) 

2541 for h in labels: 

2542 h.setParent(frame) 

2543 h.setMargin(3) 

2544 h.setTextInteractionFlags( 

2545 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2546 h.setBackgroundRole(qg.QPalette.Base) 

2547 layout.addWidget(h) 

2548 h.linkActivated.connect( 

2549 self.open_link) 

2550 

2551 if self.panel_parent is not None: 

2552 if target == 'panel': 

2553 self.panel_parent.add_panel( 

2554 name, scroller, True, volatile=False) 

2555 else: 

2556 self.panel_parent.add_tab(name, scroller) 

2557 

2558 def open_link(self, link): 

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

2560 

2561 def wheelEvent(self, wheel_event): 

2562 '' 

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

2564 

2565 n = self.wheel_pos // 120 

2566 self.wheel_pos = self.wheel_pos % 120 

2567 if n == 0: 

2568 return 

2569 

2570 amount = max( 

2571 1., 

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

2573 wdelta = amount * n 

2574 

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

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

2577 / (trmax-trmin) 

2578 

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

2580 self.zoom_tracks(anchor, wdelta) 

2581 else: 

2582 self.scroll_tracks(-wdelta) 

2583 

2584 def dragEnterEvent(self, event): 

2585 '' 

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

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

2588 event.setDropAction(qc.Qt.LinkAction) 

2589 event.accept() 

2590 

2591 def dropEvent(self, event): 

2592 '' 

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

2594 paths = list( 

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

2596 event.acceptProposedAction() 

2597 self.load(paths) 

2598 

2599 def get_phase_name(self, kind): 

2600 return self.config.get_phase_name(kind) 

2601 

2602 def set_phase_kind(self, markers, kind): 

2603 phasename = self.get_phase_name(kind) 

2604 

2605 for marker in markers: 

2606 if isinstance(marker, PhaseMarker): 

2607 if kind == 10: 

2608 marker.convert_to_marker() 

2609 else: 

2610 marker.set_phasename(phasename) 

2611 marker.set_event(self.get_active_event()) 

2612 

2613 elif isinstance(marker, EventMarker): 

2614 pass 

2615 

2616 else: 

2617 if kind != 10: 

2618 event = self.get_active_event() 

2619 marker.convert_to_phase_marker( 

2620 event, phasename, None, False) 

2621 

2622 def set_ntracks(self, ntracks): 

2623 if self.ntracks != ntracks: 

2624 self.ntracks = ntracks 

2625 if self.shown_tracks_range is not None: 

2626 low, high = self.shown_tracks_range 

2627 else: 

2628 low, high = 0, self.ntracks 

2629 

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

2631 

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

2633 

2634 low, high = range 

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

2636 high = min(self.ntracks, high) 

2637 low = max(0, low) 

2638 high = max(1, high) 

2639 

2640 if start is None: 

2641 start = float(low) 

2642 

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

2644 self.shown_tracks_range = low, high 

2645 self.shown_tracks_start = start 

2646 

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

2648 

2649 def scroll_tracks(self, shift): 

2650 shown = self.shown_tracks_range 

2651 shiftmin = -shown[0] 

2652 shiftmax = self.ntracks-shown[1] 

2653 shift = max(shiftmin, shift) 

2654 shift = min(shiftmax, shift) 

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

2656 

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

2658 

2659 self.update() 

2660 

2661 def zoom_tracks(self, anchor, delta): 

2662 ntracks_shown = self.shown_tracks_range[1] \ 

2663 - self.shown_tracks_range[0] 

2664 

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

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

2667 return 

2668 

2669 ntracks_shown += int(round(delta)) 

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

2671 

2672 u = self.shown_tracks_start 

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

2674 nv = nu + ntracks_shown 

2675 if nv > self.ntracks: 

2676 nu -= nv - self.ntracks 

2677 nv -= nv - self.ntracks 

2678 

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

2680 

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

2682 - self.shown_tracks_range[0] 

2683 

2684 self.update() 

2685 

2686 def content_time_range(self): 

2687 pile = self.get_pile() 

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

2689 if tmin is None: 

2690 tmin = initial_time_range[0] 

2691 if tmax is None: 

2692 tmax = initial_time_range[1] 

2693 

2694 return tmin, tmax 

2695 

2696 def content_deltat_range(self): 

2697 pile = self.get_pile() 

2698 

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

2700 

2701 if deltatmin is None: 

2702 deltatmin = 0.001 

2703 

2704 if deltatmax is None: 

2705 deltatmax = 1000.0 

2706 

2707 return deltatmin, deltatmax 

2708 

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

2710 if tmax < tmin: 

2711 tmin, tmax = tmax, tmin 

2712 

2713 deltatmin = self.content_deltat_range()[0] 

2714 dt = deltatmin * self.visible_length * 0.95 

2715 

2716 if dt == 0.0: 

2717 dt = 1.0 

2718 

2719 if tight: 

2720 if tmax != tmin: 

2721 dtm = tmax - tmin 

2722 tmin -= dtm*0.1 

2723 tmax += dtm*0.1 

2724 return tmin, tmax 

2725 else: 

2726 tcenter = (tmin + tmax) / 2. 

2727 tmin = tcenter - 0.5*dt 

2728 tmax = tcenter + 0.5*dt 

2729 return tmin, tmax 

2730 

2731 if tmax-tmin < dt: 

2732 vmin, vmax = self.get_time_range() 

2733 dt = min(vmax - vmin, dt) 

2734 

2735 tcenter = (tmin+tmax)/2. 

2736 etmin, etmax = tmin, tmax 

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

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

2739 dtm = tmax-tmin 

2740 if etmin == tmin: 

2741 tmin -= dtm*0.1 

2742 if etmax == tmax: 

2743 tmax += dtm*0.1 

2744 

2745 else: 

2746 dtm = tmax-tmin 

2747 tmin -= dtm*0.1 

2748 tmax += dtm*0.1 

2749 

2750 return tmin, tmax 

2751 

2752 def go_to_selection(self, tight=False): 

2753 markers = self.selected_markers() 

2754 if markers: 

2755 tmax, tmin = self.content_time_range() 

2756 for marker in markers: 

2757 tmin = min(tmin, marker.tmin) 

2758 tmax = max(tmax, marker.tmax) 

2759 

2760 else: 

2761 if tight: 

2762 vmin, vmax = self.get_time_range() 

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

2764 else: 

2765 tmin, tmax = self.content_time_range() 

2766 

2767 tmin, tmax = self.make_good_looking_time_range( 

2768 tmin, tmax, tight=tight) 

2769 

2770 self.interrupt_following() 

2771 self.set_time_range(tmin, tmax) 

2772 self.update() 

2773 

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

2775 tmax = t 

2776 if tlen is not None: 

2777 tmax = t+tlen 

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

2779 self.interrupt_following() 

2780 self.set_time_range(tmin, tmax) 

2781 self.update() 

2782 

2783 def go_to_event_by_name(self, name): 

2784 for marker in self.markers: 

2785 if isinstance(marker, EventMarker): 

2786 event = marker.get_event() 

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

2788 tmin, tmax = self.make_good_looking_time_range( 

2789 event.time, event.time) 

2790 

2791 self.interrupt_following() 

2792 self.set_time_range(tmin, tmax) 

2793 

2794 def printit(self): 

2795 from ..qt_compat import qprint 

2796 printer = qprint.QPrinter() 

2797 printer.setOrientation(qprint.QPrinter.Landscape) 

2798 

2799 dialog = qprint.QPrintDialog(printer, self) 

2800 dialog.setWindowTitle('Print') 

2801 

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

2803 return 

2804 

2805 painter = qg.QPainter() 

2806 painter.begin(printer) 

2807 page = printer.pageRect() 

2808 self.drawit( 

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

2810 

2811 painter.end() 

2812 

2813 def savesvg(self, fn=None): 

2814 

2815 if not fn: 

2816 fn, _ = qw.QFileDialog.getSaveFileName( 

2817 self, 

2818 'Save as SVG|PNG', 

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

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

2821 options=qfiledialog_options) 

2822 

2823 if fn == '': 

2824 return 

2825 

2826 fn = str(fn) 

2827 

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

2829 try: 

2830 w, h = 842, 595 

2831 margin = 0.025 

2832 m = max(w, h)*margin 

2833 

2834 generator = qsvg.QSvgGenerator() 

2835 generator.setFileName(fn) 

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

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

2838 

2839 painter = qg.QPainter() 

2840 painter.begin(generator) 

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

2842 painter.end() 

2843 

2844 except Exception as e: 

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

2846 

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

2848 pixmap = self.grab() 

2849 

2850 try: 

2851 pixmap.save(fn) 

2852 

2853 except Exception as e: 

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

2855 

2856 else: 

2857 self.fail( 

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

2859 '".png".') 

2860 

2861 def paintEvent(self, paint_ev): 

2862 ''' 

2863 Called by QT whenever widget needs to be painted. 

2864 ''' 

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

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

2867 if self.in_paint_event: 

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

2869 return 

2870 

2871 self.in_paint_event = True 

2872 

2873 painter = qg.QPainter(self) 

2874 

2875 if self.menuitem_antialias.isChecked(): 

2876 painter.setRenderHint(qg.QPainter.Antialiasing) 

2877 

2878 self.drawit(painter) 

2879 

2880 logger.debug( 

2881 'Time spent drawing: ' 

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

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

2884 (self.timer_draw - self.timer_cutout)) 

2885 

2886 logger.debug( 

2887 'Time spent processing:' 

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

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

2890 self.timer_cutout.get()) 

2891 

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

2893 self.time_last_painted = time.time() 

2894 self.in_paint_event = False 

2895 

2896 def determine_box_styles(self): 

2897 

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

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

2900 istyle = 0 

2901 trace_styles = {} 

2902 for itr, tr in enumerate(traces): 

2903 if itr > 0: 

2904 other = traces[itr-1] 

2905 if not ( 

2906 other.nslc_id == tr.nslc_id 

2907 and other.deltat == tr.deltat 

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

2909 < gap_lap_tolerance*tr.deltat): 

2910 

2911 istyle += 1 

2912 

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

2914 

2915 self.trace_styles = trace_styles 

2916 

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

2918 

2919 for v_projection in track_projections.values(): 

2920 v_projection.set_in_range(0., 1.) 

2921 

2922 def selector(x): 

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

2924 

2925 if self.trace_filter is not None: 

2926 def tselector(x): 

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

2928 

2929 else: 

2930 tselector = selector 

2931 

2932 traces = list(self.pile.iter_traces( 

2933 group_selector=selector, trace_selector=tselector)) 

2934 

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

2936 

2937 def drawbox(itrack, istyle, traces): 

2938 v_projection = track_projections[itrack] 

2939 dvmin = v_projection(0.) 

2940 dvmax = v_projection(1.) 

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

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

2943 

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

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

2946 p.fillRect(rect, style.fill_brush) 

2947 p.setPen(style.frame_pen) 

2948 p.drawRect(rect) 

2949 

2950 traces_by_style = {} 

2951 for itr, tr in enumerate(traces): 

2952 gt = self.gather(tr) 

2953 if gt not in self.key_to_row: 

2954 continue 

2955 

2956 itrack = self.key_to_row[gt] 

2957 if itrack not in track_projections: 

2958 continue 

2959 

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

2961 

2962 if len(traces) < 500: 

2963 drawbox(itrack, istyle, [tr]) 

2964 else: 

2965 if (itrack, istyle) not in traces_by_style: 

2966 traces_by_style[itrack, istyle] = [] 

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

2968 

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

2970 drawbox(itrack, istyle, traces) 

2971 

2972 def draw_visible_markers( 

2973 self, p, vcenter_projection, primary_pen): 

2974 

2975 try: 

2976 markers = self.markers.with_key_in_limited( 

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

2978 

2979 except pyrocko.pile.TooMany: 

2980 tmin = self.markers[0].tmin 

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

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

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

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

2985 v0, _ = vcenter_projection.get_out_range() 

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

2987 

2988 p.save() 

2989 

2990 pen = qg.QPen(primary_pen) 

2991 pen.setWidth(2) 

2992 pen.setStyle(qc.Qt.DotLine) 

2993 # pat = [5., 3.] 

2994 # pen.setDashPattern(pat) 

2995 p.setPen(pen) 

2996 

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

2998 s_selected = ' (all selected)' 

2999 elif self.n_selected_markers > 0: 

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

3001 else: 

3002 s_selected = '' 

3003 

3004 draw_label( 

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

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

3007 label_bg, 'LB') 

3008 

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

3010 p.drawLine(line) 

3011 p.restore() 

3012 

3013 return 

3014 

3015 for marker in markers: 

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

3017 and marker.kind in self.visible_marker_kinds: 

3018 

3019 marker.draw( 

3020 p, self.time_projection, vcenter_projection, 

3021 with_label=True) 

3022 

3023 def get_squirrel(self): 

3024 try: 

3025 return self.pile._squirrel 

3026 except AttributeError: 

3027 return None 

3028 

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

3030 sq = self.get_squirrel() 

3031 if sq is None: 

3032 return 

3033 

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

3035 v_projection = track_projections[itrack] 

3036 dvmin = v_projection(0.) 

3037 dvmax = v_projection(1.) 

3038 dtmin = time_projection.clipped(tmin, 0) 

3039 dtmax = time_projection.clipped(tmax, 1) 

3040 

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

3042 p.fillRect(rect, style.fill_brush) 

3043 p.setPen(style.frame_pen) 

3044 p.drawRect(rect) 

3045 

3046 pattern_list = [] 

3047 pattern_to_itrack = {} 

3048 for key in self.track_keys: 

3049 itrack = self.key_to_row[key] 

3050 if itrack not in track_projections: 

3051 continue 

3052 

3053 pattern = self.track_patterns[itrack] 

3054 pattern_to_itrack[tuple(pattern)] = itrack 

3055 pattern_list.append(tuple(pattern)) 

3056 

3057 vmin, vmax = self.get_time_range() 

3058 

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

3060 for coverage in sq.get_coverage( 

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

3062 itrack = pattern_to_itrack[coverage.pattern.nslc] 

3063 

3064 if coverage.changes is None: 

3065 drawbox( 

3066 itrack, coverage.tmin, coverage.tmax, 

3067 box_styles_coverage[kind][0]) 

3068 else: 

3069 t = None 

3070 pcount = 0 

3071 for tb, count in coverage.changes: 

3072 if t is not None and tb > t: 

3073 if pcount > 0: 

3074 drawbox( 

3075 itrack, t, tb, 

3076 box_styles_coverage[kind][ 

3077 min(len(box_styles_coverage)-1, 

3078 pcount)]) 

3079 

3080 t = tb 

3081 pcount = count 

3082 

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

3084 ''' 

3085 This performs the actual drawing. 

3086 ''' 

3087 

3088 self.timer_draw.start() 

3089 show_boxes = self.menuitem_showboxes.isChecked() 

3090 sq = self.get_squirrel() 

3091 

3092 if self.gather is None: 

3093 self.set_gathering() 

3094 

3095 if self.pile_has_changed: 

3096 

3097 if not self.sortingmode_change_delayed(): 

3098 self.sortingmode_change() 

3099 

3100 if show_boxes and sq is None: 

3101 self.determine_box_styles() 

3102 

3103 self.pile_has_changed = False 

3104 

3105 if h is None: 

3106 h = float(self.height()) 

3107 if w is None: 

3108 w = float(self.width()) 

3109 

3110 if printmode: 

3111 primary_color = (0, 0, 0) 

3112 else: 

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

3114 

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

3116 

3117 ax_h = float(self.ax_height) 

3118 

3119 vbottom_ax_projection = Projection() 

3120 vtop_ax_projection = Projection() 

3121 vcenter_projection = Projection() 

3122 

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

3124 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3125 vtop_ax_projection.set_out_range(0., ax_h) 

3126 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3127 vcenter_projection.set_in_range(0., 1.) 

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

3129 

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

3131 track_projections = {} 

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

3133 proj = Projection() 

3134 proj.set_out_range( 

3135 self.track_to_screen(i+0.05), 

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

3137 

3138 track_projections[i] = proj 

3139 

3140 if self.tmin > self.tmax: 

3141 return 

3142 

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

3144 vbottom_ax_projection.set_in_range(0, ax_h) 

3145 

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

3147 

3148 yscaler = pyrocko.plot.AutoScaler() 

3149 

3150 p.setPen(primary_pen) 

3151 

3152 font = qg.QFont() 

3153 font.setBold(True) 

3154 

3155 axannotfont = qg.QFont() 

3156 axannotfont.setBold(True) 

3157 axannotfont.setPointSize(8) 

3158 

3159 processed_traces = self.prepare_cutout2( 

3160 self.tmin, self.tmax, 

3161 trace_selector=self.trace_selector, 

3162 degap=self.menuitem_degap.isChecked(), 

3163 demean=self.menuitem_demean.isChecked()) 

3164 

3165 if not printmode and show_boxes: 

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

3167 or (self.view_mode is ViewMode.Waterfall 

3168 and not processed_traces): 

3169 

3170 if sq is None: 

3171 self.draw_trace_boxes( 

3172 p, self.time_projection, track_projections) 

3173 

3174 else: 

3175 self.draw_coverage( 

3176 p, self.time_projection, track_projections) 

3177 

3178 p.setFont(font) 

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

3180 

3181 color_lookup = dict( 

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

3183 

3184 self.track_to_nslc_ids = {} 

3185 nticks = 0 

3186 annot_labels = [] 

3187 

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

3189 waterfall = self.waterfall 

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

3191 waterfall.set_traces(processed_traces) 

3192 waterfall.set_cmap(self.waterfall_cmap) 

3193 waterfall.set_integrate(self.waterfall_integrate) 

3194 waterfall.set_clip( 

3195 self.waterfall_clip_min, self.waterfall_clip_max) 

3196 waterfall.show_absolute_values( 

3197 self.waterfall_show_absolute) 

3198 

3199 rect = qc.QRectF( 

3200 0, self.ax_height, 

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

3202 ) 

3203 waterfall.draw_waterfall(p, rect=rect) 

3204 

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

3206 show_scales = self.menuitem_showscalerange.isChecked() \ 

3207 or self.menuitem_showscaleaxis.isChecked() 

3208 

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

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

3211 - self.track_to_screen(0.05) 

3212 

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

3214 

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

3216 if self.menuitem_showscaleaxis.isChecked() \ 

3217 else 15 

3218 

3219 yscaler = pyrocko.plot.AutoScaler( 

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

3221 snap=show_scales 

3222 and not self.menuitem_showscaleaxis.isChecked()) 

3223 

3224 data_ranges = pyrocko.trace.minmax( 

3225 processed_traces, 

3226 key=self.scaling_key, 

3227 mode=self.scaling_base[0], 

3228 outer_mode=self.scaling_base[1]) 

3229 

3230 if not self.menuitem_fixscalerange.isChecked(): 

3231 self.old_data_ranges = data_ranges 

3232 else: 

3233 data_ranges.update(self.old_data_ranges) 

3234 

3235 self.apply_scaling_hooks(data_ranges) 

3236 

3237 trace_to_itrack = {} 

3238 track_scaling_keys = {} 

3239 track_scaling_colors = {} 

3240 for trace in processed_traces: 

3241 gt = self.gather(trace) 

3242 if gt not in self.key_to_row: 

3243 continue 

3244 

3245 itrack = self.key_to_row[gt] 

3246 if itrack not in track_projections: 

3247 continue 

3248 

3249 trace_to_itrack[trace] = itrack 

3250 

3251 if itrack not in self.track_to_nslc_ids: 

3252 self.track_to_nslc_ids[itrack] = set() 

3253 

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

3255 

3256 if itrack not in track_scaling_keys: 

3257 track_scaling_keys[itrack] = set() 

3258 

3259 scaling_key = self.scaling_key(trace) 

3260 track_scaling_keys[itrack].add(scaling_key) 

3261 

3262 color = pyrocko.plot.color( 

3263 color_lookup[self.color_gather(trace)]) 

3264 

3265 k = itrack, scaling_key 

3266 if k not in track_scaling_colors \ 

3267 and self.menuitem_colortraces.isChecked(): 

3268 track_scaling_colors[k] = color 

3269 else: 

3270 track_scaling_colors[k] = primary_color 

3271 

3272 # y axes, zero lines 

3273 trace_projections = {} 

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

3275 if itrack not in track_scaling_keys: 

3276 continue 

3277 uoff = 0 

3278 for scaling_key in track_scaling_keys[itrack]: 

3279 data_range = data_ranges[scaling_key] 

3280 dymin, dymax = data_range 

3281 ymin, ymax, yinc = yscaler.make_scale( 

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

3283 iexp = yscaler.make_exp(yinc) 

3284 factor = 10**iexp 

3285 trace_projection = track_projections[itrack].copy() 

3286 trace_projection.set_in_range(ymax, ymin) 

3287 trace_projections[itrack, scaling_key] = \ 

3288 trace_projection 

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

3290 vmin, vmax = trace_projection.get_out_range() 

3291 umax_zeroline = umax 

3292 uoffnext = uoff 

3293 

3294 if show_scales: 

3295 pen = qg.QPen(primary_pen) 

3296 k = itrack, scaling_key 

3297 if k in track_scaling_colors: 

3298 c = qg.QColor(*track_scaling_colors[ 

3299 itrack, scaling_key]) 

3300 

3301 pen.setColor(c) 

3302 

3303 p.setPen(pen) 

3304 if nlinesavail > 3: 

3305 if self.menuitem_showscaleaxis.isChecked(): 

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

3307 ny_annot = int( 

3308 math.floor(ymax/yinc) 

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

3310 

3311 for iy_annot in range(ny_annot): 

3312 y = ymin_annot + iy_annot*yinc 

3313 v = trace_projection(y) 

3314 line = qc.QLineF( 

3315 umax-10-uoff, v, umax-uoff, v) 

3316 

3317 p.drawLine(line) 

3318 if iy_annot == ny_annot - 1 \ 

3319 and iexp != 0: 

3320 sexp = ' &times; ' \ 

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

3322 else: 

3323 sexp = '' 

3324 

3325 snum = num_to_html(y/factor) 

3326 lab = Label( 

3327 p, 

3328 umax-20-uoff, 

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

3330 label_bg=None, 

3331 anchor='MR', 

3332 font=axannotfont, 

3333 color=c) 

3334 

3335 uoffnext = max( 

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

3337 

3338 annot_labels.append(lab) 

3339 if y == 0.: 

3340 umax_zeroline = \ 

3341 umax - 20 \ 

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

3343 - uoff 

3344 else: 

3345 if not show_boxes: 

3346 qpoints = make_QPolygonF( 

3347 [umax-20-uoff, 

3348 umax-10-uoff, 

3349 umax-10-uoff, 

3350 umax-20-uoff], 

3351 [vmax, vmax, vmin, vmin]) 

3352 p.drawPolyline(qpoints) 

3353 

3354 snum = num_to_html(ymin) 

3355 labmin = Label( 

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

3357 label_bg=None, 

3358 anchor='BR', 

3359 font=axannotfont, 

3360 color=c) 

3361 

3362 annot_labels.append(labmin) 

3363 snum = num_to_html(ymax) 

3364 labmax = Label( 

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

3366 label_bg=None, 

3367 anchor='TR', 

3368 font=axannotfont, 

3369 color=c) 

3370 

3371 annot_labels.append(labmax) 

3372 

3373 for lab in (labmin, labmax): 

3374 uoffnext = max( 

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

3376 

3377 if self.menuitem_showzeroline.isChecked(): 

3378 v = trace_projection(0.) 

3379 if vmin <= v <= vmax: 

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

3381 p.drawLine(line) 

3382 

3383 uoff = uoffnext 

3384 

3385 p.setFont(font) 

3386 p.setPen(primary_pen) 

3387 for trace in processed_traces: 

3388 if self.view_mode is not ViewMode.Wiggle: 

3389 break 

3390 

3391 if trace not in trace_to_itrack: 

3392 continue 

3393 

3394 itrack = trace_to_itrack[trace] 

3395 scaling_key = self.scaling_key(trace) 

3396 trace_projection = trace_projections[ 

3397 itrack, scaling_key] 

3398 

3399 vdata = trace_projection(trace.get_ydata()) 

3400 

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

3402 udata_max = float(self.time_projection( 

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

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

3405 

3406 qpoints = make_QPolygonF(udata, vdata) 

3407 

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

3409 vmin, vmax = trace_projection.get_out_range() 

3410 

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

3412 

3413 if self.menuitem_cliptraces.isChecked(): 

3414 p.setClipRect(trackrect) 

3415 

3416 if self.menuitem_colortraces.isChecked(): 

3417 color = pyrocko.plot.color( 

3418 color_lookup[self.color_gather(trace)]) 

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

3420 p.setPen(pen) 

3421 

3422 p.drawPolyline(qpoints) 

3423 

3424 if self.floating_marker: 

3425 self.floating_marker.draw_trace( 

3426 self, p, trace, 

3427 self.time_projection, trace_projection, 1.0) 

3428 

3429 for marker in self.markers.with_key_in( 

3430 self.tmin - self.markers_deltat_max, 

3431 self.tmax): 

3432 

3433 if marker.tmin < self.tmax \ 

3434 and self.tmin < marker.tmax \ 

3435 and marker.kind \ 

3436 in self.visible_marker_kinds: 

3437 marker.draw_trace( 

3438 self, p, trace, self.time_projection, 

3439 trace_projection, 1.0) 

3440 

3441 p.setPen(primary_pen) 

3442 

3443 if self.menuitem_cliptraces.isChecked(): 

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

3445 

3446 if self.floating_marker: 

3447 self.floating_marker.draw( 

3448 p, self.time_projection, vcenter_projection) 

3449 

3450 self.draw_visible_markers( 

3451 p, vcenter_projection, primary_pen) 

3452 

3453 p.setPen(primary_pen) 

3454 while font.pointSize() > 2: 

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

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

3457 - self.track_to_screen(0.05) 

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

3459 if nlinesavail > 1: 

3460 break 

3461 

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

3463 

3464 p.setFont(font) 

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

3466 

3467 for key in self.track_keys: 

3468 itrack = self.key_to_row[key] 

3469 if itrack in track_projections: 

3470 plabel = ' '.join( 

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

3472 lx = 10 

3473 ly = self.track_to_screen(itrack+0.5) 

3474 

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

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

3477 continue 

3478 

3479 contains_cursor = \ 

3480 self.track_to_screen(itrack) \ 

3481 < mouse_pos.y() \ 

3482 < self.track_to_screen(itrack+1) 

3483 

3484 if not contains_cursor: 

3485 continue 

3486 

3487 font_large = p.font() 

3488 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3489 p.setFont(font_large) 

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

3491 p.setFont(font) 

3492 

3493 for lab in annot_labels: 

3494 lab.draw() 

3495 

3496 self.timer_draw.stop() 

3497 

3498 def see_data_params(self): 

3499 

3500 min_deltat = self.content_deltat_range()[0] 

3501 

3502 # determine padding and downampling requirements 

3503 if self.lowpass is not None: 

3504 deltat_target = 1./self.lowpass * 0.25 

3505 ndecimate = min( 

3506 50, 

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

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

3509 else: 

3510 ndecimate = 1 

3511 tpad = min_deltat*5. 

3512 

3513 if self.highpass is not None: 

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

3515 

3516 nsee_points_per_trace = 5000*10 

3517 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3518 

3519 return ndecimate, tpad, tsee 

3520 

3521 def clean_update(self): 

3522 self.cached_processed_traces = None 

3523 self.update() 

3524 

3525 def get_adequate_tpad(self): 

3526 tpad = 0. 

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

3528 if f is not None: 

3529 tpad = max(tpad, 1.0/f) 

3530 

3531 for snuffling in self.snufflings: 

3532 if snuffling._post_process_hook_enabled \ 

3533 or snuffling._pre_process_hook_enabled: 

3534 

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

3536 

3537 return tpad 

3538 

3539 def prepare_cutout2( 

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

3541 demean=True, nmax=6000): 

3542 

3543 if self.pile.is_empty(): 

3544 return [] 

3545 

3546 nmax = self.visible_length 

3547 

3548 self.timer_cutout.start() 

3549 

3550 tsee = tmax-tmin 

3551 min_deltat_wo_decimate = tsee/nmax 

3552 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3553 

3554 min_deltat_allow = min_deltat_wo_decimate 

3555 if self.lowpass is not None: 

3556 target_deltat_lp = 0.25/self.lowpass 

3557 if target_deltat_lp > min_deltat_wo_decimate: 

3558 min_deltat_allow = min_deltat_w_decimate 

3559 

3560 min_deltat_allow = math.exp( 

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

3562 

3563 tmin_ = tmin 

3564 tmax_ = tmax 

3565 

3566 # fetch more than needed? 

3567 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3571 

3572 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3573 lphp = self.menuitem_lphp.isChecked() 

3574 ads = self.menuitem_allowdownsampling.isChecked() 

3575 

3576 tpad = self.get_adequate_tpad() 

3577 tpad = max(tpad, tsee) 

3578 

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

3580 vec = ( 

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

3582 self.highpass, fft_filtering, lphp, 

3583 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3584 ads, self.pile.get_update_count()) 

3585 

3586 if (self.cached_vec 

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

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

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

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

3591 and self.cached_processed_traces is not None): 

3592 

3593 logger.debug('Using cached traces') 

3594 processed_traces = self.cached_processed_traces 

3595 

3596 else: 

3597 processed_traces = [] 

3598 if self.pile.deltatmax >= min_deltat_allow: 

3599 

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

3601 def group_selector(gr): 

3602 return gr.deltatmax >= min_deltat_allow 

3603 

3604 kwargs = dict(group_selector=group_selector) 

3605 else: 

3606 kwargs = {} 

3607 

3608 if trace_selector is not None: 

3609 def trace_selectorx(tr): 

3610 return tr.deltat >= min_deltat_allow \ 

3611 and trace_selector(tr) 

3612 else: 

3613 def trace_selectorx(tr): 

3614 return tr.deltat >= min_deltat_allow 

3615 

3616 for traces in self.pile.chopper( 

3617 tmin=tmin, tmax=tmax, tpad=tpad, 

3618 want_incomplete=True, 

3619 degap=degap, 

3620 maxgap=gap_lap_tolerance, 

3621 maxlap=gap_lap_tolerance, 

3622 keep_current_files_open=True, 

3623 trace_selector=trace_selectorx, 

3624 accessor_id=id(self), 

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

3626 include_last=True, **kwargs): 

3627 

3628 if demean: 

3629 for tr in traces: 

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

3631 continue 

3632 y = tr.get_ydata() 

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

3634 

3635 traces = self.pre_process_hooks(traces) 

3636 

3637 for trace in traces: 

3638 

3639 if not (trace.meta 

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

3641 

3642 if fft_filtering: 

3643 but = pyrocko.response.ButterworthResponse 

3644 multres = pyrocko.response.MultiplyResponse 

3645 if self.lowpass is not None \ 

3646 or self.highpass is not None: 

3647 

3648 it = num.arange( 

3649 trace.data_len(), dtype=float) 

3650 detr_data, m, b = detrend( 

3651 it, trace.get_ydata()) 

3652 

3653 trace.set_ydata(detr_data) 

3654 

3655 freqs, fdata = trace.spectrum( 

3656 pad_to_pow2=True, tfade=None) 

3657 

3658 nfreqs = fdata.size 

3659 

3660 key = (trace.deltat, nfreqs) 

3661 

3662 if key not in self.tf_cache: 

3663 resps = [] 

3664 if self.lowpass is not None: 

3665 resps.append(but( 

3666 order=4, 

3667 corner=self.lowpass, 

3668 type='low')) 

3669 

3670 if self.highpass is not None: 

3671 resps.append(but( 

3672 order=4, 

3673 corner=self.highpass, 

3674 type='high')) 

3675 

3676 resp = multres(resps) 

3677 self.tf_cache[key] = \ 

3678 resp.evaluate(freqs) 

3679 

3680 filtered_data = num.fft.irfft( 

3681 fdata*self.tf_cache[key] 

3682 )[:trace.data_len()] 

3683 

3684 retrended_data = retrend( 

3685 it, filtered_data, m, b) 

3686 

3687 trace.set_ydata(retrended_data) 

3688 

3689 else: 

3690 

3691 if ads and self.lowpass is not None: 

3692 while trace.deltat \ 

3693 < min_deltat_wo_decimate: 

3694 

3695 trace.downsample(2, demean=False) 

3696 

3697 fmax = 0.5/trace.deltat 

3698 if not lphp and ( 

3699 self.lowpass is not None 

3700 and self.highpass is not None 

3701 and self.lowpass < fmax 

3702 and self.highpass < fmax 

3703 and self.highpass < self.lowpass): 

3704 

3705 trace.bandpass( 

3706 2, self.highpass, self.lowpass) 

3707 else: 

3708 if self.lowpass is not None: 

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

3710 trace.lowpass( 

3711 4, self.lowpass, 

3712 demean=False) 

3713 

3714 if self.highpass is not None: 

3715 if self.lowpass is None \ 

3716 or self.highpass \ 

3717 < self.lowpass: 

3718 

3719 if self.highpass < \ 

3720 0.5/trace.deltat: 

3721 trace.highpass( 

3722 4, self.highpass, 

3723 demean=False) 

3724 

3725 processed_traces.append(trace) 

3726 

3727 if self.rotate != 0.0: 

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

3729 cphi = math.cos(phi) 

3730 sphi = math.sin(phi) 

3731 for a in processed_traces: 

3732 for b in processed_traces: 

3733 if (a.network == b.network 

3734 and a.station == b.station 

3735 and a.location == b.location 

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

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

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

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

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

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

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

3743 

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

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

3746 a.set_ydata(aydata) 

3747 b.set_ydata(bydata) 

3748 

3749 processed_traces = self.post_process_hooks(processed_traces) 

3750 

3751 self.cached_processed_traces = processed_traces 

3752 self.cached_vec = vec 

3753 

3754 chopped_traces = [] 

3755 for trace in processed_traces: 

3756 chop_tmin = tmin_ - trace.deltat*4 

3757 chop_tmax = tmax_ + trace.deltat*4 

3758 

3759 try: 

3760 ctrace = trace.chop( 

3761 chop_tmin, chop_tmax, 

3762 inplace=False) 

3763 

3764 except pyrocko.trace.NoData: 

3765 continue 

3766 

3767 if ctrace.data_len() < 2: 

3768 continue 

3769 

3770 chopped_traces.append(ctrace) 

3771 

3772 self.timer_cutout.stop() 

3773 return chopped_traces 

3774 

3775 def pre_process_hooks(self, traces): 

3776 for snuffling in self.snufflings: 

3777 if snuffling._pre_process_hook_enabled: 

3778 traces = snuffling.pre_process_hook(traces) 

3779 

3780 return traces 

3781 

3782 def post_process_hooks(self, traces): 

3783 for snuffling in self.snufflings: 

3784 if snuffling._post_process_hook_enabled: 

3785 traces = snuffling.post_process_hook(traces) 

3786 

3787 return traces 

3788 

3789 def visible_length_change(self, ignore=None): 

3790 for menuitem, vlen in self.menuitems_visible_length: 

3791 if menuitem.isChecked(): 

3792 self.visible_length = vlen 

3793 

3794 def scaling_base_change(self, ignore=None): 

3795 for menuitem, scaling_base in self.menuitems_scaling_base: 

3796 if menuitem.isChecked(): 

3797 self.scaling_base = scaling_base 

3798 

3799 def scalingmode_change(self, ignore=None): 

3800 for menuitem, scaling_key in self.menuitems_scaling: 

3801 if menuitem.isChecked(): 

3802 self.scaling_key = scaling_key 

3803 self.update() 

3804 

3805 def apply_scaling_hooks(self, data_ranges): 

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

3807 hook = self.scaling_hooks[k] 

3808 hook(data_ranges) 

3809 

3810 def viewmode_change(self, ignore=True): 

3811 for item, mode in self.menuitems_viewmode: 

3812 if item.isChecked(): 

3813 self.view_mode = mode 

3814 break 

3815 else: 

3816 raise AttributeError('unknown view mode') 

3817 

3818 items_waterfall_disabled = ( 

3819 self.menuitem_showscaleaxis, 

3820 self.menuitem_showscalerange, 

3821 self.menuitem_showzeroline, 

3822 self.menuitem_colortraces, 

3823 self.menuitem_cliptraces, 

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

3825 ) 

3826 

3827 if self.view_mode is ViewMode.Waterfall: 

3828 self.parent().show_colorbar_ctrl(True) 

3829 self.parent().show_gain_ctrl(False) 

3830 

3831 for item in items_waterfall_disabled: 

3832 item.setDisabled(True) 

3833 

3834 self.visible_length = 180. 

3835 else: 

3836 self.parent().show_colorbar_ctrl(False) 

3837 self.parent().show_gain_ctrl(True) 

3838 

3839 for item in items_waterfall_disabled: 

3840 item.setDisabled(False) 

3841 

3842 self.visible_length_change() 

3843 self.update() 

3844 

3845 def set_scaling_hook(self, k, hook): 

3846 self.scaling_hooks[k] = hook 

3847 

3848 def remove_scaling_hook(self, k): 

3849 del self.scaling_hooks[k] 

3850 

3851 def remove_scaling_hooks(self): 

3852 self.scaling_hooks = {} 

3853 

3854 def s_sortingmode_change(self, ignore=None): 

3855 for menuitem, valfunc in self.menuitems_ssorting: 

3856 if menuitem.isChecked(): 

3857 self._ssort = valfunc 

3858 

3859 self.sortingmode_change() 

3860 

3861 def sortingmode_change(self, ignore=None): 

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

3863 if menuitem.isChecked(): 

3864 self.set_gathering(gather, color) 

3865 

3866 self.sortingmode_change_time = time.time() 

3867 

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

3869 self.lowpass = value 

3870 self.passband_check() 

3871 self.tf_cache = {} 

3872 self.update() 

3873 

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

3875 self.highpass = value 

3876 self.passband_check() 

3877 self.tf_cache = {} 

3878 self.update() 

3879 

3880 def passband_check(self): 

3881 if self.highpass and self.lowpass \ 

3882 and self.highpass >= self.lowpass: 

3883 

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

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

3886 'deactivate the highpass.' 

3887 

3888 self.update_status() 

3889 else: 

3890 oldmess = self.message 

3891 self.message = None 

3892 if oldmess is not None: 

3893 self.update_status() 

3894 

3895 def gain_change(self, value, ignore): 

3896 self.gain = value 

3897 self.update() 

3898 

3899 def rot_change(self, value, ignore): 

3900 self.rotate = value 

3901 self.update() 

3902 

3903 def waterfall_cmap_change(self, cmap): 

3904 self.waterfall_cmap = cmap 

3905 self.update() 

3906 

3907 def waterfall_clip_change(self, clip_min, clip_max): 

3908 self.waterfall_clip_min = clip_min 

3909 self.waterfall_clip_max = clip_max 

3910 self.update() 

3911 

3912 def waterfall_show_absolute_change(self, toggle): 

3913 self.waterfall_show_absolute = toggle 

3914 self.update() 

3915 

3916 def waterfall_set_integrate(self, toggle): 

3917 self.waterfall_integrate = toggle 

3918 self.update() 

3919 

3920 def set_selected_markers(self, markers): 

3921 ''' 

3922 Set a list of markers selected 

3923 

3924 :param markers: list of markers 

3925 ''' 

3926 self.deselect_all() 

3927 for m in markers: 

3928 m.selected = True 

3929 

3930 self.update() 

3931 

3932 def deselect_all(self): 

3933 for marker in self.markers: 

3934 marker.selected = False 

3935 

3936 def animate_picking(self): 

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

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

3939 

3940 def get_nslc_ids_for_track(self, ftrack): 

3941 itrack = int(ftrack) 

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

3943 

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

3945 if self.picking: 

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

3947 self.picking = None 

3948 self.picking_down = None 

3949 self.picking_timer.stop() 

3950 self.picking_timer = None 

3951 if not abort: 

3952 self.add_marker(self.floating_marker) 

3953 self.floating_marker.selected = True 

3954 self.emit_selected_markers() 

3955 

3956 self.floating_marker = None 

3957 

3958 def start_picking(self, ignore): 

3959 

3960 if not self.picking: 

3961 self.deselect_all() 

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

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

3964 

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

3966 self.picking.setGeometry( 

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

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

3969 

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

3971 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3973 self.floating_marker.selected = True 

3974 

3975 self.picking_timer = qc.QTimer() 

3976 self.picking_timer.timeout.connect( 

3977 self.animate_picking) 

3978 

3979 self.picking_timer.setInterval(50) 

3980 self.picking_timer.start() 

3981 

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

3983 if self.picking: 

3984 mouset = self.time_projection.rev(x) 

3985 dt = 0.0 

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

3987 if mouset < self.tmin: 

3988 dt = -(self.tmin - mouset) 

3989 else: 

3990 dt = mouset - self.tmax 

3991 ddt = self.tmax-self.tmin 

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

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

3994 

3995 x0 = x 

3996 if self.picking_down is not None: 

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

3998 

3999 w = abs(x-x0) 

4000 x0 = min(x0, x) 

4001 

4002 tmin, tmax = ( 

4003 self.time_projection.rev(x0), 

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

4005 

4006 tmin, tmax = ( 

4007 max(working_system_time_range[0], tmin), 

4008 min(working_system_time_range[1], tmax)) 

4009 

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

4011 

4012 self.picking.setGeometry( 

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

4014 

4015 ftrack = self.track_to_screen.rev(y) 

4016 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

4018 

4019 if dt != 0.0 and doshift: 

4020 self.interrupt_following() 

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

4022 

4023 self.update() 

4024 

4025 def update_status(self): 

4026 

4027 if self.message is None: 

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

4029 

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

4031 if not is_working_time(mouse_t): 

4032 return 

4033 

4034 if self.floating_marker: 

4035 tmi, tma = ( 

4036 self.floating_marker.tmin, 

4037 self.floating_marker.tmax) 

4038 

4039 tt, ms = gmtime_x(tmi) 

4040 

4041 if tmi == tma: 

4042 message = mystrftime( 

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

4044 tt=tt, milliseconds=ms) 

4045 else: 

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

4047 message = mystrftime( 

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

4049 tt=tt, milliseconds=ms) 

4050 else: 

4051 tt, ms = gmtime_x(mouse_t) 

4052 

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

4054 else: 

4055 message = self.message 

4056 

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

4058 sb.clearMessage() 

4059 sb.showMessage(message) 

4060 

4061 def set_sortingmode_change_delay_time(self, dt): 

4062 self.sortingmode_change_delay_time = dt 

4063 

4064 def sortingmode_change_delayed(self): 

4065 now = time.time() 

4066 return ( 

4067 self.sortingmode_change_delay_time is not None 

4068 and now - self.sortingmode_change_time 

4069 < self.sortingmode_change_delay_time) 

4070 

4071 def set_visible_marker_kinds(self, kinds): 

4072 self.deselect_all() 

4073 self.visible_marker_kinds = tuple(kinds) 

4074 self.emit_selected_markers() 

4075 

4076 def following(self): 

4077 return self.follow_timer is not None \ 

4078 and not self.following_interrupted() 

4079 

4080 def interrupt_following(self): 

4081 self.interactive_range_change_time = time.time() 

4082 

4083 def following_interrupted(self, now=None): 

4084 if now is None: 

4085 now = time.time() 

4086 return now - self.interactive_range_change_time \ 

4087 < self.interactive_range_change_delay_time 

4088 

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

4090 if tmax_start is None: 

4091 tmax_start = time.time() 

4092 self.show_all = False 

4093 self.follow_time = tlen 

4094 self.follow_timer = qc.QTimer(self) 

4095 self.follow_timer.timeout.connect( 

4096 self.follow_update) 

4097 self.follow_timer.setInterval(interval) 

4098 self.follow_timer.start() 

4099 self.follow_started = time.time() 

4100 self.follow_lapse = lapse 

4101 self.follow_tshift = self.follow_started - tmax_start 

4102 self.interactive_range_change_time = 0.0 

4103 

4104 def unfollow(self): 

4105 if self.follow_timer is not None: 

4106 self.follow_timer.stop() 

4107 self.follow_timer = None 

4108 self.interactive_range_change_time = 0.0 

4109 

4110 def follow_update(self): 

4111 rnow = time.time() 

4112 if self.follow_lapse is None: 

4113 now = rnow 

4114 else: 

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

4116 * self.follow_lapse 

4117 

4118 if self.following_interrupted(rnow): 

4119 return 

4120 self.set_time_range( 

4121 now-self.follow_time-self.follow_tshift, 

4122 now-self.follow_tshift) 

4123 

4124 self.update() 

4125 

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

4127 self.return_tag = return_tag 

4128 self.window().close() 

4129 

4130 def cleanup(self): 

4131 self.about_to_close.emit() 

4132 self.timer.stop() 

4133 if self.follow_timer is not None: 

4134 self.follow_timer.stop() 

4135 

4136 for snuffling in list(self.snufflings): 

4137 self.remove_snuffling(snuffling) 

4138 

4139 def set_error_message(self, key, value): 

4140 if value is None: 

4141 if key in self.error_messages: 

4142 del self.error_messages[key] 

4143 else: 

4144 self.error_messages[key] = value 

4145 

4146 def inputline_changed(self, text): 

4147 pass 

4148 

4149 def inputline_finished(self, text): 

4150 line = str(text) 

4151 

4152 toks = line.split() 

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

4154 if len(toks) >= 1: 

4155 command = toks[0].lower() 

4156 

4157 try: 

4158 quick_filter_commands = { 

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

4160 's': '*.%s.*.*', 

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

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

4163 

4164 if command in quick_filter_commands: 

4165 if len(toks) >= 2: 

4166 patterns = [ 

4167 quick_filter_commands[toks[0]] % pat 

4168 for pat in toks[1:]] 

4169 self.set_quick_filter_patterns(patterns, line) 

4170 else: 

4171 self.set_quick_filter_patterns(None) 

4172 

4173 self.update() 

4174 

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

4176 if len(toks) >= 2: 

4177 patterns = [] 

4178 if len(toks) == 2: 

4179 patterns = [toks[1]] 

4180 elif len(toks) >= 3: 

4181 x = { 

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

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

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

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

4186 

4187 if toks[1] in x: 

4188 patterns.extend( 

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

4190 

4191 for pattern in patterns: 

4192 if command == 'hide': 

4193 self.add_blacklist_pattern(pattern) 

4194 else: 

4195 self.remove_blacklist_pattern(pattern) 

4196 

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

4198 self.clear_blacklist() 

4199 

4200 clearit = True 

4201 

4202 self.update() 

4203 

4204 elif command == 'markers': 

4205 if len(toks) == 2: 

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

4207 kinds = self.all_marker_kinds 

4208 else: 

4209 kinds = [] 

4210 for x in toks[1]: 

4211 try: 

4212 kinds.append(int(x)) 

4213 except Exception: 

4214 pass 

4215 

4216 self.set_visible_marker_kinds(kinds) 

4217 

4218 elif len(toks) == 1: 

4219 self.set_visible_marker_kinds(()) 

4220 

4221 self.update() 

4222 

4223 elif command == 'scaling': 

4224 if len(toks) == 2: 

4225 hideit = False 

4226 error = 'wrong number of arguments' 

4227 

4228 if len(toks) >= 3: 

4229 vmin, vmax = [ 

4230 pyrocko.model.float_or_none(x) 

4231 for x in toks[-2:]] 

4232 

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

4234 if k in d: 

4235 if vmin is not None: 

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

4237 if vmax is not None: 

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

4239 

4240 if len(toks) == 1: 

4241 self.remove_scaling_hooks() 

4242 

4243 elif len(toks) == 3: 

4244 def hook(data_ranges): 

4245 for k in data_ranges: 

4246 upd(data_ranges, k, vmin, vmax) 

4247 

4248 self.set_scaling_hook('_', hook) 

4249 

4250 elif len(toks) == 4: 

4251 pattern = toks[1] 

4252 

4253 def hook(data_ranges): 

4254 for k in pyrocko.util.match_nslcs( 

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

4256 

4257 upd(data_ranges, k, vmin, vmax) 

4258 

4259 self.set_scaling_hook(pattern, hook) 

4260 

4261 elif command == 'goto': 

4262 toks2 = line.split(None, 1) 

4263 if len(toks2) == 2: 

4264 arg = toks2[1] 

4265 m = re.match( 

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

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

4268 if m: 

4269 tlen = None 

4270 if not m.group(1): 

4271 tlen = 12*32*24*60*60 

4272 elif not m.group(2): 

4273 tlen = 32*24*60*60 

4274 elif not m.group(3): 

4275 tlen = 24*60*60 

4276 elif not m.group(4): 

4277 tlen = 60*60 

4278 elif not m.group(5): 

4279 tlen = 60 

4280 

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

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

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

4284 t = pyrocko.util.str_to_time(arg) 

4285 self.go_to_time(t, tlen=tlen) 

4286 

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

4288 supl = '00:00:00' 

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

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

4291 tmin, tmax = self.get_time_range() 

4292 sdate = pyrocko.util.time_to_str( 

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

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

4295 self.go_to_time(t) 

4296 

4297 elif arg == 'today': 

4298 self.go_to_time( 

4299 day_start( 

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

4301 

4302 elif arg == 'yesterday': 

4303 self.go_to_time( 

4304 day_start( 

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

4306 

4307 else: 

4308 self.go_to_event_by_name(arg) 

4309 

4310 else: 

4311 raise PileViewerMainException( 

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

4313 

4314 except PileViewerMainException as e: 

4315 error = str(e) 

4316 hideit = False 

4317 

4318 return clearit, hideit, error 

4319 

4320 return PileViewerMain 

4321 

4322 

4323PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4324GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget) 

4325 

4326 

4327class LineEditWithAbort(qw.QLineEdit): 

4328 

4329 aborted = qc.pyqtSignal() 

4330 history_down = qc.pyqtSignal() 

4331 history_up = qc.pyqtSignal() 

4332 

4333 def keyPressEvent(self, key_event): 

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

4335 self.aborted.emit() 

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

4337 self.history_down.emit() 

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

4339 self.history_up.emit() 

4340 else: 

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

4342 

4343 

4344class PileViewer(qw.QFrame): 

4345 ''' 

4346 PileViewerMain + Controls + Inputline 

4347 ''' 

4348 

4349 def __init__( 

4350 self, pile, 

4351 ntracks_shown_max=20, 

4352 marker_editor_sortable=True, 

4353 use_opengl=None, 

4354 panel_parent=None, 

4355 *args): 

4356 

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

4358 

4359 layout = qw.QGridLayout() 

4360 layout.setContentsMargins(0, 0, 0, 0) 

4361 layout.setSpacing(0) 

4362 

4363 self.menu = PileViewerMenuBar(self) 

4364 

4365 if use_opengl is None: 

4366 use_opengl = is_macos 

4367 

4368 if use_opengl: 

4369 self.viewer = GLPileViewerMain( 

4370 pile, 

4371 ntracks_shown_max=ntracks_shown_max, 

4372 panel_parent=panel_parent, 

4373 menu=self.menu) 

4374 else: 

4375 self.viewer = PileViewerMain( 

4376 pile, 

4377 ntracks_shown_max=ntracks_shown_max, 

4378 panel_parent=panel_parent, 

4379 menu=self.menu) 

4380 

4381 self.marker_editor_sortable = marker_editor_sortable 

4382 

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

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

4385 

4386 self.input_area = qw.QFrame(self) 

4387 ia_layout = qw.QGridLayout() 

4388 ia_layout.setContentsMargins(11, 11, 11, 11) 

4389 self.input_area.setLayout(ia_layout) 

4390 

4391 self.inputline = LineEditWithAbort(self.input_area) 

4392 self.inputline.returnPressed.connect( 

4393 self.inputline_returnpressed) 

4394 self.inputline.editingFinished.connect( 

4395 self.inputline_finished) 

4396 self.inputline.aborted.connect( 

4397 self.inputline_aborted) 

4398 

4399 self.inputline.history_down.connect( 

4400 lambda: self.step_through_history(1)) 

4401 self.inputline.history_up.connect( 

4402 lambda: self.step_through_history(-1)) 

4403 

4404 self.inputline.textEdited.connect( 

4405 self.inputline_changed) 

4406 

4407 self.inputline.setPlaceholderText( 

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

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

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

4411 self.input_area.hide() 

4412 self.history = None 

4413 

4414 self.inputline_error_str = None 

4415 

4416 self.inputline_error = qw.QLabel() 

4417 self.inputline_error.hide() 

4418 

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

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

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

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

4423 

4424 pb = Progressbars(self) 

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

4426 self.progressbars = pb 

4427 

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

4429 self.scrollbar = scrollbar 

4430 layout.addWidget(scrollbar, 1, 1) 

4431 self.scrollbar.valueChanged.connect( 

4432 self.scrollbar_changed) 

4433 

4434 self.block_scrollbar_changes = False 

4435 

4436 self.viewer.want_input.connect( 

4437 self.inputline_show) 

4438 self.viewer.tracks_range_changed.connect( 

4439 self.tracks_range_changed) 

4440 self.viewer.pile_has_changed_signal.connect( 

4441 self.adjust_controls) 

4442 self.viewer.about_to_close.connect( 

4443 self.save_inputline_history) 

4444 

4445 self.setLayout(layout) 

4446 

4447 def cleanup(self): 

4448 self.viewer.cleanup() 

4449 

4450 def get_progressbars(self): 

4451 return self.progressbars 

4452 

4453 def inputline_show(self): 

4454 if not self.history: 

4455 self.load_inputline_history() 

4456 

4457 self.input_area.show() 

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

4459 self.inputline.selectAll() 

4460 

4461 def inputline_set_error(self, string): 

4462 self.inputline_error_str = string 

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

4464 self.inputline.selectAll() 

4465 self.inputline_error.setText(string) 

4466 self.input_area.show() 

4467 self.inputline_error.show() 

4468 

4469 def inputline_clear_error(self): 

4470 if self.inputline_error_str: 

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

4472 self.inputline_error_str = None 

4473 self.inputline_error.clear() 

4474 self.inputline_error.hide() 

4475 

4476 def inputline_changed(self, line): 

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

4478 self.inputline_clear_error() 

4479 

4480 def inputline_returnpressed(self): 

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

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

4483 

4484 if error: 

4485 self.inputline_set_error(error) 

4486 

4487 line = line.strip() 

4488 

4489 if line != '' and not error: 

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

4491 self.history.append(line) 

4492 

4493 if clearit: 

4494 

4495 self.inputline.blockSignals(True) 

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

4497 if qpat is None: 

4498 self.inputline.clear() 

4499 else: 

4500 self.inputline.setText(qinp) 

4501 self.inputline.blockSignals(False) 

4502 

4503 if hideit and not error: 

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

4505 self.input_area.hide() 

4506 

4507 self.hist_ind = len(self.history) 

4508 

4509 def inputline_aborted(self): 

4510 ''' 

4511 Hide the input line. 

4512 ''' 

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

4514 self.hist_ind = len(self.history) 

4515 self.input_area.hide() 

4516 

4517 def save_inputline_history(self): 

4518 ''' 

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

4520 ''' 

4521 if not self.history: 

4522 return 

4523 

4524 conf = pyrocko.config 

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

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

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

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

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

4530 

4531 def load_inputline_history(self): 

4532 ''' 

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

4534 ''' 

4535 conf = pyrocko.config 

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

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

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

4539 f.write('\n') 

4540 

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

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

4543 

4544 self.hist_ind = len(self.history) 

4545 

4546 def step_through_history(self, ud=1): 

4547 ''' 

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

4549 ''' 

4550 n = len(self.history) 

4551 self.hist_ind += ud 

4552 self.hist_ind %= (n + 1) 

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

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

4555 else: 

4556 self.inputline.setText('') 

4557 

4558 def inputline_finished(self): 

4559 pass 

4560 

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

4562 if self.block_scrollbar_changes: 

4563 return 

4564 

4565 self.scrollbar.blockSignals(True) 

4566 self.scrollbar.setPageStep(ihi-ilo) 

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

4568 self.scrollbar.setRange(0, vmax) 

4569 self.scrollbar.setValue(ilo) 

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

4571 self.scrollbar.blockSignals(False) 

4572 

4573 def scrollbar_changed(self, value): 

4574 self.block_scrollbar_changes = True 

4575 ilo = value 

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

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

4578 self.block_scrollbar_changes = False 

4579 self.update_contents() 

4580 

4581 def controls(self): 

4582 frame = qw.QFrame(self) 

4583 layout = qw.QGridLayout() 

4584 frame.setLayout(layout) 

4585 

4586 minfreq = 0.001 

4587 maxfreq = 1000.0 

4588 self.lowpass_control = ValControl(high_is_none=True) 

4589 self.lowpass_control.setup( 

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

4591 self.highpass_control = ValControl(low_is_none=True) 

4592 self.highpass_control.setup( 

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

4594 self.gain_control = ValControl() 

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

4596 self.rot_control = LinValControl() 

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

4598 self.colorbar_control = ColorbarControl(self) 

4599 

4600 self.lowpass_control.valchange.connect( 

4601 self.viewer.lowpass_change) 

4602 self.highpass_control.valchange.connect( 

4603 self.viewer.highpass_change) 

4604 self.gain_control.valchange.connect( 

4605 self.viewer.gain_change) 

4606 self.rot_control.valchange.connect( 

4607 self.viewer.rot_change) 

4608 self.colorbar_control.cmap_changed.connect( 

4609 self.viewer.waterfall_cmap_change 

4610 ) 

4611 self.colorbar_control.clip_changed.connect( 

4612 self.viewer.waterfall_clip_change 

4613 ) 

4614 self.colorbar_control.show_absolute_toggled.connect( 

4615 self.viewer.waterfall_show_absolute_change 

4616 ) 

4617 self.colorbar_control.show_integrate_toggled.connect( 

4618 self.viewer.waterfall_set_integrate 

4619 ) 

4620 

4621 for icontrol, control in enumerate(( 

4622 self.highpass_control, 

4623 self.lowpass_control, 

4624 self.gain_control, 

4625 self.rot_control, 

4626 self.colorbar_control)): 

4627 

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

4629 layout.addWidget(widget, icontrol, iwidget) 

4630 

4631 spacer = qw.QSpacerItem( 

4632 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4634 

4635 self.adjust_controls() 

4636 self.viewer.viewmode_change(ViewMode.Wiggle) 

4637 return frame 

4638 

4639 def marker_editor(self): 

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

4641 self, sortable=self.marker_editor_sortable) 

4642 

4643 editor.set_viewer(self.get_view()) 

4644 editor.get_marker_model().dataChanged.connect( 

4645 self.update_contents) 

4646 return editor 

4647 

4648 def adjust_controls(self): 

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

4650 maxfreq = 0.5/dtmin 

4651 minfreq = (0.5/dtmax)*0.0001 

4652 self.lowpass_control.set_range(minfreq, maxfreq) 

4653 self.highpass_control.set_range(minfreq, maxfreq) 

4654 

4655 def setup_snufflings(self): 

4656 self.viewer.setup_snufflings() 

4657 

4658 def get_view(self): 

4659 return self.viewer 

4660 

4661 def update_contents(self): 

4662 self.viewer.update() 

4663 

4664 def get_pile(self): 

4665 return self.viewer.get_pile() 

4666 

4667 def show_colorbar_ctrl(self, show): 

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

4669 w.setVisible(show) 

4670 

4671 def show_gain_ctrl(self, show): 

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

4673 w.setVisible(show)