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, 6, 7) 

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', 

932 ('minmax', 'minmax')), 

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

934 ('minmax', 'robust')), 

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

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

937 ] 

938 

939 self.menuitems_scaling_base = add_radiobuttongroup( 

940 scale_menu, menudef, self.scaling_base_change) 

941 

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

943 scale_menu.addSeparator() 

944 

945 self.menuitem_fixscalerange = scale_menu.addAction( 

946 'Fix Scale Ranges') 

947 self.menuitem_fixscalerange.setCheckable(True) 

948 

949 # Sort Menu 

950 def sector_dist(sta): 

951 if sta.dist_m is None: 

952 return None, None 

953 else: 

954 return ( 

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

956 m_float(sta.dist_m)) 

957 

958 menudef = [ 

959 ('Sort by Names', 

960 lambda tr: (), 

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

962 ('Sort by Distance', 

963 lambda tr: self.station_attrib( 

964 tr, 

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

966 lambda tr: (None,)), 

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

968 ('Sort by Azimuth', 

969 lambda tr: self.station_attrib( 

970 tr, 

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

972 lambda tr: (None,))), 

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

974 lambda tr: self.station_attrib( 

975 tr, 

976 sector_dist, 

977 lambda tr: (None, None))), 

978 ('Sort by Backazimuth', 

979 lambda tr: self.station_attrib( 

980 tr, 

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

982 lambda tr: (None,))), 

983 ] 

984 self.menuitems_ssorting = add_radiobuttongroup( 

985 sort_menu, menudef, self.s_sortingmode_change) 

986 sort_menu.addSeparator() 

987 

988 self._ssort = lambda tr: () 

989 

990 self.menu.addSeparator() 

991 

992 menudef = [ 

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

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

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

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

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

998 lambda tr: tr.channel)), 

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

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

1001 lambda tr: tr.channel)), 

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

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

1004 lambda tr: tr.channel)), 

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

1006 ((3, 0, 1, 2), 

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

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

1009 ((0, 1, 3), 

1010 lambda tr: tr.location)), 

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

1012 ((1, 0, 3), 

1013 lambda tr: tr.location)), 

1014 ] 

1015 

1016 self.menuitems_sorting = add_radiobuttongroup( 

1017 sort_menu, menudef, self.sortingmode_change) 

1018 

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

1020 self.config.visible_length_setting] 

1021 

1022 # View menu 

1023 self.menuitems_visible_length = add_radiobuttongroup( 

1024 view_menu, menudef, 

1025 self.visible_length_change) 

1026 view_menu.addSeparator() 

1027 

1028 view_modes = [ 

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

1030 ('Waterfall', ViewMode.Waterfall) 

1031 ] 

1032 

1033 self.menuitems_viewmode = add_radiobuttongroup( 

1034 view_menu, view_modes, 

1035 self.viewmode_change, default=ViewMode.Wiggle) 

1036 view_menu.addSeparator() 

1037 

1038 self.menuitem_cliptraces = view_menu.addAction( 

1039 'Clip Traces') 

1040 self.menuitem_cliptraces.setCheckable(True) 

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

1042 

1043 self.menuitem_showboxes = view_menu.addAction( 

1044 'Show Boxes') 

1045 self.menuitem_showboxes.setCheckable(True) 

1046 self.menuitem_showboxes.setChecked( 

1047 self.config.show_boxes) 

1048 

1049 self.menuitem_colortraces = view_menu.addAction( 

1050 'Color Traces') 

1051 self.menuitem_colortraces.setCheckable(True) 

1052 self.menuitem_antialias = view_menu.addAction( 

1053 'Antialiasing') 

1054 self.menuitem_antialias.setCheckable(True) 

1055 

1056 view_menu.addSeparator() 

1057 self.menuitem_showscalerange = view_menu.addAction( 

1058 'Show Scale Ranges') 

1059 self.menuitem_showscalerange.setCheckable(True) 

1060 self.menuitem_showscalerange.setChecked( 

1061 self.config.show_scale_ranges) 

1062 

1063 self.menuitem_showscaleaxis = view_menu.addAction( 

1064 'Show Scale Axes') 

1065 self.menuitem_showscaleaxis.setCheckable(True) 

1066 self.menuitem_showscaleaxis.setChecked( 

1067 self.config.show_scale_axes) 

1068 

1069 self.menuitem_showzeroline = view_menu.addAction( 

1070 'Show Zero Lines') 

1071 self.menuitem_showzeroline.setCheckable(True) 

1072 

1073 view_menu.addSeparator() 

1074 view_menu.addAction( 

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

1076 'Fullscreen', 

1077 self.toggle_fullscreen, 

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

1079 

1080 # Options Menu 

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

1082 self.menuitem_demean.setCheckable(True) 

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

1084 self.menuitem_demean.setShortcut( 

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

1086 

1087 self.menuitem_distances_3d = options_menu.addAction( 

1088 '3D distances', 

1089 self.distances_3d_changed) 

1090 self.menuitem_distances_3d.setCheckable(True) 

1091 

1092 self.menuitem_allowdownsampling = options_menu.addAction( 

1093 'Allow Downsampling') 

1094 self.menuitem_allowdownsampling.setCheckable(True) 

1095 self.menuitem_allowdownsampling.setChecked(True) 

1096 

1097 self.menuitem_degap = options_menu.addAction( 

1098 'Allow Degapping') 

1099 self.menuitem_degap.setCheckable(True) 

1100 self.menuitem_degap.setChecked(True) 

1101 

1102 options_menu.addSeparator() 

1103 

1104 self.menuitem_fft_filtering = options_menu.addAction( 

1105 'FFT Filtering') 

1106 self.menuitem_fft_filtering.setCheckable(True) 

1107 

1108 self.menuitem_lphp = options_menu.addAction( 

1109 'Bandpass is Low- + Highpass') 

1110 self.menuitem_lphp.setCheckable(True) 

1111 self.menuitem_lphp.setChecked(True) 

1112 

1113 options_menu.addSeparator() 

1114 self.menuitem_watch = options_menu.addAction( 

1115 'Watch Files') 

1116 self.menuitem_watch.setCheckable(True) 

1117 

1118 self.menuitem_liberal_fetch = options_menu.addAction( 

1119 'Liberal Fetch Optimization') 

1120 self.menuitem_liberal_fetch.setCheckable(True) 

1121 

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

1123 

1124 self.snufflings_menu.addAction( 

1125 'Reload Snufflings', 

1126 self.setup_snufflings) 

1127 

1128 # Disable ShadowPileTest 

1129 if False: 

1130 test_action = self.menu.addAction( 

1131 'Test', 

1132 self.toggletest) 

1133 test_action.setCheckable(True) 

1134 

1135 help_menu.addAction( 

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

1137 'Snuffler Controls', 

1138 self.help, 

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

1140 

1141 help_menu.addAction( 

1142 'About', 

1143 self.about) 

1144 

1145 self.time_projection = Projection() 

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

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

1148 

1149 self.gather = None 

1150 

1151 self.trace_filter = None 

1152 self.quick_filter = None 

1153 self.quick_filter_patterns = None, None 

1154 self.blacklist = [] 

1155 

1156 self.track_to_screen = Projection() 

1157 self.track_to_nslc_ids = {} 

1158 

1159 self.cached_vec = None 

1160 self.cached_processed_traces = None 

1161 

1162 self.timer = qc.QTimer(self) 

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

1164 self.timer.setInterval(1000) 

1165 self.timer.start() 

1166 self.pile.add_listener(self) 

1167 self.trace_styles = {} 

1168 if self.get_squirrel() is None: 

1169 self.determine_box_styles() 

1170 

1171 self.setMouseTracking(True) 

1172 

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

1174 self.snuffling_modules = {} 

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

1176 self.default_snufflings = None 

1177 self.snufflings = [] 

1178 

1179 self.stations = {} 

1180 

1181 self.timer_draw = Timer() 

1182 self.timer_cutout = Timer() 

1183 self.time_spent_painting = 0.0 

1184 self.time_last_painted = time.time() 

1185 

1186 self.interactive_range_change_time = 0.0 

1187 self.interactive_range_change_delay_time = 10.0 

1188 self.follow_timer = None 

1189 

1190 self.sortingmode_change_time = 0.0 

1191 self.sortingmode_change_delay_time = None 

1192 

1193 self.old_data_ranges = {} 

1194 

1195 self.error_messages = {} 

1196 self.return_tag = None 

1197 self.wheel_pos = 60 

1198 

1199 self.setAcceptDrops(True) 

1200 self._paths_to_load = [] 

1201 

1202 self.tf_cache = {} 

1203 

1204 self.waterfall = TraceWaterfall() 

1205 self.waterfall_cmap = 'viridis' 

1206 self.waterfall_clip_min = 0. 

1207 self.waterfall_clip_max = 1. 

1208 self.waterfall_show_absolute = False 

1209 self.waterfall_integrate = False 

1210 self.view_mode = ViewMode.Wiggle 

1211 

1212 self.automatic_updates = True 

1213 

1214 self.closing = False 

1215 self.in_paint_event = False 

1216 

1217 def fail(self, reason): 

1218 box = qw.QMessageBox(self) 

1219 box.setText(reason) 

1220 box.exec_() 

1221 

1222 def set_trace_filter(self, filter_func): 

1223 self.trace_filter = filter_func 

1224 self.sortingmode_change() 

1225 

1226 def update_trace_filter(self): 

1227 if self.blacklist: 

1228 

1229 def blacklist_func(tr): 

1230 return not pyrocko.util.match_nslc( 

1231 self.blacklist, tr.nslc_id) 

1232 

1233 else: 

1234 blacklist_func = None 

1235 

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

1237 self.set_trace_filter(None) 

1238 elif self.quick_filter is None: 

1239 self.set_trace_filter(blacklist_func) 

1240 elif blacklist_func is None: 

1241 self.set_trace_filter(self.quick_filter) 

1242 else: 

1243 self.set_trace_filter( 

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

1245 

1246 def set_quick_filter(self, filter_func): 

1247 self.quick_filter = filter_func 

1248 self.update_trace_filter() 

1249 

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

1251 if patterns is not None: 

1252 self.set_quick_filter( 

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

1254 else: 

1255 self.set_quick_filter(None) 

1256 

1257 self.quick_filter_patterns = patterns, inputline 

1258 

1259 def get_quick_filter_patterns(self): 

1260 return self.quick_filter_patterns 

1261 

1262 def add_blacklist_pattern(self, pattern): 

1263 if pattern == 'empty': 

1264 keys = set(self.pile.nslc_ids) 

1265 trs = self.pile.all( 

1266 tmin=self.tmin, 

1267 tmax=self.tmax, 

1268 load_data=False, 

1269 degap=False) 

1270 

1271 for tr in trs: 

1272 if tr.nslc_id in keys: 

1273 keys.remove(tr.nslc_id) 

1274 

1275 for key in keys: 

1276 xpattern = '.'.join(key) 

1277 if xpattern not in self.blacklist: 

1278 self.blacklist.append(xpattern) 

1279 

1280 else: 

1281 if pattern in self.blacklist: 

1282 self.blacklist.remove(pattern) 

1283 

1284 self.blacklist.append(pattern) 

1285 

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

1287 self.update_trace_filter() 

1288 

1289 def remove_blacklist_pattern(self, pattern): 

1290 if pattern in self.blacklist: 

1291 self.blacklist.remove(pattern) 

1292 else: 

1293 raise PileViewerMainException( 

1294 'Pattern not found in blacklist.') 

1295 

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

1297 self.update_trace_filter() 

1298 

1299 def clear_blacklist(self): 

1300 self.blacklist = [] 

1301 self.update_trace_filter() 

1302 

1303 def ssort(self, tr): 

1304 return self._ssort(tr) 

1305 

1306 def station_key(self, x): 

1307 return x.network, x.station 

1308 

1309 def station_keys(self, x): 

1310 return [ 

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

1312 (x.network, x.station)] 

1313 

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

1315 for sk in self.station_keys(tr): 

1316 if sk in self.stations: 

1317 station = self.stations[sk] 

1318 return getter(station) 

1319 

1320 return default_getter(tr) 

1321 

1322 def get_station(self, sk): 

1323 return self.stations[sk] 

1324 

1325 def has_station(self, station): 

1326 for sk in self.station_keys(station): 

1327 if sk in self.stations: 

1328 return True 

1329 

1330 return False 

1331 

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

1333 return self.station_attrib( 

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

1335 

1336 def set_stations(self, stations): 

1337 self.stations = {} 

1338 self.add_stations(stations) 

1339 

1340 def add_stations(self, stations): 

1341 for station in stations: 

1342 for sk in self.station_keys(station): 

1343 self.stations[sk] = station 

1344 

1345 ev = self.get_active_event() 

1346 if ev: 

1347 self.set_origin(ev) 

1348 

1349 def add_event(self, event): 

1350 marker = EventMarker(event) 

1351 self.add_marker(marker) 

1352 

1353 def add_events(self, events): 

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

1355 self.add_markers(markers) 

1356 

1357 def set_event_marker_as_origin(self, ignore=None): 

1358 selected = self.selected_markers() 

1359 if not selected: 

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

1361 return 

1362 

1363 m = selected[0] 

1364 if not isinstance(m, EventMarker): 

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

1366 return 

1367 

1368 self.set_active_event_marker(m) 

1369 

1370 def deactivate_event_marker(self): 

1371 if self.active_event_marker: 

1372 self.active_event_marker.active = False 

1373 

1374 self.active_event_marker_changed.emit() 

1375 self.active_event_marker = None 

1376 

1377 def set_active_event_marker(self, event_marker): 

1378 if self.active_event_marker: 

1379 self.active_event_marker.active = False 

1380 

1381 self.active_event_marker = event_marker 

1382 event_marker.active = True 

1383 event = event_marker.get_event() 

1384 self.set_origin(event) 

1385 self.active_event_marker_changed.emit() 

1386 

1387 def set_active_event(self, event): 

1388 for marker in self.markers: 

1389 if isinstance(marker, EventMarker): 

1390 if marker.get_event() is event: 

1391 self.set_active_event_marker(marker) 

1392 

1393 def get_active_event_marker(self): 

1394 return self.active_event_marker 

1395 

1396 def get_active_event(self): 

1397 m = self.get_active_event_marker() 

1398 if m is not None: 

1399 return m.get_event() 

1400 else: 

1401 return None 

1402 

1403 def get_active_markers(self): 

1404 emarker = self.get_active_event_marker() 

1405 if emarker is None: 

1406 return None, [] 

1407 

1408 else: 

1409 ev = emarker.get_event() 

1410 pmarkers = [ 

1411 m for m in self.markers 

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

1413 

1414 return emarker, pmarkers 

1415 

1416 def set_origin(self, location): 

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

1418 station.set_event_relative_data( 

1419 location, 

1420 distance_3d=self.menuitem_distances_3d.isChecked()) 

1421 

1422 self.sortingmode_change() 

1423 

1424 def distances_3d_changed(self): 

1425 ignore = self.menuitem_distances_3d.isChecked() 

1426 self.set_event_marker_as_origin(ignore) 

1427 

1428 def iter_snuffling_modules(self): 

1429 pjoin = os.path.join 

1430 for path in self.snuffling_paths: 

1431 

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

1433 os.mkdir(path) 

1434 

1435 for entry in os.listdir(path): 

1436 directory = path 

1437 fn = entry 

1438 d = pjoin(path, entry) 

1439 if os.path.isdir(d): 

1440 directory = d 

1441 if os.path.isfile( 

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

1443 fn = 'snuffling.py' 

1444 

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

1446 continue 

1447 

1448 name = fn[:-3] 

1449 

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

1451 self.snuffling_modules[directory, name] = \ 

1452 pyrocko.gui.snuffling.SnufflingModule( 

1453 directory, name, self) 

1454 

1455 yield self.snuffling_modules[directory, name] 

1456 

1457 def setup_snufflings(self): 

1458 # user snufflings 

1459 for mod in self.iter_snuffling_modules(): 

1460 try: 

1461 mod.load_if_needed() 

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

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

1464 

1465 # load the default snufflings on first run 

1466 if self.default_snufflings is None: 

1467 self.default_snufflings = pyrocko.gui\ 

1468 .snufflings.__snufflings__() 

1469 for snuffling in self.default_snufflings: 

1470 self.add_snuffling(snuffling) 

1471 

1472 def set_panel_parent(self, panel_parent): 

1473 self.panel_parent = panel_parent 

1474 

1475 def get_panel_parent(self): 

1476 return self.panel_parent 

1477 

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

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

1480 snuffling.init_gui( 

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

1482 self.snufflings.append(snuffling) 

1483 self.update() 

1484 

1485 def remove_snuffling(self, snuffling): 

1486 snuffling.delete_gui() 

1487 self.update() 

1488 self.snufflings.remove(snuffling) 

1489 snuffling.pre_destroy() 

1490 

1491 def add_snuffling_menuitem(self, item): 

1492 self.snufflings_menu.addAction(item) 

1493 item.setParent(self.snufflings_menu) 

1494 sort_actions(self.snufflings_menu) 

1495 

1496 def remove_snuffling_menuitem(self, item): 

1497 self.snufflings_menu.removeAction(item) 

1498 

1499 def add_snuffling_help_menuitem(self, item): 

1500 self.snuffling_help.addAction(item) 

1501 item.setParent(self.snuffling_help) 

1502 sort_actions(self.snuffling_help) 

1503 

1504 def remove_snuffling_help_menuitem(self, item): 

1505 self.snuffling_help.removeAction(item) 

1506 

1507 def add_panel_toggler(self, item): 

1508 self.toggle_panel_menu.addAction(item) 

1509 item.setParent(self.toggle_panel_menu) 

1510 sort_actions(self.toggle_panel_menu) 

1511 

1512 def remove_panel_toggler(self, item): 

1513 self.toggle_panel_menu.removeAction(item) 

1514 

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

1516 cache_dir=None, force_cache=False): 

1517 

1518 if cache_dir is None: 

1519 cache_dir = pyrocko.config.config().cache_dir 

1520 if isinstance(paths, str): 

1521 paths = [paths] 

1522 

1523 fns = pyrocko.util.select_files( 

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

1525 

1526 if not fns: 

1527 return 

1528 

1529 cache = pyrocko.pile.get_cache(cache_dir) 

1530 

1531 t = [time.time()] 

1532 

1533 def update_bar(label, value): 

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

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

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

1537 else: 

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

1539 

1540 return pbs.set_status(label, value) 

1541 

1542 def update_progress(label, i, n): 

1543 abort = False 

1544 

1545 qw.qApp.processEvents() 

1546 if n != 0: 

1547 perc = i*100/n 

1548 else: 

1549 perc = 100 

1550 abort |= update_bar(label, perc) 

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

1552 

1553 tnow = time.time() 

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

1555 self.update() 

1556 t[0] = tnow 

1557 

1558 return abort 

1559 

1560 self.automatic_updates = False 

1561 

1562 self.pile.load_files( 

1563 sorted(fns), 

1564 filename_attributes=regex, 

1565 cache=cache, 

1566 fileformat=format, 

1567 show_progress=False, 

1568 update_progress=update_progress) 

1569 

1570 self.automatic_updates = True 

1571 self.update() 

1572 

1573 def load_queued(self): 

1574 if not self._paths_to_load: 

1575 return 

1576 paths = self._paths_to_load 

1577 self._paths_to_load = [] 

1578 self.load(paths) 

1579 

1580 def load_soon(self, paths): 

1581 self._paths_to_load.extend(paths) 

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

1583 

1584 def open_waveforms(self): 

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

1586 

1587 fns, _ = qw.QFileDialog.getOpenFileNames( 

1588 self, caption, options=qfiledialog_options) 

1589 

1590 if fns: 

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

1592 

1593 def open_waveform_directory(self): 

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

1595 

1596 dn = qw.QFileDialog.getExistingDirectory( 

1597 self, caption, options=qfiledialog_options) 

1598 

1599 if dn: 

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

1601 

1602 def open_stations(self, fns=None): 

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

1604 

1605 if not fns: 

1606 fns, _ = qw.QFileDialog.getOpenFileNames( 

1607 self, caption, options=qfiledialog_options) 

1608 

1609 try: 

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

1611 for stat in stations: 

1612 self.add_stations(stat) 

1613 

1614 except Exception as e: 

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

1616 

1617 def open_stations_xml(self, fns=None): 

1618 from pyrocko.io import stationxml 

1619 

1620 caption = 'Select one or more StationXML files' 

1621 if not fns: 

1622 fns, _ = qw.QFileDialog.getOpenFileNames( 

1623 self, caption, options=qfiledialog_options, 

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

1625 ';;All files (*)') 

1626 

1627 try: 

1628 stations = [ 

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

1630 for x in fns] 

1631 

1632 for stat in stations: 

1633 self.add_stations(stat) 

1634 

1635 except Exception as e: 

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

1637 

1638 def add_traces(self, traces): 

1639 if traces: 

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

1641 self.pile.add_file(mtf) 

1642 ticket = (self.pile, mtf) 

1643 return ticket 

1644 else: 

1645 return (None, None) 

1646 

1647 def release_data(self, tickets): 

1648 for ticket in tickets: 

1649 pile, mtf = ticket 

1650 if pile is not None: 

1651 pile.remove_file(mtf) 

1652 

1653 def periodical(self): 

1654 if self.menuitem_watch.isChecked(): 

1655 if self.pile.reload_modified(): 

1656 self.update() 

1657 

1658 def get_pile(self): 

1659 return self.pile 

1660 

1661 def pile_changed(self, what): 

1662 self.pile_has_changed = True 

1663 self.pile_has_changed_signal.emit() 

1664 if self.automatic_updates: 

1665 self.update() 

1666 

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

1668 

1669 if gather is None: 

1670 def gather_func(tr): 

1671 return tr.nslc_id 

1672 

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

1674 

1675 else: 

1676 def gather_func(tr): 

1677 return ( 

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

1679 

1680 if color is None: 

1681 def color(tr): 

1682 return tr.location 

1683 

1684 self.gather = gather_func 

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

1686 

1687 self.color_gather = color 

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

1689 previous_ntracks = self.ntracks 

1690 self.set_ntracks(len(keys)) 

1691 

1692 if self.shown_tracks_range is None or \ 

1693 previous_ntracks == 0 or \ 

1694 self.show_all: 

1695 

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

1697 key_at_top = None 

1698 n = high-low 

1699 

1700 else: 

1701 low, high = self.shown_tracks_range 

1702 key_at_top = self.track_keys[low] 

1703 n = high-low 

1704 

1705 self.track_keys = sorted(keys) 

1706 

1707 track_patterns = [] 

1708 for k in self.track_keys: 

1709 pat = ['*', '*', '*', '*'] 

1710 for i, j in enumerate(gather): 

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

1712 

1713 track_patterns.append(pat) 

1714 

1715 self.track_patterns = track_patterns 

1716 

1717 if key_at_top is not None: 

1718 try: 

1719 ind = self.track_keys.index(key_at_top) 

1720 low = ind 

1721 high = low+n 

1722 except Exception: 

1723 pass 

1724 

1725 self.set_tracks_range((low, high)) 

1726 

1727 self.key_to_row = dict( 

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

1729 

1730 def inrange(x, r): 

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

1732 

1733 def trace_selector(trace): 

1734 gt = self.gather(trace) 

1735 return ( 

1736 gt in self.key_to_row and 

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

1738 

1739 self.trace_selector = lambda x: \ 

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

1741 and trace_selector(x) 

1742 

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

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

1745 self.show_all: 

1746 

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

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

1749 tlen = (tmax - tmin) 

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

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

1752 

1753 def set_time_range(self, tmin, tmax): 

1754 if tmin is None: 

1755 tmin = initial_time_range[0] 

1756 

1757 if tmax is None: 

1758 tmax = initial_time_range[1] 

1759 

1760 if tmin > tmax: 

1761 tmin, tmax = tmax, tmin 

1762 

1763 if tmin == tmax: 

1764 tmin -= 1. 

1765 tmax += 1. 

1766 

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

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

1769 

1770 min_deltat = self.content_deltat_range()[0] 

1771 if (tmax - tmin < min_deltat): 

1772 m = (tmin + tmax) / 2. 

1773 tmin = m - min_deltat/2. 

1774 tmax = m + min_deltat/2. 

1775 

1776 self.time_projection.set_in_range(tmin, tmax) 

1777 self.tmin, self.tmax = tmin, tmax 

1778 

1779 def get_time_range(self): 

1780 return self.tmin, self.tmax 

1781 

1782 def ypart(self, y): 

1783 if y < self.ax_height: 

1784 return -1 

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

1786 return 1 

1787 else: 

1788 return 0 

1789 

1790 def time_fractional_digits(self): 

1791 min_deltat = self.content_deltat_range()[0] 

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

1793 

1794 def write_markers(self, fn=None): 

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

1796 if not fn: 

1797 fn, _ = qw.QFileDialog.getSaveFileName( 

1798 self, caption, options=qfiledialog_options) 

1799 if fn: 

1800 try: 

1801 Marker.save_markers( 

1802 self.markers, fn, 

1803 fdigits=self.time_fractional_digits()) 

1804 

1805 except Exception as e: 

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

1807 

1808 def write_selected_markers(self, fn=None): 

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

1810 if not fn: 

1811 fn, _ = qw.QFileDialog.getSaveFileName( 

1812 self, caption, options=qfiledialog_options) 

1813 if fn: 

1814 try: 

1815 Marker.save_markers( 

1816 self.iter_selected_markers(), 

1817 fn, 

1818 fdigits=self.time_fractional_digits()) 

1819 

1820 except Exception as e: 

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

1822 

1823 def read_events(self, fn=None): 

1824 ''' 

1825 Open QFileDialog to open, read and add 

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

1827 representation to the pile viewer. 

1828 ''' 

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

1830 if not fn: 

1831 fn, _ = qw.QFileDialog.getOpenFileName( 

1832 self, caption, options=qfiledialog_options) 

1833 if fn: 

1834 try: 

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

1836 self.associate_phases_to_events() 

1837 

1838 except Exception as e: 

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

1840 

1841 def read_markers(self, fn=None): 

1842 ''' 

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

1844 ''' 

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

1846 if not fn: 

1847 fn, _ = qw.QFileDialog.getOpenFileName( 

1848 self, caption, options=qfiledialog_options) 

1849 if fn: 

1850 try: 

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

1852 self.associate_phases_to_events() 

1853 

1854 except Exception as e: 

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

1856 

1857 def associate_phases_to_events(self): 

1858 associate_phases_to_events(self.markers) 

1859 

1860 def add_marker(self, marker): 

1861 # need index to inform QAbstactTableModel about upcoming change, 

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

1863 self.markers.insert(marker) 

1864 i = self.markers.remove(marker) 

1865 

1866 self.begin_markers_add.emit(i, i) 

1867 self.markers.insert(marker) 

1868 self.end_markers_add.emit() 

1869 self.markers_deltat_max = max( 

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

1871 

1872 def add_markers(self, markers): 

1873 if not self.markers: 

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

1875 self.markers.insert_many(markers) 

1876 self.end_markers_add.emit() 

1877 self.update_markers_deltat_max() 

1878 else: 

1879 for marker in markers: 

1880 self.add_marker(marker) 

1881 

1882 def update_markers_deltat_max(self): 

1883 if self.markers: 

1884 self.markers_deltat_max = max( 

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

1886 

1887 def remove_marker(self, marker): 

1888 ''' 

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

1890 

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

1892 ''' 

1893 

1894 if marker is self.active_event_marker: 

1895 self.deactivate_event_marker() 

1896 

1897 try: 

1898 i = self.markers.index(marker) 

1899 self.begin_markers_remove.emit(i, i) 

1900 self.markers.remove_at(i) 

1901 self.end_markers_remove.emit() 

1902 except ValueError: 

1903 pass 

1904 

1905 def remove_markers(self, markers): 

1906 ''' 

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

1908 

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

1910 instances 

1911 ''' 

1912 

1913 if markers is self.markers: 

1914 markers = list(markers) 

1915 

1916 for marker in markers: 

1917 self.remove_marker(marker) 

1918 

1919 self.update_markers_deltat_max() 

1920 

1921 def remove_selected_markers(self): 

1922 def delete_segment(istart, iend): 

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

1924 for _ in range(iend - istart): 

1925 self.markers.remove_at(istart) 

1926 

1927 self.end_markers_remove.emit() 

1928 

1929 istart = None 

1930 ipos = 0 

1931 markers = self.markers 

1932 nmarkers = len(self.markers) 

1933 while ipos < nmarkers: 

1934 marker = markers[ipos] 

1935 if marker.is_selected(): 

1936 if marker is self.active_event_marker: 

1937 self.deactivate_event_marker() 

1938 

1939 if istart is None: 

1940 istart = ipos 

1941 else: 

1942 if istart is not None: 

1943 delete_segment(istart, ipos) 

1944 nmarkers -= ipos - istart 

1945 ipos = istart - 1 

1946 istart = None 

1947 

1948 ipos += 1 

1949 

1950 if istart is not None: 

1951 delete_segment(istart, ipos) 

1952 

1953 self.update_markers_deltat_max() 

1954 

1955 def selected_markers(self): 

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

1957 

1958 def iter_selected_markers(self): 

1959 for marker in self.markers: 

1960 if marker.is_selected(): 

1961 yield marker 

1962 

1963 def get_markers(self): 

1964 return self.markers 

1965 

1966 def mousePressEvent(self, mouse_ev): 

1967 self.show_all = False 

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

1969 

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

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

1972 if self.picking: 

1973 if self.picking_down is None: 

1974 self.picking_down = ( 

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

1976 mouse_ev.y()) 

1977 

1978 elif marker is not None: 

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

1980 self.deselect_all() 

1981 marker.selected = True 

1982 self.emit_selected_markers() 

1983 self.update() 

1984 else: 

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

1986 self.track_trange = self.tmin, self.tmax 

1987 

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

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

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

1991 self.update_status() 

1992 

1993 def mouseReleaseEvent(self, mouse_ev): 

1994 if self.ignore_releases: 

1995 self.ignore_releases -= 1 

1996 return 

1997 

1998 if self.picking: 

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

2000 self.emit_selected_markers() 

2001 

2002 if self.track_start: 

2003 self.update() 

2004 

2005 self.track_start = None 

2006 self.track_trange = None 

2007 self.update_status() 

2008 

2009 def mouseDoubleClickEvent(self, mouse_ev): 

2010 self.show_all = False 

2011 self.start_picking(None) 

2012 self.ignore_releases = 1 

2013 

2014 def mouseMoveEvent(self, mouse_ev): 

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

2016 

2017 if self.picking: 

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

2019 

2020 elif self.track_start is not None: 

2021 x0, y0 = self.track_start 

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

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

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

2025 dy = 0 

2026 

2027 tmin0, tmax0 = self.track_trange 

2028 

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

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

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

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

2033 

2034 self.interrupt_following() 

2035 self.set_time_range( 

2036 tmin0 - dt - dtr*frac, 

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

2038 

2039 self.update() 

2040 else: 

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

2042 

2043 self.update_status() 

2044 

2045 def nslc_ids_under_cursor(self, x, y): 

2046 ftrack = self.track_to_screen.rev(y) 

2047 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2048 return nslc_ids 

2049 

2050 def marker_under_cursor(self, x, y): 

2051 mouset = self.time_projection.rev(x) 

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

2053 relevant_nslc_ids = None 

2054 for marker in self.markers: 

2055 if marker.kind not in self.visible_marker_kinds: 

2056 continue 

2057 

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

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

2060 

2061 if relevant_nslc_ids is None: 

2062 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2063 

2064 marker_nslc_ids = marker.get_nslc_ids() 

2065 if not marker_nslc_ids: 

2066 return marker 

2067 

2068 for nslc_id in marker_nslc_ids: 

2069 if nslc_id in relevant_nslc_ids: 

2070 return marker 

2071 

2072 def hoovering(self, x, y): 

2073 mouset = self.time_projection.rev(x) 

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

2075 needupdate = False 

2076 haveone = False 

2077 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2078 for marker in self.markers: 

2079 if marker.kind not in self.visible_marker_kinds: 

2080 continue 

2081 

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

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

2084 

2085 if state: 

2086 xstate = False 

2087 

2088 marker_nslc_ids = marker.get_nslc_ids() 

2089 if not marker_nslc_ids: 

2090 xstate = True 

2091 

2092 for nslc in relevant_nslc_ids: 

2093 if marker.match_nslc(nslc): 

2094 xstate = True 

2095 

2096 state = xstate 

2097 

2098 if state: 

2099 haveone = True 

2100 oldstate = marker.is_alerted() 

2101 if oldstate != state: 

2102 needupdate = True 

2103 marker.set_alerted(state) 

2104 if state: 

2105 self.message = marker.hoover_message() 

2106 

2107 if not haveone: 

2108 self.message = None 

2109 

2110 if needupdate: 

2111 self.update() 

2112 

2113 def event(self, event): 

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

2115 self.keyPressEvent(event) 

2116 return True 

2117 else: 

2118 return base.event(self, event) 

2119 

2120 def keyPressEvent(self, key_event): 

2121 self.show_all = False 

2122 dt = self.tmax - self.tmin 

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

2124 

2125 key = key_event.key() 

2126 try: 

2127 keytext = str(key_event.text()) 

2128 except UnicodeEncodeError: 

2129 return 

2130 

2131 if key == qc.Qt.Key_Space: 

2132 self.interrupt_following() 

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

2134 

2135 elif key == qc.Qt.Key_Up: 

2136 for m in self.selected_markers(): 

2137 if isinstance(m, PhaseMarker): 

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

2139 p = 0 

2140 else: 

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

2142 m.set_polarity(p) 

2143 

2144 elif key == qc.Qt.Key_Down: 

2145 for m in self.selected_markers(): 

2146 if isinstance(m, PhaseMarker): 

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

2148 p = 0 

2149 else: 

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

2151 m.set_polarity(p) 

2152 

2153 elif key == qc.Qt.Key_B: 

2154 dt = self.tmax - self.tmin 

2155 self.interrupt_following() 

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

2157 

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

2159 self.interrupt_following() 

2160 

2161 tgo = None 

2162 

2163 class TraceDummy(object): 

2164 def __init__(self, marker): 

2165 self._marker = marker 

2166 

2167 @property 

2168 def nslc_id(self): 

2169 return self._marker.one_nslc() 

2170 

2171 def marker_to_itrack(marker): 

2172 try: 

2173 return self.key_to_row.get( 

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

2175 

2176 except MarkerOneNSLCRequired: 

2177 return -1 

2178 

2179 emarker, pmarkers = self.get_active_markers() 

2180 pmarkers = [ 

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

2182 pmarkers.sort(key=lambda m: ( 

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

2184 

2185 if key == qc.Qt.Key_Backtab: 

2186 pmarkers.reverse() 

2187 

2188 smarkers = self.selected_markers() 

2189 iselected = [] 

2190 for sm in smarkers: 

2191 try: 

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

2193 except ValueError: 

2194 pass 

2195 

2196 if iselected: 

2197 icurrent = max(iselected) + 1 

2198 else: 

2199 icurrent = 0 

2200 

2201 if icurrent < len(pmarkers): 

2202 self.deselect_all() 

2203 cmarker = pmarkers[icurrent] 

2204 cmarker.selected = True 

2205 tgo = cmarker.tmin 

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

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

2208 

2209 itrack = marker_to_itrack(cmarker) 

2210 if itrack != -1: 

2211 if itrack < self.shown_tracks_range[0]: 

2212 self.scroll_tracks( 

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

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

2215 self.scroll_tracks( 

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

2217 

2218 if itrack not in self.track_to_nslc_ids: 

2219 self.go_to_selection() 

2220 

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

2222 smarkers = self.selected_markers() 

2223 tgo = None 

2224 dir = str(keytext) 

2225 if smarkers: 

2226 tmid = smarkers[0].tmin 

2227 for smarker in smarkers: 

2228 if dir == 'n': 

2229 tmid = max(smarker.tmin, tmid) 

2230 else: 

2231 tmid = min(smarker.tmin, tmid) 

2232 

2233 tgo = tmid 

2234 

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

2236 for marker in sorted( 

2237 self.markers, 

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

2239 

2240 t = marker.tmin 

2241 if t > tmid and \ 

2242 marker.kind in self.visible_marker_kinds and \ 

2243 (dir == 'n' or 

2244 isinstance(marker, EventMarker)): 

2245 

2246 self.deselect_all() 

2247 marker.selected = True 

2248 tgo = t 

2249 break 

2250 else: 

2251 for marker in sorted( 

2252 self.markers, 

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

2254 reverse=True): 

2255 

2256 t = marker.tmin 

2257 if t < tmid and \ 

2258 marker.kind in self.visible_marker_kinds and \ 

2259 (dir == 'p' or 

2260 isinstance(marker, EventMarker)): 

2261 self.deselect_all() 

2262 marker.selected = True 

2263 tgo = t 

2264 break 

2265 

2266 if tgo is not None: 

2267 self.interrupt_following() 

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

2269 

2270 elif keytext == 'r': 

2271 if self.pile.reload_modified(): 

2272 self.reloaded = True 

2273 

2274 elif keytext == 'R': 

2275 self.setup_snufflings() 

2276 

2277 elif key == qc.Qt.Key_Backspace: 

2278 self.remove_selected_markers() 

2279 

2280 elif keytext == 'a': 

2281 for marker in self.markers: 

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

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

2284 marker.kind in self.visible_marker_kinds): 

2285 marker.selected = True 

2286 else: 

2287 marker.selected = False 

2288 

2289 elif keytext == 'A': 

2290 for marker in self.markers: 

2291 if marker.kind in self.visible_marker_kinds: 

2292 marker.selected = True 

2293 

2294 elif keytext == 'd': 

2295 self.deselect_all() 

2296 

2297 elif keytext == 'E': 

2298 self.deactivate_event_marker() 

2299 

2300 elif keytext == 'e': 

2301 markers = self.selected_markers() 

2302 event_markers_in_spe = [ 

2303 marker for marker in markers 

2304 if not isinstance(marker, PhaseMarker)] 

2305 

2306 phase_markers = [ 

2307 marker for marker in markers 

2308 if isinstance(marker, PhaseMarker)] 

2309 

2310 if len(event_markers_in_spe) == 1: 

2311 event_marker = event_markers_in_spe[0] 

2312 if not isinstance(event_marker, EventMarker): 

2313 nslcs = list(event_marker.nslc_ids) 

2314 lat, lon = 0.0, 0.0 

2315 old = self.get_active_event() 

2316 if len(nslcs) == 1: 

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

2318 elif old is not None: 

2319 lat, lon = old.lat, old.lon 

2320 

2321 event_marker.convert_to_event_marker(lat, lon) 

2322 

2323 self.set_active_event_marker(event_marker) 

2324 event = event_marker.get_event() 

2325 for marker in phase_markers: 

2326 marker.set_event(event) 

2327 

2328 else: 

2329 for marker in event_markers_in_spe: 

2330 marker.convert_to_event_marker() 

2331 

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

2333 for marker in self.selected_markers(): 

2334 marker.set_kind(int(keytext)) 

2335 self.emit_selected_markers() 

2336 

2337 elif key in fkey_map: 

2338 self.handle_fkeys(key) 

2339 

2340 elif key == qc.Qt.Key_Escape: 

2341 if self.picking: 

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

2343 

2344 elif key == qc.Qt.Key_PageDown: 

2345 self.scroll_tracks( 

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

2347 

2348 elif key == qc.Qt.Key_PageUp: 

2349 self.scroll_tracks( 

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

2351 

2352 elif key == qc.Qt.Key_Plus: 

2353 self.zoom_tracks(0., 1.) 

2354 

2355 elif key == qc.Qt.Key_Minus: 

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

2357 

2358 elif key == qc.Qt.Key_Equal: 

2359 ntracks_shown = self.shown_tracks_range[1] - \ 

2360 self.shown_tracks_range[0] 

2361 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2362 self.zoom_tracks(0., dtracks) 

2363 

2364 elif key == qc.Qt.Key_Colon: 

2365 self.want_input.emit() 

2366 

2367 elif keytext == 'f': 

2368 self.toggle_fullscreen() 

2369 

2370 elif keytext == 'g': 

2371 self.go_to_selection() 

2372 

2373 elif keytext == 'G': 

2374 self.go_to_selection(tight=True) 

2375 

2376 elif keytext == 'm': 

2377 self.toggle_marker_editor() 

2378 

2379 elif keytext == 'c': 

2380 self.toggle_main_controls() 

2381 

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

2383 dir = 1 

2384 amount = 1 

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

2386 dir = -1 

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

2388 amount = 10 

2389 self.nudge_selected_markers(dir*amount) 

2390 else: 

2391 super().keyPressEvent(key_event) 

2392 

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

2394 self.emit_selected_markers() 

2395 

2396 self.update() 

2397 self.update_status() 

2398 

2399 def handle_fkeys(self, key): 

2400 self.set_phase_kind( 

2401 self.selected_markers(), 

2402 fkey_map[key] + 1) 

2403 self.emit_selected_markers() 

2404 

2405 def emit_selected_markers(self): 

2406 ibounds = [] 

2407 last_selected = False 

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

2409 this_selected = marker.is_selected() 

2410 if this_selected != last_selected: 

2411 ibounds.append(imarker) 

2412 

2413 last_selected = this_selected 

2414 

2415 if last_selected: 

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

2417 

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

2419 self.n_selected_markers = sum( 

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

2421 self.marker_selection_changed.emit(chunks) 

2422 

2423 def toggle_marker_editor(self): 

2424 self.panel_parent.toggle_marker_editor() 

2425 

2426 def toggle_main_controls(self): 

2427 self.panel_parent.toggle_main_controls() 

2428 

2429 def nudge_selected_markers(self, npixels): 

2430 a, b = self.time_projection.ur 

2431 c, d = self.time_projection.xr 

2432 for marker in self.selected_markers(): 

2433 if not isinstance(marker, EventMarker): 

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

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

2436 

2437 def toggle_fullscreen(self): 

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

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

2440 self.window().showNormal() 

2441 else: 

2442 if is_macos: 

2443 self.window().showMaximized() 

2444 else: 

2445 self.window().showFullScreen() 

2446 

2447 def about(self): 

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

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

2450 txt = f.read() 

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

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

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

2454 

2455 def help(self): 

2456 class MyScrollArea(qw.QScrollArea): 

2457 

2458 def sizeHint(self): 

2459 s = qc.QSize() 

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

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

2462 return s 

2463 

2464 with open(pyrocko.util.data_file( 

2465 'snuffler_help.html')) as f: 

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

2467 

2468 with open(pyrocko.util.data_file( 

2469 'snuffler_help_epilog.html')) as f: 

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

2471 

2472 for h in [hcheat, hepilog]: 

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

2474 h.setWordWrap(True) 

2475 

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

2477 

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

2479 scroller = qw.QScrollArea() 

2480 frame = qw.QFrame(scroller) 

2481 frame.setLineWidth(0) 

2482 layout = qw.QVBoxLayout() 

2483 layout.setContentsMargins(0, 0, 0, 0) 

2484 layout.setSpacing(0) 

2485 frame.setLayout(layout) 

2486 scroller.setWidget(frame) 

2487 scroller.setWidgetResizable(True) 

2488 frame.setBackgroundRole(qg.QPalette.Base) 

2489 for h in labels: 

2490 h.setParent(frame) 

2491 h.setMargin(3) 

2492 h.setTextInteractionFlags( 

2493 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2494 h.setBackgroundRole(qg.QPalette.Base) 

2495 layout.addWidget(h) 

2496 h.linkActivated.connect( 

2497 self.open_link) 

2498 

2499 if self.panel_parent is not None: 

2500 if target == 'panel': 

2501 self.panel_parent.add_panel( 

2502 name, scroller, True, volatile=False) 

2503 else: 

2504 self.panel_parent.add_tab(name, scroller) 

2505 

2506 def open_link(self, link): 

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

2508 

2509 def wheelEvent(self, wheel_event): 

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

2511 

2512 n = self.wheel_pos // 120 

2513 self.wheel_pos = self.wheel_pos % 120 

2514 if n == 0: 

2515 return 

2516 

2517 amount = max( 

2518 1., 

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

2520 wdelta = amount * n 

2521 

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

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

2524 / (trmax-trmin) 

2525 

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

2527 self.zoom_tracks(anchor, wdelta) 

2528 else: 

2529 self.scroll_tracks(-wdelta) 

2530 

2531 def dragEnterEvent(self, event): 

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

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

2534 event.setDropAction(qc.Qt.LinkAction) 

2535 event.accept() 

2536 

2537 def dropEvent(self, event): 

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

2539 paths = list( 

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

2541 event.acceptProposedAction() 

2542 self.load(paths) 

2543 

2544 def get_phase_name(self, kind): 

2545 return self.config.get_phase_name(kind) 

2546 

2547 def set_phase_kind(self, markers, kind): 

2548 phasename = self.get_phase_name(kind) 

2549 

2550 for marker in markers: 

2551 if isinstance(marker, PhaseMarker): 

2552 if kind == 10: 

2553 marker.convert_to_marker() 

2554 else: 

2555 marker.set_phasename(phasename) 

2556 marker.set_event(self.get_active_event()) 

2557 

2558 elif isinstance(marker, EventMarker): 

2559 pass 

2560 

2561 else: 

2562 if kind != 10: 

2563 event = self.get_active_event() 

2564 marker.convert_to_phase_marker( 

2565 event, phasename, None, False) 

2566 

2567 def set_ntracks(self, ntracks): 

2568 if self.ntracks != ntracks: 

2569 self.ntracks = ntracks 

2570 if self.shown_tracks_range is not None: 

2571 l, h = self.shown_tracks_range 

2572 else: 

2573 l, h = 0, self.ntracks 

2574 

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

2576 

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

2578 

2579 low, high = range 

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

2581 high = min(self.ntracks, high) 

2582 low = max(0, low) 

2583 high = max(1, high) 

2584 

2585 if start is None: 

2586 start = float(low) 

2587 

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

2589 self.shown_tracks_range = low, high 

2590 self.shown_tracks_start = start 

2591 

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

2593 

2594 def scroll_tracks(self, shift): 

2595 shown = self.shown_tracks_range 

2596 shiftmin = -shown[0] 

2597 shiftmax = self.ntracks-shown[1] 

2598 shift = max(shiftmin, shift) 

2599 shift = min(shiftmax, shift) 

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

2601 

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

2603 

2604 self.update() 

2605 

2606 def zoom_tracks(self, anchor, delta): 

2607 ntracks_shown = self.shown_tracks_range[1] \ 

2608 - self.shown_tracks_range[0] 

2609 

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

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

2612 return 

2613 

2614 ntracks_shown += int(round(delta)) 

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

2616 

2617 u = self.shown_tracks_start 

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

2619 nv = nu + ntracks_shown 

2620 if nv > self.ntracks: 

2621 nu -= nv - self.ntracks 

2622 nv -= nv - self.ntracks 

2623 

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

2625 

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

2627 - self.shown_tracks_range[0] 

2628 

2629 self.update() 

2630 

2631 def content_time_range(self): 

2632 pile = self.get_pile() 

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

2634 if tmin is None: 

2635 tmin = initial_time_range[0] 

2636 if tmax is None: 

2637 tmax = initial_time_range[1] 

2638 

2639 return tmin, tmax 

2640 

2641 def content_deltat_range(self): 

2642 pile = self.get_pile() 

2643 

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

2645 

2646 if deltatmin is None: 

2647 deltatmin = 0.001 

2648 

2649 if deltatmax is None: 

2650 deltatmax = 1000.0 

2651 

2652 return deltatmin, deltatmax 

2653 

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

2655 if tmax < tmin: 

2656 tmin, tmax = tmax, tmin 

2657 

2658 deltatmin = self.content_deltat_range()[0] 

2659 dt = deltatmin * self.visible_length * 0.95 

2660 

2661 if dt == 0.0: 

2662 dt = 1.0 

2663 

2664 if tight: 

2665 if tmax != tmin: 

2666 dtm = tmax - tmin 

2667 tmin -= dtm*0.1 

2668 tmax += dtm*0.1 

2669 return tmin, tmax 

2670 else: 

2671 tcenter = (tmin + tmax) / 2. 

2672 tmin = tcenter - 0.5*dt 

2673 tmax = tcenter + 0.5*dt 

2674 return tmin, tmax 

2675 

2676 if tmax-tmin < dt: 

2677 vmin, vmax = self.get_time_range() 

2678 dt = min(vmax - vmin, dt) 

2679 

2680 tcenter = (tmin+tmax)/2. 

2681 etmin, etmax = tmin, tmax 

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

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

2684 dtm = tmax-tmin 

2685 if etmin == tmin: 

2686 tmin -= dtm*0.1 

2687 if etmax == tmax: 

2688 tmax += dtm*0.1 

2689 

2690 else: 

2691 dtm = tmax-tmin 

2692 tmin -= dtm*0.1 

2693 tmax += dtm*0.1 

2694 

2695 return tmin, tmax 

2696 

2697 def go_to_selection(self, tight=False): 

2698 markers = self.selected_markers() 

2699 if markers: 

2700 tmax, tmin = self.content_time_range() 

2701 for marker in markers: 

2702 tmin = min(tmin, marker.tmin) 

2703 tmax = max(tmax, marker.tmax) 

2704 

2705 else: 

2706 if tight: 

2707 vmin, vmax = self.get_time_range() 

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

2709 else: 

2710 tmin, tmax = self.content_time_range() 

2711 

2712 tmin, tmax = self.make_good_looking_time_range( 

2713 tmin, tmax, tight=tight) 

2714 

2715 self.interrupt_following() 

2716 self.set_time_range(tmin, tmax) 

2717 self.update() 

2718 

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

2720 tmax = t 

2721 if tlen is not None: 

2722 tmax = t+tlen 

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

2724 self.interrupt_following() 

2725 self.set_time_range(tmin, tmax) 

2726 self.update() 

2727 

2728 def go_to_event_by_name(self, name): 

2729 for marker in self.markers: 

2730 if isinstance(marker, EventMarker): 

2731 event = marker.get_event() 

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

2733 tmin, tmax = self.make_good_looking_time_range( 

2734 event.time, event.time) 

2735 

2736 self.interrupt_following() 

2737 self.set_time_range(tmin, tmax) 

2738 

2739 def printit(self): 

2740 from .qt_compat import qprint 

2741 printer = qprint.QPrinter() 

2742 printer.setOrientation(qprint.QPrinter.Landscape) 

2743 

2744 dialog = qprint.QPrintDialog(printer, self) 

2745 dialog.setWindowTitle('Print') 

2746 

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

2748 return 

2749 

2750 painter = qg.QPainter() 

2751 painter.begin(printer) 

2752 page = printer.pageRect() 

2753 self.drawit( 

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

2755 

2756 painter.end() 

2757 

2758 def savesvg(self, fn=None): 

2759 

2760 if not fn: 

2761 fn, _ = qw.QFileDialog.getSaveFileName( 

2762 self, 

2763 'Save as SVG|PNG', 

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

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

2766 options=qfiledialog_options) 

2767 

2768 if fn == '': 

2769 return 

2770 

2771 fn = str(fn) 

2772 

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

2774 try: 

2775 w, h = 842, 595 

2776 margin = 0.025 

2777 m = max(w, h)*margin 

2778 

2779 generator = qsvg.QSvgGenerator() 

2780 generator.setFileName(fn) 

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

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

2783 

2784 painter = qg.QPainter() 

2785 painter.begin(generator) 

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

2787 painter.end() 

2788 

2789 except Exception as e: 

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

2791 

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

2793 pixmap = self.grab() 

2794 

2795 try: 

2796 pixmap.save(fn) 

2797 

2798 except Exception as e: 

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

2800 

2801 else: 

2802 self.fail( 

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

2804 '".png".') 

2805 

2806 def paintEvent(self, paint_ev): 

2807 ''' 

2808 Called by QT whenever widget needs to be painted. 

2809 ''' 

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

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

2812 if self.in_paint_event: 

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

2814 return 

2815 

2816 self.in_paint_event = True 

2817 

2818 painter = qg.QPainter(self) 

2819 

2820 if self.menuitem_antialias.isChecked(): 

2821 painter.setRenderHint(qg.QPainter.Antialiasing) 

2822 

2823 self.drawit(painter) 

2824 

2825 logger.debug( 

2826 'Time spent drawing: ' 

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

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

2829 (self.timer_draw - self.timer_cutout)) 

2830 

2831 logger.debug( 

2832 'Time spent processing:' 

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

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

2835 self.timer_cutout.get()) 

2836 

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

2838 self.time_last_painted = time.time() 

2839 self.in_paint_event = False 

2840 

2841 def determine_box_styles(self): 

2842 

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

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

2845 istyle = 0 

2846 trace_styles = {} 

2847 for itr, tr in enumerate(traces): 

2848 if itr > 0: 

2849 other = traces[itr-1] 

2850 if not ( 

2851 other.nslc_id == tr.nslc_id 

2852 and other.deltat == tr.deltat 

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

2854 < gap_lap_tolerance*tr.deltat): 

2855 

2856 istyle += 1 

2857 

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

2859 

2860 self.trace_styles = trace_styles 

2861 

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

2863 

2864 for v_projection in track_projections.values(): 

2865 v_projection.set_in_range(0., 1.) 

2866 

2867 def selector(x): 

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

2869 

2870 if self.trace_filter is not None: 

2871 def tselector(x): 

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

2873 

2874 else: 

2875 tselector = selector 

2876 

2877 traces = list(self.pile.iter_traces( 

2878 group_selector=selector, trace_selector=tselector)) 

2879 

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

2881 

2882 def drawbox(itrack, istyle, traces): 

2883 v_projection = track_projections[itrack] 

2884 dvmin = v_projection(0.) 

2885 dvmax = v_projection(1.) 

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

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

2888 

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

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

2891 p.fillRect(rect, style.fill_brush) 

2892 p.setPen(style.frame_pen) 

2893 p.drawRect(rect) 

2894 

2895 traces_by_style = {} 

2896 for itr, tr in enumerate(traces): 

2897 gt = self.gather(tr) 

2898 if gt not in self.key_to_row: 

2899 continue 

2900 

2901 itrack = self.key_to_row[gt] 

2902 if itrack not in track_projections: 

2903 continue 

2904 

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

2906 

2907 if len(traces) < 500: 

2908 drawbox(itrack, istyle, [tr]) 

2909 else: 

2910 if (itrack, istyle) not in traces_by_style: 

2911 traces_by_style[itrack, istyle] = [] 

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

2913 

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

2915 drawbox(itrack, istyle, traces) 

2916 

2917 def draw_visible_markers( 

2918 self, p, vcenter_projection, primary_pen): 

2919 

2920 try: 

2921 markers = self.markers.with_key_in_limited( 

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

2923 

2924 except pyrocko.pile.TooMany: 

2925 tmin = self.markers[0].tmin 

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

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

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

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

2930 v0, _ = vcenter_projection.get_out_range() 

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

2932 

2933 p.save() 

2934 

2935 pen = qg.QPen(primary_pen) 

2936 pen.setWidth(2) 

2937 pen.setStyle(qc.Qt.DotLine) 

2938 # pat = [5., 3.] 

2939 # pen.setDashPattern(pat) 

2940 p.setPen(pen) 

2941 

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

2943 s_selected = ' (all selected)' 

2944 elif self.n_selected_markers > 0: 

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

2946 else: 

2947 s_selected = '' 

2948 

2949 draw_label( 

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

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

2952 label_bg, 'LB') 

2953 

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

2955 p.drawLine(line) 

2956 p.restore() 

2957 

2958 return 

2959 

2960 for marker in markers: 

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

2962 and marker.kind in self.visible_marker_kinds: 

2963 

2964 marker.draw( 

2965 p, self.time_projection, vcenter_projection, 

2966 with_label=True) 

2967 

2968 def get_squirrel(self): 

2969 try: 

2970 return self.pile._squirrel 

2971 except AttributeError: 

2972 return None 

2973 

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

2975 sq = self.get_squirrel() 

2976 if sq is None: 

2977 return 

2978 

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

2980 v_projection = track_projections[itrack] 

2981 dvmin = v_projection(0.) 

2982 dvmax = v_projection(1.) 

2983 dtmin = time_projection.clipped(tmin) 

2984 dtmax = time_projection.clipped(tmax) 

2985 

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

2987 p.fillRect(rect, style.fill_brush) 

2988 p.setPen(style.frame_pen) 

2989 p.drawRect(rect) 

2990 

2991 pattern_list = [] 

2992 pattern_to_itrack = {} 

2993 for key in self.track_keys: 

2994 itrack = self.key_to_row[key] 

2995 if itrack not in track_projections: 

2996 continue 

2997 

2998 pattern = self.track_patterns[itrack] 

2999 pattern_to_itrack[tuple(pattern)] = itrack 

3000 pattern_list.append(tuple(pattern)) 

3001 

3002 vmin, vmax = self.get_time_range() 

3003 

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

3005 for coverage in sq.get_coverage( 

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

3007 itrack = pattern_to_itrack[coverage.pattern.nslc] 

3008 

3009 if coverage.changes is None: 

3010 drawbox( 

3011 itrack, coverage.tmin, coverage.tmax, 

3012 box_styles_coverage[kind][0]) 

3013 else: 

3014 t = None 

3015 pcount = 0 

3016 for tb, count in coverage.changes: 

3017 if t is not None and tb > t: 

3018 if pcount > 0: 

3019 drawbox( 

3020 itrack, t, tb, 

3021 box_styles_coverage[kind][ 

3022 min(len(box_styles_coverage)-1, 

3023 pcount)]) 

3024 

3025 t = tb 

3026 pcount = count 

3027 

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

3029 ''' 

3030 This performs the actual drawing. 

3031 ''' 

3032 

3033 self.timer_draw.start() 

3034 show_boxes = self.menuitem_showboxes.isChecked() 

3035 sq = self.get_squirrel() 

3036 

3037 if self.gather is None: 

3038 self.set_gathering() 

3039 

3040 if self.pile_has_changed: 

3041 

3042 if not self.sortingmode_change_delayed(): 

3043 self.sortingmode_change() 

3044 

3045 if show_boxes and sq is None: 

3046 self.determine_box_styles() 

3047 

3048 self.pile_has_changed = False 

3049 

3050 if h is None: 

3051 h = float(self.height()) 

3052 if w is None: 

3053 w = float(self.width()) 

3054 

3055 if printmode: 

3056 primary_color = (0, 0, 0) 

3057 else: 

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

3059 

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

3061 

3062 ax_h = float(self.ax_height) 

3063 

3064 vbottom_ax_projection = Projection() 

3065 vtop_ax_projection = Projection() 

3066 vcenter_projection = Projection() 

3067 

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

3069 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3070 vtop_ax_projection.set_out_range(0., ax_h) 

3071 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3072 vcenter_projection.set_in_range(0., 1.) 

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

3074 

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

3076 track_projections = {} 

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

3078 proj = Projection() 

3079 proj.set_out_range( 

3080 self.track_to_screen(i+0.05), 

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

3082 

3083 track_projections[i] = proj 

3084 

3085 if self.tmin > self.tmax: 

3086 return 

3087 

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

3089 vbottom_ax_projection.set_in_range(0, ax_h) 

3090 

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

3092 

3093 yscaler = pyrocko.plot.AutoScaler() 

3094 

3095 p.setPen(primary_pen) 

3096 

3097 font = qg.QFont() 

3098 font.setBold(True) 

3099 

3100 axannotfont = qg.QFont() 

3101 axannotfont.setBold(True) 

3102 axannotfont.setPointSize(8) 

3103 

3104 processed_traces = self.prepare_cutout2( 

3105 self.tmin, self.tmax, 

3106 trace_selector=self.trace_selector, 

3107 degap=self.menuitem_degap.isChecked(), 

3108 demean=self.menuitem_demean.isChecked()) 

3109 

3110 if not printmode and show_boxes: 

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

3112 or (self.view_mode is ViewMode.Waterfall 

3113 and not processed_traces): 

3114 

3115 if sq is None: 

3116 self.draw_trace_boxes( 

3117 p, self.time_projection, track_projections) 

3118 

3119 else: 

3120 self.draw_coverage( 

3121 p, self.time_projection, track_projections) 

3122 

3123 p.setFont(font) 

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

3125 

3126 color_lookup = dict( 

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

3128 

3129 self.track_to_nslc_ids = {} 

3130 nticks = 0 

3131 annot_labels = [] 

3132 

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

3134 waterfall = self.waterfall 

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

3136 waterfall.set_traces(processed_traces) 

3137 waterfall.set_cmap(self.waterfall_cmap) 

3138 waterfall.set_integrate(self.waterfall_integrate) 

3139 waterfall.set_clip( 

3140 self.waterfall_clip_min, self.waterfall_clip_max) 

3141 waterfall.show_absolute_values( 

3142 self.waterfall_show_absolute) 

3143 

3144 rect = qc.QRectF( 

3145 0, self.ax_height, 

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

3147 ) 

3148 waterfall.draw_waterfall(p, rect=rect) 

3149 

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

3151 show_scales = self.menuitem_showscalerange.isChecked() \ 

3152 or self.menuitem_showscaleaxis.isChecked() 

3153 

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

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

3156 - self.track_to_screen(0.05) 

3157 

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

3159 

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

3161 if self.menuitem_showscaleaxis.isChecked() \ 

3162 else 15 

3163 

3164 yscaler = pyrocko.plot.AutoScaler( 

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

3166 snap=show_scales 

3167 and not self.menuitem_showscaleaxis.isChecked()) 

3168 

3169 data_ranges = pyrocko.trace.minmax( 

3170 processed_traces, 

3171 key=self.scaling_key, 

3172 mode=self.scaling_base[0], 

3173 outer_mode=self.scaling_base[1]) 

3174 

3175 if not self.menuitem_fixscalerange.isChecked(): 

3176 self.old_data_ranges = data_ranges 

3177 else: 

3178 data_ranges.update(self.old_data_ranges) 

3179 

3180 self.apply_scaling_hooks(data_ranges) 

3181 

3182 trace_to_itrack = {} 

3183 track_scaling_keys = {} 

3184 track_scaling_colors = {} 

3185 for trace in processed_traces: 

3186 gt = self.gather(trace) 

3187 if gt not in self.key_to_row: 

3188 continue 

3189 

3190 itrack = self.key_to_row[gt] 

3191 if itrack not in track_projections: 

3192 continue 

3193 

3194 trace_to_itrack[trace] = itrack 

3195 

3196 if itrack not in self.track_to_nslc_ids: 

3197 self.track_to_nslc_ids[itrack] = set() 

3198 

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

3200 

3201 if itrack not in track_scaling_keys: 

3202 track_scaling_keys[itrack] = set() 

3203 

3204 scaling_key = self.scaling_key(trace) 

3205 track_scaling_keys[itrack].add(scaling_key) 

3206 

3207 color = pyrocko.plot.color( 

3208 color_lookup[self.color_gather(trace)]) 

3209 

3210 k = itrack, scaling_key 

3211 if k not in track_scaling_colors \ 

3212 and self.menuitem_colortraces.isChecked(): 

3213 track_scaling_colors[k] = color 

3214 else: 

3215 track_scaling_colors[k] = primary_color 

3216 

3217 # y axes, zero lines 

3218 trace_projections = {} 

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

3220 if itrack not in track_scaling_keys: 

3221 continue 

3222 uoff = 0 

3223 for scaling_key in track_scaling_keys[itrack]: 

3224 data_range = data_ranges[scaling_key] 

3225 dymin, dymax = data_range 

3226 ymin, ymax, yinc = yscaler.make_scale( 

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

3228 iexp = yscaler.make_exp(yinc) 

3229 factor = 10**iexp 

3230 trace_projection = track_projections[itrack].copy() 

3231 trace_projection.set_in_range(ymax, ymin) 

3232 trace_projections[itrack, scaling_key] = \ 

3233 trace_projection 

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

3235 vmin, vmax = trace_projection.get_out_range() 

3236 umax_zeroline = umax 

3237 uoffnext = uoff 

3238 

3239 if show_scales: 

3240 pen = qg.QPen(primary_pen) 

3241 k = itrack, scaling_key 

3242 if k in track_scaling_colors: 

3243 c = qg.QColor(*track_scaling_colors[ 

3244 itrack, scaling_key]) 

3245 

3246 pen.setColor(c) 

3247 

3248 p.setPen(pen) 

3249 if nlinesavail > 3: 

3250 if self.menuitem_showscaleaxis.isChecked(): 

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

3252 ny_annot = int( 

3253 math.floor(ymax/yinc) 

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

3255 

3256 for iy_annot in range(ny_annot): 

3257 y = ymin_annot + iy_annot*yinc 

3258 v = trace_projection(y) 

3259 line = qc.QLineF( 

3260 umax-10-uoff, v, umax-uoff, v) 

3261 

3262 p.drawLine(line) 

3263 if iy_annot == ny_annot - 1 \ 

3264 and iexp != 0: 

3265 sexp = ' &times; ' \ 

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

3267 else: 

3268 sexp = '' 

3269 

3270 snum = num_to_html(y/factor) 

3271 lab = Label( 

3272 p, 

3273 umax-20-uoff, 

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

3275 label_bg=None, 

3276 anchor='MR', 

3277 font=axannotfont, 

3278 color=c) 

3279 

3280 uoffnext = max( 

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

3282 

3283 annot_labels.append(lab) 

3284 if y == 0.: 

3285 umax_zeroline = \ 

3286 umax - 20 \ 

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

3288 - uoff 

3289 else: 

3290 if not show_boxes: 

3291 qpoints = make_QPolygonF( 

3292 [umax-20-uoff, 

3293 umax-10-uoff, 

3294 umax-10-uoff, 

3295 umax-20-uoff], 

3296 [vmax, vmax, vmin, vmin]) 

3297 p.drawPolyline(qpoints) 

3298 

3299 snum = num_to_html(ymin) 

3300 labmin = Label( 

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

3302 label_bg=None, 

3303 anchor='BR', 

3304 font=axannotfont, 

3305 color=c) 

3306 

3307 annot_labels.append(labmin) 

3308 snum = num_to_html(ymax) 

3309 labmax = Label( 

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

3311 label_bg=None, 

3312 anchor='TR', 

3313 font=axannotfont, 

3314 color=c) 

3315 

3316 annot_labels.append(labmax) 

3317 

3318 for lab in (labmin, labmax): 

3319 uoffnext = max( 

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

3321 

3322 if self.menuitem_showzeroline.isChecked(): 

3323 v = trace_projection(0.) 

3324 if vmin <= v <= vmax: 

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

3326 p.drawLine(line) 

3327 

3328 uoff = uoffnext 

3329 

3330 p.setFont(font) 

3331 p.setPen(primary_pen) 

3332 for trace in processed_traces: 

3333 if self.view_mode is not ViewMode.Wiggle: 

3334 break 

3335 

3336 if trace not in trace_to_itrack: 

3337 continue 

3338 

3339 itrack = trace_to_itrack[trace] 

3340 scaling_key = self.scaling_key(trace) 

3341 trace_projection = trace_projections[ 

3342 itrack, scaling_key] 

3343 

3344 vdata = trace_projection(trace.get_ydata()) 

3345 

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

3347 udata_max = float(self.time_projection( 

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

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

3350 

3351 qpoints = make_QPolygonF(udata, vdata) 

3352 

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

3354 vmin, vmax = trace_projection.get_out_range() 

3355 

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

3357 

3358 if self.menuitem_cliptraces.isChecked(): 

3359 p.setClipRect(trackrect) 

3360 

3361 if self.menuitem_colortraces.isChecked(): 

3362 color = pyrocko.plot.color( 

3363 color_lookup[self.color_gather(trace)]) 

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

3365 p.setPen(pen) 

3366 

3367 p.drawPolyline(qpoints) 

3368 

3369 if self.floating_marker: 

3370 self.floating_marker.draw_trace( 

3371 self, p, trace, 

3372 self.time_projection, trace_projection, 1.0) 

3373 

3374 for marker in self.markers.with_key_in( 

3375 self.tmin - self.markers_deltat_max, 

3376 self.tmax): 

3377 

3378 if marker.tmin < self.tmax \ 

3379 and self.tmin < marker.tmax \ 

3380 and marker.kind \ 

3381 in self.visible_marker_kinds: 

3382 marker.draw_trace( 

3383 self, p, trace, self.time_projection, 

3384 trace_projection, 1.0) 

3385 

3386 p.setPen(primary_pen) 

3387 

3388 if self.menuitem_cliptraces.isChecked(): 

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

3390 

3391 if self.floating_marker: 

3392 self.floating_marker.draw( 

3393 p, self.time_projection, vcenter_projection) 

3394 

3395 self.draw_visible_markers( 

3396 p, vcenter_projection, primary_pen) 

3397 

3398 p.setPen(primary_pen) 

3399 while font.pointSize() > 2: 

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

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

3402 - self.track_to_screen(0.05) 

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

3404 if nlinesavail > 1: 

3405 break 

3406 

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

3408 

3409 p.setFont(font) 

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

3411 

3412 for key in self.track_keys: 

3413 itrack = self.key_to_row[key] 

3414 if itrack in track_projections: 

3415 plabel = ' '.join( 

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

3417 lx = 10 

3418 ly = self.track_to_screen(itrack+0.5) 

3419 

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

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

3422 continue 

3423 

3424 contains_cursor = \ 

3425 self.track_to_screen(itrack) \ 

3426 < mouse_pos.y() \ 

3427 < self.track_to_screen(itrack+1) 

3428 

3429 if not contains_cursor: 

3430 continue 

3431 

3432 font_large = p.font() 

3433 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3434 p.setFont(font_large) 

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

3436 p.setFont(font) 

3437 

3438 for lab in annot_labels: 

3439 lab.draw() 

3440 

3441 self.timer_draw.stop() 

3442 

3443 def see_data_params(self): 

3444 

3445 min_deltat = self.content_deltat_range()[0] 

3446 

3447 # determine padding and downampling requirements 

3448 if self.lowpass is not None: 

3449 deltat_target = 1./self.lowpass * 0.25 

3450 ndecimate = min( 

3451 50, 

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

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

3454 else: 

3455 ndecimate = 1 

3456 tpad = min_deltat*5. 

3457 

3458 if self.highpass is not None: 

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

3460 

3461 nsee_points_per_trace = 5000*10 

3462 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3463 

3464 return ndecimate, tpad, tsee 

3465 

3466 def clean_update(self): 

3467 self.cached_processed_traces = None 

3468 self.update() 

3469 

3470 def get_adequate_tpad(self): 

3471 tpad = 0. 

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

3473 if f is not None: 

3474 tpad = max(tpad, 1.0/f) 

3475 

3476 for snuffling in self.snufflings: 

3477 if snuffling._post_process_hook_enabled \ 

3478 or snuffling._pre_process_hook_enabled: 

3479 

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

3481 

3482 return tpad 

3483 

3484 def prepare_cutout2( 

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

3486 demean=True, nmax=6000): 

3487 

3488 if self.pile.is_empty(): 

3489 return [] 

3490 

3491 nmax = self.visible_length 

3492 

3493 self.timer_cutout.start() 

3494 

3495 tsee = tmax-tmin 

3496 min_deltat_wo_decimate = tsee/nmax 

3497 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3498 

3499 min_deltat_allow = min_deltat_wo_decimate 

3500 if self.lowpass is not None: 

3501 target_deltat_lp = 0.25/self.lowpass 

3502 if target_deltat_lp > min_deltat_wo_decimate: 

3503 min_deltat_allow = min_deltat_w_decimate 

3504 

3505 min_deltat_allow = math.exp( 

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

3507 

3508 tmin_ = tmin 

3509 tmax_ = tmax 

3510 

3511 # fetch more than needed? 

3512 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3516 

3517 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3518 lphp = self.menuitem_lphp.isChecked() 

3519 ads = self.menuitem_allowdownsampling.isChecked() 

3520 

3521 tpad = self.get_adequate_tpad() 

3522 tpad = max(tpad, tsee) 

3523 

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

3525 vec = ( 

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

3527 self.highpass, fft_filtering, lphp, 

3528 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3529 ads, self.pile.get_update_count()) 

3530 

3531 if (self.cached_vec 

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

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

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

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

3536 and self.cached_processed_traces is not None): 

3537 

3538 logger.debug('Using cached traces') 

3539 processed_traces = self.cached_processed_traces 

3540 

3541 else: 

3542 processed_traces = [] 

3543 if self.pile.deltatmax >= min_deltat_allow: 

3544 

3545 def group_selector(gr): 

3546 return gr.deltatmax >= min_deltat_allow 

3547 

3548 if trace_selector is not None: 

3549 def trace_selectorx(tr): 

3550 return tr.deltat >= min_deltat_allow \ 

3551 and trace_selector(tr) 

3552 else: 

3553 def trace_selectorx(tr): 

3554 return tr.deltat >= min_deltat_allow 

3555 

3556 for traces in self.pile.chopper( 

3557 tmin=tmin, tmax=tmax, tpad=tpad, 

3558 want_incomplete=True, 

3559 degap=degap, 

3560 maxgap=gap_lap_tolerance, 

3561 maxlap=gap_lap_tolerance, 

3562 keep_current_files_open=True, 

3563 group_selector=group_selector, 

3564 trace_selector=trace_selectorx, 

3565 accessor_id=id(self), 

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

3567 include_last=True): 

3568 

3569 if demean: 

3570 for tr in traces: 

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

3572 continue 

3573 y = tr.get_ydata() 

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

3575 

3576 traces = self.pre_process_hooks(traces) 

3577 

3578 for trace in traces: 

3579 

3580 if not (trace.meta 

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

3582 

3583 if fft_filtering: 

3584 but = pyrocko.response.ButterworthResponse 

3585 multres = pyrocko.response.MultiplyResponse 

3586 if self.lowpass is not None \ 

3587 or self.highpass is not None: 

3588 

3589 it = num.arange( 

3590 trace.data_len(), dtype=float) 

3591 detr_data, m, b = detrend( 

3592 it, trace.get_ydata()) 

3593 

3594 trace.set_ydata(detr_data) 

3595 

3596 freqs, fdata = trace.spectrum( 

3597 pad_to_pow2=True, tfade=None) 

3598 

3599 nfreqs = fdata.size 

3600 

3601 key = (trace.deltat, nfreqs) 

3602 

3603 if key not in self.tf_cache: 

3604 resps = [] 

3605 if self.lowpass is not None: 

3606 resps.append(but( 

3607 order=4, 

3608 corner=self.lowpass, 

3609 type='low')) 

3610 

3611 if self.highpass is not None: 

3612 resps.append(but( 

3613 order=4, 

3614 corner=self.highpass, 

3615 type='high')) 

3616 

3617 resp = multres(resps) 

3618 self.tf_cache[key] = \ 

3619 resp.evaluate(freqs) 

3620 

3621 filtered_data = num.fft.irfft( 

3622 fdata*self.tf_cache[key] 

3623 )[:trace.data_len()] 

3624 

3625 retrended_data = retrend( 

3626 it, filtered_data, m, b) 

3627 

3628 trace.set_ydata(retrended_data) 

3629 

3630 else: 

3631 

3632 if ads and self.lowpass is not None: 

3633 while trace.deltat \ 

3634 < min_deltat_wo_decimate: 

3635 

3636 trace.downsample(2, demean=False) 

3637 

3638 fmax = 0.5/trace.deltat 

3639 if not lphp and ( 

3640 self.lowpass is not None 

3641 and self.highpass is not None 

3642 and self.lowpass < fmax 

3643 and self.highpass < fmax 

3644 and self.highpass < self.lowpass): 

3645 

3646 trace.bandpass( 

3647 2, self.highpass, self.lowpass) 

3648 else: 

3649 if self.lowpass is not None: 

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

3651 trace.lowpass( 

3652 4, self.lowpass, 

3653 demean=False) 

3654 

3655 if self.highpass is not None: 

3656 if self.lowpass is None \ 

3657 or self.highpass \ 

3658 < self.lowpass: 

3659 

3660 if self.highpass < \ 

3661 0.5/trace.deltat: 

3662 trace.highpass( 

3663 4, self.highpass, 

3664 demean=False) 

3665 

3666 processed_traces.append(trace) 

3667 

3668 if self.rotate != 0.0: 

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

3670 cphi = math.cos(phi) 

3671 sphi = math.sin(phi) 

3672 for a in processed_traces: 

3673 for b in processed_traces: 

3674 if (a.network == b.network 

3675 and a.station == b.station 

3676 and a.location == b.location 

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

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

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

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

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

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

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

3684 

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

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

3687 a.set_ydata(aydata) 

3688 b.set_ydata(bydata) 

3689 

3690 processed_traces = self.post_process_hooks(processed_traces) 

3691 

3692 self.cached_processed_traces = processed_traces 

3693 self.cached_vec = vec 

3694 

3695 chopped_traces = [] 

3696 for trace in processed_traces: 

3697 chop_tmin = tmin_ - trace.deltat*4 

3698 chop_tmax = tmax_ + trace.deltat*4 

3699 

3700 try: 

3701 ctrace = trace.chop( 

3702 chop_tmin, chop_tmax, 

3703 inplace=False) 

3704 

3705 except pyrocko.trace.NoData: 

3706 continue 

3707 

3708 if ctrace.data_len() < 2: 

3709 continue 

3710 

3711 chopped_traces.append(ctrace) 

3712 

3713 self.timer_cutout.stop() 

3714 return chopped_traces 

3715 

3716 def pre_process_hooks(self, traces): 

3717 for snuffling in self.snufflings: 

3718 if snuffling._pre_process_hook_enabled: 

3719 traces = snuffling.pre_process_hook(traces) 

3720 

3721 return traces 

3722 

3723 def post_process_hooks(self, traces): 

3724 for snuffling in self.snufflings: 

3725 if snuffling._post_process_hook_enabled: 

3726 traces = snuffling.post_process_hook(traces) 

3727 

3728 return traces 

3729 

3730 def visible_length_change(self, ignore=None): 

3731 for menuitem, vlen in self.menuitems_visible_length: 

3732 if menuitem.isChecked(): 

3733 self.visible_length = vlen 

3734 

3735 def scaling_base_change(self, ignore=None): 

3736 for menuitem, scaling_base in self.menuitems_scaling_base: 

3737 if menuitem.isChecked(): 

3738 self.scaling_base = scaling_base 

3739 

3740 def scalingmode_change(self, ignore=None): 

3741 for menuitem, scaling_key in self.menuitems_scaling: 

3742 if menuitem.isChecked(): 

3743 self.scaling_key = scaling_key 

3744 self.update() 

3745 

3746 def apply_scaling_hooks(self, data_ranges): 

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

3748 hook = self.scaling_hooks[k] 

3749 hook(data_ranges) 

3750 

3751 def viewmode_change(self, ignore=True): 

3752 for item, mode in self.menuitems_viewmode: 

3753 if item.isChecked(): 

3754 self.view_mode = mode 

3755 break 

3756 else: 

3757 raise AttributeError('unknown view mode') 

3758 

3759 items_waterfall_disabled = ( 

3760 self.menuitem_showscaleaxis, 

3761 self.menuitem_showscalerange, 

3762 self.menuitem_showzeroline, 

3763 self.menuitem_colortraces, 

3764 self.menuitem_cliptraces, 

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

3766 ) 

3767 

3768 if self.view_mode is ViewMode.Waterfall: 

3769 self.parent().show_colorbar_ctrl(True) 

3770 self.parent().show_gain_ctrl(False) 

3771 

3772 for item in items_waterfall_disabled: 

3773 item.setDisabled(True) 

3774 

3775 self.visible_length = 180. 

3776 else: 

3777 self.parent().show_colorbar_ctrl(False) 

3778 self.parent().show_gain_ctrl(True) 

3779 

3780 for item in items_waterfall_disabled: 

3781 item.setDisabled(False) 

3782 

3783 self.visible_length_change() 

3784 self.update() 

3785 

3786 def set_scaling_hook(self, k, hook): 

3787 self.scaling_hooks[k] = hook 

3788 

3789 def remove_scaling_hook(self, k): 

3790 del self.scaling_hooks[k] 

3791 

3792 def remove_scaling_hooks(self): 

3793 self.scaling_hooks = {} 

3794 

3795 def s_sortingmode_change(self, ignore=None): 

3796 for menuitem, valfunc in self.menuitems_ssorting: 

3797 if menuitem.isChecked(): 

3798 self._ssort = valfunc 

3799 

3800 self.sortingmode_change() 

3801 

3802 def sortingmode_change(self, ignore=None): 

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

3804 if menuitem.isChecked(): 

3805 self.set_gathering(gather, color) 

3806 

3807 self.sortingmode_change_time = time.time() 

3808 

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

3810 self.lowpass = value 

3811 self.passband_check() 

3812 self.tf_cache = {} 

3813 self.update() 

3814 

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

3816 self.highpass = value 

3817 self.passband_check() 

3818 self.tf_cache = {} 

3819 self.update() 

3820 

3821 def passband_check(self): 

3822 if self.highpass and self.lowpass \ 

3823 and self.highpass >= self.lowpass: 

3824 

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

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

3827 'deactivate the highpass.' 

3828 

3829 self.update_status() 

3830 else: 

3831 oldmess = self.message 

3832 self.message = None 

3833 if oldmess is not None: 

3834 self.update_status() 

3835 

3836 def gain_change(self, value, ignore): 

3837 self.gain = value 

3838 self.update() 

3839 

3840 def rot_change(self, value, ignore): 

3841 self.rotate = value 

3842 self.update() 

3843 

3844 def waterfall_cmap_change(self, cmap): 

3845 self.waterfall_cmap = cmap 

3846 self.update() 

3847 

3848 def waterfall_clip_change(self, clip_min, clip_max): 

3849 self.waterfall_clip_min = clip_min 

3850 self.waterfall_clip_max = clip_max 

3851 self.update() 

3852 

3853 def waterfall_show_absolute_change(self, toggle): 

3854 self.waterfall_show_absolute = toggle 

3855 self.update() 

3856 

3857 def waterfall_set_integrate(self, toggle): 

3858 self.waterfall_integrate = toggle 

3859 self.update() 

3860 

3861 def set_selected_markers(self, markers): 

3862 ''' 

3863 Set a list of markers selected 

3864 

3865 :param markers: list of markers 

3866 ''' 

3867 self.deselect_all() 

3868 for m in markers: 

3869 m.selected = True 

3870 

3871 self.update() 

3872 

3873 def deselect_all(self): 

3874 for marker in self.markers: 

3875 marker.selected = False 

3876 

3877 def animate_picking(self): 

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

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

3880 

3881 def get_nslc_ids_for_track(self, ftrack): 

3882 itrack = int(ftrack) 

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

3884 

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

3886 if self.picking: 

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

3888 self.picking = None 

3889 self.picking_down = None 

3890 self.picking_timer.stop() 

3891 self.picking_timer = None 

3892 if not abort: 

3893 self.add_marker(self.floating_marker) 

3894 self.floating_marker.selected = True 

3895 self.emit_selected_markers() 

3896 

3897 self.floating_marker = None 

3898 

3899 def start_picking(self, ignore): 

3900 

3901 if not self.picking: 

3902 self.deselect_all() 

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

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

3905 

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

3907 self.picking.setGeometry( 

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

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

3910 

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

3912 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3914 self.floating_marker.selected = True 

3915 

3916 self.picking_timer = qc.QTimer() 

3917 self.picking_timer.timeout.connect( 

3918 self.animate_picking) 

3919 

3920 self.picking_timer.setInterval(50) 

3921 self.picking_timer.start() 

3922 

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

3924 if self.picking: 

3925 mouset = self.time_projection.rev(x) 

3926 dt = 0.0 

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

3928 if mouset < self.tmin: 

3929 dt = -(self.tmin - mouset) 

3930 else: 

3931 dt = mouset - self.tmax 

3932 ddt = self.tmax-self.tmin 

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

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

3935 

3936 x0 = x 

3937 if self.picking_down is not None: 

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

3939 

3940 w = abs(x-x0) 

3941 x0 = min(x0, x) 

3942 

3943 tmin, tmax = ( 

3944 self.time_projection.rev(x0), 

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

3946 

3947 tmin, tmax = ( 

3948 max(working_system_time_range[0], tmin), 

3949 min(working_system_time_range[1], tmax)) 

3950 

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

3952 

3953 self.picking.setGeometry( 

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

3955 

3956 ftrack = self.track_to_screen.rev(y) 

3957 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3959 

3960 if dt != 0.0 and doshift: 

3961 self.interrupt_following() 

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

3963 

3964 self.update() 

3965 

3966 def update_status(self): 

3967 

3968 if self.message is None: 

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

3970 

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

3972 if not is_working_time(mouse_t): 

3973 return 

3974 

3975 if self.floating_marker: 

3976 tmi, tma = ( 

3977 self.floating_marker.tmin, 

3978 self.floating_marker.tmax) 

3979 

3980 tt, ms = gmtime_x(tmi) 

3981 

3982 if tmi == tma: 

3983 message = mystrftime( 

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

3985 tt=tt, milliseconds=ms) 

3986 else: 

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

3988 message = mystrftime( 

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

3990 tt=tt, milliseconds=ms) 

3991 else: 

3992 tt, ms = gmtime_x(mouse_t) 

3993 

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

3995 else: 

3996 message = self.message 

3997 

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

3999 sb.clearMessage() 

4000 sb.showMessage(message) 

4001 

4002 def set_sortingmode_change_delay_time(self, dt): 

4003 self.sortingmode_change_delay_time = dt 

4004 

4005 def sortingmode_change_delayed(self): 

4006 now = time.time() 

4007 return ( 

4008 self.sortingmode_change_delay_time is not None 

4009 and now - self.sortingmode_change_time 

4010 < self.sortingmode_change_delay_time) 

4011 

4012 def set_visible_marker_kinds(self, kinds): 

4013 self.deselect_all() 

4014 self.visible_marker_kinds = tuple(kinds) 

4015 self.emit_selected_markers() 

4016 

4017 def following(self): 

4018 return self.follow_timer is not None \ 

4019 and not self.following_interrupted() 

4020 

4021 def interrupt_following(self): 

4022 self.interactive_range_change_time = time.time() 

4023 

4024 def following_interrupted(self, now=None): 

4025 if now is None: 

4026 now = time.time() 

4027 return now - self.interactive_range_change_time \ 

4028 < self.interactive_range_change_delay_time 

4029 

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

4031 if tmax_start is None: 

4032 tmax_start = time.time() 

4033 self.show_all = False 

4034 self.follow_time = tlen 

4035 self.follow_timer = qc.QTimer(self) 

4036 self.follow_timer.timeout.connect( 

4037 self.follow_update) 

4038 self.follow_timer.setInterval(interval) 

4039 self.follow_timer.start() 

4040 self.follow_started = time.time() 

4041 self.follow_lapse = lapse 

4042 self.follow_tshift = self.follow_started - tmax_start 

4043 self.interactive_range_change_time = 0.0 

4044 

4045 def unfollow(self): 

4046 if self.follow_timer is not None: 

4047 self.follow_timer.stop() 

4048 self.follow_timer = None 

4049 self.interactive_range_change_time = 0.0 

4050 

4051 def follow_update(self): 

4052 rnow = time.time() 

4053 if self.follow_lapse is None: 

4054 now = rnow 

4055 else: 

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

4057 * self.follow_lapse 

4058 

4059 if self.following_interrupted(rnow): 

4060 return 

4061 self.set_time_range( 

4062 now-self.follow_time-self.follow_tshift, 

4063 now-self.follow_tshift) 

4064 

4065 self.update() 

4066 

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

4068 self.return_tag = return_tag 

4069 self.window().close() 

4070 

4071 def cleanup(self): 

4072 self.about_to_close.emit() 

4073 self.timer.stop() 

4074 if self.follow_timer is not None: 

4075 self.follow_timer.stop() 

4076 

4077 for snuffling in list(self.snufflings): 

4078 self.remove_snuffling(snuffling) 

4079 

4080 def set_error_message(self, key, value): 

4081 if value is None: 

4082 if key in self.error_messages: 

4083 del self.error_messages[key] 

4084 else: 

4085 self.error_messages[key] = value 

4086 

4087 def inputline_changed(self, text): 

4088 pass 

4089 

4090 def inputline_finished(self, text): 

4091 line = str(text) 

4092 

4093 toks = line.split() 

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

4095 if len(toks) >= 1: 

4096 command = toks[0].lower() 

4097 

4098 try: 

4099 quick_filter_commands = { 

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

4101 's': '*.%s.*.*', 

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

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

4104 

4105 if command in quick_filter_commands: 

4106 if len(toks) >= 2: 

4107 patterns = [ 

4108 quick_filter_commands[toks[0]] % pat 

4109 for pat in toks[1:]] 

4110 self.set_quick_filter_patterns(patterns, line) 

4111 else: 

4112 self.set_quick_filter_patterns(None) 

4113 

4114 self.update() 

4115 

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

4117 if len(toks) >= 2: 

4118 patterns = [] 

4119 if len(toks) == 2: 

4120 patterns = [toks[1]] 

4121 elif len(toks) >= 3: 

4122 x = { 

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

4124 's': '*.%s.*.*', 

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

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

4127 

4128 if toks[1] in x: 

4129 patterns.extend( 

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

4131 

4132 for pattern in patterns: 

4133 if command == 'hide': 

4134 self.add_blacklist_pattern(pattern) 

4135 else: 

4136 self.remove_blacklist_pattern(pattern) 

4137 

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

4139 self.clear_blacklist() 

4140 

4141 clearit = True 

4142 

4143 self.update() 

4144 

4145 elif command == 'markers': 

4146 if len(toks) == 2: 

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

4148 kinds = self.all_marker_kinds 

4149 else: 

4150 kinds = [] 

4151 for x in toks[1]: 

4152 try: 

4153 kinds.append(int(x)) 

4154 except Exception: 

4155 pass 

4156 

4157 self.set_visible_marker_kinds(kinds) 

4158 

4159 elif len(toks) == 1: 

4160 self.set_visible_marker_kinds(()) 

4161 

4162 self.update() 

4163 

4164 elif command == 'scaling': 

4165 if len(toks) == 2: 

4166 hideit = False 

4167 error = 'wrong number of arguments' 

4168 

4169 if len(toks) >= 3: 

4170 vmin, vmax = [ 

4171 pyrocko.model.float_or_none(x) 

4172 for x in toks[-2:]] 

4173 

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

4175 if k in d: 

4176 if vmin is not None: 

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

4178 if vmax is not None: 

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

4180 

4181 if len(toks) == 1: 

4182 self.remove_scaling_hooks() 

4183 

4184 elif len(toks) == 3: 

4185 def hook(data_ranges): 

4186 for k in data_ranges: 

4187 upd(data_ranges, k, vmin, vmax) 

4188 

4189 self.set_scaling_hook('_', hook) 

4190 

4191 elif len(toks) == 4: 

4192 pattern = toks[1] 

4193 

4194 def hook(data_ranges): 

4195 for k in pyrocko.util.match_nslcs( 

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

4197 

4198 upd(data_ranges, k, vmin, vmax) 

4199 

4200 self.set_scaling_hook(pattern, hook) 

4201 

4202 elif command == 'goto': 

4203 toks2 = line.split(None, 1) 

4204 if len(toks2) == 2: 

4205 arg = toks2[1] 

4206 m = re.match( 

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

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

4209 if m: 

4210 tlen = None 

4211 if not m.group(1): 

4212 tlen = 12*32*24*60*60 

4213 elif not m.group(2): 

4214 tlen = 32*24*60*60 

4215 elif not m.group(3): 

4216 tlen = 24*60*60 

4217 elif not m.group(4): 

4218 tlen = 60*60 

4219 elif not m.group(5): 

4220 tlen = 60 

4221 

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

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

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

4225 t = pyrocko.util.str_to_time(arg) 

4226 self.go_to_time(t, tlen=tlen) 

4227 

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

4229 supl = '00:00:00' 

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

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

4232 tmin, tmax = self.get_time_range() 

4233 sdate = pyrocko.util.time_to_str( 

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

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

4236 self.go_to_time(t) 

4237 

4238 elif arg == 'today': 

4239 self.go_to_time( 

4240 day_start( 

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

4242 

4243 elif arg == 'yesterday': 

4244 self.go_to_time( 

4245 day_start( 

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

4247 

4248 else: 

4249 self.go_to_event_by_name(arg) 

4250 

4251 else: 

4252 raise PileViewerMainException( 

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

4254 

4255 except PileViewerMainException as e: 

4256 error = str(e) 

4257 hideit = False 

4258 

4259 return clearit, hideit, error 

4260 

4261 return PileViewerMain 

4262 

4263 

4264PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4265GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget) 

4266 

4267 

4268class LineEditWithAbort(qw.QLineEdit): 

4269 

4270 aborted = qc.pyqtSignal() 

4271 history_down = qc.pyqtSignal() 

4272 history_up = qc.pyqtSignal() 

4273 

4274 def keyPressEvent(self, key_event): 

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

4276 self.aborted.emit() 

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

4278 self.history_down.emit() 

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

4280 self.history_up.emit() 

4281 else: 

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

4283 

4284 

4285class PileViewer(qw.QFrame): 

4286 ''' 

4287 PileViewerMain + Controls + Inputline 

4288 ''' 

4289 

4290 def __init__( 

4291 self, pile, 

4292 ntracks_shown_max=20, 

4293 marker_editor_sortable=True, 

4294 use_opengl=None, 

4295 panel_parent=None, 

4296 *args): 

4297 

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

4299 

4300 layout = qw.QGridLayout() 

4301 layout.setContentsMargins(0, 0, 0, 0) 

4302 layout.setSpacing(0) 

4303 

4304 self.menu = PileViewerMenuBar(self) 

4305 

4306 if use_opengl is None: 

4307 use_opengl = is_macos 

4308 

4309 if use_opengl: 

4310 self.viewer = GLPileViewerMain( 

4311 pile, 

4312 ntracks_shown_max=ntracks_shown_max, 

4313 panel_parent=panel_parent, 

4314 menu=self.menu) 

4315 else: 

4316 self.viewer = PileViewerMain( 

4317 pile, 

4318 ntracks_shown_max=ntracks_shown_max, 

4319 panel_parent=panel_parent, 

4320 menu=self.menu) 

4321 

4322 self.marker_editor_sortable = marker_editor_sortable 

4323 

4324 self.setFrameShape(qw.QFrame.StyledPanel) 

4325 self.setFrameShadow(qw.QFrame.Sunken) 

4326 

4327 self.input_area = qw.QFrame(self) 

4328 ia_layout = qw.QGridLayout() 

4329 ia_layout.setContentsMargins(11, 11, 11, 11) 

4330 self.input_area.setLayout(ia_layout) 

4331 

4332 self.inputline = LineEditWithAbort(self.input_area) 

4333 self.inputline.returnPressed.connect( 

4334 self.inputline_returnpressed) 

4335 self.inputline.editingFinished.connect( 

4336 self.inputline_finished) 

4337 self.inputline.aborted.connect( 

4338 self.inputline_aborted) 

4339 

4340 self.inputline.history_down.connect( 

4341 lambda: self.step_through_history(1)) 

4342 self.inputline.history_up.connect( 

4343 lambda: self.step_through_history(-1)) 

4344 

4345 self.inputline.textEdited.connect( 

4346 self.inputline_changed) 

4347 

4348 self.inputline.setPlaceholderText( 

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

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

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

4352 self.input_area.hide() 

4353 self.history = None 

4354 

4355 self.inputline_error_str = None 

4356 

4357 self.inputline_error = qw.QLabel() 

4358 self.inputline_error.hide() 

4359 

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

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

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

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

4364 

4365 pb = Progressbars(self) 

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

4367 self.progressbars = pb 

4368 

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

4370 self.scrollbar = scrollbar 

4371 layout.addWidget(scrollbar, 1, 1) 

4372 self.scrollbar.valueChanged.connect( 

4373 self.scrollbar_changed) 

4374 

4375 self.block_scrollbar_changes = False 

4376 

4377 self.viewer.want_input.connect( 

4378 self.inputline_show) 

4379 self.viewer.tracks_range_changed.connect( 

4380 self.tracks_range_changed) 

4381 self.viewer.pile_has_changed_signal.connect( 

4382 self.adjust_controls) 

4383 self.viewer.about_to_close.connect( 

4384 self.save_inputline_history) 

4385 

4386 self.setLayout(layout) 

4387 

4388 def cleanup(self): 

4389 self.viewer.cleanup() 

4390 

4391 def get_progressbars(self): 

4392 return self.progressbars 

4393 

4394 def inputline_show(self): 

4395 if not self.history: 

4396 self.load_inputline_history() 

4397 

4398 self.input_area.show() 

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

4400 self.inputline.selectAll() 

4401 

4402 def inputline_set_error(self, string): 

4403 self.inputline_error_str = string 

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

4405 self.inputline.selectAll() 

4406 self.inputline_error.setText(string) 

4407 self.input_area.show() 

4408 self.inputline_error.show() 

4409 

4410 def inputline_clear_error(self): 

4411 if self.inputline_error_str: 

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

4413 self.inputline_error_str = None 

4414 self.inputline_error.clear() 

4415 self.inputline_error.hide() 

4416 

4417 def inputline_changed(self, line): 

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

4419 self.inputline_clear_error() 

4420 

4421 def inputline_returnpressed(self): 

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

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

4424 

4425 if error: 

4426 self.inputline_set_error(error) 

4427 

4428 line = line.strip() 

4429 

4430 if line != '' and not error: 

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

4432 self.history.append(line) 

4433 

4434 if clearit: 

4435 

4436 self.inputline.blockSignals(True) 

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

4438 if qpat is None: 

4439 self.inputline.clear() 

4440 else: 

4441 self.inputline.setText(qinp) 

4442 self.inputline.blockSignals(False) 

4443 

4444 if hideit and not error: 

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

4446 self.input_area.hide() 

4447 

4448 self.hist_ind = len(self.history) 

4449 

4450 def inputline_aborted(self): 

4451 ''' 

4452 Hide the input line. 

4453 ''' 

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

4455 self.hist_ind = len(self.history) 

4456 self.input_area.hide() 

4457 

4458 def save_inputline_history(self): 

4459 ''' 

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

4461 ''' 

4462 if not self.history: 

4463 return 

4464 

4465 conf = pyrocko.config 

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

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

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

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

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

4471 

4472 def load_inputline_history(self): 

4473 ''' 

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

4475 ''' 

4476 conf = pyrocko.config 

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

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

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

4480 f.write('\n') 

4481 

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

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

4484 

4485 self.hist_ind = len(self.history) 

4486 

4487 def step_through_history(self, ud=1): 

4488 ''' 

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

4490 ''' 

4491 n = len(self.history) 

4492 self.hist_ind += ud 

4493 self.hist_ind %= (n + 1) 

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

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

4496 else: 

4497 self.inputline.setText('') 

4498 

4499 def inputline_finished(self): 

4500 pass 

4501 

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

4503 if self.block_scrollbar_changes: 

4504 return 

4505 

4506 self.scrollbar.blockSignals(True) 

4507 self.scrollbar.setPageStep(ihi-ilo) 

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

4509 self.scrollbar.setRange(0, vmax) 

4510 self.scrollbar.setValue(ilo) 

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

4512 self.scrollbar.blockSignals(False) 

4513 

4514 def scrollbar_changed(self, value): 

4515 self.block_scrollbar_changes = True 

4516 ilo = value 

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

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

4519 self.block_scrollbar_changes = False 

4520 self.update_contents() 

4521 

4522 def controls(self): 

4523 frame = qw.QFrame(self) 

4524 layout = qw.QGridLayout() 

4525 frame.setLayout(layout) 

4526 

4527 minfreq = 0.001 

4528 maxfreq = 1000.0 

4529 self.lowpass_control = ValControl(high_is_none=True) 

4530 self.lowpass_control.setup( 

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

4532 self.highpass_control = ValControl(low_is_none=True) 

4533 self.highpass_control.setup( 

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

4535 self.gain_control = ValControl() 

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

4537 self.rot_control = LinValControl() 

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

4539 self.colorbar_control = ColorbarControl(self) 

4540 

4541 self.lowpass_control.valchange.connect( 

4542 self.viewer.lowpass_change) 

4543 self.highpass_control.valchange.connect( 

4544 self.viewer.highpass_change) 

4545 self.gain_control.valchange.connect( 

4546 self.viewer.gain_change) 

4547 self.rot_control.valchange.connect( 

4548 self.viewer.rot_change) 

4549 self.colorbar_control.cmap_changed.connect( 

4550 self.viewer.waterfall_cmap_change 

4551 ) 

4552 self.colorbar_control.clip_changed.connect( 

4553 self.viewer.waterfall_clip_change 

4554 ) 

4555 self.colorbar_control.show_absolute_toggled.connect( 

4556 self.viewer.waterfall_show_absolute_change 

4557 ) 

4558 self.colorbar_control.show_integrate_toggled.connect( 

4559 self.viewer.waterfall_set_integrate 

4560 ) 

4561 

4562 for icontrol, control in enumerate(( 

4563 self.highpass_control, 

4564 self.lowpass_control, 

4565 self.gain_control, 

4566 self.rot_control, 

4567 self.colorbar_control)): 

4568 

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

4570 layout.addWidget(widget, icontrol, iwidget) 

4571 

4572 spacer = qw.QSpacerItem( 

4573 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4575 

4576 self.adjust_controls() 

4577 self.viewer.viewmode_change(ViewMode.Wiggle) 

4578 return frame 

4579 

4580 def marker_editor(self): 

4581 editor = pyrocko.gui.marker_editor.MarkerEditor( 

4582 self, sortable=self.marker_editor_sortable) 

4583 

4584 editor.set_viewer(self.get_view()) 

4585 editor.get_marker_model().dataChanged.connect( 

4586 self.update_contents) 

4587 return editor 

4588 

4589 def adjust_controls(self): 

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

4591 maxfreq = 0.5/dtmin 

4592 minfreq = (0.5/dtmax)*0.001 

4593 self.lowpass_control.set_range(minfreq, maxfreq) 

4594 self.highpass_control.set_range(minfreq, maxfreq) 

4595 

4596 def setup_snufflings(self): 

4597 self.viewer.setup_snufflings() 

4598 

4599 def get_view(self): 

4600 return self.viewer 

4601 

4602 def update_contents(self): 

4603 self.viewer.update() 

4604 

4605 def get_pile(self): 

4606 return self.viewer.get_pile() 

4607 

4608 def show_colorbar_ctrl(self, show): 

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

4610 w.setVisible(show) 

4611 

4612 def show_gain_ctrl(self, show): 

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

4614 w.setVisible(show)