1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

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

5from __future__ import absolute_import, print_function 

6 

7import os 

8import time 

9import calendar 

10import datetime 

11import re 

12import math 

13import logging 

14import operator 

15import copy 

16import enum 

17from itertools import groupby 

18 

19import numpy as num 

20import pyrocko.model 

21import pyrocko.pile 

22import pyrocko.trace 

23import pyrocko.response 

24import pyrocko.util 

25import pyrocko.plot 

26import pyrocko.gui.snuffling 

27import pyrocko.gui.snufflings 

28import pyrocko.gui.marker_editor 

29 

30from pyrocko.util import hpfloat, gmtime_x, mystrftime 

31 

32from .marker import associate_phases_to_events, MarkerOneNSLCRequired 

33 

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

35 PhaseMarker, make_QPolygonF, draw_label, Label, 

36 Progressbars, ColorbarControl) 

37 

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

39 

40from .pile_viewer_waterfall import TraceWaterfall 

41 

42import scipy.stats as sstats 

43import platform 

44 

45MIN_LABEL_SIZE_PT = 6 

46 

47 

48qc.QString = str 

49 

50qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \ 

51 qw.QFileDialog.DontUseSheet 

52 

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

54 

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

56 

57 

58def detrend(x, y): 

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

60 y_detrended = y - slope * x - offset 

61 return y_detrended, slope, offset 

62 

63 

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

65 return x * slope + y_detrended + offset 

66 

67 

68class Global(object): 

69 appOnDemand = None 

70 

71 

72class NSLC(object): 

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

74 self.network = n 

75 self.station = s 

76 self.location = l 

77 self.channel = c 

78 

79 

80class m_float(float): 

81 

82 def __str__(self): 

83 if abs(self) >= 10000.: 

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

85 elif abs(self) >= 1000.: 

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

87 else: 

88 return '%.5g m' % self 

89 

90 def __lt__(self, other): 

91 if other is None: 

92 return True 

93 return float(self) < float(other) 

94 

95 def __gt__(self, other): 

96 if other is None: 

97 return False 

98 return float(self) > float(other) 

99 

100 

101def m_float_or_none(x): 

102 if x is None: 

103 return None 

104 else: 

105 return m_float(x) 

106 

107 

108def make_chunks(items): 

109 ''' 

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

111 ''' 

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

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

114 

115 

116class deg_float(float): 

117 

118 def __str__(self): 

119 return '%4.0f' % self 

120 

121 def __lt__(self, other): 

122 if other is None: 

123 return True 

124 return float(self) < float(other) 

125 

126 def __gt__(self, other): 

127 if other is None: 

128 return False 

129 return float(self) > float(other) 

130 

131 

132def deg_float_or_none(x): 

133 if x is None: 

134 return None 

135 else: 

136 return deg_float(x) 

137 

138 

139class sector_int(int): 

140 

141 def __str__(self): 

142 return '[%i]' % self 

143 

144 def __lt__(self, other): 

145 if other is None: 

146 return True 

147 return int(self) < int(other) 

148 

149 def __gt__(self, other): 

150 if other is None: 

151 return False 

152 return int(self) > int(other) 

153 

154 

155def num_to_html(num): 

156 snum = '%g' % num 

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

158 if m: 

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

160 

161 return snum 

162 

163 

164gap_lap_tolerance = 5. 

165 

166 

167class ViewMode(enum.Enum): 

168 Wiggle = 1 

169 Waterfall = 2 

170 

171 

172class Timer(object): 

173 def __init__(self): 

174 self._start = None 

175 self._stop = None 

176 

177 def start(self): 

178 self._start = os.times() 

179 

180 def stop(self): 

181 self._stop = os.times() 

182 

183 def get(self): 

184 a = self._start 

185 b = self._stop 

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

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

188 else: 

189 return tuple([0.] * 5) 

190 

191 def __sub__(self, other): 

192 a = self.get() 

193 b = other.get() 

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

195 

196 

197class ObjectStyle(object): 

198 def __init__(self, frame_pen, fill_brush): 

199 self.frame_pen = frame_pen 

200 self.fill_brush = fill_brush 

201 

202 

203box_styles = [] 

204box_alpha = 100 

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

206 'scarletred'.split(): 

207 

208 box_styles.append(ObjectStyle( 

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

210 qg.QBrush(qg.QColor( 

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

212 )) 

213 

214box_styles_coverage = {} 

215 

216box_styles_coverage['waveform'] = [ 

217 ObjectStyle( 

218 qg.QPen( 

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

220 1, qc.Qt.DashLine), 

221 qg.QBrush(qg.QColor( 

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

223 ), 

224 ObjectStyle( 

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

226 qg.QBrush(qg.QColor( 

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

228 ), 

229 ObjectStyle( 

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

231 qg.QBrush(qg.QColor( 

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

233 )] 

234 

235box_styles_coverage['waveform_promise'] = [ 

236 ObjectStyle( 

237 qg.QPen( 

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

239 1, qc.Qt.DashLine), 

240 qg.QBrush(qg.QColor( 

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

242 ), 

243 ObjectStyle( 

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

245 qg.QBrush(qg.QColor( 

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

247 ), 

248 ObjectStyle( 

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

250 qg.QBrush(qg.QColor( 

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

252 )] 

253 

254sday = 60*60*24. # \ 

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

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

257 

258acceptable_tincs = num.array([ 

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

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

261 

262 

263working_system_time_range = \ 

264 pyrocko.util.working_system_time_range() 

265 

266initial_time_range = [] 

267 

268try: 

269 initial_time_range.append( 

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

271except Exception: 

272 initial_time_range.append(working_system_time_range[0]) 

273 

274try: 

275 initial_time_range.append( 

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

277except Exception: 

278 initial_time_range.append(working_system_time_range[1]) 

279 

280 

281def is_working_time(t): 

282 return working_system_time_range[0] <= t and \ 

283 t <= working_system_time_range[1] 

284 

285 

286def fancy_time_ax_format(inc): 

287 l0_fmt_brief = '' 

288 l2_fmt = '' 

289 l2_trig = 0 

290 if inc < 0.000001: 

291 l0_fmt = '.%n' 

292 l0_center = False 

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

294 l1_trig = 6 

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

296 l2_trig = 3 

297 elif inc < 0.001: 

298 l0_fmt = '.%u' 

299 l0_center = False 

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

301 l1_trig = 6 

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

303 l2_trig = 3 

304 elif inc < 1: 

305 l0_fmt = '.%r' 

306 l0_center = False 

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

308 l1_trig = 6 

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

310 l2_trig = 3 

311 elif inc < 60: 

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

313 l0_center = False 

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

315 l1_trig = 3 

316 elif inc < 3600: 

317 l0_fmt = '%H:%M' 

318 l0_center = False 

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

320 l1_trig = 3 

321 elif inc < sday: 

322 l0_fmt = '%H:%M' 

323 l0_center = False 

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

325 l1_trig = 3 

326 elif inc < smonth: 

327 l0_fmt = '%a %d' 

328 l0_fmt_brief = '%d' 

329 l0_center = True 

330 l1_fmt = '%b, %Y' 

331 l1_trig = 2 

332 elif inc < syear: 

333 l0_fmt = '%b' 

334 l0_center = True 

335 l1_fmt = '%Y' 

336 l1_trig = 1 

337 else: 

338 l0_fmt = '%Y' 

339 l0_center = False 

340 l1_fmt = '' 

341 l1_trig = 0 

342 

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

344 

345 

346def day_start(timestamp): 

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

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

349 return calendar.timegm(tts) 

350 

351 

352def month_start(timestamp): 

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

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

355 return calendar.timegm(tts) 

356 

357 

358def year_start(timestamp): 

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

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

361 return calendar.timegm(tts) 

362 

363 

364def time_nice_value(inc0): 

365 if inc0 < acceptable_tincs[0]: 

366 return pyrocko.plot.nice_value(inc0) 

367 elif inc0 > acceptable_tincs[-1]: 

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

369 else: 

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

371 return acceptable_tincs[i] 

372 

373 

374class TimeScaler(pyrocko.plot.AutoScaler): 

375 def __init__(self): 

376 pyrocko.plot.AutoScaler.__init__(self) 

377 self.mode = 'min-max' 

378 

379 def make_scale(self, data_range): 

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

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

382 

383 data_min = min(data_range) 

384 data_max = max(data_range) 

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

386 

387 mi, ma = data_min, data_max 

388 nmi = mi 

389 if self.mode != 'off': 

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

391 

392 nma = ma 

393 if self.mode != 'off': 

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

395 

396 mi, ma = nmi, nma 

397 

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

399 mi -= 1.0 

400 ma += 1.0 

401 

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

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

404 

405 # make nice tick increment 

406 if self.inc is not None: 

407 inc = self.inc 

408 else: 

409 if self.approx_ticks > 0.: 

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

411 else: 

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

413 

414 if inc == 0.0: 

415 inc = 1.0 

416 

417 if is_reverse: 

418 return ma, mi, -inc 

419 else: 

420 return mi, ma, inc 

421 

422 def make_ticks(self, data_range): 

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

424 

425 is_reverse = False 

426 if inc < 0: 

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

428 is_reverse = True 

429 

430 ticks = [] 

431 

432 if inc < sday: 

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

434 if inc < 0.001: 

435 mi_day = hpfloat(mi_day) 

436 

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

438 if inc < 0.001: 

439 base = hpfloat(base) 

440 

441 base_day = mi_day 

442 i = 0 

443 while True: 

444 tick = base+i*inc 

445 if tick > ma: 

446 break 

447 

448 tick_day = day_start(tick) 

449 if tick_day > base_day: 

450 base_day = tick_day 

451 base = base_day 

452 i = 0 

453 else: 

454 ticks.append(tick) 

455 i += 1 

456 

457 elif inc < smonth: 

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

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

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

461 if mi_day == mi: 

462 dt_base += delta 

463 i = 0 

464 while True: 

465 current = dt_base + i*delta 

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

467 if tick > ma: 

468 break 

469 ticks.append(tick) 

470 i += 1 

471 

472 elif inc < syear: 

473 mi_month = month_start(max( 

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

475 

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

477 while True: 

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

479 m += 1 

480 if m > 12: 

481 y, m = y+1, 1 

482 

483 if tick > ma: 

484 break 

485 

486 if tick >= mi: 

487 ticks.append(tick) 

488 

489 else: 

490 mi_year = year_start(max( 

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

492 

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

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

495 

496 while True: 

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

498 y += incy 

499 if tick > ma: 

500 break 

501 if tick >= mi: 

502 ticks.append(tick) 

503 

504 if is_reverse: 

505 ticks.reverse() 

506 

507 return ticks, inc 

508 

509 

510def need_l1_tick(tt, ms, l1_trig): 

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

512 

513 

514def tick_to_labels(tick, inc): 

515 tt, ms = gmtime_x(tick) 

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

517 fancy_time_ax_format(inc) 

518 

519 l0 = mystrftime(l0_fmt, tt, ms) 

520 l0_brief = mystrftime(l0_fmt_brief, tt, ms) 

521 l1, l2 = None, None 

522 if need_l1_tick(tt, ms, l1_trig): 

523 l1 = mystrftime(l1_fmt, tt, ms) 

524 if need_l1_tick(tt, ms, l2_trig): 

525 l2 = mystrftime(l2_fmt, tt, ms) 

526 

527 return l0, l0_brief, l0_center, l1, l2 

528 

529 

530def l1_l2_tick(tick, inc): 

531 tt, ms = gmtime_x(tick) 

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

533 fancy_time_ax_format(inc) 

534 

535 l1 = mystrftime(l1_fmt, tt, ms) 

536 l2 = mystrftime(l2_fmt, tt, ms) 

537 return l1, l2 

538 

539 

540class TimeAx(TimeScaler): 

541 def __init__(self, *args): 

542 TimeScaler.__init__(self, *args) 

543 

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

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

546 p.setPen(pen) 

547 font = qg.QFont() 

548 font.setBold(True) 

549 p.setFont(font) 

550 fm = p.fontMetrics() 

551 ticklen = 10 

552 pad = 10 

553 tmin, tmax = xprojection.get_in_range() 

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

555 l1_hits = 0 

556 l2_hits = 0 

557 

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

559 uumin, uumax = xprojection.get_out_range() 

560 first_tick_with_label = None 

561 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 (we assume that synthetic data is shown) 

574 if l2: 

575 l2 = None 

576 elif l1: 

577 l1 = None 

578 

579 if l0_center: 

580 ushift = (umin_approx_next-umin)/2. 

581 else: 

582 ushift = 0. 

583 

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

585 label0 = l0x 

586 rect0 = fm.boundingRect(label0) 

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

588 break 

589 

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

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

592 

593 if first_tick_with_label is None: 

594 first_tick_with_label = tick 

595 p.drawText(qc.QPointF( 

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

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

598 

599 if l1: 

600 label1 = l1 

601 rect1 = fm.boundingRect(label1) 

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

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

604 

605 p.drawText(qc.QPointF( 

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

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

608 label1) 

609 

610 l1_hits += 1 

611 

612 if l2: 

613 label2 = l2 

614 rect2 = fm.boundingRect(label2) 

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

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

617 

618 p.drawText(qc.QPointF( 

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

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

621 ticklen), label2) 

622 

623 l2_hits += 1 

624 

625 if first_tick_with_label is None: 

626 first_tick_with_label = tmin 

627 

628 l1, l2 = l1_l2_tick(first_tick_with_label, inc) 

629 

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

631 tmax - tmin < 3600*24: 

632 

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

634 if l2: 

635 l2 = None 

636 elif l1: 

637 l1 = None 

638 

639 if l1_hits == 0 and l1: 

640 label1 = l1 

641 rect1 = fm.boundingRect(label1) 

642 p.drawText(qc.QPointF( 

643 uumin+pad, 

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

645 label1) 

646 

647 l1_hits += 1 

648 

649 if l2_hits == 0 and l2: 

650 label2 = l2 

651 rect2 = fm.boundingRect(label2) 

652 p.drawText(qc.QPointF( 

653 uumin+pad, 

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

655 label2) 

656 

657 v = yprojection(0) 

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

659 

660 

661class Projection(object): 

662 def __init__(self): 

663 self.xr = 0., 1. 

664 self.ur = 0., 1. 

665 

666 def set_in_range(self, xmin, xmax): 

667 if xmax == xmin: 

668 xmax = xmin + 1. 

669 

670 self.xr = xmin, xmax 

671 

672 def get_in_range(self): 

673 return self.xr 

674 

675 def set_out_range(self, umin, umax): 

676 if umax == umin: 

677 umax = umin + 1. 

678 

679 self.ur = umin, umax 

680 

681 def get_out_range(self): 

682 return self.ur 

683 

684 def __call__(self, x): 

685 umin, umax = self.ur 

686 xmin, xmax = self.xr 

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

688 

689 def clipped(self, x): 

690 umin, umax = self.ur 

691 xmin, xmax = self.xr 

692 return min(umax, max(umin, umin + (x-xmin)*((umax-umin)/(xmax-xmin)))) 

693 

694 def rev(self, u): 

695 umin, umax = self.ur 

696 xmin, xmax = self.xr 

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

698 

699 def copy(self): 

700 return copy.copy(self) 

701 

702 

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

704 group = qw.QActionGroup(menu) 

705 group.setExclusive(True) 

706 menuitems = [] 

707 

708 for name, value, *shortcut in menudef: 

709 action = menu.addAction(name) 

710 action.setCheckable(True) 

711 action.setActionGroup(group) 

712 if shortcut: 

713 action.setShortcut(shortcut[0]) 

714 

715 menuitems.append((action, value)) 

716 if default is not None and ( 

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

718 value == default): 

719 action.setChecked(True) 

720 

721 group.triggered.connect(target) 

722 

723 if default is None: 

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

725 

726 return menuitems 

727 

728 

729def sort_actions(menu): 

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

731 for action in actions: 

732 menu.removeAction(action) 

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

734 

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

736 if help_action: 

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

738 for action in actions: 

739 menu.addAction(action) 

740 

741 

742fkey_map = dict(zip( 

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

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

745 range(10))) 

746 

747 

748class PileViewerMainException(Exception): 

749 pass 

750 

751 

752class PileViewerMenuBar(qw.QMenuBar): 

753 ... 

754 

755 

756class PileViewerMenu(qw.QMenu): 

757 ... 

758 

759 

760def MakePileViewerMainClass(base): 

761 

762 class PileViewerMain(base): 

763 

764 want_input = qc.pyqtSignal() 

765 about_to_close = qc.pyqtSignal() 

766 pile_has_changed_signal = qc.pyqtSignal() 

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

768 

769 begin_markers_add = qc.pyqtSignal(int, int) 

770 end_markers_add = qc.pyqtSignal() 

771 begin_markers_remove = qc.pyqtSignal(int, int) 

772 end_markers_remove = qc.pyqtSignal() 

773 

774 marker_selection_changed = qc.pyqtSignal(list) 

775 active_event_marker_changed = qc.pyqtSignal() 

776 

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

778 menu=None): 

779 base.__init__(self, *args) 

780 

781 self.pile = pile 

782 self.ax_height = 80 

783 self.panel_parent = panel_parent 

784 

785 self.click_tolerance = 5 

786 

787 self.ntracks_shown_max = ntracks_shown_max 

788 self.initial_ntracks_shown_max = ntracks_shown_max 

789 self.ntracks = 0 

790 self.show_all = True 

791 self.shown_tracks_range = None 

792 self.track_start = None 

793 self.track_trange = None 

794 

795 self.lowpass = None 

796 self.highpass = None 

797 self.gain = 1.0 

798 self.rotate = 0.0 

799 self.picking_down = None 

800 self.picking = None 

801 self.floating_marker = None 

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

803 self.markers_deltat_max = 0. 

804 self.n_selected_markers = 0 

805 self.all_marker_kinds = (0, 1, 2, 3, 4, 5) 

806 self.visible_marker_kinds = self.all_marker_kinds 

807 self.active_event_marker = None 

808 self.ignore_releases = 0 

809 self.message = None 

810 self.reloaded = False 

811 self.pile_has_changed = False 

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

813 

814 self.tax = TimeAx() 

815 self.setBackgroundRole(qg.QPalette.Base) 

816 self.setAutoFillBackground(True) 

817 poli = qw.QSizePolicy( 

818 qw.QSizePolicy.Expanding, 

819 qw.QSizePolicy.Expanding) 

820 

821 self.setSizePolicy(poli) 

822 self.setMinimumSize(300, 200) 

823 self.setFocusPolicy(qc.Qt.ClickFocus) 

824 

825 self.menu = menu or PileViewerMenu(self) 

826 

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

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

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

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

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

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

833 

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

835 

836 self.snufflings_menu = self.toggle_panel_menu.addMenu( 

837 'Run Snuffling') 

838 self.toggle_panel_menu.addSeparator() 

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

840 help_menu.addSeparator() 

841 

842 file_menu.addAction( 

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

844 'Open waveform files...', 

845 self.open_waveforms, 

846 qg.QKeySequence.Open) 

847 

848 file_menu.addAction( 

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

850 'Open waveform directory...', 

851 self.open_waveform_directory) 

852 

853 file_menu.addAction( 

854 'Open station files...', 

855 self.open_stations) 

856 

857 file_menu.addAction( 

858 'Open StationXML files...', 

859 self.open_stations_xml) 

860 

861 file_menu.addAction( 

862 'Open event file...', 

863 self.read_events) 

864 

865 file_menu.addSeparator() 

866 file_menu.addAction( 

867 'Open marker file...', 

868 self.read_markers) 

869 

870 file_menu.addAction( 

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

872 'Save markers...', 

873 self.write_markers, 

874 qg.QKeySequence.Save) 

875 

876 file_menu.addAction( 

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

878 'Save selected markers...', 

879 self.write_selected_markers, 

880 qg.QKeySequence.SaveAs) 

881 

882 file_menu.addSeparator() 

883 file_menu.addAction( 

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

885 'Print', 

886 self.printit, 

887 qg.QKeySequence.Print) 

888 

889 file_menu.addAction( 

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

891 'Save as SVG or PNG', 

892 self.savesvg, 

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

894 

895 file_menu.addSeparator() 

896 close = file_menu.addAction( 

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

898 'Close', 

899 self.myclose) 

900 close.setShortcuts( 

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

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

903 

904 # Scale Menu 

905 menudef = [ 

906 ('Individual Scale', 

907 lambda tr: tr.nslc_id, 

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

909 ('Common Scale', 

910 lambda tr: None, 

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

912 ('Common Scale per Station', 

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

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

915 ('Common Scale per Station Location', 

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

917 ('Common Scale per Component', 

918 lambda tr: (tr.channel)), 

919 ] 

920 

921 self.menuitems_scaling = add_radiobuttongroup( 

922 scale_menu, menudef, self.scalingmode_change, 

923 default=self.config.trace_scale) 

924 scale_menu.addSeparator() 

925 

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

927 self.scaling_hooks = {} 

928 self.scalingmode_change() 

929 

930 menudef = [ 

931 ('Scaling based on Minimum and Maximum', 'minmax'), 

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

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

934 ] 

935 

936 self.menuitems_scaling_base = add_radiobuttongroup( 

937 scale_menu, menudef, self.scaling_base_change) 

938 

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

940 scale_menu.addSeparator() 

941 

942 self.menuitem_fixscalerange = scale_menu.addAction( 

943 'Fix Scale Ranges') 

944 self.menuitem_fixscalerange.setCheckable(True) 

945 

946 # Sort Menu 

947 def sector_dist(sta): 

948 if sta.dist_m is None: 

949 return None, None 

950 else: 

951 return ( 

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

953 m_float(sta.dist_m)) 

954 

955 menudef = [ 

956 ('Sort by Names', 

957 lambda tr: (), 

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

959 ('Sort by Distance', 

960 lambda tr: self.station_attrib( 

961 tr, 

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

963 lambda tr: (None,)), 

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

965 ('Sort by Azimuth', 

966 lambda tr: self.station_attrib( 

967 tr, 

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

969 lambda tr: (None,))), 

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

971 lambda tr: self.station_attrib( 

972 tr, 

973 sector_dist, 

974 lambda tr: (None, None))), 

975 ('Sort by Backazimuth', 

976 lambda tr: self.station_attrib( 

977 tr, 

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

979 lambda tr: (None,))), 

980 ] 

981 self.menuitems_ssorting = add_radiobuttongroup( 

982 sort_menu, menudef, self.s_sortingmode_change) 

983 sort_menu.addSeparator() 

984 

985 self._ssort = lambda tr: () 

986 

987 self.menu.addSeparator() 

988 

989 menudef = [ 

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

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

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

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

994 ((0, 1, 3, 2), 

995 lambda tr: tr.channel)), 

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

997 ((1, 0, 3, 2), 

998 lambda tr: tr.channel)), 

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

1000 ((2, 0, 1, 3), 

1001 lambda tr: tr.channel)), 

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

1003 ((3, 0, 1, 2), 

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

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

1006 ((0, 1, 3), 

1007 lambda tr: tr.location)), 

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

1009 ((1, 0, 3), 

1010 lambda tr: tr.location)), 

1011 ] 

1012 

1013 self.menuitems_sorting = add_radiobuttongroup( 

1014 sort_menu, menudef, self.sortingmode_change) 

1015 

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

1017 self.config.visible_length_setting] 

1018 

1019 # View menu 

1020 self.menuitems_visible_length = add_radiobuttongroup( 

1021 view_menu, menudef, 

1022 self.visible_length_change) 

1023 view_menu.addSeparator() 

1024 

1025 view_modes = [ 

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

1027 ('Waterfall', ViewMode.Waterfall) 

1028 ] 

1029 

1030 self.menuitems_viewmode = add_radiobuttongroup( 

1031 view_menu, view_modes, 

1032 self.viewmode_change, default=ViewMode.Wiggle) 

1033 view_menu.addSeparator() 

1034 

1035 self.menuitem_cliptraces = view_menu.addAction( 

1036 'Clip Traces') 

1037 self.menuitem_cliptraces.setCheckable(True) 

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

1039 

1040 self.menuitem_showboxes = view_menu.addAction( 

1041 'Show Boxes') 

1042 self.menuitem_showboxes.setCheckable(True) 

1043 self.menuitem_showboxes.setChecked( 

1044 self.config.show_boxes) 

1045 

1046 self.menuitem_colortraces = view_menu.addAction( 

1047 'Color Traces') 

1048 self.menuitem_colortraces.setCheckable(True) 

1049 self.menuitem_antialias = view_menu.addAction( 

1050 'Antialiasing') 

1051 self.menuitem_antialias.setCheckable(True) 

1052 

1053 view_menu.addSeparator() 

1054 self.menuitem_showscalerange = view_menu.addAction( 

1055 'Show Scale Ranges') 

1056 self.menuitem_showscalerange.setCheckable(True) 

1057 self.menuitem_showscalerange.setChecked( 

1058 self.config.show_scale_ranges) 

1059 

1060 self.menuitem_showscaleaxis = view_menu.addAction( 

1061 'Show Scale Axes') 

1062 self.menuitem_showscaleaxis.setCheckable(True) 

1063 self.menuitem_showscaleaxis.setChecked( 

1064 self.config.show_scale_axes) 

1065 

1066 self.menuitem_showzeroline = view_menu.addAction( 

1067 'Show Zero Lines') 

1068 self.menuitem_showzeroline.setCheckable(True) 

1069 

1070 view_menu.addSeparator() 

1071 view_menu.addAction( 

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

1073 'Fullscreen', 

1074 self.toggle_fullscreen, 

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

1076 

1077 # Options Menu 

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

1079 self.menuitem_demean.setCheckable(True) 

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

1081 self.menuitem_demean.setShortcut( 

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

1083 

1084 self.menuitem_distances_3d = options_menu.addAction( 

1085 '3D distances', 

1086 self.distances_3d_changed) 

1087 self.menuitem_distances_3d.setCheckable(True) 

1088 

1089 self.menuitem_allowdownsampling = options_menu.addAction( 

1090 'Allow Downsampling') 

1091 self.menuitem_allowdownsampling.setCheckable(True) 

1092 self.menuitem_allowdownsampling.setChecked(True) 

1093 

1094 self.menuitem_degap = options_menu.addAction( 

1095 'Allow Degapping') 

1096 self.menuitem_degap.setCheckable(True) 

1097 self.menuitem_degap.setChecked(True) 

1098 

1099 options_menu.addSeparator() 

1100 

1101 self.menuitem_fft_filtering = options_menu.addAction( 

1102 'FFT Filtering') 

1103 self.menuitem_fft_filtering.setCheckable(True) 

1104 

1105 self.menuitem_lphp = options_menu.addAction( 

1106 'Bandpass is Low- + Highpass') 

1107 self.menuitem_lphp.setCheckable(True) 

1108 self.menuitem_lphp.setChecked(True) 

1109 

1110 options_menu.addSeparator() 

1111 self.menuitem_watch = options_menu.addAction( 

1112 'Watch Files') 

1113 self.menuitem_watch.setCheckable(True) 

1114 

1115 self.menuitem_liberal_fetch = options_menu.addAction( 

1116 'Liberal Fetch Optimization') 

1117 self.menuitem_liberal_fetch.setCheckable(True) 

1118 

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

1120 

1121 self.snufflings_menu.addAction( 

1122 'Reload Snufflings', 

1123 self.setup_snufflings) 

1124 

1125 # Disable ShadowPileTest 

1126 if False: 

1127 test_action = self.menu.addAction( 

1128 'Test', 

1129 self.toggletest) 

1130 test_action.setCheckable(True) 

1131 

1132 help_menu.addAction( 

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

1134 'Snuffler Controls', 

1135 self.help, 

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

1137 

1138 help_menu.addAction( 

1139 'About', 

1140 self.about) 

1141 

1142 self.time_projection = Projection() 

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

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

1145 

1146 self.gather = None 

1147 

1148 self.trace_filter = None 

1149 self.quick_filter = None 

1150 self.quick_filter_patterns = None, None 

1151 self.blacklist = [] 

1152 

1153 self.track_to_screen = Projection() 

1154 self.track_to_nslc_ids = {} 

1155 

1156 self.cached_vec = None 

1157 self.cached_processed_traces = None 

1158 

1159 self.timer = qc.QTimer(self) 

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

1161 self.timer.setInterval(1000) 

1162 self.timer.start() 

1163 self.pile.add_listener(self) 

1164 self.trace_styles = {} 

1165 if self.get_squirrel() is None: 

1166 self.determine_box_styles() 

1167 

1168 self.setMouseTracking(True) 

1169 

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

1171 self.snuffling_modules = {} 

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

1173 self.default_snufflings = None 

1174 self.snufflings = [] 

1175 

1176 self.stations = {} 

1177 

1178 self.timer_draw = Timer() 

1179 self.timer_cutout = Timer() 

1180 self.time_spent_painting = 0.0 

1181 self.time_last_painted = time.time() 

1182 

1183 self.interactive_range_change_time = 0.0 

1184 self.interactive_range_change_delay_time = 10.0 

1185 self.follow_timer = None 

1186 

1187 self.sortingmode_change_time = 0.0 

1188 self.sortingmode_change_delay_time = None 

1189 

1190 self.old_data_ranges = {} 

1191 

1192 self.error_messages = {} 

1193 self.return_tag = None 

1194 self.wheel_pos = 60 

1195 

1196 self.setAcceptDrops(True) 

1197 self._paths_to_load = [] 

1198 

1199 self.tf_cache = {} 

1200 

1201 self.waterfall = TraceWaterfall() 

1202 self.waterfall_cmap = 'viridis' 

1203 self.waterfall_clip_min = 0. 

1204 self.waterfall_clip_max = 1. 

1205 self.waterfall_show_absolute = False 

1206 self.waterfall_integrate = False 

1207 self.view_mode = ViewMode.Wiggle 

1208 

1209 self.automatic_updates = True 

1210 

1211 self.closing = False 

1212 self.in_paint_event = False 

1213 

1214 def fail(self, reason): 

1215 box = qw.QMessageBox(self) 

1216 box.setText(reason) 

1217 box.exec_() 

1218 

1219 def set_trace_filter(self, filter_func): 

1220 self.trace_filter = filter_func 

1221 self.sortingmode_change() 

1222 

1223 def update_trace_filter(self): 

1224 if self.blacklist: 

1225 

1226 def blacklist_func(tr): 

1227 return not pyrocko.util.match_nslc( 

1228 self.blacklist, tr.nslc_id) 

1229 

1230 else: 

1231 blacklist_func = None 

1232 

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

1234 self.set_trace_filter(None) 

1235 elif self.quick_filter is None: 

1236 self.set_trace_filter(blacklist_func) 

1237 elif blacklist_func is None: 

1238 self.set_trace_filter(self.quick_filter) 

1239 else: 

1240 self.set_trace_filter( 

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

1242 

1243 def set_quick_filter(self, filter_func): 

1244 self.quick_filter = filter_func 

1245 self.update_trace_filter() 

1246 

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

1248 if patterns is not None: 

1249 self.set_quick_filter( 

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

1251 else: 

1252 self.set_quick_filter(None) 

1253 

1254 self.quick_filter_patterns = patterns, inputline 

1255 

1256 def get_quick_filter_patterns(self): 

1257 return self.quick_filter_patterns 

1258 

1259 def add_blacklist_pattern(self, pattern): 

1260 if pattern == 'empty': 

1261 keys = set(self.pile.nslc_ids) 

1262 trs = self.pile.all( 

1263 tmin=self.tmin, 

1264 tmax=self.tmax, 

1265 load_data=False, 

1266 degap=False) 

1267 

1268 for tr in trs: 

1269 if tr.nslc_id in keys: 

1270 keys.remove(tr.nslc_id) 

1271 

1272 for key in keys: 

1273 xpattern = '.'.join(key) 

1274 if xpattern not in self.blacklist: 

1275 self.blacklist.append(xpattern) 

1276 

1277 else: 

1278 if pattern in self.blacklist: 

1279 self.blacklist.remove(pattern) 

1280 

1281 self.blacklist.append(pattern) 

1282 

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

1284 self.update_trace_filter() 

1285 

1286 def remove_blacklist_pattern(self, pattern): 

1287 if pattern in self.blacklist: 

1288 self.blacklist.remove(pattern) 

1289 else: 

1290 raise PileViewerMainException( 

1291 'Pattern not found in blacklist.') 

1292 

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

1294 self.update_trace_filter() 

1295 

1296 def clear_blacklist(self): 

1297 self.blacklist = [] 

1298 self.update_trace_filter() 

1299 

1300 def ssort(self, tr): 

1301 return self._ssort(tr) 

1302 

1303 def station_key(self, x): 

1304 return x.network, x.station 

1305 

1306 def station_keys(self, x): 

1307 return [ 

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

1309 (x.network, x.station)] 

1310 

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

1312 for sk in self.station_keys(tr): 

1313 if sk in self.stations: 

1314 station = self.stations[sk] 

1315 return getter(station) 

1316 

1317 return default_getter(tr) 

1318 

1319 def get_station(self, sk): 

1320 return self.stations[sk] 

1321 

1322 def has_station(self, station): 

1323 for sk in self.station_keys(station): 

1324 if sk in self.stations: 

1325 return True 

1326 

1327 return False 

1328 

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

1330 return self.station_attrib( 

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

1332 

1333 def set_stations(self, stations): 

1334 self.stations = {} 

1335 self.add_stations(stations) 

1336 

1337 def add_stations(self, stations): 

1338 for station in stations: 

1339 for sk in self.station_keys(station): 

1340 self.stations[sk] = station 

1341 

1342 ev = self.get_active_event() 

1343 if ev: 

1344 self.set_origin(ev) 

1345 

1346 def add_event(self, event): 

1347 marker = EventMarker(event) 

1348 self.add_marker(marker) 

1349 

1350 def add_events(self, events): 

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

1352 self.add_markers(markers) 

1353 

1354 def set_event_marker_as_origin(self, ignore=None): 

1355 selected = self.selected_markers() 

1356 if not selected: 

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

1358 return 

1359 

1360 m = selected[0] 

1361 if not isinstance(m, EventMarker): 

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

1363 return 

1364 

1365 self.set_active_event_marker(m) 

1366 

1367 def deactivate_event_marker(self): 

1368 if self.active_event_marker: 

1369 self.active_event_marker.active = False 

1370 

1371 self.active_event_marker_changed.emit() 

1372 self.active_event_marker = None 

1373 

1374 def set_active_event_marker(self, event_marker): 

1375 if self.active_event_marker: 

1376 self.active_event_marker.active = False 

1377 

1378 self.active_event_marker = event_marker 

1379 event_marker.active = True 

1380 event = event_marker.get_event() 

1381 self.set_origin(event) 

1382 self.active_event_marker_changed.emit() 

1383 

1384 def set_active_event(self, event): 

1385 for marker in self.markers: 

1386 if isinstance(marker, EventMarker): 

1387 if marker.get_event() is event: 

1388 self.set_active_event_marker(marker) 

1389 

1390 def get_active_event_marker(self): 

1391 return self.active_event_marker 

1392 

1393 def get_active_event(self): 

1394 m = self.get_active_event_marker() 

1395 if m is not None: 

1396 return m.get_event() 

1397 else: 

1398 return None 

1399 

1400 def get_active_markers(self): 

1401 emarker = self.get_active_event_marker() 

1402 if emarker is None: 

1403 return None, [] 

1404 

1405 else: 

1406 ev = emarker.get_event() 

1407 pmarkers = [ 

1408 m for m in self.markers 

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

1410 

1411 return emarker, pmarkers 

1412 

1413 def set_origin(self, location): 

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

1415 station.set_event_relative_data( 

1416 location, 

1417 distance_3d=self.menuitem_distances_3d.isChecked()) 

1418 

1419 self.sortingmode_change() 

1420 

1421 def distances_3d_changed(self): 

1422 ignore = self.menuitem_distances_3d.isChecked() 

1423 self.set_event_marker_as_origin(ignore) 

1424 

1425 def iter_snuffling_modules(self): 

1426 pjoin = os.path.join 

1427 for path in self.snuffling_paths: 

1428 

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

1430 os.mkdir(path) 

1431 

1432 for entry in os.listdir(path): 

1433 directory = path 

1434 fn = entry 

1435 d = pjoin(path, entry) 

1436 if os.path.isdir(d): 

1437 directory = d 

1438 if os.path.isfile( 

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

1440 fn = 'snuffling.py' 

1441 

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

1443 continue 

1444 

1445 name = fn[:-3] 

1446 

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

1448 self.snuffling_modules[directory, name] = \ 

1449 pyrocko.gui.snuffling.SnufflingModule( 

1450 directory, name, self) 

1451 

1452 yield self.snuffling_modules[directory, name] 

1453 

1454 def setup_snufflings(self): 

1455 # user snufflings 

1456 for mod in self.iter_snuffling_modules(): 

1457 try: 

1458 mod.load_if_needed() 

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

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

1461 

1462 # load the default snufflings on first run 

1463 if self.default_snufflings is None: 

1464 self.default_snufflings = pyrocko.gui\ 

1465 .snufflings.__snufflings__() 

1466 for snuffling in self.default_snufflings: 

1467 self.add_snuffling(snuffling) 

1468 

1469 def set_panel_parent(self, panel_parent): 

1470 self.panel_parent = panel_parent 

1471 

1472 def get_panel_parent(self): 

1473 return self.panel_parent 

1474 

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

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

1477 snuffling.init_gui( 

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

1479 self.snufflings.append(snuffling) 

1480 self.update() 

1481 

1482 def remove_snuffling(self, snuffling): 

1483 snuffling.delete_gui() 

1484 self.update() 

1485 self.snufflings.remove(snuffling) 

1486 snuffling.pre_destroy() 

1487 

1488 def add_snuffling_menuitem(self, item): 

1489 self.snufflings_menu.addAction(item) 

1490 item.setParent(self.snufflings_menu) 

1491 sort_actions(self.snufflings_menu) 

1492 

1493 def remove_snuffling_menuitem(self, item): 

1494 self.snufflings_menu.removeAction(item) 

1495 

1496 def add_snuffling_help_menuitem(self, item): 

1497 self.snuffling_help.addAction(item) 

1498 item.setParent(self.snuffling_help) 

1499 sort_actions(self.snuffling_help) 

1500 

1501 def remove_snuffling_help_menuitem(self, item): 

1502 self.snuffling_help.removeAction(item) 

1503 

1504 def add_panel_toggler(self, item): 

1505 self.toggle_panel_menu.addAction(item) 

1506 item.setParent(self.toggle_panel_menu) 

1507 sort_actions(self.toggle_panel_menu) 

1508 

1509 def remove_panel_toggler(self, item): 

1510 self.toggle_panel_menu.removeAction(item) 

1511 

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

1513 cache_dir=None, force_cache=False): 

1514 

1515 if cache_dir is None: 

1516 cache_dir = pyrocko.config.config().cache_dir 

1517 if isinstance(paths, str): 

1518 paths = [paths] 

1519 

1520 fns = pyrocko.util.select_files( 

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

1522 

1523 if not fns: 

1524 return 

1525 

1526 cache = pyrocko.pile.get_cache(cache_dir) 

1527 

1528 t = [time.time()] 

1529 

1530 def update_bar(label, value): 

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

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

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

1534 else: 

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

1536 

1537 return pbs.set_status(label, value) 

1538 

1539 def update_progress(label, i, n): 

1540 abort = False 

1541 

1542 qw.qApp.processEvents() 

1543 if n != 0: 

1544 perc = i*100/n 

1545 else: 

1546 perc = 100 

1547 abort |= update_bar(label, perc) 

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

1549 

1550 tnow = time.time() 

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

1552 self.update() 

1553 t[0] = tnow 

1554 

1555 return abort 

1556 

1557 self.automatic_updates = False 

1558 

1559 self.pile.load_files( 

1560 sorted(fns), 

1561 filename_attributes=regex, 

1562 cache=cache, 

1563 fileformat=format, 

1564 show_progress=False, 

1565 update_progress=update_progress) 

1566 

1567 self.automatic_updates = True 

1568 self.update() 

1569 

1570 def load_queued(self): 

1571 if not self._paths_to_load: 

1572 return 

1573 paths = self._paths_to_load 

1574 self._paths_to_load = [] 

1575 self.load(paths) 

1576 

1577 def load_soon(self, paths): 

1578 self._paths_to_load.extend(paths) 

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

1580 

1581 def open_waveforms(self): 

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

1583 

1584 fns, _ = qw.QFileDialog.getOpenFileNames( 

1585 self, caption, options=qfiledialog_options) 

1586 

1587 if fns: 

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

1589 

1590 def open_waveform_directory(self): 

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

1592 

1593 dn = qw.QFileDialog.getExistingDirectory( 

1594 self, caption, options=qfiledialog_options) 

1595 

1596 if dn: 

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

1598 

1599 def open_stations(self, fns=None): 

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

1601 

1602 if not fns: 

1603 fns, _ = qw.QFileDialog.getOpenFileNames( 

1604 self, caption, options=qfiledialog_options) 

1605 

1606 try: 

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

1608 for stat in stations: 

1609 self.add_stations(stat) 

1610 

1611 except Exception as e: 

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

1613 

1614 def open_stations_xml(self, fns=None): 

1615 from pyrocko.io import stationxml 

1616 

1617 caption = 'Select one or more StationXML files' 

1618 if not fns: 

1619 fns, _ = qw.QFileDialog.getOpenFileNames( 

1620 self, caption, options=qfiledialog_options, 

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

1622 ';;All files (*)') 

1623 

1624 try: 

1625 stations = [ 

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

1627 for x in fns] 

1628 

1629 for stat in stations: 

1630 self.add_stations(stat) 

1631 

1632 except Exception as e: 

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

1634 

1635 def add_traces(self, traces): 

1636 if traces: 

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

1638 self.pile.add_file(mtf) 

1639 ticket = (self.pile, mtf) 

1640 return ticket 

1641 else: 

1642 return (None, None) 

1643 

1644 def release_data(self, tickets): 

1645 for ticket in tickets: 

1646 pile, mtf = ticket 

1647 if pile is not None: 

1648 pile.remove_file(mtf) 

1649 

1650 def periodical(self): 

1651 if self.menuitem_watch.isChecked(): 

1652 if self.pile.reload_modified(): 

1653 self.update() 

1654 

1655 def get_pile(self): 

1656 return self.pile 

1657 

1658 def pile_changed(self, what): 

1659 self.pile_has_changed = True 

1660 self.pile_has_changed_signal.emit() 

1661 if self.automatic_updates: 

1662 self.update() 

1663 

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

1665 

1666 if gather is None: 

1667 def gather_func(tr): 

1668 return tr.nslc_id 

1669 

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

1671 

1672 else: 

1673 def gather_func(tr): 

1674 return ( 

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

1676 

1677 if color is None: 

1678 def color(tr): 

1679 return tr.location 

1680 

1681 self.gather = gather_func 

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

1683 

1684 self.color_gather = color 

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

1686 previous_ntracks = self.ntracks 

1687 self.set_ntracks(len(keys)) 

1688 

1689 if self.shown_tracks_range is None or \ 

1690 previous_ntracks == 0 or \ 

1691 self.show_all: 

1692 

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

1694 key_at_top = None 

1695 n = high-low 

1696 

1697 else: 

1698 low, high = self.shown_tracks_range 

1699 key_at_top = self.track_keys[low] 

1700 n = high-low 

1701 

1702 self.track_keys = sorted(keys) 

1703 

1704 track_patterns = [] 

1705 for k in self.track_keys: 

1706 pat = ['*', '*', '*', '*'] 

1707 for i, j in enumerate(gather): 

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

1709 

1710 track_patterns.append(pat) 

1711 

1712 self.track_patterns = track_patterns 

1713 

1714 if key_at_top is not None: 

1715 try: 

1716 ind = self.track_keys.index(key_at_top) 

1717 low = ind 

1718 high = low+n 

1719 except Exception: 

1720 pass 

1721 

1722 self.set_tracks_range((low, high)) 

1723 

1724 self.key_to_row = dict( 

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

1726 

1727 def inrange(x, r): 

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

1729 

1730 def trace_selector(trace): 

1731 gt = self.gather(trace) 

1732 return ( 

1733 gt in self.key_to_row and 

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

1735 

1736 self.trace_selector = lambda x: \ 

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

1738 and trace_selector(x) 

1739 

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

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

1742 self.show_all: 

1743 

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

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

1746 tlen = (tmax - tmin) 

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

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

1749 

1750 def set_time_range(self, tmin, tmax): 

1751 if tmin is None: 

1752 tmin = initial_time_range[0] 

1753 

1754 if tmax is None: 

1755 tmax = initial_time_range[1] 

1756 

1757 if tmin > tmax: 

1758 tmin, tmax = tmax, tmin 

1759 

1760 if tmin == tmax: 

1761 tmin -= 1. 

1762 tmax += 1. 

1763 

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

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

1766 

1767 min_deltat = self.content_deltat_range()[0] 

1768 if (tmax - tmin < min_deltat): 

1769 m = (tmin + tmax) / 2. 

1770 tmin = m - min_deltat/2. 

1771 tmax = m + min_deltat/2. 

1772 

1773 self.time_projection.set_in_range(tmin, tmax) 

1774 self.tmin, self.tmax = tmin, tmax 

1775 

1776 def get_time_range(self): 

1777 return self.tmin, self.tmax 

1778 

1779 def ypart(self, y): 

1780 if y < self.ax_height: 

1781 return -1 

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

1783 return 1 

1784 else: 

1785 return 0 

1786 

1787 def time_fractional_digits(self): 

1788 min_deltat = self.content_deltat_range()[0] 

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

1790 

1791 def write_markers(self, fn=None): 

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

1793 if not fn: 

1794 fn, _ = qw.QFileDialog.getSaveFileName( 

1795 self, caption, options=qfiledialog_options) 

1796 if fn: 

1797 try: 

1798 Marker.save_markers( 

1799 self.markers, fn, 

1800 fdigits=self.time_fractional_digits()) 

1801 

1802 except Exception as e: 

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

1804 

1805 def write_selected_markers(self, fn=None): 

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

1807 if not fn: 

1808 fn, _ = qw.QFileDialog.getSaveFileName( 

1809 self, caption, options=qfiledialog_options) 

1810 if fn: 

1811 try: 

1812 Marker.save_markers( 

1813 self.iter_selected_markers(), 

1814 fn, 

1815 fdigits=self.time_fractional_digits()) 

1816 

1817 except Exception as e: 

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

1819 

1820 def read_events(self, fn=None): 

1821 ''' 

1822 Open QFileDialog to open, read and add 

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

1824 representation to the pile viewer. 

1825 ''' 

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

1827 if not fn: 

1828 fn, _ = qw.QFileDialog.getOpenFileName( 

1829 self, caption, options=qfiledialog_options) 

1830 if fn: 

1831 try: 

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

1833 self.associate_phases_to_events() 

1834 

1835 except Exception as e: 

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

1837 

1838 def read_markers(self, fn=None): 

1839 ''' 

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

1841 ''' 

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

1843 if not fn: 

1844 fn, _ = qw.QFileDialog.getOpenFileName( 

1845 self, caption, options=qfiledialog_options) 

1846 if fn: 

1847 try: 

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

1849 self.associate_phases_to_events() 

1850 

1851 except Exception as e: 

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

1853 

1854 def associate_phases_to_events(self): 

1855 associate_phases_to_events(self.markers) 

1856 

1857 def add_marker(self, marker): 

1858 # need index to inform QAbstactTableModel about upcoming change, 

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

1860 self.markers.insert(marker) 

1861 i = self.markers.remove(marker) 

1862 

1863 self.begin_markers_add.emit(i, i) 

1864 self.markers.insert(marker) 

1865 self.end_markers_add.emit() 

1866 self.markers_deltat_max = max( 

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

1868 

1869 def add_markers(self, markers): 

1870 if not self.markers: 

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

1872 self.markers.insert_many(markers) 

1873 self.end_markers_add.emit() 

1874 self.update_markers_deltat_max() 

1875 else: 

1876 for marker in markers: 

1877 self.add_marker(marker) 

1878 

1879 def update_markers_deltat_max(self): 

1880 if self.markers: 

1881 self.markers_deltat_max = max( 

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

1883 

1884 def remove_marker(self, marker): 

1885 ''' 

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

1887 

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

1889 ''' 

1890 

1891 if marker is self.active_event_marker: 

1892 self.deactivate_event_marker() 

1893 

1894 try: 

1895 i = self.markers.index(marker) 

1896 self.begin_markers_remove.emit(i, i) 

1897 self.markers.remove_at(i) 

1898 self.end_markers_remove.emit() 

1899 except ValueError: 

1900 pass 

1901 

1902 def remove_markers(self, markers): 

1903 ''' 

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

1905 

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

1907 instances 

1908 ''' 

1909 

1910 if markers is self.markers: 

1911 markers = list(markers) 

1912 

1913 for marker in markers: 

1914 self.remove_marker(marker) 

1915 

1916 self.update_markers_deltat_max() 

1917 

1918 def remove_selected_markers(self): 

1919 def delete_segment(istart, iend): 

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

1921 for _ in range(iend - istart): 

1922 self.markers.remove_at(istart) 

1923 

1924 self.end_markers_remove.emit() 

1925 

1926 istart = None 

1927 ipos = 0 

1928 markers = self.markers 

1929 nmarkers = len(self.markers) 

1930 while ipos < nmarkers: 

1931 marker = markers[ipos] 

1932 if marker.is_selected(): 

1933 if marker is self.active_event_marker: 

1934 self.deactivate_event_marker() 

1935 

1936 if istart is None: 

1937 istart = ipos 

1938 else: 

1939 if istart is not None: 

1940 delete_segment(istart, ipos) 

1941 nmarkers -= ipos - istart 

1942 ipos = istart - 1 

1943 istart = None 

1944 

1945 ipos += 1 

1946 

1947 if istart is not None: 

1948 delete_segment(istart, ipos) 

1949 

1950 self.update_markers_deltat_max() 

1951 

1952 def selected_markers(self): 

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

1954 

1955 def iter_selected_markers(self): 

1956 for marker in self.markers: 

1957 if marker.is_selected(): 

1958 yield marker 

1959 

1960 def get_markers(self): 

1961 return self.markers 

1962 

1963 def mousePressEvent(self, mouse_ev): 

1964 self.show_all = False 

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

1966 

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

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

1969 if self.picking: 

1970 if self.picking_down is None: 

1971 self.picking_down = ( 

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

1973 mouse_ev.y()) 

1974 

1975 elif marker is not None: 

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

1977 self.deselect_all() 

1978 marker.selected = True 

1979 self.emit_selected_markers() 

1980 self.update() 

1981 else: 

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

1983 self.track_trange = self.tmin, self.tmax 

1984 

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

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

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

1988 self.update_status() 

1989 

1990 def mouseReleaseEvent(self, mouse_ev): 

1991 if self.ignore_releases: 

1992 self.ignore_releases -= 1 

1993 return 

1994 

1995 if self.picking: 

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

1997 self.emit_selected_markers() 

1998 

1999 if self.track_start: 

2000 self.update() 

2001 

2002 self.track_start = None 

2003 self.track_trange = None 

2004 self.update_status() 

2005 

2006 def mouseDoubleClickEvent(self, mouse_ev): 

2007 self.show_all = False 

2008 self.start_picking(None) 

2009 self.ignore_releases = 1 

2010 

2011 def mouseMoveEvent(self, mouse_ev): 

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

2013 

2014 if self.picking: 

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

2016 

2017 elif self.track_start is not None: 

2018 x0, y0 = self.track_start 

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

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

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

2022 dy = 0 

2023 

2024 tmin0, tmax0 = self.track_trange 

2025 

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

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

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

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

2030 

2031 self.interrupt_following() 

2032 self.set_time_range( 

2033 tmin0 - dt - dtr*frac, 

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

2035 

2036 self.update() 

2037 else: 

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

2039 

2040 self.update_status() 

2041 

2042 def nslc_ids_under_cursor(self, x, y): 

2043 ftrack = self.track_to_screen.rev(y) 

2044 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2045 return nslc_ids 

2046 

2047 def marker_under_cursor(self, x, y): 

2048 mouset = self.time_projection.rev(x) 

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

2050 relevant_nslc_ids = None 

2051 for marker in self.markers: 

2052 if marker.kind not in self.visible_marker_kinds: 

2053 continue 

2054 

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

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

2057 

2058 if relevant_nslc_ids is None: 

2059 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2060 

2061 marker_nslc_ids = marker.get_nslc_ids() 

2062 if not marker_nslc_ids: 

2063 return marker 

2064 

2065 for nslc_id in marker_nslc_ids: 

2066 if nslc_id in relevant_nslc_ids: 

2067 return marker 

2068 

2069 def hoovering(self, x, y): 

2070 mouset = self.time_projection.rev(x) 

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

2072 needupdate = False 

2073 haveone = False 

2074 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2075 for marker in self.markers: 

2076 if marker.kind not in self.visible_marker_kinds: 

2077 continue 

2078 

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

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

2081 

2082 if state: 

2083 xstate = False 

2084 

2085 marker_nslc_ids = marker.get_nslc_ids() 

2086 if not marker_nslc_ids: 

2087 xstate = True 

2088 

2089 for nslc in relevant_nslc_ids: 

2090 if marker.match_nslc(nslc): 

2091 xstate = True 

2092 

2093 state = xstate 

2094 

2095 if state: 

2096 haveone = True 

2097 oldstate = marker.is_alerted() 

2098 if oldstate != state: 

2099 needupdate = True 

2100 marker.set_alerted(state) 

2101 if state: 

2102 self.message = marker.hoover_message() 

2103 

2104 if not haveone: 

2105 self.message = None 

2106 

2107 if needupdate: 

2108 self.update() 

2109 

2110 def event(self, event): 

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

2112 self.keyPressEvent(event) 

2113 return True 

2114 else: 

2115 return base.event(self, event) 

2116 

2117 def keyPressEvent(self, key_event): 

2118 self.show_all = False 

2119 dt = self.tmax - self.tmin 

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

2121 

2122 key = key_event.key() 

2123 try: 

2124 keytext = str(key_event.text()) 

2125 except UnicodeEncodeError: 

2126 return 

2127 

2128 if key == qc.Qt.Key_Space: 

2129 self.interrupt_following() 

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

2131 

2132 elif key == qc.Qt.Key_Up: 

2133 for m in self.selected_markers(): 

2134 if isinstance(m, PhaseMarker): 

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

2136 p = 0 

2137 else: 

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

2139 m.set_polarity(p) 

2140 

2141 elif key == qc.Qt.Key_Down: 

2142 for m in self.selected_markers(): 

2143 if isinstance(m, PhaseMarker): 

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

2145 p = 0 

2146 else: 

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

2148 m.set_polarity(p) 

2149 

2150 elif key == qc.Qt.Key_B: 

2151 dt = self.tmax - self.tmin 

2152 self.interrupt_following() 

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

2154 

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

2156 self.interrupt_following() 

2157 

2158 tgo = None 

2159 

2160 class TraceDummy(object): 

2161 def __init__(self, marker): 

2162 self._marker = marker 

2163 

2164 @property 

2165 def nslc_id(self): 

2166 return self._marker.one_nslc() 

2167 

2168 def marker_to_itrack(marker): 

2169 try: 

2170 return self.key_to_row.get( 

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

2172 

2173 except MarkerOneNSLCRequired: 

2174 return -1 

2175 

2176 emarker, pmarkers = self.get_active_markers() 

2177 pmarkers = [ 

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

2179 pmarkers.sort(key=lambda m: ( 

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

2181 

2182 if key == qc.Qt.Key_Backtab: 

2183 pmarkers.reverse() 

2184 

2185 smarkers = self.selected_markers() 

2186 iselected = [] 

2187 for sm in smarkers: 

2188 try: 

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

2190 except ValueError: 

2191 pass 

2192 

2193 if iselected: 

2194 icurrent = max(iselected) + 1 

2195 else: 

2196 icurrent = 0 

2197 

2198 if icurrent < len(pmarkers): 

2199 self.deselect_all() 

2200 cmarker = pmarkers[icurrent] 

2201 cmarker.selected = True 

2202 tgo = cmarker.tmin 

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

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

2205 

2206 itrack = marker_to_itrack(cmarker) 

2207 if itrack != -1: 

2208 if itrack < self.shown_tracks_range[0]: 

2209 self.scroll_tracks( 

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

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

2212 self.scroll_tracks( 

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

2214 

2215 if itrack not in self.track_to_nslc_ids: 

2216 self.go_to_selection() 

2217 

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

2219 smarkers = self.selected_markers() 

2220 tgo = None 

2221 dir = str(keytext) 

2222 if smarkers: 

2223 tmid = smarkers[0].tmin 

2224 for smarker in smarkers: 

2225 if dir == 'n': 

2226 tmid = max(smarker.tmin, tmid) 

2227 else: 

2228 tmid = min(smarker.tmin, tmid) 

2229 

2230 tgo = tmid 

2231 

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

2233 for marker in sorted( 

2234 self.markers, 

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

2236 

2237 t = marker.tmin 

2238 if t > tmid and \ 

2239 marker.kind in self.visible_marker_kinds and \ 

2240 (dir == 'n' or 

2241 isinstance(marker, EventMarker)): 

2242 

2243 self.deselect_all() 

2244 marker.selected = True 

2245 tgo = t 

2246 break 

2247 else: 

2248 for marker in sorted( 

2249 self.markers, 

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

2251 reverse=True): 

2252 

2253 t = marker.tmin 

2254 if t < tmid and \ 

2255 marker.kind in self.visible_marker_kinds and \ 

2256 (dir == 'p' or 

2257 isinstance(marker, EventMarker)): 

2258 self.deselect_all() 

2259 marker.selected = True 

2260 tgo = t 

2261 break 

2262 

2263 if tgo is not None: 

2264 self.interrupt_following() 

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

2266 

2267 elif keytext == 'r': 

2268 if self.pile.reload_modified(): 

2269 self.reloaded = True 

2270 

2271 elif keytext == 'R': 

2272 self.setup_snufflings() 

2273 

2274 elif key == qc.Qt.Key_Backspace: 

2275 self.remove_selected_markers() 

2276 

2277 elif keytext == 'a': 

2278 for marker in self.markers: 

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

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

2281 marker.kind in self.visible_marker_kinds): 

2282 marker.selected = True 

2283 else: 

2284 marker.selected = False 

2285 

2286 elif keytext == 'A': 

2287 for marker in self.markers: 

2288 if marker.kind in self.visible_marker_kinds: 

2289 marker.selected = True 

2290 

2291 elif keytext == 'd': 

2292 self.deselect_all() 

2293 

2294 elif keytext == 'E': 

2295 self.deactivate_event_marker() 

2296 

2297 elif keytext == 'e': 

2298 markers = self.selected_markers() 

2299 event_markers_in_spe = [ 

2300 marker for marker in markers 

2301 if not isinstance(marker, PhaseMarker)] 

2302 

2303 phase_markers = [ 

2304 marker for marker in markers 

2305 if isinstance(marker, PhaseMarker)] 

2306 

2307 if len(event_markers_in_spe) == 1: 

2308 event_marker = event_markers_in_spe[0] 

2309 if not isinstance(event_marker, EventMarker): 

2310 nslcs = list(event_marker.nslc_ids) 

2311 lat, lon = 0.0, 0.0 

2312 old = self.get_active_event() 

2313 if len(nslcs) == 1: 

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

2315 elif old is not None: 

2316 lat, lon = old.lat, old.lon 

2317 

2318 event_marker.convert_to_event_marker(lat, lon) 

2319 

2320 self.set_active_event_marker(event_marker) 

2321 event = event_marker.get_event() 

2322 for marker in phase_markers: 

2323 marker.set_event(event) 

2324 

2325 else: 

2326 for marker in event_markers_in_spe: 

2327 marker.convert_to_event_marker() 

2328 

2329 elif keytext in ('0', '1', '2', '3', '4', '5'): 

2330 for marker in self.selected_markers(): 

2331 marker.set_kind(int(keytext)) 

2332 self.emit_selected_markers() 

2333 

2334 elif key in fkey_map: 

2335 self.handle_fkeys(key) 

2336 

2337 elif key == qc.Qt.Key_Escape: 

2338 if self.picking: 

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

2340 

2341 elif key == qc.Qt.Key_PageDown: 

2342 self.scroll_tracks( 

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

2344 

2345 elif key == qc.Qt.Key_PageUp: 

2346 self.scroll_tracks( 

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

2348 

2349 elif key == qc.Qt.Key_Plus: 

2350 self.zoom_tracks(0., 1.) 

2351 

2352 elif key == qc.Qt.Key_Minus: 

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

2354 

2355 elif key == qc.Qt.Key_Equal: 

2356 ntracks_shown = self.shown_tracks_range[1] - \ 

2357 self.shown_tracks_range[0] 

2358 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2359 self.zoom_tracks(0., dtracks) 

2360 

2361 elif key == qc.Qt.Key_Colon: 

2362 self.want_input.emit() 

2363 

2364 elif keytext == 'f': 

2365 self.toggle_fullscreen() 

2366 

2367 elif keytext == 'g': 

2368 self.go_to_selection() 

2369 

2370 elif keytext == 'G': 

2371 self.go_to_selection(tight=True) 

2372 

2373 elif keytext == 'm': 

2374 self.toggle_marker_editor() 

2375 

2376 elif keytext == 'c': 

2377 self.toggle_main_controls() 

2378 

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

2380 dir = 1 

2381 amount = 1 

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

2383 dir = -1 

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

2385 amount = 10 

2386 self.nudge_selected_markers(dir*amount) 

2387 else: 

2388 super().keyPressEvent(key_event) 

2389 

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

2391 self.emit_selected_markers() 

2392 

2393 self.update() 

2394 self.update_status() 

2395 

2396 def handle_fkeys(self, key): 

2397 self.set_phase_kind( 

2398 self.selected_markers(), 

2399 fkey_map[key] + 1) 

2400 self.emit_selected_markers() 

2401 

2402 def emit_selected_markers(self): 

2403 ibounds = [] 

2404 last_selected = False 

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

2406 this_selected = marker.is_selected() 

2407 if this_selected != last_selected: 

2408 ibounds.append(imarker) 

2409 

2410 last_selected = this_selected 

2411 

2412 if last_selected: 

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

2414 

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

2416 self.n_selected_markers = sum( 

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

2418 self.marker_selection_changed.emit(chunks) 

2419 

2420 def toggle_marker_editor(self): 

2421 self.panel_parent.toggle_marker_editor() 

2422 

2423 def toggle_main_controls(self): 

2424 self.panel_parent.toggle_main_controls() 

2425 

2426 def nudge_selected_markers(self, npixels): 

2427 a, b = self.time_projection.ur 

2428 c, d = self.time_projection.xr 

2429 for marker in self.selected_markers(): 

2430 if not isinstance(marker, EventMarker): 

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

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

2433 

2434 def toggle_fullscreen(self): 

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

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

2437 self.window().showNormal() 

2438 else: 

2439 if is_macos: 

2440 self.window().showMaximized() 

2441 else: 

2442 self.window().showFullScreen() 

2443 

2444 def about(self): 

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

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

2447 txt = f.read() 

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

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

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

2451 

2452 def help(self): 

2453 class MyScrollArea(qw.QScrollArea): 

2454 

2455 def sizeHint(self): 

2456 s = qc.QSize() 

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

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

2459 return s 

2460 

2461 with open(pyrocko.util.data_file( 

2462 'snuffler_help.html')) as f: 

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

2464 

2465 with open(pyrocko.util.data_file( 

2466 'snuffler_help_epilog.html')) as f: 

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

2468 

2469 for h in [hcheat, hepilog]: 

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

2471 h.setWordWrap(True) 

2472 

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

2474 

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

2476 scroller = qw.QScrollArea() 

2477 frame = qw.QFrame(scroller) 

2478 frame.setLineWidth(0) 

2479 layout = qw.QVBoxLayout() 

2480 layout.setContentsMargins(0, 0, 0, 0) 

2481 layout.setSpacing(0) 

2482 frame.setLayout(layout) 

2483 scroller.setWidget(frame) 

2484 scroller.setWidgetResizable(True) 

2485 frame.setBackgroundRole(qg.QPalette.Base) 

2486 for h in labels: 

2487 h.setParent(frame) 

2488 h.setMargin(3) 

2489 h.setTextInteractionFlags( 

2490 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2491 h.setBackgroundRole(qg.QPalette.Base) 

2492 layout.addWidget(h) 

2493 h.linkActivated.connect( 

2494 self.open_link) 

2495 

2496 if self.panel_parent is not None: 

2497 if target == 'panel': 

2498 self.panel_parent.add_panel( 

2499 name, scroller, True, volatile=False) 

2500 else: 

2501 self.panel_parent.add_tab(name, scroller) 

2502 

2503 def open_link(self, link): 

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

2505 

2506 def wheelEvent(self, wheel_event): 

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

2508 

2509 n = self.wheel_pos // 120 

2510 self.wheel_pos = self.wheel_pos % 120 

2511 if n == 0: 

2512 return 

2513 

2514 amount = max( 

2515 1., 

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

2517 wdelta = amount * n 

2518 

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

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

2521 / (trmax-trmin) 

2522 

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

2524 self.zoom_tracks(anchor, wdelta) 

2525 else: 

2526 self.scroll_tracks(-wdelta) 

2527 

2528 def dragEnterEvent(self, event): 

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

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

2531 event.setDropAction(qc.Qt.LinkAction) 

2532 event.accept() 

2533 

2534 def dropEvent(self, event): 

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

2536 paths = list( 

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

2538 event.acceptProposedAction() 

2539 self.load(paths) 

2540 

2541 def get_phase_name(self, kind): 

2542 return self.config.get_phase_name(kind) 

2543 

2544 def set_phase_kind(self, markers, kind): 

2545 phasename = self.get_phase_name(kind) 

2546 

2547 for marker in markers: 

2548 if isinstance(marker, PhaseMarker): 

2549 if kind == 10: 

2550 marker.convert_to_marker() 

2551 else: 

2552 marker.set_phasename(phasename) 

2553 marker.set_event(self.get_active_event()) 

2554 

2555 elif isinstance(marker, EventMarker): 

2556 pass 

2557 

2558 else: 

2559 if kind != 10: 

2560 event = self.get_active_event() 

2561 marker.convert_to_phase_marker( 

2562 event, phasename, None, False) 

2563 

2564 def set_ntracks(self, ntracks): 

2565 if self.ntracks != ntracks: 

2566 self.ntracks = ntracks 

2567 if self.shown_tracks_range is not None: 

2568 l, h = self.shown_tracks_range 

2569 else: 

2570 l, h = 0, self.ntracks 

2571 

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

2573 

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

2575 

2576 low, high = range 

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

2578 high = min(self.ntracks, high) 

2579 low = max(0, low) 

2580 high = max(1, high) 

2581 

2582 if start is None: 

2583 start = float(low) 

2584 

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

2586 self.shown_tracks_range = low, high 

2587 self.shown_tracks_start = start 

2588 

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

2590 

2591 def scroll_tracks(self, shift): 

2592 shown = self.shown_tracks_range 

2593 shiftmin = -shown[0] 

2594 shiftmax = self.ntracks-shown[1] 

2595 shift = max(shiftmin, shift) 

2596 shift = min(shiftmax, shift) 

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

2598 

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

2600 

2601 self.update() 

2602 

2603 def zoom_tracks(self, anchor, delta): 

2604 ntracks_shown = self.shown_tracks_range[1] \ 

2605 - self.shown_tracks_range[0] 

2606 

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

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

2609 return 

2610 

2611 ntracks_shown += int(round(delta)) 

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

2613 

2614 u = self.shown_tracks_start 

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

2616 nv = nu + ntracks_shown 

2617 if nv > self.ntracks: 

2618 nu -= nv - self.ntracks 

2619 nv -= nv - self.ntracks 

2620 

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

2622 

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

2624 - self.shown_tracks_range[0] 

2625 

2626 self.update() 

2627 

2628 def content_time_range(self): 

2629 pile = self.get_pile() 

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

2631 if tmin is None: 

2632 tmin = initial_time_range[0] 

2633 if tmax is None: 

2634 tmax = initial_time_range[1] 

2635 

2636 return tmin, tmax 

2637 

2638 def content_deltat_range(self): 

2639 pile = self.get_pile() 

2640 

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

2642 

2643 if deltatmin is None: 

2644 deltatmin = 0.001 

2645 

2646 if deltatmax is None: 

2647 deltatmax = 1000.0 

2648 

2649 return deltatmin, deltatmax 

2650 

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

2652 if tmax < tmin: 

2653 tmin, tmax = tmax, tmin 

2654 

2655 deltatmin = self.content_deltat_range()[0] 

2656 dt = deltatmin * self.visible_length * 0.95 

2657 

2658 if dt == 0.0: 

2659 dt = 1.0 

2660 

2661 if tight: 

2662 if tmax != tmin: 

2663 dtm = tmax - tmin 

2664 tmin -= dtm*0.1 

2665 tmax += dtm*0.1 

2666 return tmin, tmax 

2667 else: 

2668 tcenter = (tmin + tmax) / 2. 

2669 tmin = tcenter - 0.5*dt 

2670 tmax = tcenter + 0.5*dt 

2671 return tmin, tmax 

2672 

2673 if tmax-tmin < dt: 

2674 vmin, vmax = self.get_time_range() 

2675 dt = min(vmax - vmin, dt) 

2676 

2677 tcenter = (tmin+tmax)/2. 

2678 etmin, etmax = tmin, tmax 

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

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

2681 dtm = tmax-tmin 

2682 if etmin == tmin: 

2683 tmin -= dtm*0.1 

2684 if etmax == tmax: 

2685 tmax += dtm*0.1 

2686 

2687 else: 

2688 dtm = tmax-tmin 

2689 tmin -= dtm*0.1 

2690 tmax += dtm*0.1 

2691 

2692 return tmin, tmax 

2693 

2694 def go_to_selection(self, tight=False): 

2695 markers = self.selected_markers() 

2696 if markers: 

2697 tmax, tmin = self.content_time_range() 

2698 for marker in markers: 

2699 tmin = min(tmin, marker.tmin) 

2700 tmax = max(tmax, marker.tmax) 

2701 

2702 else: 

2703 if tight: 

2704 vmin, vmax = self.get_time_range() 

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

2706 else: 

2707 tmin, tmax = self.content_time_range() 

2708 

2709 tmin, tmax = self.make_good_looking_time_range( 

2710 tmin, tmax, tight=tight) 

2711 

2712 self.interrupt_following() 

2713 self.set_time_range(tmin, tmax) 

2714 self.update() 

2715 

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

2717 tmax = t 

2718 if tlen is not None: 

2719 tmax = t+tlen 

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

2721 self.interrupt_following() 

2722 self.set_time_range(tmin, tmax) 

2723 self.update() 

2724 

2725 def go_to_event_by_name(self, name): 

2726 for marker in self.markers: 

2727 if isinstance(marker, EventMarker): 

2728 event = marker.get_event() 

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

2730 tmin, tmax = self.make_good_looking_time_range( 

2731 event.time, event.time) 

2732 

2733 self.interrupt_following() 

2734 self.set_time_range(tmin, tmax) 

2735 

2736 def printit(self): 

2737 from .qt_compat import qprint 

2738 printer = qprint.QPrinter() 

2739 printer.setOrientation(qprint.QPrinter.Landscape) 

2740 

2741 dialog = qprint.QPrintDialog(printer, self) 

2742 dialog.setWindowTitle('Print') 

2743 

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

2745 return 

2746 

2747 painter = qg.QPainter() 

2748 painter.begin(printer) 

2749 page = printer.pageRect() 

2750 self.drawit( 

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

2752 

2753 painter.end() 

2754 

2755 def savesvg(self, fn=None): 

2756 

2757 if not fn: 

2758 fn, _ = qw.QFileDialog.getSaveFileName( 

2759 self, 

2760 'Save as SVG|PNG', 

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

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

2763 options=qfiledialog_options) 

2764 

2765 if fn == '': 

2766 return 

2767 

2768 fn = str(fn) 

2769 

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

2771 try: 

2772 w, h = 842, 595 

2773 margin = 0.025 

2774 m = max(w, h)*margin 

2775 

2776 generator = qsvg.QSvgGenerator() 

2777 generator.setFileName(fn) 

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

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

2780 

2781 painter = qg.QPainter() 

2782 painter.begin(generator) 

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

2784 painter.end() 

2785 

2786 except Exception as e: 

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

2788 

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

2790 pixmap = self.grab() 

2791 

2792 try: 

2793 pixmap.save(fn) 

2794 

2795 except Exception as e: 

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

2797 

2798 else: 

2799 self.fail( 

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

2801 '".png".') 

2802 

2803 def paintEvent(self, paint_ev): 

2804 ''' 

2805 Called by QT whenever widget needs to be painted. 

2806 ''' 

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

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

2809 if self.in_paint_event: 

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

2811 return 

2812 

2813 self.in_paint_event = True 

2814 

2815 painter = qg.QPainter(self) 

2816 

2817 if self.menuitem_antialias.isChecked(): 

2818 painter.setRenderHint(qg.QPainter.Antialiasing) 

2819 

2820 self.drawit(painter) 

2821 

2822 logger.debug( 

2823 'Time spent drawing: ' 

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

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

2826 (self.timer_draw - self.timer_cutout)) 

2827 

2828 logger.debug( 

2829 'Time spent processing:' 

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

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

2832 self.timer_cutout.get()) 

2833 

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

2835 self.time_last_painted = time.time() 

2836 self.in_paint_event = False 

2837 

2838 def determine_box_styles(self): 

2839 

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

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

2842 istyle = 0 

2843 trace_styles = {} 

2844 for itr, tr in enumerate(traces): 

2845 if itr > 0: 

2846 other = traces[itr-1] 

2847 if not ( 

2848 other.nslc_id == tr.nslc_id 

2849 and other.deltat == tr.deltat 

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

2851 < gap_lap_tolerance*tr.deltat): 

2852 

2853 istyle += 1 

2854 

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

2856 

2857 self.trace_styles = trace_styles 

2858 

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

2860 

2861 for v_projection in track_projections.values(): 

2862 v_projection.set_in_range(0., 1.) 

2863 

2864 def selector(x): 

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

2866 

2867 if self.trace_filter is not None: 

2868 def tselector(x): 

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

2870 

2871 else: 

2872 tselector = selector 

2873 

2874 traces = list(self.pile.iter_traces( 

2875 group_selector=selector, trace_selector=tselector)) 

2876 

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

2878 

2879 def drawbox(itrack, istyle, traces): 

2880 v_projection = track_projections[itrack] 

2881 dvmin = v_projection(0.) 

2882 dvmax = v_projection(1.) 

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

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

2885 

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

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

2888 p.fillRect(rect, style.fill_brush) 

2889 p.setPen(style.frame_pen) 

2890 p.drawRect(rect) 

2891 

2892 traces_by_style = {} 

2893 for itr, tr in enumerate(traces): 

2894 gt = self.gather(tr) 

2895 if gt not in self.key_to_row: 

2896 continue 

2897 

2898 itrack = self.key_to_row[gt] 

2899 if itrack not in track_projections: 

2900 continue 

2901 

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

2903 

2904 if len(traces) < 500: 

2905 drawbox(itrack, istyle, [tr]) 

2906 else: 

2907 if (itrack, istyle) not in traces_by_style: 

2908 traces_by_style[itrack, istyle] = [] 

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

2910 

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

2912 drawbox(itrack, istyle, traces) 

2913 

2914 def draw_visible_markers( 

2915 self, p, vcenter_projection, primary_pen): 

2916 

2917 try: 

2918 markers = self.markers.with_key_in_limited( 

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

2920 

2921 except pyrocko.pile.TooMany: 

2922 tmin = self.markers[0].tmin 

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

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

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

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

2927 v0, _ = vcenter_projection.get_out_range() 

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

2929 

2930 p.save() 

2931 

2932 pen = qg.QPen(primary_pen) 

2933 pen.setWidth(2) 

2934 pen.setStyle(qc.Qt.DotLine) 

2935 # pat = [5., 3.] 

2936 # pen.setDashPattern(pat) 

2937 p.setPen(pen) 

2938 

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

2940 s_selected = ' (all selected)' 

2941 elif self.n_selected_markers > 0: 

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

2943 else: 

2944 s_selected = '' 

2945 

2946 draw_label( 

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

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

2949 label_bg, 'LB') 

2950 

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

2952 p.drawLine(line) 

2953 p.restore() 

2954 

2955 return 

2956 

2957 for marker in markers: 

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

2959 and marker.kind in self.visible_marker_kinds: 

2960 

2961 marker.draw( 

2962 p, self.time_projection, vcenter_projection, 

2963 with_label=True) 

2964 

2965 def get_squirrel(self): 

2966 try: 

2967 return self.pile._squirrel 

2968 except AttributeError: 

2969 return None 

2970 

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

2972 sq = self.get_squirrel() 

2973 if sq is None: 

2974 return 

2975 

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

2977 v_projection = track_projections[itrack] 

2978 dvmin = v_projection(0.) 

2979 dvmax = v_projection(1.) 

2980 dtmin = time_projection.clipped(tmin) 

2981 dtmax = time_projection.clipped(tmax) 

2982 

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

2984 p.fillRect(rect, style.fill_brush) 

2985 p.setPen(style.frame_pen) 

2986 p.drawRect(rect) 

2987 

2988 pattern_list = [] 

2989 pattern_to_itrack = {} 

2990 for key in self.track_keys: 

2991 itrack = self.key_to_row[key] 

2992 if itrack not in track_projections: 

2993 continue 

2994 

2995 pattern = self.track_patterns[itrack] 

2996 pattern_to_itrack[tuple(pattern)] = itrack 

2997 pattern_list.append(tuple(pattern)) 

2998 

2999 vmin, vmax = self.get_time_range() 

3000 

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

3002 for coverage in sq.get_coverage( 

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

3004 itrack = pattern_to_itrack[coverage.pattern.nslc] 

3005 

3006 if coverage.changes is None: 

3007 drawbox( 

3008 itrack, coverage.tmin, coverage.tmax, 

3009 box_styles_coverage[kind][0]) 

3010 else: 

3011 t = None 

3012 pcount = 0 

3013 for tb, count in coverage.changes: 

3014 if t is not None and tb > t: 

3015 if pcount > 0: 

3016 drawbox( 

3017 itrack, t, tb, 

3018 box_styles_coverage[kind][ 

3019 min(len(box_styles_coverage)-1, 

3020 pcount)]) 

3021 

3022 t = tb 

3023 pcount = count 

3024 

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

3026 ''' 

3027 This performs the actual drawing. 

3028 ''' 

3029 

3030 self.timer_draw.start() 

3031 show_boxes = self.menuitem_showboxes.isChecked() 

3032 sq = self.get_squirrel() 

3033 

3034 if self.gather is None: 

3035 self.set_gathering() 

3036 

3037 if self.pile_has_changed: 

3038 

3039 if not self.sortingmode_change_delayed(): 

3040 self.sortingmode_change() 

3041 

3042 if show_boxes and sq is None: 

3043 self.determine_box_styles() 

3044 

3045 self.pile_has_changed = False 

3046 

3047 if h is None: 

3048 h = float(self.height()) 

3049 if w is None: 

3050 w = float(self.width()) 

3051 

3052 if printmode: 

3053 primary_color = (0, 0, 0) 

3054 else: 

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

3056 

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

3058 

3059 ax_h = float(self.ax_height) 

3060 

3061 vbottom_ax_projection = Projection() 

3062 vtop_ax_projection = Projection() 

3063 vcenter_projection = Projection() 

3064 

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

3066 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3067 vtop_ax_projection.set_out_range(0., ax_h) 

3068 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3069 vcenter_projection.set_in_range(0., 1.) 

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

3071 

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

3073 track_projections = {} 

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

3075 proj = Projection() 

3076 proj.set_out_range( 

3077 self.track_to_screen(i+0.05), 

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

3079 

3080 track_projections[i] = proj 

3081 

3082 if self.tmin > self.tmax: 

3083 return 

3084 

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

3086 vbottom_ax_projection.set_in_range(0, ax_h) 

3087 

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

3089 

3090 yscaler = pyrocko.plot.AutoScaler() 

3091 

3092 p.setPen(primary_pen) 

3093 

3094 font = qg.QFont() 

3095 font.setBold(True) 

3096 

3097 axannotfont = qg.QFont() 

3098 axannotfont.setBold(True) 

3099 axannotfont.setPointSize(8) 

3100 

3101 processed_traces = self.prepare_cutout2( 

3102 self.tmin, self.tmax, 

3103 trace_selector=self.trace_selector, 

3104 degap=self.menuitem_degap.isChecked(), 

3105 demean=self.menuitem_demean.isChecked()) 

3106 

3107 if not printmode and show_boxes: 

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

3109 or (self.view_mode is ViewMode.Waterfall 

3110 and not processed_traces): 

3111 

3112 if sq is None: 

3113 self.draw_trace_boxes( 

3114 p, self.time_projection, track_projections) 

3115 

3116 else: 

3117 self.draw_coverage( 

3118 p, self.time_projection, track_projections) 

3119 

3120 p.setFont(font) 

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

3122 

3123 color_lookup = dict( 

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

3125 

3126 self.track_to_nslc_ids = {} 

3127 nticks = 0 

3128 annot_labels = [] 

3129 

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

3131 waterfall = self.waterfall 

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

3133 waterfall.set_traces(processed_traces) 

3134 waterfall.set_cmap(self.waterfall_cmap) 

3135 waterfall.set_integrate(self.waterfall_integrate) 

3136 waterfall.set_clip( 

3137 self.waterfall_clip_min, self.waterfall_clip_max) 

3138 waterfall.show_absolute_values( 

3139 self.waterfall_show_absolute) 

3140 

3141 rect = qc.QRectF( 

3142 0, self.ax_height, 

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

3144 ) 

3145 waterfall.draw_waterfall(p, rect=rect) 

3146 

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

3148 show_scales = self.menuitem_showscalerange.isChecked() \ 

3149 or self.menuitem_showscaleaxis.isChecked() 

3150 

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

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

3153 - self.track_to_screen(0.05) 

3154 

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

3156 

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

3158 if self.menuitem_showscaleaxis.isChecked() \ 

3159 else 15 

3160 

3161 yscaler = pyrocko.plot.AutoScaler( 

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

3163 snap=show_scales 

3164 and not self.menuitem_showscaleaxis.isChecked()) 

3165 

3166 data_ranges = pyrocko.trace.minmax( 

3167 processed_traces, 

3168 key=self.scaling_key, 

3169 mode=self.scaling_base) 

3170 

3171 if not self.menuitem_fixscalerange.isChecked(): 

3172 self.old_data_ranges = data_ranges 

3173 else: 

3174 data_ranges.update(self.old_data_ranges) 

3175 

3176 self.apply_scaling_hooks(data_ranges) 

3177 

3178 trace_to_itrack = {} 

3179 track_scaling_keys = {} 

3180 track_scaling_colors = {} 

3181 for trace in processed_traces: 

3182 gt = self.gather(trace) 

3183 if gt not in self.key_to_row: 

3184 continue 

3185 

3186 itrack = self.key_to_row[gt] 

3187 if itrack not in track_projections: 

3188 continue 

3189 

3190 trace_to_itrack[trace] = itrack 

3191 

3192 if itrack not in self.track_to_nslc_ids: 

3193 self.track_to_nslc_ids[itrack] = set() 

3194 

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

3196 

3197 if itrack not in track_scaling_keys: 

3198 track_scaling_keys[itrack] = set() 

3199 

3200 scaling_key = self.scaling_key(trace) 

3201 track_scaling_keys[itrack].add(scaling_key) 

3202 

3203 color = pyrocko.plot.color( 

3204 color_lookup[self.color_gather(trace)]) 

3205 

3206 k = itrack, scaling_key 

3207 if k not in track_scaling_colors \ 

3208 and self.menuitem_colortraces.isChecked(): 

3209 track_scaling_colors[k] = color 

3210 else: 

3211 track_scaling_colors[k] = primary_color 

3212 

3213 # y axes, zero lines 

3214 trace_projections = {} 

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

3216 if itrack not in track_scaling_keys: 

3217 continue 

3218 uoff = 0 

3219 for scaling_key in track_scaling_keys[itrack]: 

3220 data_range = data_ranges[scaling_key] 

3221 dymin, dymax = data_range 

3222 ymin, ymax, yinc = yscaler.make_scale( 

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

3224 iexp = yscaler.make_exp(yinc) 

3225 factor = 10**iexp 

3226 trace_projection = track_projections[itrack].copy() 

3227 trace_projection.set_in_range(ymax, ymin) 

3228 trace_projections[itrack, scaling_key] = \ 

3229 trace_projection 

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

3231 vmin, vmax = trace_projection.get_out_range() 

3232 umax_zeroline = umax 

3233 uoffnext = uoff 

3234 

3235 if show_scales: 

3236 pen = qg.QPen(primary_pen) 

3237 k = itrack, scaling_key 

3238 if k in track_scaling_colors: 

3239 c = qg.QColor(*track_scaling_colors[ 

3240 itrack, scaling_key]) 

3241 

3242 pen.setColor(c) 

3243 

3244 p.setPen(pen) 

3245 if nlinesavail > 3: 

3246 if self.menuitem_showscaleaxis.isChecked(): 

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

3248 ny_annot = int( 

3249 math.floor(ymax/yinc) 

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

3251 

3252 for iy_annot in range(ny_annot): 

3253 y = ymin_annot + iy_annot*yinc 

3254 v = trace_projection(y) 

3255 line = qc.QLineF( 

3256 umax-10-uoff, v, umax-uoff, v) 

3257 

3258 p.drawLine(line) 

3259 if iy_annot == ny_annot - 1 \ 

3260 and iexp != 0: 

3261 sexp = ' &times; ' \ 

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

3263 else: 

3264 sexp = '' 

3265 

3266 snum = num_to_html(y/factor) 

3267 lab = Label( 

3268 p, 

3269 umax-20-uoff, 

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

3271 label_bg=None, 

3272 anchor='MR', 

3273 font=axannotfont, 

3274 color=c) 

3275 

3276 uoffnext = max( 

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

3278 

3279 annot_labels.append(lab) 

3280 if y == 0.: 

3281 umax_zeroline = \ 

3282 umax - 20 \ 

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

3284 - uoff 

3285 else: 

3286 if not show_boxes: 

3287 qpoints = make_QPolygonF( 

3288 [umax-20-uoff, 

3289 umax-10-uoff, 

3290 umax-10-uoff, 

3291 umax-20-uoff], 

3292 [vmax, vmax, vmin, vmin]) 

3293 p.drawPolyline(qpoints) 

3294 

3295 snum = num_to_html(ymin) 

3296 labmin = Label( 

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

3298 label_bg=None, 

3299 anchor='BR', 

3300 font=axannotfont, 

3301 color=c) 

3302 

3303 annot_labels.append(labmin) 

3304 snum = num_to_html(ymax) 

3305 labmax = Label( 

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

3307 label_bg=None, 

3308 anchor='TR', 

3309 font=axannotfont, 

3310 color=c) 

3311 

3312 annot_labels.append(labmax) 

3313 

3314 for lab in (labmin, labmax): 

3315 uoffnext = max( 

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

3317 

3318 if self.menuitem_showzeroline.isChecked(): 

3319 v = trace_projection(0.) 

3320 if vmin <= v <= vmax: 

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

3322 p.drawLine(line) 

3323 

3324 uoff = uoffnext 

3325 

3326 p.setFont(font) 

3327 p.setPen(primary_pen) 

3328 for trace in processed_traces: 

3329 if self.view_mode is not ViewMode.Wiggle: 

3330 break 

3331 

3332 if trace not in trace_to_itrack: 

3333 continue 

3334 

3335 itrack = trace_to_itrack[trace] 

3336 scaling_key = self.scaling_key(trace) 

3337 trace_projection = trace_projections[ 

3338 itrack, scaling_key] 

3339 

3340 vdata = trace_projection(trace.get_ydata()) 

3341 

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

3343 udata_max = float(self.time_projection( 

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

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

3346 

3347 qpoints = make_QPolygonF(udata, vdata) 

3348 

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

3350 vmin, vmax = trace_projection.get_out_range() 

3351 

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

3353 

3354 if self.menuitem_cliptraces.isChecked(): 

3355 p.setClipRect(trackrect) 

3356 

3357 if self.menuitem_colortraces.isChecked(): 

3358 color = pyrocko.plot.color( 

3359 color_lookup[self.color_gather(trace)]) 

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

3361 p.setPen(pen) 

3362 

3363 p.drawPolyline(qpoints) 

3364 

3365 if self.floating_marker: 

3366 self.floating_marker.draw_trace( 

3367 self, p, trace, 

3368 self.time_projection, trace_projection, 1.0) 

3369 

3370 for marker in self.markers.with_key_in( 

3371 self.tmin - self.markers_deltat_max, 

3372 self.tmax): 

3373 

3374 if marker.tmin < self.tmax \ 

3375 and self.tmin < marker.tmax \ 

3376 and marker.kind \ 

3377 in self.visible_marker_kinds: 

3378 marker.draw_trace( 

3379 self, p, trace, self.time_projection, 

3380 trace_projection, 1.0) 

3381 

3382 p.setPen(primary_pen) 

3383 

3384 if self.menuitem_cliptraces.isChecked(): 

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

3386 

3387 if self.floating_marker: 

3388 self.floating_marker.draw( 

3389 p, self.time_projection, vcenter_projection) 

3390 

3391 self.draw_visible_markers( 

3392 p, vcenter_projection, primary_pen) 

3393 

3394 p.setPen(primary_pen) 

3395 while font.pointSize() > 2: 

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

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

3398 - self.track_to_screen(0.05) 

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

3400 if nlinesavail > 1: 

3401 break 

3402 

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

3404 

3405 p.setFont(font) 

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

3407 

3408 for key in self.track_keys: 

3409 itrack = self.key_to_row[key] 

3410 if itrack in track_projections: 

3411 plabel = ' '.join( 

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

3413 lx = 10 

3414 ly = self.track_to_screen(itrack+0.5) 

3415 

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

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

3418 continue 

3419 

3420 contains_cursor = \ 

3421 self.track_to_screen(itrack) \ 

3422 < mouse_pos.y() \ 

3423 < self.track_to_screen(itrack+1) 

3424 

3425 if not contains_cursor: 

3426 continue 

3427 

3428 font_large = p.font() 

3429 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3430 p.setFont(font_large) 

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

3432 p.setFont(font) 

3433 

3434 for lab in annot_labels: 

3435 lab.draw() 

3436 

3437 self.timer_draw.stop() 

3438 

3439 def see_data_params(self): 

3440 

3441 min_deltat = self.content_deltat_range()[0] 

3442 

3443 # determine padding and downampling requirements 

3444 if self.lowpass is not None: 

3445 deltat_target = 1./self.lowpass * 0.25 

3446 ndecimate = min( 

3447 50, 

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

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

3450 else: 

3451 ndecimate = 1 

3452 tpad = min_deltat*5. 

3453 

3454 if self.highpass is not None: 

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

3456 

3457 nsee_points_per_trace = 5000*10 

3458 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3459 

3460 return ndecimate, tpad, tsee 

3461 

3462 def clean_update(self): 

3463 self.cached_processed_traces = None 

3464 self.update() 

3465 

3466 def get_adequate_tpad(self): 

3467 tpad = 0. 

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

3469 if f is not None: 

3470 tpad = max(tpad, 1.0/f) 

3471 

3472 for snuffling in self.snufflings: 

3473 if snuffling._post_process_hook_enabled \ 

3474 or snuffling._pre_process_hook_enabled: 

3475 

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

3477 

3478 return tpad 

3479 

3480 def prepare_cutout2( 

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

3482 demean=True, nmax=6000): 

3483 

3484 if self.pile.is_empty(): 

3485 return [] 

3486 

3487 nmax = self.visible_length 

3488 

3489 self.timer_cutout.start() 

3490 

3491 tsee = tmax-tmin 

3492 min_deltat_wo_decimate = tsee/nmax 

3493 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3494 

3495 min_deltat_allow = min_deltat_wo_decimate 

3496 if self.lowpass is not None: 

3497 target_deltat_lp = 0.25/self.lowpass 

3498 if target_deltat_lp > min_deltat_wo_decimate: 

3499 min_deltat_allow = min_deltat_w_decimate 

3500 

3501 min_deltat_allow = math.exp( 

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

3503 

3504 tmin_ = tmin 

3505 tmax_ = tmax 

3506 

3507 # fetch more than needed? 

3508 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3512 

3513 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3514 lphp = self.menuitem_lphp.isChecked() 

3515 ads = self.menuitem_allowdownsampling.isChecked() 

3516 

3517 tpad = self.get_adequate_tpad() 

3518 tpad = max(tpad, tsee) 

3519 

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

3521 vec = ( 

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

3523 self.highpass, fft_filtering, lphp, 

3524 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3525 ads, self.pile.get_update_count()) 

3526 

3527 if (self.cached_vec 

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

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

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

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

3532 and self.cached_processed_traces is not None): 

3533 

3534 logger.debug('Using cached traces') 

3535 processed_traces = self.cached_processed_traces 

3536 

3537 else: 

3538 processed_traces = [] 

3539 if self.pile.deltatmax >= min_deltat_allow: 

3540 

3541 def group_selector(gr): 

3542 return gr.deltatmax >= min_deltat_allow 

3543 

3544 if trace_selector is not None: 

3545 def trace_selectorx(tr): 

3546 return tr.deltat >= min_deltat_allow \ 

3547 and trace_selector(tr) 

3548 else: 

3549 def trace_selectorx(tr): 

3550 return tr.deltat >= min_deltat_allow 

3551 

3552 for traces in self.pile.chopper( 

3553 tmin=tmin, tmax=tmax, tpad=tpad, 

3554 want_incomplete=True, 

3555 degap=degap, 

3556 maxgap=gap_lap_tolerance, 

3557 maxlap=gap_lap_tolerance, 

3558 keep_current_files_open=True, 

3559 group_selector=group_selector, 

3560 trace_selector=trace_selectorx, 

3561 accessor_id=id(self), 

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

3563 include_last=True): 

3564 

3565 if demean: 

3566 for tr in traces: 

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

3568 continue 

3569 y = tr.get_ydata() 

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

3571 

3572 traces = self.pre_process_hooks(traces) 

3573 

3574 for trace in traces: 

3575 

3576 if not (trace.meta 

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

3578 

3579 if fft_filtering: 

3580 but = pyrocko.response.ButterworthResponse 

3581 multres = pyrocko.response.MultiplyResponse 

3582 if self.lowpass is not None \ 

3583 or self.highpass is not None: 

3584 

3585 it = num.arange( 

3586 trace.data_len(), dtype=float) 

3587 detr_data, m, b = detrend( 

3588 it, trace.get_ydata()) 

3589 

3590 trace.set_ydata(detr_data) 

3591 

3592 freqs, fdata = trace.spectrum( 

3593 pad_to_pow2=True, tfade=None) 

3594 

3595 nfreqs = fdata.size 

3596 

3597 key = (trace.deltat, nfreqs) 

3598 

3599 if key not in self.tf_cache: 

3600 resps = [] 

3601 if self.lowpass is not None: 

3602 resps.append(but( 

3603 order=4, 

3604 corner=self.lowpass, 

3605 type='low')) 

3606 

3607 if self.highpass is not None: 

3608 resps.append(but( 

3609 order=4, 

3610 corner=self.highpass, 

3611 type='high')) 

3612 

3613 resp = multres(resps) 

3614 self.tf_cache[key] = \ 

3615 resp.evaluate(freqs) 

3616 

3617 filtered_data = num.fft.irfft( 

3618 fdata*self.tf_cache[key] 

3619 )[:trace.data_len()] 

3620 

3621 retrended_data = retrend( 

3622 it, filtered_data, m, b) 

3623 

3624 trace.set_ydata(retrended_data) 

3625 

3626 else: 

3627 

3628 if ads and self.lowpass is not None: 

3629 while trace.deltat \ 

3630 < min_deltat_wo_decimate: 

3631 

3632 trace.downsample(2, demean=False) 

3633 

3634 fmax = 0.5/trace.deltat 

3635 if not lphp and ( 

3636 self.lowpass is not None 

3637 and self.highpass is not None 

3638 and self.lowpass < fmax 

3639 and self.highpass < fmax 

3640 and self.highpass < self.lowpass): 

3641 

3642 trace.bandpass( 

3643 2, self.highpass, self.lowpass) 

3644 else: 

3645 if self.lowpass is not None: 

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

3647 trace.lowpass( 

3648 4, self.lowpass, 

3649 demean=False) 

3650 

3651 if self.highpass is not None: 

3652 if self.lowpass is None \ 

3653 or self.highpass \ 

3654 < self.lowpass: 

3655 

3656 if self.highpass < \ 

3657 0.5/trace.deltat: 

3658 trace.highpass( 

3659 4, self.highpass, 

3660 demean=False) 

3661 

3662 processed_traces.append(trace) 

3663 

3664 if self.rotate != 0.0: 

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

3666 cphi = math.cos(phi) 

3667 sphi = math.sin(phi) 

3668 for a in processed_traces: 

3669 for b in processed_traces: 

3670 if (a.network == b.network 

3671 and a.station == b.station 

3672 and a.location == b.location 

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

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

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

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

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

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

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

3680 

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

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

3683 a.set_ydata(aydata) 

3684 b.set_ydata(bydata) 

3685 

3686 processed_traces = self.post_process_hooks(processed_traces) 

3687 

3688 self.cached_processed_traces = processed_traces 

3689 self.cached_vec = vec 

3690 

3691 chopped_traces = [] 

3692 for trace in processed_traces: 

3693 chop_tmin = tmin_ - trace.deltat*4 

3694 chop_tmax = tmax_ + trace.deltat*4 

3695 

3696 try: 

3697 ctrace = trace.chop( 

3698 chop_tmin, chop_tmax, 

3699 inplace=False) 

3700 

3701 except pyrocko.trace.NoData: 

3702 continue 

3703 

3704 if ctrace.data_len() < 2: 

3705 continue 

3706 

3707 chopped_traces.append(ctrace) 

3708 

3709 self.timer_cutout.stop() 

3710 return chopped_traces 

3711 

3712 def pre_process_hooks(self, traces): 

3713 for snuffling in self.snufflings: 

3714 if snuffling._pre_process_hook_enabled: 

3715 traces = snuffling.pre_process_hook(traces) 

3716 

3717 return traces 

3718 

3719 def post_process_hooks(self, traces): 

3720 for snuffling in self.snufflings: 

3721 if snuffling._post_process_hook_enabled: 

3722 traces = snuffling.post_process_hook(traces) 

3723 

3724 return traces 

3725 

3726 def visible_length_change(self, ignore=None): 

3727 for menuitem, vlen in self.menuitems_visible_length: 

3728 if menuitem.isChecked(): 

3729 self.visible_length = vlen 

3730 

3731 def scaling_base_change(self, ignore=None): 

3732 for menuitem, scaling_base in self.menuitems_scaling_base: 

3733 if menuitem.isChecked(): 

3734 self.scaling_base = scaling_base 

3735 

3736 def scalingmode_change(self, ignore=None): 

3737 for menuitem, scaling_key in self.menuitems_scaling: 

3738 if menuitem.isChecked(): 

3739 self.scaling_key = scaling_key 

3740 self.update() 

3741 

3742 def apply_scaling_hooks(self, data_ranges): 

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

3744 hook = self.scaling_hooks[k] 

3745 hook(data_ranges) 

3746 

3747 def viewmode_change(self, ignore=True): 

3748 for item, mode in self.menuitems_viewmode: 

3749 if item.isChecked(): 

3750 self.view_mode = mode 

3751 break 

3752 else: 

3753 raise AttributeError('unknown view mode') 

3754 

3755 items_waterfall_disabled = ( 

3756 self.menuitem_showscaleaxis, 

3757 self.menuitem_showscalerange, 

3758 self.menuitem_showzeroline, 

3759 self.menuitem_colortraces, 

3760 self.menuitem_cliptraces, 

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

3762 ) 

3763 

3764 if self.view_mode is ViewMode.Waterfall: 

3765 self.parent().show_colorbar_ctrl(True) 

3766 self.parent().show_gain_ctrl(False) 

3767 

3768 for item in items_waterfall_disabled: 

3769 item.setDisabled(True) 

3770 

3771 self.visible_length = 180. 

3772 else: 

3773 self.parent().show_colorbar_ctrl(False) 

3774 self.parent().show_gain_ctrl(True) 

3775 

3776 for item in items_waterfall_disabled: 

3777 item.setDisabled(False) 

3778 

3779 self.visible_length_change() 

3780 self.update() 

3781 

3782 def set_scaling_hook(self, k, hook): 

3783 self.scaling_hooks[k] = hook 

3784 

3785 def remove_scaling_hook(self, k): 

3786 del self.scaling_hooks[k] 

3787 

3788 def remove_scaling_hooks(self): 

3789 self.scaling_hooks = {} 

3790 

3791 def s_sortingmode_change(self, ignore=None): 

3792 for menuitem, valfunc in self.menuitems_ssorting: 

3793 if menuitem.isChecked(): 

3794 self._ssort = valfunc 

3795 

3796 self.sortingmode_change() 

3797 

3798 def sortingmode_change(self, ignore=None): 

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

3800 if menuitem.isChecked(): 

3801 self.set_gathering(gather, color) 

3802 

3803 self.sortingmode_change_time = time.time() 

3804 

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

3806 self.lowpass = value 

3807 self.passband_check() 

3808 self.tf_cache = {} 

3809 self.update() 

3810 

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

3812 self.highpass = value 

3813 self.passband_check() 

3814 self.tf_cache = {} 

3815 self.update() 

3816 

3817 def passband_check(self): 

3818 if self.highpass and self.lowpass \ 

3819 and self.highpass >= self.lowpass: 

3820 

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

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

3823 'deactivate the highpass.' 

3824 

3825 self.update_status() 

3826 else: 

3827 oldmess = self.message 

3828 self.message = None 

3829 if oldmess is not None: 

3830 self.update_status() 

3831 

3832 def gain_change(self, value, ignore): 

3833 self.gain = value 

3834 self.update() 

3835 

3836 def rot_change(self, value, ignore): 

3837 self.rotate = value 

3838 self.update() 

3839 

3840 def waterfall_cmap_change(self, cmap): 

3841 self.waterfall_cmap = cmap 

3842 self.update() 

3843 

3844 def waterfall_clip_change(self, clip_min, clip_max): 

3845 self.waterfall_clip_min = clip_min 

3846 self.waterfall_clip_max = clip_max 

3847 self.update() 

3848 

3849 def waterfall_show_absolute_change(self, toggle): 

3850 self.waterfall_show_absolute = toggle 

3851 self.update() 

3852 

3853 def waterfall_set_integrate(self, toggle): 

3854 self.waterfall_integrate = toggle 

3855 self.update() 

3856 

3857 def set_selected_markers(self, markers): 

3858 ''' 

3859 Set a list of markers selected 

3860 

3861 :param markers: list of markers 

3862 ''' 

3863 self.deselect_all() 

3864 for m in markers: 

3865 m.selected = True 

3866 

3867 self.update() 

3868 

3869 def deselect_all(self): 

3870 for marker in self.markers: 

3871 marker.selected = False 

3872 

3873 def animate_picking(self): 

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

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

3876 

3877 def get_nslc_ids_for_track(self, ftrack): 

3878 itrack = int(ftrack) 

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

3880 

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

3882 if self.picking: 

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

3884 self.picking = None 

3885 self.picking_down = None 

3886 self.picking_timer.stop() 

3887 self.picking_timer = None 

3888 if not abort: 

3889 self.add_marker(self.floating_marker) 

3890 self.floating_marker.selected = True 

3891 self.emit_selected_markers() 

3892 

3893 self.floating_marker = None 

3894 

3895 def start_picking(self, ignore): 

3896 

3897 if not self.picking: 

3898 self.deselect_all() 

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

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

3901 

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

3903 self.picking.setGeometry( 

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

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

3906 

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

3908 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3910 self.floating_marker.selected = True 

3911 

3912 self.picking_timer = qc.QTimer() 

3913 self.picking_timer.timeout.connect( 

3914 self.animate_picking) 

3915 

3916 self.picking_timer.setInterval(50) 

3917 self.picking_timer.start() 

3918 

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

3920 if self.picking: 

3921 mouset = self.time_projection.rev(x) 

3922 dt = 0.0 

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

3924 if mouset < self.tmin: 

3925 dt = -(self.tmin - mouset) 

3926 else: 

3927 dt = mouset - self.tmax 

3928 ddt = self.tmax-self.tmin 

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

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

3931 

3932 x0 = x 

3933 if self.picking_down is not None: 

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

3935 

3936 w = abs(x-x0) 

3937 x0 = min(x0, x) 

3938 

3939 tmin, tmax = ( 

3940 self.time_projection.rev(x0), 

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

3942 

3943 tmin, tmax = ( 

3944 max(working_system_time_range[0], tmin), 

3945 min(working_system_time_range[1], tmax)) 

3946 

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

3948 

3949 self.picking.setGeometry( 

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

3951 

3952 ftrack = self.track_to_screen.rev(y) 

3953 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3955 

3956 if dt != 0.0 and doshift: 

3957 self.interrupt_following() 

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

3959 

3960 self.update() 

3961 

3962 def update_status(self): 

3963 

3964 if self.message is None: 

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

3966 

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

3968 if not is_working_time(mouse_t): 

3969 return 

3970 

3971 if self.floating_marker: 

3972 tmi, tma = ( 

3973 self.floating_marker.tmin, 

3974 self.floating_marker.tmax) 

3975 

3976 tt, ms = gmtime_x(tmi) 

3977 

3978 if tmi == tma: 

3979 message = mystrftime( 

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

3981 tt=tt, milliseconds=ms) 

3982 else: 

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

3984 message = mystrftime( 

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

3986 tt=tt, milliseconds=ms) 

3987 else: 

3988 tt, ms = gmtime_x(mouse_t) 

3989 

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

3991 else: 

3992 message = self.message 

3993 

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

3995 sb.clearMessage() 

3996 sb.showMessage(message) 

3997 

3998 def set_sortingmode_change_delay_time(self, dt): 

3999 self.sortingmode_change_delay_time = dt 

4000 

4001 def sortingmode_change_delayed(self): 

4002 now = time.time() 

4003 return ( 

4004 self.sortingmode_change_delay_time is not None 

4005 and now - self.sortingmode_change_time 

4006 < self.sortingmode_change_delay_time) 

4007 

4008 def set_visible_marker_kinds(self, kinds): 

4009 self.deselect_all() 

4010 self.visible_marker_kinds = tuple(kinds) 

4011 self.emit_selected_markers() 

4012 

4013 def following(self): 

4014 return self.follow_timer is not None \ 

4015 and not self.following_interrupted() 

4016 

4017 def interrupt_following(self): 

4018 self.interactive_range_change_time = time.time() 

4019 

4020 def following_interrupted(self, now=None): 

4021 if now is None: 

4022 now = time.time() 

4023 return now - self.interactive_range_change_time \ 

4024 < self.interactive_range_change_delay_time 

4025 

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

4027 if tmax_start is None: 

4028 tmax_start = time.time() 

4029 self.show_all = False 

4030 self.follow_time = tlen 

4031 self.follow_timer = qc.QTimer(self) 

4032 self.follow_timer.timeout.connect( 

4033 self.follow_update) 

4034 self.follow_timer.setInterval(interval) 

4035 self.follow_timer.start() 

4036 self.follow_started = time.time() 

4037 self.follow_lapse = lapse 

4038 self.follow_tshift = self.follow_started - tmax_start 

4039 self.interactive_range_change_time = 0.0 

4040 

4041 def unfollow(self): 

4042 if self.follow_timer is not None: 

4043 self.follow_timer.stop() 

4044 self.follow_timer = None 

4045 self.interactive_range_change_time = 0.0 

4046 

4047 def follow_update(self): 

4048 rnow = time.time() 

4049 if self.follow_lapse is None: 

4050 now = rnow 

4051 else: 

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

4053 * self.follow_lapse 

4054 

4055 if self.following_interrupted(rnow): 

4056 return 

4057 self.set_time_range( 

4058 now-self.follow_time-self.follow_tshift, 

4059 now-self.follow_tshift) 

4060 

4061 self.update() 

4062 

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

4064 self.return_tag = return_tag 

4065 self.window().close() 

4066 

4067 def cleanup(self): 

4068 self.about_to_close.emit() 

4069 self.timer.stop() 

4070 if self.follow_timer is not None: 

4071 self.follow_timer.stop() 

4072 

4073 for snuffling in list(self.snufflings): 

4074 self.remove_snuffling(snuffling) 

4075 

4076 def set_error_message(self, key, value): 

4077 if value is None: 

4078 if key in self.error_messages: 

4079 del self.error_messages[key] 

4080 else: 

4081 self.error_messages[key] = value 

4082 

4083 def inputline_changed(self, text): 

4084 pass 

4085 

4086 def inputline_finished(self, text): 

4087 line = str(text) 

4088 

4089 toks = line.split() 

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

4091 if len(toks) >= 1: 

4092 command = toks[0].lower() 

4093 

4094 try: 

4095 quick_filter_commands = { 

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

4097 's': '*.%s.*.*', 

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

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

4100 

4101 if command in quick_filter_commands: 

4102 if len(toks) >= 2: 

4103 patterns = [ 

4104 quick_filter_commands[toks[0]] % pat 

4105 for pat in toks[1:]] 

4106 self.set_quick_filter_patterns(patterns, line) 

4107 else: 

4108 self.set_quick_filter_patterns(None) 

4109 

4110 self.update() 

4111 

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

4113 if len(toks) >= 2: 

4114 patterns = [] 

4115 if len(toks) == 2: 

4116 patterns = [toks[1]] 

4117 elif len(toks) >= 3: 

4118 x = { 

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

4120 's': '*.%s.*.*', 

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

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

4123 

4124 if toks[1] in x: 

4125 patterns.extend( 

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

4127 

4128 for pattern in patterns: 

4129 if command == 'hide': 

4130 self.add_blacklist_pattern(pattern) 

4131 else: 

4132 self.remove_blacklist_pattern(pattern) 

4133 

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

4135 self.clear_blacklist() 

4136 

4137 clearit = True 

4138 

4139 self.update() 

4140 

4141 elif command == 'markers': 

4142 if len(toks) == 2: 

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

4144 kinds = self.all_marker_kinds 

4145 else: 

4146 kinds = [] 

4147 for x in toks[1]: 

4148 try: 

4149 kinds.append(int(x)) 

4150 except Exception: 

4151 pass 

4152 

4153 self.set_visible_marker_kinds(kinds) 

4154 

4155 elif len(toks) == 1: 

4156 self.set_visible_marker_kinds(()) 

4157 

4158 self.update() 

4159 

4160 elif command == 'scaling': 

4161 if len(toks) == 2: 

4162 hideit = False 

4163 error = 'wrong number of arguments' 

4164 

4165 if len(toks) >= 3: 

4166 vmin, vmax = [ 

4167 pyrocko.model.float_or_none(x) 

4168 for x in toks[-2:]] 

4169 

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

4171 if k in d: 

4172 if vmin is not None: 

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

4174 if vmax is not None: 

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

4176 

4177 if len(toks) == 1: 

4178 self.remove_scaling_hooks() 

4179 

4180 elif len(toks) == 3: 

4181 def hook(data_ranges): 

4182 for k in data_ranges: 

4183 upd(data_ranges, k, vmin, vmax) 

4184 

4185 self.set_scaling_hook('_', hook) 

4186 

4187 elif len(toks) == 4: 

4188 pattern = toks[1] 

4189 

4190 def hook(data_ranges): 

4191 for k in pyrocko.util.match_nslcs( 

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

4193 

4194 upd(data_ranges, k, vmin, vmax) 

4195 

4196 self.set_scaling_hook(pattern, hook) 

4197 

4198 elif command == 'goto': 

4199 toks2 = line.split(None, 1) 

4200 if len(toks2) == 2: 

4201 arg = toks2[1] 

4202 m = re.match( 

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

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

4205 if m: 

4206 tlen = None 

4207 if not m.group(1): 

4208 tlen = 12*32*24*60*60 

4209 elif not m.group(2): 

4210 tlen = 32*24*60*60 

4211 elif not m.group(3): 

4212 tlen = 24*60*60 

4213 elif not m.group(4): 

4214 tlen = 60*60 

4215 elif not m.group(5): 

4216 tlen = 60 

4217 

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

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

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

4221 t = pyrocko.util.str_to_time(arg) 

4222 self.go_to_time(t, tlen=tlen) 

4223 

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

4225 supl = '00:00:00' 

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

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

4228 tmin, tmax = self.get_time_range() 

4229 sdate = pyrocko.util.time_to_str( 

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

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

4232 self.go_to_time(t) 

4233 

4234 elif arg == 'today': 

4235 self.go_to_time( 

4236 day_start( 

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

4238 

4239 elif arg == 'yesterday': 

4240 self.go_to_time( 

4241 day_start( 

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

4243 

4244 else: 

4245 self.go_to_event_by_name(arg) 

4246 

4247 else: 

4248 raise PileViewerMainException( 

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

4250 

4251 except PileViewerMainException as e: 

4252 error = str(e) 

4253 hideit = False 

4254 

4255 return clearit, hideit, error 

4256 

4257 return PileViewerMain 

4258 

4259 

4260PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4261GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget) 

4262 

4263 

4264class LineEditWithAbort(qw.QLineEdit): 

4265 

4266 aborted = qc.pyqtSignal() 

4267 history_down = qc.pyqtSignal() 

4268 history_up = qc.pyqtSignal() 

4269 

4270 def keyPressEvent(self, key_event): 

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

4272 self.aborted.emit() 

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

4274 self.history_down.emit() 

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

4276 self.history_up.emit() 

4277 else: 

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

4279 

4280 

4281class PileViewer(qw.QFrame): 

4282 ''' 

4283 PileViewerMain + Controls + Inputline 

4284 ''' 

4285 

4286 def __init__( 

4287 self, pile, 

4288 ntracks_shown_max=20, 

4289 marker_editor_sortable=True, 

4290 use_opengl=None, 

4291 panel_parent=None, 

4292 *args): 

4293 

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

4295 

4296 layout = qw.QGridLayout() 

4297 layout.setContentsMargins(0, 0, 0, 0) 

4298 layout.setSpacing(0) 

4299 

4300 self.menu = PileViewerMenuBar(self) 

4301 

4302 if use_opengl is None: 

4303 use_opengl = is_macos 

4304 

4305 if use_opengl: 

4306 self.viewer = GLPileViewerMain( 

4307 pile, 

4308 ntracks_shown_max=ntracks_shown_max, 

4309 panel_parent=panel_parent, 

4310 menu=self.menu) 

4311 else: 

4312 self.viewer = PileViewerMain( 

4313 pile, 

4314 ntracks_shown_max=ntracks_shown_max, 

4315 panel_parent=panel_parent, 

4316 menu=self.menu) 

4317 

4318 self.marker_editor_sortable = marker_editor_sortable 

4319 

4320 self.setFrameShape(qw.QFrame.StyledPanel) 

4321 self.setFrameShadow(qw.QFrame.Sunken) 

4322 

4323 self.input_area = qw.QFrame(self) 

4324 ia_layout = qw.QGridLayout() 

4325 ia_layout.setContentsMargins(11, 11, 11, 11) 

4326 self.input_area.setLayout(ia_layout) 

4327 

4328 self.inputline = LineEditWithAbort(self.input_area) 

4329 self.inputline.returnPressed.connect( 

4330 self.inputline_returnpressed) 

4331 self.inputline.editingFinished.connect( 

4332 self.inputline_finished) 

4333 self.inputline.aborted.connect( 

4334 self.inputline_aborted) 

4335 

4336 self.inputline.history_down.connect( 

4337 lambda: self.step_through_history(1)) 

4338 self.inputline.history_up.connect( 

4339 lambda: self.step_through_history(-1)) 

4340 

4341 self.inputline.textEdited.connect( 

4342 self.inputline_changed) 

4343 

4344 self.inputline.setPlaceholderText( 

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

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

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

4348 self.input_area.hide() 

4349 self.history = None 

4350 

4351 self.inputline_error_str = None 

4352 

4353 self.inputline_error = qw.QLabel() 

4354 self.inputline_error.hide() 

4355 

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

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

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

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

4360 

4361 pb = Progressbars(self) 

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

4363 self.progressbars = pb 

4364 

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

4366 self.scrollbar = scrollbar 

4367 layout.addWidget(scrollbar, 1, 1) 

4368 self.scrollbar.valueChanged.connect( 

4369 self.scrollbar_changed) 

4370 

4371 self.block_scrollbar_changes = False 

4372 

4373 self.viewer.want_input.connect( 

4374 self.inputline_show) 

4375 self.viewer.tracks_range_changed.connect( 

4376 self.tracks_range_changed) 

4377 self.viewer.pile_has_changed_signal.connect( 

4378 self.adjust_controls) 

4379 self.viewer.about_to_close.connect( 

4380 self.save_inputline_history) 

4381 

4382 self.setLayout(layout) 

4383 

4384 def cleanup(self): 

4385 self.viewer.cleanup() 

4386 

4387 def get_progressbars(self): 

4388 return self.progressbars 

4389 

4390 def inputline_show(self): 

4391 if not self.history: 

4392 self.load_inputline_history() 

4393 

4394 self.input_area.show() 

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

4396 self.inputline.selectAll() 

4397 

4398 def inputline_set_error(self, string): 

4399 self.inputline_error_str = string 

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

4401 self.inputline.selectAll() 

4402 self.inputline_error.setText(string) 

4403 self.input_area.show() 

4404 self.inputline_error.show() 

4405 

4406 def inputline_clear_error(self): 

4407 if self.inputline_error_str: 

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

4409 self.inputline_error_str = None 

4410 self.inputline_error.clear() 

4411 self.inputline_error.hide() 

4412 

4413 def inputline_changed(self, line): 

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

4415 self.inputline_clear_error() 

4416 

4417 def inputline_returnpressed(self): 

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

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

4420 

4421 if error: 

4422 self.inputline_set_error(error) 

4423 

4424 line = line.strip() 

4425 

4426 if line != '' and not error: 

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

4428 self.history.append(line) 

4429 

4430 if clearit: 

4431 

4432 self.inputline.blockSignals(True) 

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

4434 if qpat is None: 

4435 self.inputline.clear() 

4436 else: 

4437 self.inputline.setText(qinp) 

4438 self.inputline.blockSignals(False) 

4439 

4440 if hideit and not error: 

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

4442 self.input_area.hide() 

4443 

4444 self.hist_ind = len(self.history) 

4445 

4446 def inputline_aborted(self): 

4447 ''' 

4448 Hide the input line. 

4449 ''' 

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

4451 self.hist_ind = len(self.history) 

4452 self.input_area.hide() 

4453 

4454 def save_inputline_history(self): 

4455 ''' 

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

4457 ''' 

4458 if not self.history: 

4459 return 

4460 

4461 conf = pyrocko.config 

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

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

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

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

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

4467 

4468 def load_inputline_history(self): 

4469 ''' 

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

4471 ''' 

4472 conf = pyrocko.config 

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

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

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

4476 f.write('\n') 

4477 

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

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

4480 

4481 self.hist_ind = len(self.history) 

4482 

4483 def step_through_history(self, ud=1): 

4484 ''' 

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

4486 ''' 

4487 n = len(self.history) 

4488 self.hist_ind += ud 

4489 self.hist_ind %= (n + 1) 

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

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

4492 else: 

4493 self.inputline.setText('') 

4494 

4495 def inputline_finished(self): 

4496 pass 

4497 

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

4499 if self.block_scrollbar_changes: 

4500 return 

4501 

4502 self.scrollbar.blockSignals(True) 

4503 self.scrollbar.setPageStep(ihi-ilo) 

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

4505 self.scrollbar.setRange(0, vmax) 

4506 self.scrollbar.setValue(ilo) 

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

4508 self.scrollbar.blockSignals(False) 

4509 

4510 def scrollbar_changed(self, value): 

4511 self.block_scrollbar_changes = True 

4512 ilo = value 

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

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

4515 self.block_scrollbar_changes = False 

4516 self.update_contents() 

4517 

4518 def controls(self): 

4519 frame = qw.QFrame(self) 

4520 layout = qw.QGridLayout() 

4521 frame.setLayout(layout) 

4522 

4523 minfreq = 0.001 

4524 maxfreq = 1000.0 

4525 self.lowpass_control = ValControl(high_is_none=True) 

4526 self.lowpass_control.setup( 

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

4528 self.highpass_control = ValControl(low_is_none=True) 

4529 self.highpass_control.setup( 

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

4531 self.gain_control = ValControl() 

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

4533 self.rot_control = LinValControl() 

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

4535 self.colorbar_control = ColorbarControl(self) 

4536 

4537 self.lowpass_control.valchange.connect( 

4538 self.viewer.lowpass_change) 

4539 self.highpass_control.valchange.connect( 

4540 self.viewer.highpass_change) 

4541 self.gain_control.valchange.connect( 

4542 self.viewer.gain_change) 

4543 self.rot_control.valchange.connect( 

4544 self.viewer.rot_change) 

4545 self.colorbar_control.cmap_changed.connect( 

4546 self.viewer.waterfall_cmap_change 

4547 ) 

4548 self.colorbar_control.clip_changed.connect( 

4549 self.viewer.waterfall_clip_change 

4550 ) 

4551 self.colorbar_control.show_absolute_toggled.connect( 

4552 self.viewer.waterfall_show_absolute_change 

4553 ) 

4554 self.colorbar_control.show_integrate_toggled.connect( 

4555 self.viewer.waterfall_set_integrate 

4556 ) 

4557 

4558 for icontrol, control in enumerate(( 

4559 self.highpass_control, 

4560 self.lowpass_control, 

4561 self.gain_control, 

4562 self.rot_control, 

4563 self.colorbar_control)): 

4564 

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

4566 layout.addWidget(widget, icontrol, iwidget) 

4567 

4568 spacer = qw.QSpacerItem( 

4569 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4571 

4572 self.adjust_controls() 

4573 self.viewer.viewmode_change(ViewMode.Wiggle) 

4574 return frame 

4575 

4576 def marker_editor(self): 

4577 editor = pyrocko.gui.marker_editor.MarkerEditor( 

4578 self, sortable=self.marker_editor_sortable) 

4579 

4580 editor.set_viewer(self.get_view()) 

4581 editor.get_marker_model().dataChanged.connect( 

4582 self.update_contents) 

4583 return editor 

4584 

4585 def adjust_controls(self): 

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

4587 maxfreq = 0.5/dtmin 

4588 minfreq = (0.5/dtmax)*0.001 

4589 self.lowpass_control.set_range(minfreq, maxfreq) 

4590 self.highpass_control.set_range(minfreq, maxfreq) 

4591 

4592 def setup_snufflings(self): 

4593 self.viewer.setup_snufflings() 

4594 

4595 def get_view(self): 

4596 return self.viewer 

4597 

4598 def update_contents(self): 

4599 self.viewer.update() 

4600 

4601 def get_pile(self): 

4602 return self.viewer.get_pile() 

4603 

4604 def show_colorbar_ctrl(self, show): 

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

4606 w.setVisible(show) 

4607 

4608 def show_gain_ctrl(self, show): 

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

4610 w.setVisible(show)