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, umax_pad): 

690 umin, umax = self.ur 

691 xmin, xmax = self.xr 

692 return min( 

693 umax-umax_pad, 

694 max(umin, umin + (x-xmin)*((umax-umin)/(xmax-xmin)))) 

695 

696 def rev(self, u): 

697 umin, umax = self.ur 

698 xmin, xmax = self.xr 

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

700 

701 def copy(self): 

702 return copy.copy(self) 

703 

704 

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

706 group = qw.QActionGroup(menu) 

707 group.setExclusive(True) 

708 menuitems = [] 

709 

710 for name, value, *shortcut in menudef: 

711 action = menu.addAction(name) 

712 action.setCheckable(True) 

713 action.setActionGroup(group) 

714 if shortcut: 

715 action.setShortcut(shortcut[0]) 

716 

717 menuitems.append((action, value)) 

718 if default is not None and ( 

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

720 value == default): 

721 action.setChecked(True) 

722 

723 group.triggered.connect(target) 

724 

725 if default is None: 

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

727 

728 return menuitems 

729 

730 

731def sort_actions(menu): 

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

733 for action in actions: 

734 menu.removeAction(action) 

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

736 

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

738 if help_action: 

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

740 for action in actions: 

741 menu.addAction(action) 

742 

743 

744fkey_map = dict(zip( 

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

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

747 range(10))) 

748 

749 

750class PileViewerMainException(Exception): 

751 pass 

752 

753 

754class PileViewerMenuBar(qw.QMenuBar): 

755 ... 

756 

757 

758class PileViewerMenu(qw.QMenu): 

759 ... 

760 

761 

762def MakePileViewerMainClass(base): 

763 

764 class PileViewerMain(base): 

765 

766 want_input = qc.pyqtSignal() 

767 about_to_close = qc.pyqtSignal() 

768 pile_has_changed_signal = qc.pyqtSignal() 

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

770 

771 begin_markers_add = qc.pyqtSignal(int, int) 

772 end_markers_add = qc.pyqtSignal() 

773 begin_markers_remove = qc.pyqtSignal(int, int) 

774 end_markers_remove = qc.pyqtSignal() 

775 

776 marker_selection_changed = qc.pyqtSignal(list) 

777 active_event_marker_changed = qc.pyqtSignal() 

778 

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

780 menu=None): 

781 base.__init__(self, *args) 

782 

783 self.pile = pile 

784 self.ax_height = 80 

785 self.panel_parent = panel_parent 

786 

787 self.click_tolerance = 5 

788 

789 self.ntracks_shown_max = ntracks_shown_max 

790 self.initial_ntracks_shown_max = ntracks_shown_max 

791 self.ntracks = 0 

792 self.show_all = True 

793 self.shown_tracks_range = None 

794 self.track_start = None 

795 self.track_trange = None 

796 

797 self.lowpass = None 

798 self.highpass = None 

799 self.gain = 1.0 

800 self.rotate = 0.0 

801 self.picking_down = None 

802 self.picking = None 

803 self.floating_marker = None 

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

805 self.markers_deltat_max = 0. 

806 self.n_selected_markers = 0 

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

808 self.visible_marker_kinds = self.all_marker_kinds 

809 self.active_event_marker = None 

810 self.ignore_releases = 0 

811 self.message = None 

812 self.reloaded = False 

813 self.pile_has_changed = False 

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

815 

816 self.tax = TimeAx() 

817 self.setBackgroundRole(qg.QPalette.Base) 

818 self.setAutoFillBackground(True) 

819 poli = qw.QSizePolicy( 

820 qw.QSizePolicy.Expanding, 

821 qw.QSizePolicy.Expanding) 

822 

823 self.setSizePolicy(poli) 

824 self.setMinimumSize(300, 200) 

825 self.setFocusPolicy(qc.Qt.ClickFocus) 

826 

827 self.menu = menu or PileViewerMenu(self) 

828 

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

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

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

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

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

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

835 

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

837 

838 self.snufflings_menu = self.toggle_panel_menu.addMenu( 

839 'Run Snuffling') 

840 self.toggle_panel_menu.addSeparator() 

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

842 help_menu.addSeparator() 

843 

844 file_menu.addAction( 

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

846 'Open waveform files...', 

847 self.open_waveforms, 

848 qg.QKeySequence.Open) 

849 

850 file_menu.addAction( 

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

852 'Open waveform directory...', 

853 self.open_waveform_directory) 

854 

855 file_menu.addAction( 

856 'Open station files...', 

857 self.open_stations) 

858 

859 file_menu.addAction( 

860 'Open StationXML files...', 

861 self.open_stations_xml) 

862 

863 file_menu.addAction( 

864 'Open event file...', 

865 self.read_events) 

866 

867 file_menu.addSeparator() 

868 file_menu.addAction( 

869 'Open marker file...', 

870 self.read_markers) 

871 

872 file_menu.addAction( 

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

874 'Save markers...', 

875 self.write_markers, 

876 qg.QKeySequence.Save) 

877 

878 file_menu.addAction( 

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

880 'Save selected markers...', 

881 self.write_selected_markers, 

882 qg.QKeySequence.SaveAs) 

883 

884 file_menu.addSeparator() 

885 file_menu.addAction( 

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

887 'Print', 

888 self.printit, 

889 qg.QKeySequence.Print) 

890 

891 file_menu.addAction( 

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

893 'Save as SVG or PNG', 

894 self.savesvg, 

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

896 

897 file_menu.addSeparator() 

898 close = file_menu.addAction( 

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

900 'Close', 

901 self.myclose) 

902 close.setShortcuts( 

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

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

905 

906 # Scale Menu 

907 menudef = [ 

908 ('Individual Scale', 

909 lambda tr: tr.nslc_id, 

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

911 ('Common Scale', 

912 lambda tr: None, 

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

914 ('Common Scale per Station', 

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

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

917 ('Common Scale per Station Location', 

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

919 ('Common Scale per Component', 

920 lambda tr: (tr.channel)), 

921 ] 

922 

923 self.menuitems_scaling = add_radiobuttongroup( 

924 scale_menu, menudef, self.scalingmode_change, 

925 default=self.config.trace_scale) 

926 scale_menu.addSeparator() 

927 

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

929 self.scaling_hooks = {} 

930 self.scalingmode_change() 

931 

932 menudef = [ 

933 ('Scaling based on Minimum and Maximum', 

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

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

936 ('minmax', 'robust')), 

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

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

939 ] 

940 

941 self.menuitems_scaling_base = add_radiobuttongroup( 

942 scale_menu, menudef, self.scaling_base_change) 

943 

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

945 scale_menu.addSeparator() 

946 

947 self.menuitem_fixscalerange = scale_menu.addAction( 

948 'Fix Scale Ranges') 

949 self.menuitem_fixscalerange.setCheckable(True) 

950 

951 # Sort Menu 

952 def sector_dist(sta): 

953 if sta.dist_m is None: 

954 return None, None 

955 else: 

956 return ( 

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

958 m_float(sta.dist_m)) 

959 

960 menudef = [ 

961 ('Sort by Names', 

962 lambda tr: (), 

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

964 ('Sort by Distance', 

965 lambda tr: self.station_attrib( 

966 tr, 

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

968 lambda tr: (None,)), 

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

970 ('Sort by Azimuth', 

971 lambda tr: self.station_attrib( 

972 tr, 

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

974 lambda tr: (None,))), 

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

976 lambda tr: self.station_attrib( 

977 tr, 

978 sector_dist, 

979 lambda tr: (None, None))), 

980 ('Sort by Backazimuth', 

981 lambda tr: self.station_attrib( 

982 tr, 

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

984 lambda tr: (None,))), 

985 ] 

986 self.menuitems_ssorting = add_radiobuttongroup( 

987 sort_menu, menudef, self.s_sortingmode_change) 

988 sort_menu.addSeparator() 

989 

990 self._ssort = lambda tr: () 

991 

992 self.menu.addSeparator() 

993 

994 menudef = [ 

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

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

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

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

999 ((0, 1, 3, 2), 

1000 lambda tr: tr.channel)), 

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

1002 ((1, 0, 3, 2), 

1003 lambda tr: tr.channel)), 

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

1005 ((2, 0, 1, 3), 

1006 lambda tr: tr.channel)), 

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

1008 ((3, 0, 1, 2), 

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

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

1011 ((0, 1, 3), 

1012 lambda tr: tr.location)), 

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

1014 ((1, 0, 3), 

1015 lambda tr: tr.location)), 

1016 ] 

1017 

1018 self.menuitems_sorting = add_radiobuttongroup( 

1019 sort_menu, menudef, self.sortingmode_change) 

1020 

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

1022 self.config.visible_length_setting] 

1023 

1024 # View menu 

1025 self.menuitems_visible_length = add_radiobuttongroup( 

1026 view_menu, menudef, 

1027 self.visible_length_change) 

1028 view_menu.addSeparator() 

1029 

1030 view_modes = [ 

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

1032 ('Waterfall', ViewMode.Waterfall) 

1033 ] 

1034 

1035 self.menuitems_viewmode = add_radiobuttongroup( 

1036 view_menu, view_modes, 

1037 self.viewmode_change, default=ViewMode.Wiggle) 

1038 view_menu.addSeparator() 

1039 

1040 self.menuitem_cliptraces = view_menu.addAction( 

1041 'Clip Traces') 

1042 self.menuitem_cliptraces.setCheckable(True) 

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

1044 

1045 self.menuitem_showboxes = view_menu.addAction( 

1046 'Show Boxes') 

1047 self.menuitem_showboxes.setCheckable(True) 

1048 self.menuitem_showboxes.setChecked( 

1049 self.config.show_boxes) 

1050 

1051 self.menuitem_colortraces = view_menu.addAction( 

1052 'Color Traces') 

1053 self.menuitem_colortraces.setCheckable(True) 

1054 self.menuitem_antialias = view_menu.addAction( 

1055 'Antialiasing') 

1056 self.menuitem_antialias.setCheckable(True) 

1057 

1058 view_menu.addSeparator() 

1059 self.menuitem_showscalerange = view_menu.addAction( 

1060 'Show Scale Ranges') 

1061 self.menuitem_showscalerange.setCheckable(True) 

1062 self.menuitem_showscalerange.setChecked( 

1063 self.config.show_scale_ranges) 

1064 

1065 self.menuitem_showscaleaxis = view_menu.addAction( 

1066 'Show Scale Axes') 

1067 self.menuitem_showscaleaxis.setCheckable(True) 

1068 self.menuitem_showscaleaxis.setChecked( 

1069 self.config.show_scale_axes) 

1070 

1071 self.menuitem_showzeroline = view_menu.addAction( 

1072 'Show Zero Lines') 

1073 self.menuitem_showzeroline.setCheckable(True) 

1074 

1075 view_menu.addSeparator() 

1076 view_menu.addAction( 

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

1078 'Fullscreen', 

1079 self.toggle_fullscreen, 

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

1081 

1082 # Options Menu 

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

1084 self.menuitem_demean.setCheckable(True) 

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

1086 self.menuitem_demean.setShortcut( 

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

1088 

1089 self.menuitem_distances_3d = options_menu.addAction( 

1090 '3D distances', 

1091 self.distances_3d_changed) 

1092 self.menuitem_distances_3d.setCheckable(True) 

1093 

1094 self.menuitem_allowdownsampling = options_menu.addAction( 

1095 'Allow Downsampling') 

1096 self.menuitem_allowdownsampling.setCheckable(True) 

1097 self.menuitem_allowdownsampling.setChecked(True) 

1098 

1099 self.menuitem_degap = options_menu.addAction( 

1100 'Allow Degapping') 

1101 self.menuitem_degap.setCheckable(True) 

1102 self.menuitem_degap.setChecked(True) 

1103 

1104 options_menu.addSeparator() 

1105 

1106 self.menuitem_fft_filtering = options_menu.addAction( 

1107 'FFT Filtering') 

1108 self.menuitem_fft_filtering.setCheckable(True) 

1109 

1110 self.menuitem_lphp = options_menu.addAction( 

1111 'Bandpass is Low- + Highpass') 

1112 self.menuitem_lphp.setCheckable(True) 

1113 self.menuitem_lphp.setChecked(True) 

1114 

1115 options_menu.addSeparator() 

1116 self.menuitem_watch = options_menu.addAction( 

1117 'Watch Files') 

1118 self.menuitem_watch.setCheckable(True) 

1119 

1120 self.menuitem_liberal_fetch = options_menu.addAction( 

1121 'Liberal Fetch Optimization') 

1122 self.menuitem_liberal_fetch.setCheckable(True) 

1123 

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

1125 

1126 self.snufflings_menu.addAction( 

1127 'Reload Snufflings', 

1128 self.setup_snufflings) 

1129 

1130 # Disable ShadowPileTest 

1131 if False: 

1132 test_action = self.menu.addAction( 

1133 'Test', 

1134 self.toggletest) 

1135 test_action.setCheckable(True) 

1136 

1137 help_menu.addAction( 

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

1139 'Snuffler Controls', 

1140 self.help, 

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

1142 

1143 help_menu.addAction( 

1144 'About', 

1145 self.about) 

1146 

1147 self.time_projection = Projection() 

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

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

1150 

1151 self.gather = None 

1152 

1153 self.trace_filter = None 

1154 self.quick_filter = None 

1155 self.quick_filter_patterns = None, None 

1156 self.blacklist = [] 

1157 

1158 self.track_to_screen = Projection() 

1159 self.track_to_nslc_ids = {} 

1160 

1161 self.cached_vec = None 

1162 self.cached_processed_traces = None 

1163 

1164 self.timer = qc.QTimer(self) 

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

1166 self.timer.setInterval(1000) 

1167 self.timer.start() 

1168 self.pile.add_listener(self) 

1169 self.trace_styles = {} 

1170 if self.get_squirrel() is None: 

1171 self.determine_box_styles() 

1172 

1173 self.setMouseTracking(True) 

1174 

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

1176 self.snuffling_modules = {} 

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

1178 self.default_snufflings = None 

1179 self.snufflings = [] 

1180 

1181 self.stations = {} 

1182 

1183 self.timer_draw = Timer() 

1184 self.timer_cutout = Timer() 

1185 self.time_spent_painting = 0.0 

1186 self.time_last_painted = time.time() 

1187 

1188 self.interactive_range_change_time = 0.0 

1189 self.interactive_range_change_delay_time = 10.0 

1190 self.follow_timer = None 

1191 

1192 self.sortingmode_change_time = 0.0 

1193 self.sortingmode_change_delay_time = None 

1194 

1195 self.old_data_ranges = {} 

1196 

1197 self.error_messages = {} 

1198 self.return_tag = None 

1199 self.wheel_pos = 60 

1200 

1201 self.setAcceptDrops(True) 

1202 self._paths_to_load = [] 

1203 

1204 self.tf_cache = {} 

1205 

1206 self.waterfall = TraceWaterfall() 

1207 self.waterfall_cmap = 'viridis' 

1208 self.waterfall_clip_min = 0. 

1209 self.waterfall_clip_max = 1. 

1210 self.waterfall_show_absolute = False 

1211 self.waterfall_integrate = False 

1212 self.view_mode = ViewMode.Wiggle 

1213 

1214 self.automatic_updates = True 

1215 

1216 self.closing = False 

1217 self.in_paint_event = False 

1218 

1219 def fail(self, reason): 

1220 box = qw.QMessageBox(self) 

1221 box.setText(reason) 

1222 box.exec_() 

1223 

1224 def set_trace_filter(self, filter_func): 

1225 self.trace_filter = filter_func 

1226 self.sortingmode_change() 

1227 

1228 def update_trace_filter(self): 

1229 if self.blacklist: 

1230 

1231 def blacklist_func(tr): 

1232 return not pyrocko.util.match_nslc( 

1233 self.blacklist, tr.nslc_id) 

1234 

1235 else: 

1236 blacklist_func = None 

1237 

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

1239 self.set_trace_filter(None) 

1240 elif self.quick_filter is None: 

1241 self.set_trace_filter(blacklist_func) 

1242 elif blacklist_func is None: 

1243 self.set_trace_filter(self.quick_filter) 

1244 else: 

1245 self.set_trace_filter( 

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

1247 

1248 def set_quick_filter(self, filter_func): 

1249 self.quick_filter = filter_func 

1250 self.update_trace_filter() 

1251 

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

1253 if patterns is not None: 

1254 self.set_quick_filter( 

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

1256 else: 

1257 self.set_quick_filter(None) 

1258 

1259 self.quick_filter_patterns = patterns, inputline 

1260 

1261 def get_quick_filter_patterns(self): 

1262 return self.quick_filter_patterns 

1263 

1264 def add_blacklist_pattern(self, pattern): 

1265 if pattern == 'empty': 

1266 keys = set(self.pile.nslc_ids) 

1267 trs = self.pile.all( 

1268 tmin=self.tmin, 

1269 tmax=self.tmax, 

1270 load_data=False, 

1271 degap=False) 

1272 

1273 for tr in trs: 

1274 if tr.nslc_id in keys: 

1275 keys.remove(tr.nslc_id) 

1276 

1277 for key in keys: 

1278 xpattern = '.'.join(key) 

1279 if xpattern not in self.blacklist: 

1280 self.blacklist.append(xpattern) 

1281 

1282 else: 

1283 if pattern in self.blacklist: 

1284 self.blacklist.remove(pattern) 

1285 

1286 self.blacklist.append(pattern) 

1287 

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

1289 self.update_trace_filter() 

1290 

1291 def remove_blacklist_pattern(self, pattern): 

1292 if pattern in self.blacklist: 

1293 self.blacklist.remove(pattern) 

1294 else: 

1295 raise PileViewerMainException( 

1296 'Pattern not found in blacklist.') 

1297 

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

1299 self.update_trace_filter() 

1300 

1301 def clear_blacklist(self): 

1302 self.blacklist = [] 

1303 self.update_trace_filter() 

1304 

1305 def ssort(self, tr): 

1306 return self._ssort(tr) 

1307 

1308 def station_key(self, x): 

1309 return x.network, x.station 

1310 

1311 def station_keys(self, x): 

1312 return [ 

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

1314 (x.network, x.station)] 

1315 

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

1317 for sk in self.station_keys(tr): 

1318 if sk in self.stations: 

1319 station = self.stations[sk] 

1320 return getter(station) 

1321 

1322 return default_getter(tr) 

1323 

1324 def get_station(self, sk): 

1325 return self.stations[sk] 

1326 

1327 def has_station(self, station): 

1328 for sk in self.station_keys(station): 

1329 if sk in self.stations: 

1330 return True 

1331 

1332 return False 

1333 

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

1335 return self.station_attrib( 

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

1337 

1338 def set_stations(self, stations): 

1339 self.stations = {} 

1340 self.add_stations(stations) 

1341 

1342 def add_stations(self, stations): 

1343 for station in stations: 

1344 for sk in self.station_keys(station): 

1345 self.stations[sk] = station 

1346 

1347 ev = self.get_active_event() 

1348 if ev: 

1349 self.set_origin(ev) 

1350 

1351 def add_event(self, event): 

1352 marker = EventMarker(event) 

1353 self.add_marker(marker) 

1354 

1355 def add_events(self, events): 

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

1357 self.add_markers(markers) 

1358 

1359 def set_event_marker_as_origin(self, ignore=None): 

1360 selected = self.selected_markers() 

1361 if not selected: 

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

1363 return 

1364 

1365 m = selected[0] 

1366 if not isinstance(m, EventMarker): 

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

1368 return 

1369 

1370 self.set_active_event_marker(m) 

1371 

1372 def deactivate_event_marker(self): 

1373 if self.active_event_marker: 

1374 self.active_event_marker.active = False 

1375 

1376 self.active_event_marker_changed.emit() 

1377 self.active_event_marker = None 

1378 

1379 def set_active_event_marker(self, event_marker): 

1380 if self.active_event_marker: 

1381 self.active_event_marker.active = False 

1382 

1383 self.active_event_marker = event_marker 

1384 event_marker.active = True 

1385 event = event_marker.get_event() 

1386 self.set_origin(event) 

1387 self.active_event_marker_changed.emit() 

1388 

1389 def set_active_event(self, event): 

1390 for marker in self.markers: 

1391 if isinstance(marker, EventMarker): 

1392 if marker.get_event() is event: 

1393 self.set_active_event_marker(marker) 

1394 

1395 def get_active_event_marker(self): 

1396 return self.active_event_marker 

1397 

1398 def get_active_event(self): 

1399 m = self.get_active_event_marker() 

1400 if m is not None: 

1401 return m.get_event() 

1402 else: 

1403 return None 

1404 

1405 def get_active_markers(self): 

1406 emarker = self.get_active_event_marker() 

1407 if emarker is None: 

1408 return None, [] 

1409 

1410 else: 

1411 ev = emarker.get_event() 

1412 pmarkers = [ 

1413 m for m in self.markers 

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

1415 

1416 return emarker, pmarkers 

1417 

1418 def set_origin(self, location): 

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

1420 station.set_event_relative_data( 

1421 location, 

1422 distance_3d=self.menuitem_distances_3d.isChecked()) 

1423 

1424 self.sortingmode_change() 

1425 

1426 def distances_3d_changed(self): 

1427 ignore = self.menuitem_distances_3d.isChecked() 

1428 self.set_event_marker_as_origin(ignore) 

1429 

1430 def iter_snuffling_modules(self): 

1431 pjoin = os.path.join 

1432 for path in self.snuffling_paths: 

1433 

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

1435 os.mkdir(path) 

1436 

1437 for entry in os.listdir(path): 

1438 directory = path 

1439 fn = entry 

1440 d = pjoin(path, entry) 

1441 if os.path.isdir(d): 

1442 directory = d 

1443 if os.path.isfile( 

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

1445 fn = 'snuffling.py' 

1446 

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

1448 continue 

1449 

1450 name = fn[:-3] 

1451 

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

1453 self.snuffling_modules[directory, name] = \ 

1454 pyrocko.gui.snuffling.SnufflingModule( 

1455 directory, name, self) 

1456 

1457 yield self.snuffling_modules[directory, name] 

1458 

1459 def setup_snufflings(self): 

1460 # user snufflings 

1461 for mod in self.iter_snuffling_modules(): 

1462 try: 

1463 mod.load_if_needed() 

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

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

1466 

1467 # load the default snufflings on first run 

1468 if self.default_snufflings is None: 

1469 self.default_snufflings = pyrocko.gui\ 

1470 .snufflings.__snufflings__() 

1471 for snuffling in self.default_snufflings: 

1472 self.add_snuffling(snuffling) 

1473 

1474 def set_panel_parent(self, panel_parent): 

1475 self.panel_parent = panel_parent 

1476 

1477 def get_panel_parent(self): 

1478 return self.panel_parent 

1479 

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

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

1482 snuffling.init_gui( 

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

1484 self.snufflings.append(snuffling) 

1485 self.update() 

1486 

1487 def remove_snuffling(self, snuffling): 

1488 snuffling.delete_gui() 

1489 self.update() 

1490 self.snufflings.remove(snuffling) 

1491 snuffling.pre_destroy() 

1492 

1493 def add_snuffling_menuitem(self, item): 

1494 self.snufflings_menu.addAction(item) 

1495 item.setParent(self.snufflings_menu) 

1496 sort_actions(self.snufflings_menu) 

1497 

1498 def remove_snuffling_menuitem(self, item): 

1499 self.snufflings_menu.removeAction(item) 

1500 

1501 def add_snuffling_help_menuitem(self, item): 

1502 self.snuffling_help.addAction(item) 

1503 item.setParent(self.snuffling_help) 

1504 sort_actions(self.snuffling_help) 

1505 

1506 def remove_snuffling_help_menuitem(self, item): 

1507 self.snuffling_help.removeAction(item) 

1508 

1509 def add_panel_toggler(self, item): 

1510 self.toggle_panel_menu.addAction(item) 

1511 item.setParent(self.toggle_panel_menu) 

1512 sort_actions(self.toggle_panel_menu) 

1513 

1514 def remove_panel_toggler(self, item): 

1515 self.toggle_panel_menu.removeAction(item) 

1516 

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

1518 cache_dir=None, force_cache=False): 

1519 

1520 if cache_dir is None: 

1521 cache_dir = pyrocko.config.config().cache_dir 

1522 if isinstance(paths, str): 

1523 paths = [paths] 

1524 

1525 fns = pyrocko.util.select_files( 

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

1527 

1528 if not fns: 

1529 return 

1530 

1531 cache = pyrocko.pile.get_cache(cache_dir) 

1532 

1533 t = [time.time()] 

1534 

1535 def update_bar(label, value): 

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

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

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

1539 else: 

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

1541 

1542 return pbs.set_status(label, value) 

1543 

1544 def update_progress(label, i, n): 

1545 abort = False 

1546 

1547 qw.qApp.processEvents() 

1548 if n != 0: 

1549 perc = i*100/n 

1550 else: 

1551 perc = 100 

1552 abort |= update_bar(label, perc) 

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

1554 

1555 tnow = time.time() 

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

1557 self.update() 

1558 t[0] = tnow 

1559 

1560 return abort 

1561 

1562 self.automatic_updates = False 

1563 

1564 self.pile.load_files( 

1565 sorted(fns), 

1566 filename_attributes=regex, 

1567 cache=cache, 

1568 fileformat=format, 

1569 show_progress=False, 

1570 update_progress=update_progress) 

1571 

1572 self.automatic_updates = True 

1573 self.update() 

1574 

1575 def load_queued(self): 

1576 if not self._paths_to_load: 

1577 return 

1578 paths = self._paths_to_load 

1579 self._paths_to_load = [] 

1580 self.load(paths) 

1581 

1582 def load_soon(self, paths): 

1583 self._paths_to_load.extend(paths) 

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

1585 

1586 def open_waveforms(self): 

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

1588 

1589 fns, _ = qw.QFileDialog.getOpenFileNames( 

1590 self, caption, options=qfiledialog_options) 

1591 

1592 if fns: 

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

1594 

1595 def open_waveform_directory(self): 

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

1597 

1598 dn = qw.QFileDialog.getExistingDirectory( 

1599 self, caption, options=qfiledialog_options) 

1600 

1601 if dn: 

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

1603 

1604 def open_stations(self, fns=None): 

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

1606 

1607 if not fns: 

1608 fns, _ = qw.QFileDialog.getOpenFileNames( 

1609 self, caption, options=qfiledialog_options) 

1610 

1611 try: 

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

1613 for stat in stations: 

1614 self.add_stations(stat) 

1615 

1616 except Exception as e: 

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

1618 

1619 def open_stations_xml(self, fns=None): 

1620 from pyrocko.io import stationxml 

1621 

1622 caption = 'Select one or more StationXML files' 

1623 if not fns: 

1624 fns, _ = qw.QFileDialog.getOpenFileNames( 

1625 self, caption, options=qfiledialog_options, 

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

1627 ';;All files (*)') 

1628 

1629 try: 

1630 stations = [ 

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

1632 for x in fns] 

1633 

1634 for stat in stations: 

1635 self.add_stations(stat) 

1636 

1637 except Exception as e: 

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

1639 

1640 def add_traces(self, traces): 

1641 if traces: 

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

1643 self.pile.add_file(mtf) 

1644 ticket = (self.pile, mtf) 

1645 return ticket 

1646 else: 

1647 return (None, None) 

1648 

1649 def release_data(self, tickets): 

1650 for ticket in tickets: 

1651 pile, mtf = ticket 

1652 if pile is not None: 

1653 pile.remove_file(mtf) 

1654 

1655 def periodical(self): 

1656 if self.menuitem_watch.isChecked(): 

1657 if self.pile.reload_modified(): 

1658 self.update() 

1659 

1660 def get_pile(self): 

1661 return self.pile 

1662 

1663 def pile_changed(self, what): 

1664 self.pile_has_changed = True 

1665 self.pile_has_changed_signal.emit() 

1666 if self.automatic_updates: 

1667 self.update() 

1668 

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

1670 

1671 if gather is None: 

1672 def gather_func(tr): 

1673 return tr.nslc_id 

1674 

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

1676 

1677 else: 

1678 def gather_func(tr): 

1679 return ( 

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

1681 

1682 if color is None: 

1683 def color(tr): 

1684 return tr.location 

1685 

1686 self.gather = gather_func 

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

1688 

1689 self.color_gather = color 

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

1691 previous_ntracks = self.ntracks 

1692 self.set_ntracks(len(keys)) 

1693 

1694 if self.shown_tracks_range is None or \ 

1695 previous_ntracks == 0 or \ 

1696 self.show_all: 

1697 

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

1699 key_at_top = None 

1700 n = high-low 

1701 

1702 else: 

1703 low, high = self.shown_tracks_range 

1704 key_at_top = self.track_keys[low] 

1705 n = high-low 

1706 

1707 self.track_keys = sorted(keys) 

1708 

1709 track_patterns = [] 

1710 for k in self.track_keys: 

1711 pat = ['*', '*', '*', '*'] 

1712 for i, j in enumerate(gather): 

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

1714 

1715 track_patterns.append(pat) 

1716 

1717 self.track_patterns = track_patterns 

1718 

1719 if key_at_top is not None: 

1720 try: 

1721 ind = self.track_keys.index(key_at_top) 

1722 low = ind 

1723 high = low+n 

1724 except Exception: 

1725 pass 

1726 

1727 self.set_tracks_range((low, high)) 

1728 

1729 self.key_to_row = dict( 

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

1731 

1732 def inrange(x, r): 

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

1734 

1735 def trace_selector(trace): 

1736 gt = self.gather(trace) 

1737 return ( 

1738 gt in self.key_to_row and 

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

1740 

1741 self.trace_selector = lambda x: \ 

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

1743 and trace_selector(x) 

1744 

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

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

1747 self.show_all: 

1748 

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

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

1751 tlen = (tmax - tmin) 

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

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

1754 

1755 def set_time_range(self, tmin, tmax): 

1756 if tmin is None: 

1757 tmin = initial_time_range[0] 

1758 

1759 if tmax is None: 

1760 tmax = initial_time_range[1] 

1761 

1762 if tmin > tmax: 

1763 tmin, tmax = tmax, tmin 

1764 

1765 if tmin == tmax: 

1766 tmin -= 1. 

1767 tmax += 1. 

1768 

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

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

1771 

1772 min_deltat = self.content_deltat_range()[0] 

1773 if (tmax - tmin < min_deltat): 

1774 m = (tmin + tmax) / 2. 

1775 tmin = m - min_deltat/2. 

1776 tmax = m + min_deltat/2. 

1777 

1778 self.time_projection.set_in_range(tmin, tmax) 

1779 self.tmin, self.tmax = tmin, tmax 

1780 

1781 def get_time_range(self): 

1782 return self.tmin, self.tmax 

1783 

1784 def ypart(self, y): 

1785 if y < self.ax_height: 

1786 return -1 

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

1788 return 1 

1789 else: 

1790 return 0 

1791 

1792 def time_fractional_digits(self): 

1793 min_deltat = self.content_deltat_range()[0] 

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

1795 

1796 def write_markers(self, fn=None): 

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

1798 if not fn: 

1799 fn, _ = qw.QFileDialog.getSaveFileName( 

1800 self, caption, options=qfiledialog_options) 

1801 if fn: 

1802 try: 

1803 Marker.save_markers( 

1804 self.markers, fn, 

1805 fdigits=self.time_fractional_digits()) 

1806 

1807 except Exception as e: 

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

1809 

1810 def write_selected_markers(self, fn=None): 

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

1812 if not fn: 

1813 fn, _ = qw.QFileDialog.getSaveFileName( 

1814 self, caption, options=qfiledialog_options) 

1815 if fn: 

1816 try: 

1817 Marker.save_markers( 

1818 self.iter_selected_markers(), 

1819 fn, 

1820 fdigits=self.time_fractional_digits()) 

1821 

1822 except Exception as e: 

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

1824 

1825 def read_events(self, fn=None): 

1826 ''' 

1827 Open QFileDialog to open, read and add 

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

1829 representation to the pile viewer. 

1830 ''' 

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

1832 if not fn: 

1833 fn, _ = qw.QFileDialog.getOpenFileName( 

1834 self, caption, options=qfiledialog_options) 

1835 if fn: 

1836 try: 

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

1838 self.associate_phases_to_events() 

1839 

1840 except Exception as e: 

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

1842 

1843 def read_markers(self, fn=None): 

1844 ''' 

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

1846 ''' 

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

1848 if not fn: 

1849 fn, _ = qw.QFileDialog.getOpenFileName( 

1850 self, caption, options=qfiledialog_options) 

1851 if fn: 

1852 try: 

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

1854 self.associate_phases_to_events() 

1855 

1856 except Exception as e: 

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

1858 

1859 def associate_phases_to_events(self): 

1860 associate_phases_to_events(self.markers) 

1861 

1862 def add_marker(self, marker): 

1863 # need index to inform QAbstactTableModel about upcoming change, 

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

1865 self.markers.insert(marker) 

1866 i = self.markers.remove(marker) 

1867 

1868 self.begin_markers_add.emit(i, i) 

1869 self.markers.insert(marker) 

1870 self.end_markers_add.emit() 

1871 self.markers_deltat_max = max( 

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

1873 

1874 def add_markers(self, markers): 

1875 if not self.markers: 

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

1877 self.markers.insert_many(markers) 

1878 self.end_markers_add.emit() 

1879 self.update_markers_deltat_max() 

1880 else: 

1881 for marker in markers: 

1882 self.add_marker(marker) 

1883 

1884 def update_markers_deltat_max(self): 

1885 if self.markers: 

1886 self.markers_deltat_max = max( 

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

1888 

1889 def remove_marker(self, marker): 

1890 ''' 

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

1892 

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

1894 ''' 

1895 

1896 if marker is self.active_event_marker: 

1897 self.deactivate_event_marker() 

1898 

1899 try: 

1900 i = self.markers.index(marker) 

1901 self.begin_markers_remove.emit(i, i) 

1902 self.markers.remove_at(i) 

1903 self.end_markers_remove.emit() 

1904 except ValueError: 

1905 pass 

1906 

1907 def remove_markers(self, markers): 

1908 ''' 

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

1910 

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

1912 instances 

1913 ''' 

1914 

1915 if markers is self.markers: 

1916 markers = list(markers) 

1917 

1918 for marker in markers: 

1919 self.remove_marker(marker) 

1920 

1921 self.update_markers_deltat_max() 

1922 

1923 def remove_selected_markers(self): 

1924 def delete_segment(istart, iend): 

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

1926 for _ in range(iend - istart): 

1927 self.markers.remove_at(istart) 

1928 

1929 self.end_markers_remove.emit() 

1930 

1931 istart = None 

1932 ipos = 0 

1933 markers = self.markers 

1934 nmarkers = len(self.markers) 

1935 while ipos < nmarkers: 

1936 marker = markers[ipos] 

1937 if marker.is_selected(): 

1938 if marker is self.active_event_marker: 

1939 self.deactivate_event_marker() 

1940 

1941 if istart is None: 

1942 istart = ipos 

1943 else: 

1944 if istart is not None: 

1945 delete_segment(istart, ipos) 

1946 nmarkers -= ipos - istart 

1947 ipos = istart - 1 

1948 istart = None 

1949 

1950 ipos += 1 

1951 

1952 if istart is not None: 

1953 delete_segment(istart, ipos) 

1954 

1955 self.update_markers_deltat_max() 

1956 

1957 def selected_markers(self): 

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

1959 

1960 def iter_selected_markers(self): 

1961 for marker in self.markers: 

1962 if marker.is_selected(): 

1963 yield marker 

1964 

1965 def get_markers(self): 

1966 return self.markers 

1967 

1968 def mousePressEvent(self, mouse_ev): 

1969 self.show_all = False 

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

1971 

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

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

1974 if self.picking: 

1975 if self.picking_down is None: 

1976 self.picking_down = ( 

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

1978 mouse_ev.y()) 

1979 

1980 elif marker is not None: 

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

1982 self.deselect_all() 

1983 marker.selected = True 

1984 self.emit_selected_markers() 

1985 self.update() 

1986 else: 

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

1988 self.track_trange = self.tmin, self.tmax 

1989 

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

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

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

1993 self.update_status() 

1994 

1995 def mouseReleaseEvent(self, mouse_ev): 

1996 if self.ignore_releases: 

1997 self.ignore_releases -= 1 

1998 return 

1999 

2000 if self.picking: 

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

2002 self.emit_selected_markers() 

2003 

2004 if self.track_start: 

2005 self.update() 

2006 

2007 self.track_start = None 

2008 self.track_trange = None 

2009 self.update_status() 

2010 

2011 def mouseDoubleClickEvent(self, mouse_ev): 

2012 self.show_all = False 

2013 self.start_picking(None) 

2014 self.ignore_releases = 1 

2015 

2016 def mouseMoveEvent(self, mouse_ev): 

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

2018 

2019 if self.picking: 

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

2021 

2022 elif self.track_start is not None: 

2023 x0, y0 = self.track_start 

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

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

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

2027 dy = 0 

2028 

2029 tmin0, tmax0 = self.track_trange 

2030 

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

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

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

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

2035 

2036 self.interrupt_following() 

2037 self.set_time_range( 

2038 tmin0 - dt - dtr*frac, 

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

2040 

2041 self.update() 

2042 else: 

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

2044 

2045 self.update_status() 

2046 

2047 def nslc_ids_under_cursor(self, x, y): 

2048 ftrack = self.track_to_screen.rev(y) 

2049 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2050 return nslc_ids 

2051 

2052 def marker_under_cursor(self, x, y): 

2053 mouset = self.time_projection.rev(x) 

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

2055 relevant_nslc_ids = None 

2056 for marker in self.markers: 

2057 if marker.kind not in self.visible_marker_kinds: 

2058 continue 

2059 

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

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

2062 

2063 if relevant_nslc_ids is None: 

2064 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2065 

2066 marker_nslc_ids = marker.get_nslc_ids() 

2067 if not marker_nslc_ids: 

2068 return marker 

2069 

2070 for nslc_id in marker_nslc_ids: 

2071 if nslc_id in relevant_nslc_ids: 

2072 return marker 

2073 

2074 def hoovering(self, x, y): 

2075 mouset = self.time_projection.rev(x) 

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

2077 needupdate = False 

2078 haveone = False 

2079 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2080 for marker in self.markers: 

2081 if marker.kind not in self.visible_marker_kinds: 

2082 continue 

2083 

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

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

2086 

2087 if state: 

2088 xstate = False 

2089 

2090 marker_nslc_ids = marker.get_nslc_ids() 

2091 if not marker_nslc_ids: 

2092 xstate = True 

2093 

2094 for nslc in relevant_nslc_ids: 

2095 if marker.match_nslc(nslc): 

2096 xstate = True 

2097 

2098 state = xstate 

2099 

2100 if state: 

2101 haveone = True 

2102 oldstate = marker.is_alerted() 

2103 if oldstate != state: 

2104 needupdate = True 

2105 marker.set_alerted(state) 

2106 if state: 

2107 self.message = marker.hoover_message() 

2108 

2109 if not haveone: 

2110 self.message = None 

2111 

2112 if needupdate: 

2113 self.update() 

2114 

2115 def event(self, event): 

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

2117 self.keyPressEvent(event) 

2118 return True 

2119 else: 

2120 return base.event(self, event) 

2121 

2122 def keyPressEvent(self, key_event): 

2123 self.show_all = False 

2124 dt = self.tmax - self.tmin 

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

2126 

2127 key = key_event.key() 

2128 try: 

2129 keytext = str(key_event.text()) 

2130 except UnicodeEncodeError: 

2131 return 

2132 

2133 if key == qc.Qt.Key_Space: 

2134 self.interrupt_following() 

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

2136 

2137 elif key == qc.Qt.Key_Up: 

2138 for m in self.selected_markers(): 

2139 if isinstance(m, PhaseMarker): 

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

2141 p = 0 

2142 else: 

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

2144 m.set_polarity(p) 

2145 

2146 elif key == qc.Qt.Key_Down: 

2147 for m in self.selected_markers(): 

2148 if isinstance(m, PhaseMarker): 

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

2150 p = 0 

2151 else: 

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

2153 m.set_polarity(p) 

2154 

2155 elif key == qc.Qt.Key_B: 

2156 dt = self.tmax - self.tmin 

2157 self.interrupt_following() 

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

2159 

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

2161 self.interrupt_following() 

2162 

2163 tgo = None 

2164 

2165 class TraceDummy(object): 

2166 def __init__(self, marker): 

2167 self._marker = marker 

2168 

2169 @property 

2170 def nslc_id(self): 

2171 return self._marker.one_nslc() 

2172 

2173 def marker_to_itrack(marker): 

2174 try: 

2175 return self.key_to_row.get( 

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

2177 

2178 except MarkerOneNSLCRequired: 

2179 return -1 

2180 

2181 emarker, pmarkers = self.get_active_markers() 

2182 pmarkers = [ 

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

2184 pmarkers.sort(key=lambda m: ( 

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

2186 

2187 if key == qc.Qt.Key_Backtab: 

2188 pmarkers.reverse() 

2189 

2190 smarkers = self.selected_markers() 

2191 iselected = [] 

2192 for sm in smarkers: 

2193 try: 

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

2195 except ValueError: 

2196 pass 

2197 

2198 if iselected: 

2199 icurrent = max(iselected) + 1 

2200 else: 

2201 icurrent = 0 

2202 

2203 if icurrent < len(pmarkers): 

2204 self.deselect_all() 

2205 cmarker = pmarkers[icurrent] 

2206 cmarker.selected = True 

2207 tgo = cmarker.tmin 

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

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

2210 

2211 itrack = marker_to_itrack(cmarker) 

2212 if itrack != -1: 

2213 if itrack < self.shown_tracks_range[0]: 

2214 self.scroll_tracks( 

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

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

2217 self.scroll_tracks( 

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

2219 

2220 if itrack not in self.track_to_nslc_ids: 

2221 self.go_to_selection() 

2222 

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

2224 smarkers = self.selected_markers() 

2225 tgo = None 

2226 dir = str(keytext) 

2227 if smarkers: 

2228 tmid = smarkers[0].tmin 

2229 for smarker in smarkers: 

2230 if dir == 'n': 

2231 tmid = max(smarker.tmin, tmid) 

2232 else: 

2233 tmid = min(smarker.tmin, tmid) 

2234 

2235 tgo = tmid 

2236 

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

2238 for marker in sorted( 

2239 self.markers, 

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

2241 

2242 t = marker.tmin 

2243 if t > tmid and \ 

2244 marker.kind in self.visible_marker_kinds and \ 

2245 (dir == 'n' or 

2246 isinstance(marker, EventMarker)): 

2247 

2248 self.deselect_all() 

2249 marker.selected = True 

2250 tgo = t 

2251 break 

2252 else: 

2253 for marker in sorted( 

2254 self.markers, 

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

2256 reverse=True): 

2257 

2258 t = marker.tmin 

2259 if t < tmid and \ 

2260 marker.kind in self.visible_marker_kinds and \ 

2261 (dir == 'p' or 

2262 isinstance(marker, EventMarker)): 

2263 self.deselect_all() 

2264 marker.selected = True 

2265 tgo = t 

2266 break 

2267 

2268 if tgo is not None: 

2269 self.interrupt_following() 

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

2271 

2272 elif keytext == 'r': 

2273 if self.pile.reload_modified(): 

2274 self.reloaded = True 

2275 

2276 elif keytext == 'R': 

2277 self.setup_snufflings() 

2278 

2279 elif key == qc.Qt.Key_Backspace: 

2280 self.remove_selected_markers() 

2281 

2282 elif keytext == 'a': 

2283 for marker in self.markers: 

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

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

2286 marker.kind in self.visible_marker_kinds): 

2287 marker.selected = True 

2288 else: 

2289 marker.selected = False 

2290 

2291 elif keytext == 'A': 

2292 for marker in self.markers: 

2293 if marker.kind in self.visible_marker_kinds: 

2294 marker.selected = True 

2295 

2296 elif keytext == 'd': 

2297 self.deselect_all() 

2298 

2299 elif keytext == 'E': 

2300 self.deactivate_event_marker() 

2301 

2302 elif keytext == 'e': 

2303 markers = self.selected_markers() 

2304 event_markers_in_spe = [ 

2305 marker for marker in markers 

2306 if not isinstance(marker, PhaseMarker)] 

2307 

2308 phase_markers = [ 

2309 marker for marker in markers 

2310 if isinstance(marker, PhaseMarker)] 

2311 

2312 if len(event_markers_in_spe) == 1: 

2313 event_marker = event_markers_in_spe[0] 

2314 if not isinstance(event_marker, EventMarker): 

2315 nslcs = list(event_marker.nslc_ids) 

2316 lat, lon = 0.0, 0.0 

2317 old = self.get_active_event() 

2318 if len(nslcs) == 1: 

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

2320 elif old is not None: 

2321 lat, lon = old.lat, old.lon 

2322 

2323 event_marker.convert_to_event_marker(lat, lon) 

2324 

2325 self.set_active_event_marker(event_marker) 

2326 event = event_marker.get_event() 

2327 for marker in phase_markers: 

2328 marker.set_event(event) 

2329 

2330 else: 

2331 for marker in event_markers_in_spe: 

2332 marker.convert_to_event_marker() 

2333 

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

2335 for marker in self.selected_markers(): 

2336 marker.set_kind(int(keytext)) 

2337 self.emit_selected_markers() 

2338 

2339 elif key in fkey_map: 

2340 self.handle_fkeys(key) 

2341 

2342 elif key == qc.Qt.Key_Escape: 

2343 if self.picking: 

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

2345 

2346 elif key == qc.Qt.Key_PageDown: 

2347 self.scroll_tracks( 

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

2349 

2350 elif key == qc.Qt.Key_PageUp: 

2351 self.scroll_tracks( 

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

2353 

2354 elif key == qc.Qt.Key_Plus: 

2355 self.zoom_tracks(0., 1.) 

2356 

2357 elif key == qc.Qt.Key_Minus: 

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

2359 

2360 elif key == qc.Qt.Key_Equal: 

2361 ntracks_shown = self.shown_tracks_range[1] - \ 

2362 self.shown_tracks_range[0] 

2363 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2364 self.zoom_tracks(0., dtracks) 

2365 

2366 elif key == qc.Qt.Key_Colon: 

2367 self.want_input.emit() 

2368 

2369 elif keytext == 'f': 

2370 self.toggle_fullscreen() 

2371 

2372 elif keytext == 'g': 

2373 self.go_to_selection() 

2374 

2375 elif keytext == 'G': 

2376 self.go_to_selection(tight=True) 

2377 

2378 elif keytext == 'm': 

2379 self.toggle_marker_editor() 

2380 

2381 elif keytext == 'c': 

2382 self.toggle_main_controls() 

2383 

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

2385 dir = 1 

2386 amount = 1 

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

2388 dir = -1 

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

2390 amount = 10 

2391 self.nudge_selected_markers(dir*amount) 

2392 else: 

2393 super().keyPressEvent(key_event) 

2394 

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

2396 self.emit_selected_markers() 

2397 

2398 self.update() 

2399 self.update_status() 

2400 

2401 def handle_fkeys(self, key): 

2402 self.set_phase_kind( 

2403 self.selected_markers(), 

2404 fkey_map[key] + 1) 

2405 self.emit_selected_markers() 

2406 

2407 def emit_selected_markers(self): 

2408 ibounds = [] 

2409 last_selected = False 

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

2411 this_selected = marker.is_selected() 

2412 if this_selected != last_selected: 

2413 ibounds.append(imarker) 

2414 

2415 last_selected = this_selected 

2416 

2417 if last_selected: 

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

2419 

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

2421 self.n_selected_markers = sum( 

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

2423 self.marker_selection_changed.emit(chunks) 

2424 

2425 def toggle_marker_editor(self): 

2426 self.panel_parent.toggle_marker_editor() 

2427 

2428 def toggle_main_controls(self): 

2429 self.panel_parent.toggle_main_controls() 

2430 

2431 def nudge_selected_markers(self, npixels): 

2432 a, b = self.time_projection.ur 

2433 c, d = self.time_projection.xr 

2434 for marker in self.selected_markers(): 

2435 if not isinstance(marker, EventMarker): 

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

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

2438 

2439 def toggle_fullscreen(self): 

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

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

2442 self.window().showNormal() 

2443 else: 

2444 if is_macos: 

2445 self.window().showMaximized() 

2446 else: 

2447 self.window().showFullScreen() 

2448 

2449 def about(self): 

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

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

2452 txt = f.read() 

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

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

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

2456 

2457 def help(self): 

2458 class MyScrollArea(qw.QScrollArea): 

2459 

2460 def sizeHint(self): 

2461 s = qc.QSize() 

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

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

2464 return s 

2465 

2466 with open(pyrocko.util.data_file( 

2467 'snuffler_help.html')) as f: 

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

2469 

2470 with open(pyrocko.util.data_file( 

2471 'snuffler_help_epilog.html')) as f: 

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

2473 

2474 for h in [hcheat, hepilog]: 

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

2476 h.setWordWrap(True) 

2477 

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

2479 

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

2481 scroller = qw.QScrollArea() 

2482 frame = qw.QFrame(scroller) 

2483 frame.setLineWidth(0) 

2484 layout = qw.QVBoxLayout() 

2485 layout.setContentsMargins(0, 0, 0, 0) 

2486 layout.setSpacing(0) 

2487 frame.setLayout(layout) 

2488 scroller.setWidget(frame) 

2489 scroller.setWidgetResizable(True) 

2490 frame.setBackgroundRole(qg.QPalette.Base) 

2491 for h in labels: 

2492 h.setParent(frame) 

2493 h.setMargin(3) 

2494 h.setTextInteractionFlags( 

2495 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2496 h.setBackgroundRole(qg.QPalette.Base) 

2497 layout.addWidget(h) 

2498 h.linkActivated.connect( 

2499 self.open_link) 

2500 

2501 if self.panel_parent is not None: 

2502 if target == 'panel': 

2503 self.panel_parent.add_panel( 

2504 name, scroller, True, volatile=False) 

2505 else: 

2506 self.panel_parent.add_tab(name, scroller) 

2507 

2508 def open_link(self, link): 

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

2510 

2511 def wheelEvent(self, wheel_event): 

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

2513 

2514 n = self.wheel_pos // 120 

2515 self.wheel_pos = self.wheel_pos % 120 

2516 if n == 0: 

2517 return 

2518 

2519 amount = max( 

2520 1., 

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

2522 wdelta = amount * n 

2523 

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

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

2526 / (trmax-trmin) 

2527 

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

2529 self.zoom_tracks(anchor, wdelta) 

2530 else: 

2531 self.scroll_tracks(-wdelta) 

2532 

2533 def dragEnterEvent(self, event): 

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

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

2536 event.setDropAction(qc.Qt.LinkAction) 

2537 event.accept() 

2538 

2539 def dropEvent(self, event): 

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

2541 paths = list( 

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

2543 event.acceptProposedAction() 

2544 self.load(paths) 

2545 

2546 def get_phase_name(self, kind): 

2547 return self.config.get_phase_name(kind) 

2548 

2549 def set_phase_kind(self, markers, kind): 

2550 phasename = self.get_phase_name(kind) 

2551 

2552 for marker in markers: 

2553 if isinstance(marker, PhaseMarker): 

2554 if kind == 10: 

2555 marker.convert_to_marker() 

2556 else: 

2557 marker.set_phasename(phasename) 

2558 marker.set_event(self.get_active_event()) 

2559 

2560 elif isinstance(marker, EventMarker): 

2561 pass 

2562 

2563 else: 

2564 if kind != 10: 

2565 event = self.get_active_event() 

2566 marker.convert_to_phase_marker( 

2567 event, phasename, None, False) 

2568 

2569 def set_ntracks(self, ntracks): 

2570 if self.ntracks != ntracks: 

2571 self.ntracks = ntracks 

2572 if self.shown_tracks_range is not None: 

2573 l, h = self.shown_tracks_range 

2574 else: 

2575 l, h = 0, self.ntracks 

2576 

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

2578 

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

2580 

2581 low, high = range 

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

2583 high = min(self.ntracks, high) 

2584 low = max(0, low) 

2585 high = max(1, high) 

2586 

2587 if start is None: 

2588 start = float(low) 

2589 

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

2591 self.shown_tracks_range = low, high 

2592 self.shown_tracks_start = start 

2593 

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

2595 

2596 def scroll_tracks(self, shift): 

2597 shown = self.shown_tracks_range 

2598 shiftmin = -shown[0] 

2599 shiftmax = self.ntracks-shown[1] 

2600 shift = max(shiftmin, shift) 

2601 shift = min(shiftmax, shift) 

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

2603 

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

2605 

2606 self.update() 

2607 

2608 def zoom_tracks(self, anchor, delta): 

2609 ntracks_shown = self.shown_tracks_range[1] \ 

2610 - self.shown_tracks_range[0] 

2611 

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

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

2614 return 

2615 

2616 ntracks_shown += int(round(delta)) 

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

2618 

2619 u = self.shown_tracks_start 

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

2621 nv = nu + ntracks_shown 

2622 if nv > self.ntracks: 

2623 nu -= nv - self.ntracks 

2624 nv -= nv - self.ntracks 

2625 

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

2627 

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

2629 - self.shown_tracks_range[0] 

2630 

2631 self.update() 

2632 

2633 def content_time_range(self): 

2634 pile = self.get_pile() 

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

2636 if tmin is None: 

2637 tmin = initial_time_range[0] 

2638 if tmax is None: 

2639 tmax = initial_time_range[1] 

2640 

2641 return tmin, tmax 

2642 

2643 def content_deltat_range(self): 

2644 pile = self.get_pile() 

2645 

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

2647 

2648 if deltatmin is None: 

2649 deltatmin = 0.001 

2650 

2651 if deltatmax is None: 

2652 deltatmax = 1000.0 

2653 

2654 return deltatmin, deltatmax 

2655 

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

2657 if tmax < tmin: 

2658 tmin, tmax = tmax, tmin 

2659 

2660 deltatmin = self.content_deltat_range()[0] 

2661 dt = deltatmin * self.visible_length * 0.95 

2662 

2663 if dt == 0.0: 

2664 dt = 1.0 

2665 

2666 if tight: 

2667 if tmax != tmin: 

2668 dtm = tmax - tmin 

2669 tmin -= dtm*0.1 

2670 tmax += dtm*0.1 

2671 return tmin, tmax 

2672 else: 

2673 tcenter = (tmin + tmax) / 2. 

2674 tmin = tcenter - 0.5*dt 

2675 tmax = tcenter + 0.5*dt 

2676 return tmin, tmax 

2677 

2678 if tmax-tmin < dt: 

2679 vmin, vmax = self.get_time_range() 

2680 dt = min(vmax - vmin, dt) 

2681 

2682 tcenter = (tmin+tmax)/2. 

2683 etmin, etmax = tmin, tmax 

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

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

2686 dtm = tmax-tmin 

2687 if etmin == tmin: 

2688 tmin -= dtm*0.1 

2689 if etmax == tmax: 

2690 tmax += dtm*0.1 

2691 

2692 else: 

2693 dtm = tmax-tmin 

2694 tmin -= dtm*0.1 

2695 tmax += dtm*0.1 

2696 

2697 return tmin, tmax 

2698 

2699 def go_to_selection(self, tight=False): 

2700 markers = self.selected_markers() 

2701 if markers: 

2702 tmax, tmin = self.content_time_range() 

2703 for marker in markers: 

2704 tmin = min(tmin, marker.tmin) 

2705 tmax = max(tmax, marker.tmax) 

2706 

2707 else: 

2708 if tight: 

2709 vmin, vmax = self.get_time_range() 

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

2711 else: 

2712 tmin, tmax = self.content_time_range() 

2713 

2714 tmin, tmax = self.make_good_looking_time_range( 

2715 tmin, tmax, tight=tight) 

2716 

2717 self.interrupt_following() 

2718 self.set_time_range(tmin, tmax) 

2719 self.update() 

2720 

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

2722 tmax = t 

2723 if tlen is not None: 

2724 tmax = t+tlen 

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

2726 self.interrupt_following() 

2727 self.set_time_range(tmin, tmax) 

2728 self.update() 

2729 

2730 def go_to_event_by_name(self, name): 

2731 for marker in self.markers: 

2732 if isinstance(marker, EventMarker): 

2733 event = marker.get_event() 

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

2735 tmin, tmax = self.make_good_looking_time_range( 

2736 event.time, event.time) 

2737 

2738 self.interrupt_following() 

2739 self.set_time_range(tmin, tmax) 

2740 

2741 def printit(self): 

2742 from .qt_compat import qprint 

2743 printer = qprint.QPrinter() 

2744 printer.setOrientation(qprint.QPrinter.Landscape) 

2745 

2746 dialog = qprint.QPrintDialog(printer, self) 

2747 dialog.setWindowTitle('Print') 

2748 

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

2750 return 

2751 

2752 painter = qg.QPainter() 

2753 painter.begin(printer) 

2754 page = printer.pageRect() 

2755 self.drawit( 

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

2757 

2758 painter.end() 

2759 

2760 def savesvg(self, fn=None): 

2761 

2762 if not fn: 

2763 fn, _ = qw.QFileDialog.getSaveFileName( 

2764 self, 

2765 'Save as SVG|PNG', 

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

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

2768 options=qfiledialog_options) 

2769 

2770 if fn == '': 

2771 return 

2772 

2773 fn = str(fn) 

2774 

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

2776 try: 

2777 w, h = 842, 595 

2778 margin = 0.025 

2779 m = max(w, h)*margin 

2780 

2781 generator = qsvg.QSvgGenerator() 

2782 generator.setFileName(fn) 

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

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

2785 

2786 painter = qg.QPainter() 

2787 painter.begin(generator) 

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

2789 painter.end() 

2790 

2791 except Exception as e: 

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

2793 

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

2795 pixmap = self.grab() 

2796 

2797 try: 

2798 pixmap.save(fn) 

2799 

2800 except Exception as e: 

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

2802 

2803 else: 

2804 self.fail( 

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

2806 '".png".') 

2807 

2808 def paintEvent(self, paint_ev): 

2809 ''' 

2810 Called by QT whenever widget needs to be painted. 

2811 ''' 

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

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

2814 if self.in_paint_event: 

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

2816 return 

2817 

2818 self.in_paint_event = True 

2819 

2820 painter = qg.QPainter(self) 

2821 

2822 if self.menuitem_antialias.isChecked(): 

2823 painter.setRenderHint(qg.QPainter.Antialiasing) 

2824 

2825 self.drawit(painter) 

2826 

2827 logger.debug( 

2828 'Time spent drawing: ' 

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

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

2831 (self.timer_draw - self.timer_cutout)) 

2832 

2833 logger.debug( 

2834 'Time spent processing:' 

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

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

2837 self.timer_cutout.get()) 

2838 

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

2840 self.time_last_painted = time.time() 

2841 self.in_paint_event = False 

2842 

2843 def determine_box_styles(self): 

2844 

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

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

2847 istyle = 0 

2848 trace_styles = {} 

2849 for itr, tr in enumerate(traces): 

2850 if itr > 0: 

2851 other = traces[itr-1] 

2852 if not ( 

2853 other.nslc_id == tr.nslc_id 

2854 and other.deltat == tr.deltat 

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

2856 < gap_lap_tolerance*tr.deltat): 

2857 

2858 istyle += 1 

2859 

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

2861 

2862 self.trace_styles = trace_styles 

2863 

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

2865 

2866 for v_projection in track_projections.values(): 

2867 v_projection.set_in_range(0., 1.) 

2868 

2869 def selector(x): 

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

2871 

2872 if self.trace_filter is not None: 

2873 def tselector(x): 

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

2875 

2876 else: 

2877 tselector = selector 

2878 

2879 traces = list(self.pile.iter_traces( 

2880 group_selector=selector, trace_selector=tselector)) 

2881 

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

2883 

2884 def drawbox(itrack, istyle, traces): 

2885 v_projection = track_projections[itrack] 

2886 dvmin = v_projection(0.) 

2887 dvmax = v_projection(1.) 

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

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

2890 

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

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

2893 p.fillRect(rect, style.fill_brush) 

2894 p.setPen(style.frame_pen) 

2895 p.drawRect(rect) 

2896 

2897 traces_by_style = {} 

2898 for itr, tr in enumerate(traces): 

2899 gt = self.gather(tr) 

2900 if gt not in self.key_to_row: 

2901 continue 

2902 

2903 itrack = self.key_to_row[gt] 

2904 if itrack not in track_projections: 

2905 continue 

2906 

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

2908 

2909 if len(traces) < 500: 

2910 drawbox(itrack, istyle, [tr]) 

2911 else: 

2912 if (itrack, istyle) not in traces_by_style: 

2913 traces_by_style[itrack, istyle] = [] 

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

2915 

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

2917 drawbox(itrack, istyle, traces) 

2918 

2919 def draw_visible_markers( 

2920 self, p, vcenter_projection, primary_pen): 

2921 

2922 try: 

2923 markers = self.markers.with_key_in_limited( 

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

2925 

2926 except pyrocko.pile.TooMany: 

2927 tmin = self.markers[0].tmin 

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

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

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

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

2932 v0, _ = vcenter_projection.get_out_range() 

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

2934 

2935 p.save() 

2936 

2937 pen = qg.QPen(primary_pen) 

2938 pen.setWidth(2) 

2939 pen.setStyle(qc.Qt.DotLine) 

2940 # pat = [5., 3.] 

2941 # pen.setDashPattern(pat) 

2942 p.setPen(pen) 

2943 

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

2945 s_selected = ' (all selected)' 

2946 elif self.n_selected_markers > 0: 

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

2948 else: 

2949 s_selected = '' 

2950 

2951 draw_label( 

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

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

2954 label_bg, 'LB') 

2955 

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

2957 p.drawLine(line) 

2958 p.restore() 

2959 

2960 return 

2961 

2962 for marker in markers: 

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

2964 and marker.kind in self.visible_marker_kinds: 

2965 

2966 marker.draw( 

2967 p, self.time_projection, vcenter_projection, 

2968 with_label=True) 

2969 

2970 def get_squirrel(self): 

2971 try: 

2972 return self.pile._squirrel 

2973 except AttributeError: 

2974 return None 

2975 

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

2977 sq = self.get_squirrel() 

2978 if sq is None: 

2979 return 

2980 

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

2982 v_projection = track_projections[itrack] 

2983 dvmin = v_projection(0.) 

2984 dvmax = v_projection(1.) 

2985 dtmin = time_projection.clipped(tmin, 0) 

2986 dtmax = time_projection.clipped(tmax, 1) 

2987 

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

2989 p.fillRect(rect, style.fill_brush) 

2990 p.setPen(style.frame_pen) 

2991 p.drawRect(rect) 

2992 

2993 pattern_list = [] 

2994 pattern_to_itrack = {} 

2995 for key in self.track_keys: 

2996 itrack = self.key_to_row[key] 

2997 if itrack not in track_projections: 

2998 continue 

2999 

3000 pattern = self.track_patterns[itrack] 

3001 pattern_to_itrack[tuple(pattern)] = itrack 

3002 pattern_list.append(tuple(pattern)) 

3003 

3004 vmin, vmax = self.get_time_range() 

3005 

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

3007 for coverage in sq.get_coverage( 

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

3009 itrack = pattern_to_itrack[coverage.pattern.nslc] 

3010 

3011 if coverage.changes is None: 

3012 drawbox( 

3013 itrack, coverage.tmin, coverage.tmax, 

3014 box_styles_coverage[kind][0]) 

3015 else: 

3016 t = None 

3017 pcount = 0 

3018 for tb, count in coverage.changes: 

3019 if t is not None and tb > t: 

3020 if pcount > 0: 

3021 drawbox( 

3022 itrack, t, tb, 

3023 box_styles_coverage[kind][ 

3024 min(len(box_styles_coverage)-1, 

3025 pcount)]) 

3026 

3027 t = tb 

3028 pcount = count 

3029 

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

3031 ''' 

3032 This performs the actual drawing. 

3033 ''' 

3034 

3035 self.timer_draw.start() 

3036 show_boxes = self.menuitem_showboxes.isChecked() 

3037 sq = self.get_squirrel() 

3038 

3039 if self.gather is None: 

3040 self.set_gathering() 

3041 

3042 if self.pile_has_changed: 

3043 

3044 if not self.sortingmode_change_delayed(): 

3045 self.sortingmode_change() 

3046 

3047 if show_boxes and sq is None: 

3048 self.determine_box_styles() 

3049 

3050 self.pile_has_changed = False 

3051 

3052 if h is None: 

3053 h = float(self.height()) 

3054 if w is None: 

3055 w = float(self.width()) 

3056 

3057 if printmode: 

3058 primary_color = (0, 0, 0) 

3059 else: 

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

3061 

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

3063 

3064 ax_h = float(self.ax_height) 

3065 

3066 vbottom_ax_projection = Projection() 

3067 vtop_ax_projection = Projection() 

3068 vcenter_projection = Projection() 

3069 

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

3071 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3072 vtop_ax_projection.set_out_range(0., ax_h) 

3073 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3074 vcenter_projection.set_in_range(0., 1.) 

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

3076 

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

3078 track_projections = {} 

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

3080 proj = Projection() 

3081 proj.set_out_range( 

3082 self.track_to_screen(i+0.05), 

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

3084 

3085 track_projections[i] = proj 

3086 

3087 if self.tmin > self.tmax: 

3088 return 

3089 

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

3091 vbottom_ax_projection.set_in_range(0, ax_h) 

3092 

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

3094 

3095 yscaler = pyrocko.plot.AutoScaler() 

3096 

3097 p.setPen(primary_pen) 

3098 

3099 font = qg.QFont() 

3100 font.setBold(True) 

3101 

3102 axannotfont = qg.QFont() 

3103 axannotfont.setBold(True) 

3104 axannotfont.setPointSize(8) 

3105 

3106 processed_traces = self.prepare_cutout2( 

3107 self.tmin, self.tmax, 

3108 trace_selector=self.trace_selector, 

3109 degap=self.menuitem_degap.isChecked(), 

3110 demean=self.menuitem_demean.isChecked()) 

3111 

3112 if not printmode and show_boxes: 

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

3114 or (self.view_mode is ViewMode.Waterfall 

3115 and not processed_traces): 

3116 

3117 if sq is None: 

3118 self.draw_trace_boxes( 

3119 p, self.time_projection, track_projections) 

3120 

3121 else: 

3122 self.draw_coverage( 

3123 p, self.time_projection, track_projections) 

3124 

3125 p.setFont(font) 

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

3127 

3128 color_lookup = dict( 

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

3130 

3131 self.track_to_nslc_ids = {} 

3132 nticks = 0 

3133 annot_labels = [] 

3134 

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

3136 waterfall = self.waterfall 

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

3138 waterfall.set_traces(processed_traces) 

3139 waterfall.set_cmap(self.waterfall_cmap) 

3140 waterfall.set_integrate(self.waterfall_integrate) 

3141 waterfall.set_clip( 

3142 self.waterfall_clip_min, self.waterfall_clip_max) 

3143 waterfall.show_absolute_values( 

3144 self.waterfall_show_absolute) 

3145 

3146 rect = qc.QRectF( 

3147 0, self.ax_height, 

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

3149 ) 

3150 waterfall.draw_waterfall(p, rect=rect) 

3151 

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

3153 show_scales = self.menuitem_showscalerange.isChecked() \ 

3154 or self.menuitem_showscaleaxis.isChecked() 

3155 

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

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

3158 - self.track_to_screen(0.05) 

3159 

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

3161 

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

3163 if self.menuitem_showscaleaxis.isChecked() \ 

3164 else 15 

3165 

3166 yscaler = pyrocko.plot.AutoScaler( 

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

3168 snap=show_scales 

3169 and not self.menuitem_showscaleaxis.isChecked()) 

3170 

3171 data_ranges = pyrocko.trace.minmax( 

3172 processed_traces, 

3173 key=self.scaling_key, 

3174 mode=self.scaling_base[0], 

3175 outer_mode=self.scaling_base[1]) 

3176 

3177 if not self.menuitem_fixscalerange.isChecked(): 

3178 self.old_data_ranges = data_ranges 

3179 else: 

3180 data_ranges.update(self.old_data_ranges) 

3181 

3182 self.apply_scaling_hooks(data_ranges) 

3183 

3184 trace_to_itrack = {} 

3185 track_scaling_keys = {} 

3186 track_scaling_colors = {} 

3187 for trace in processed_traces: 

3188 gt = self.gather(trace) 

3189 if gt not in self.key_to_row: 

3190 continue 

3191 

3192 itrack = self.key_to_row[gt] 

3193 if itrack not in track_projections: 

3194 continue 

3195 

3196 trace_to_itrack[trace] = itrack 

3197 

3198 if itrack not in self.track_to_nslc_ids: 

3199 self.track_to_nslc_ids[itrack] = set() 

3200 

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

3202 

3203 if itrack not in track_scaling_keys: 

3204 track_scaling_keys[itrack] = set() 

3205 

3206 scaling_key = self.scaling_key(trace) 

3207 track_scaling_keys[itrack].add(scaling_key) 

3208 

3209 color = pyrocko.plot.color( 

3210 color_lookup[self.color_gather(trace)]) 

3211 

3212 k = itrack, scaling_key 

3213 if k not in track_scaling_colors \ 

3214 and self.menuitem_colortraces.isChecked(): 

3215 track_scaling_colors[k] = color 

3216 else: 

3217 track_scaling_colors[k] = primary_color 

3218 

3219 # y axes, zero lines 

3220 trace_projections = {} 

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

3222 if itrack not in track_scaling_keys: 

3223 continue 

3224 uoff = 0 

3225 for scaling_key in track_scaling_keys[itrack]: 

3226 data_range = data_ranges[scaling_key] 

3227 dymin, dymax = data_range 

3228 ymin, ymax, yinc = yscaler.make_scale( 

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

3230 iexp = yscaler.make_exp(yinc) 

3231 factor = 10**iexp 

3232 trace_projection = track_projections[itrack].copy() 

3233 trace_projection.set_in_range(ymax, ymin) 

3234 trace_projections[itrack, scaling_key] = \ 

3235 trace_projection 

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

3237 vmin, vmax = trace_projection.get_out_range() 

3238 umax_zeroline = umax 

3239 uoffnext = uoff 

3240 

3241 if show_scales: 

3242 pen = qg.QPen(primary_pen) 

3243 k = itrack, scaling_key 

3244 if k in track_scaling_colors: 

3245 c = qg.QColor(*track_scaling_colors[ 

3246 itrack, scaling_key]) 

3247 

3248 pen.setColor(c) 

3249 

3250 p.setPen(pen) 

3251 if nlinesavail > 3: 

3252 if self.menuitem_showscaleaxis.isChecked(): 

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

3254 ny_annot = int( 

3255 math.floor(ymax/yinc) 

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

3257 

3258 for iy_annot in range(ny_annot): 

3259 y = ymin_annot + iy_annot*yinc 

3260 v = trace_projection(y) 

3261 line = qc.QLineF( 

3262 umax-10-uoff, v, umax-uoff, v) 

3263 

3264 p.drawLine(line) 

3265 if iy_annot == ny_annot - 1 \ 

3266 and iexp != 0: 

3267 sexp = ' &times; ' \ 

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

3269 else: 

3270 sexp = '' 

3271 

3272 snum = num_to_html(y/factor) 

3273 lab = Label( 

3274 p, 

3275 umax-20-uoff, 

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

3277 label_bg=None, 

3278 anchor='MR', 

3279 font=axannotfont, 

3280 color=c) 

3281 

3282 uoffnext = max( 

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

3284 

3285 annot_labels.append(lab) 

3286 if y == 0.: 

3287 umax_zeroline = \ 

3288 umax - 20 \ 

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

3290 - uoff 

3291 else: 

3292 if not show_boxes: 

3293 qpoints = make_QPolygonF( 

3294 [umax-20-uoff, 

3295 umax-10-uoff, 

3296 umax-10-uoff, 

3297 umax-20-uoff], 

3298 [vmax, vmax, vmin, vmin]) 

3299 p.drawPolyline(qpoints) 

3300 

3301 snum = num_to_html(ymin) 

3302 labmin = Label( 

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

3304 label_bg=None, 

3305 anchor='BR', 

3306 font=axannotfont, 

3307 color=c) 

3308 

3309 annot_labels.append(labmin) 

3310 snum = num_to_html(ymax) 

3311 labmax = Label( 

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

3313 label_bg=None, 

3314 anchor='TR', 

3315 font=axannotfont, 

3316 color=c) 

3317 

3318 annot_labels.append(labmax) 

3319 

3320 for lab in (labmin, labmax): 

3321 uoffnext = max( 

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

3323 

3324 if self.menuitem_showzeroline.isChecked(): 

3325 v = trace_projection(0.) 

3326 if vmin <= v <= vmax: 

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

3328 p.drawLine(line) 

3329 

3330 uoff = uoffnext 

3331 

3332 p.setFont(font) 

3333 p.setPen(primary_pen) 

3334 for trace in processed_traces: 

3335 if self.view_mode is not ViewMode.Wiggle: 

3336 break 

3337 

3338 if trace not in trace_to_itrack: 

3339 continue 

3340 

3341 itrack = trace_to_itrack[trace] 

3342 scaling_key = self.scaling_key(trace) 

3343 trace_projection = trace_projections[ 

3344 itrack, scaling_key] 

3345 

3346 vdata = trace_projection(trace.get_ydata()) 

3347 

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

3349 udata_max = float(self.time_projection( 

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

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

3352 

3353 qpoints = make_QPolygonF(udata, vdata) 

3354 

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

3356 vmin, vmax = trace_projection.get_out_range() 

3357 

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

3359 

3360 if self.menuitem_cliptraces.isChecked(): 

3361 p.setClipRect(trackrect) 

3362 

3363 if self.menuitem_colortraces.isChecked(): 

3364 color = pyrocko.plot.color( 

3365 color_lookup[self.color_gather(trace)]) 

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

3367 p.setPen(pen) 

3368 

3369 p.drawPolyline(qpoints) 

3370 

3371 if self.floating_marker: 

3372 self.floating_marker.draw_trace( 

3373 self, p, trace, 

3374 self.time_projection, trace_projection, 1.0) 

3375 

3376 for marker in self.markers.with_key_in( 

3377 self.tmin - self.markers_deltat_max, 

3378 self.tmax): 

3379 

3380 if marker.tmin < self.tmax \ 

3381 and self.tmin < marker.tmax \ 

3382 and marker.kind \ 

3383 in self.visible_marker_kinds: 

3384 marker.draw_trace( 

3385 self, p, trace, self.time_projection, 

3386 trace_projection, 1.0) 

3387 

3388 p.setPen(primary_pen) 

3389 

3390 if self.menuitem_cliptraces.isChecked(): 

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

3392 

3393 if self.floating_marker: 

3394 self.floating_marker.draw( 

3395 p, self.time_projection, vcenter_projection) 

3396 

3397 self.draw_visible_markers( 

3398 p, vcenter_projection, primary_pen) 

3399 

3400 p.setPen(primary_pen) 

3401 while font.pointSize() > 2: 

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

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

3404 - self.track_to_screen(0.05) 

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

3406 if nlinesavail > 1: 

3407 break 

3408 

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

3410 

3411 p.setFont(font) 

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

3413 

3414 for key in self.track_keys: 

3415 itrack = self.key_to_row[key] 

3416 if itrack in track_projections: 

3417 plabel = ' '.join( 

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

3419 lx = 10 

3420 ly = self.track_to_screen(itrack+0.5) 

3421 

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

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

3424 continue 

3425 

3426 contains_cursor = \ 

3427 self.track_to_screen(itrack) \ 

3428 < mouse_pos.y() \ 

3429 < self.track_to_screen(itrack+1) 

3430 

3431 if not contains_cursor: 

3432 continue 

3433 

3434 font_large = p.font() 

3435 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3436 p.setFont(font_large) 

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

3438 p.setFont(font) 

3439 

3440 for lab in annot_labels: 

3441 lab.draw() 

3442 

3443 self.timer_draw.stop() 

3444 

3445 def see_data_params(self): 

3446 

3447 min_deltat = self.content_deltat_range()[0] 

3448 

3449 # determine padding and downampling requirements 

3450 if self.lowpass is not None: 

3451 deltat_target = 1./self.lowpass * 0.25 

3452 ndecimate = min( 

3453 50, 

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

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

3456 else: 

3457 ndecimate = 1 

3458 tpad = min_deltat*5. 

3459 

3460 if self.highpass is not None: 

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

3462 

3463 nsee_points_per_trace = 5000*10 

3464 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3465 

3466 return ndecimate, tpad, tsee 

3467 

3468 def clean_update(self): 

3469 self.cached_processed_traces = None 

3470 self.update() 

3471 

3472 def get_adequate_tpad(self): 

3473 tpad = 0. 

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

3475 if f is not None: 

3476 tpad = max(tpad, 1.0/f) 

3477 

3478 for snuffling in self.snufflings: 

3479 if snuffling._post_process_hook_enabled \ 

3480 or snuffling._pre_process_hook_enabled: 

3481 

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

3483 

3484 return tpad 

3485 

3486 def prepare_cutout2( 

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

3488 demean=True, nmax=6000): 

3489 

3490 if self.pile.is_empty(): 

3491 return [] 

3492 

3493 nmax = self.visible_length 

3494 

3495 self.timer_cutout.start() 

3496 

3497 tsee = tmax-tmin 

3498 min_deltat_wo_decimate = tsee/nmax 

3499 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3500 

3501 min_deltat_allow = min_deltat_wo_decimate 

3502 if self.lowpass is not None: 

3503 target_deltat_lp = 0.25/self.lowpass 

3504 if target_deltat_lp > min_deltat_wo_decimate: 

3505 min_deltat_allow = min_deltat_w_decimate 

3506 

3507 min_deltat_allow = math.exp( 

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

3509 

3510 tmin_ = tmin 

3511 tmax_ = tmax 

3512 

3513 # fetch more than needed? 

3514 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3518 

3519 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3520 lphp = self.menuitem_lphp.isChecked() 

3521 ads = self.menuitem_allowdownsampling.isChecked() 

3522 

3523 tpad = self.get_adequate_tpad() 

3524 tpad = max(tpad, tsee) 

3525 

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

3527 vec = ( 

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

3529 self.highpass, fft_filtering, lphp, 

3530 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3531 ads, self.pile.get_update_count()) 

3532 

3533 if (self.cached_vec 

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

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

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

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

3538 and self.cached_processed_traces is not None): 

3539 

3540 logger.debug('Using cached traces') 

3541 processed_traces = self.cached_processed_traces 

3542 

3543 else: 

3544 processed_traces = [] 

3545 if self.pile.deltatmax >= min_deltat_allow: 

3546 

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

3548 def group_selector(gr): 

3549 return gr.deltatmax >= min_deltat_allow 

3550 

3551 kwargs = dict(group_selector=group_selector) 

3552 else: 

3553 kwargs = {} 

3554 

3555 if trace_selector is not None: 

3556 def trace_selectorx(tr): 

3557 return tr.deltat >= min_deltat_allow \ 

3558 and trace_selector(tr) 

3559 else: 

3560 def trace_selectorx(tr): 

3561 return tr.deltat >= min_deltat_allow 

3562 

3563 for traces in self.pile.chopper( 

3564 tmin=tmin, tmax=tmax, tpad=tpad, 

3565 want_incomplete=True, 

3566 degap=degap, 

3567 maxgap=gap_lap_tolerance, 

3568 maxlap=gap_lap_tolerance, 

3569 keep_current_files_open=True, 

3570 trace_selector=trace_selectorx, 

3571 accessor_id=id(self), 

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

3573 include_last=True, **kwargs): 

3574 

3575 if demean: 

3576 for tr in traces: 

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

3578 continue 

3579 y = tr.get_ydata() 

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

3581 

3582 traces = self.pre_process_hooks(traces) 

3583 

3584 for trace in traces: 

3585 

3586 if not (trace.meta 

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

3588 

3589 if fft_filtering: 

3590 but = pyrocko.response.ButterworthResponse 

3591 multres = pyrocko.response.MultiplyResponse 

3592 if self.lowpass is not None \ 

3593 or self.highpass is not None: 

3594 

3595 it = num.arange( 

3596 trace.data_len(), dtype=float) 

3597 detr_data, m, b = detrend( 

3598 it, trace.get_ydata()) 

3599 

3600 trace.set_ydata(detr_data) 

3601 

3602 freqs, fdata = trace.spectrum( 

3603 pad_to_pow2=True, tfade=None) 

3604 

3605 nfreqs = fdata.size 

3606 

3607 key = (trace.deltat, nfreqs) 

3608 

3609 if key not in self.tf_cache: 

3610 resps = [] 

3611 if self.lowpass is not None: 

3612 resps.append(but( 

3613 order=4, 

3614 corner=self.lowpass, 

3615 type='low')) 

3616 

3617 if self.highpass is not None: 

3618 resps.append(but( 

3619 order=4, 

3620 corner=self.highpass, 

3621 type='high')) 

3622 

3623 resp = multres(resps) 

3624 self.tf_cache[key] = \ 

3625 resp.evaluate(freqs) 

3626 

3627 filtered_data = num.fft.irfft( 

3628 fdata*self.tf_cache[key] 

3629 )[:trace.data_len()] 

3630 

3631 retrended_data = retrend( 

3632 it, filtered_data, m, b) 

3633 

3634 trace.set_ydata(retrended_data) 

3635 

3636 else: 

3637 

3638 if ads and self.lowpass is not None: 

3639 while trace.deltat \ 

3640 < min_deltat_wo_decimate: 

3641 

3642 trace.downsample(2, demean=False) 

3643 

3644 fmax = 0.5/trace.deltat 

3645 if not lphp and ( 

3646 self.lowpass is not None 

3647 and self.highpass is not None 

3648 and self.lowpass < fmax 

3649 and self.highpass < fmax 

3650 and self.highpass < self.lowpass): 

3651 

3652 trace.bandpass( 

3653 2, self.highpass, self.lowpass) 

3654 else: 

3655 if self.lowpass is not None: 

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

3657 trace.lowpass( 

3658 4, self.lowpass, 

3659 demean=False) 

3660 

3661 if self.highpass is not None: 

3662 if self.lowpass is None \ 

3663 or self.highpass \ 

3664 < self.lowpass: 

3665 

3666 if self.highpass < \ 

3667 0.5/trace.deltat: 

3668 trace.highpass( 

3669 4, self.highpass, 

3670 demean=False) 

3671 

3672 processed_traces.append(trace) 

3673 

3674 if self.rotate != 0.0: 

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

3676 cphi = math.cos(phi) 

3677 sphi = math.sin(phi) 

3678 for a in processed_traces: 

3679 for b in processed_traces: 

3680 if (a.network == b.network 

3681 and a.station == b.station 

3682 and a.location == b.location 

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

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

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

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

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

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

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

3690 

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

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

3693 a.set_ydata(aydata) 

3694 b.set_ydata(bydata) 

3695 

3696 processed_traces = self.post_process_hooks(processed_traces) 

3697 

3698 self.cached_processed_traces = processed_traces 

3699 self.cached_vec = vec 

3700 

3701 chopped_traces = [] 

3702 for trace in processed_traces: 

3703 chop_tmin = tmin_ - trace.deltat*4 

3704 chop_tmax = tmax_ + trace.deltat*4 

3705 

3706 try: 

3707 ctrace = trace.chop( 

3708 chop_tmin, chop_tmax, 

3709 inplace=False) 

3710 

3711 except pyrocko.trace.NoData: 

3712 continue 

3713 

3714 if ctrace.data_len() < 2: 

3715 continue 

3716 

3717 chopped_traces.append(ctrace) 

3718 

3719 self.timer_cutout.stop() 

3720 return chopped_traces 

3721 

3722 def pre_process_hooks(self, traces): 

3723 for snuffling in self.snufflings: 

3724 if snuffling._pre_process_hook_enabled: 

3725 traces = snuffling.pre_process_hook(traces) 

3726 

3727 return traces 

3728 

3729 def post_process_hooks(self, traces): 

3730 for snuffling in self.snufflings: 

3731 if snuffling._post_process_hook_enabled: 

3732 traces = snuffling.post_process_hook(traces) 

3733 

3734 return traces 

3735 

3736 def visible_length_change(self, ignore=None): 

3737 for menuitem, vlen in self.menuitems_visible_length: 

3738 if menuitem.isChecked(): 

3739 self.visible_length = vlen 

3740 

3741 def scaling_base_change(self, ignore=None): 

3742 for menuitem, scaling_base in self.menuitems_scaling_base: 

3743 if menuitem.isChecked(): 

3744 self.scaling_base = scaling_base 

3745 

3746 def scalingmode_change(self, ignore=None): 

3747 for menuitem, scaling_key in self.menuitems_scaling: 

3748 if menuitem.isChecked(): 

3749 self.scaling_key = scaling_key 

3750 self.update() 

3751 

3752 def apply_scaling_hooks(self, data_ranges): 

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

3754 hook = self.scaling_hooks[k] 

3755 hook(data_ranges) 

3756 

3757 def viewmode_change(self, ignore=True): 

3758 for item, mode in self.menuitems_viewmode: 

3759 if item.isChecked(): 

3760 self.view_mode = mode 

3761 break 

3762 else: 

3763 raise AttributeError('unknown view mode') 

3764 

3765 items_waterfall_disabled = ( 

3766 self.menuitem_showscaleaxis, 

3767 self.menuitem_showscalerange, 

3768 self.menuitem_showzeroline, 

3769 self.menuitem_colortraces, 

3770 self.menuitem_cliptraces, 

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

3772 ) 

3773 

3774 if self.view_mode is ViewMode.Waterfall: 

3775 self.parent().show_colorbar_ctrl(True) 

3776 self.parent().show_gain_ctrl(False) 

3777 

3778 for item in items_waterfall_disabled: 

3779 item.setDisabled(True) 

3780 

3781 self.visible_length = 180. 

3782 else: 

3783 self.parent().show_colorbar_ctrl(False) 

3784 self.parent().show_gain_ctrl(True) 

3785 

3786 for item in items_waterfall_disabled: 

3787 item.setDisabled(False) 

3788 

3789 self.visible_length_change() 

3790 self.update() 

3791 

3792 def set_scaling_hook(self, k, hook): 

3793 self.scaling_hooks[k] = hook 

3794 

3795 def remove_scaling_hook(self, k): 

3796 del self.scaling_hooks[k] 

3797 

3798 def remove_scaling_hooks(self): 

3799 self.scaling_hooks = {} 

3800 

3801 def s_sortingmode_change(self, ignore=None): 

3802 for menuitem, valfunc in self.menuitems_ssorting: 

3803 if menuitem.isChecked(): 

3804 self._ssort = valfunc 

3805 

3806 self.sortingmode_change() 

3807 

3808 def sortingmode_change(self, ignore=None): 

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

3810 if menuitem.isChecked(): 

3811 self.set_gathering(gather, color) 

3812 

3813 self.sortingmode_change_time = time.time() 

3814 

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

3816 self.lowpass = value 

3817 self.passband_check() 

3818 self.tf_cache = {} 

3819 self.update() 

3820 

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

3822 self.highpass = value 

3823 self.passband_check() 

3824 self.tf_cache = {} 

3825 self.update() 

3826 

3827 def passband_check(self): 

3828 if self.highpass and self.lowpass \ 

3829 and self.highpass >= self.lowpass: 

3830 

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

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

3833 'deactivate the highpass.' 

3834 

3835 self.update_status() 

3836 else: 

3837 oldmess = self.message 

3838 self.message = None 

3839 if oldmess is not None: 

3840 self.update_status() 

3841 

3842 def gain_change(self, value, ignore): 

3843 self.gain = value 

3844 self.update() 

3845 

3846 def rot_change(self, value, ignore): 

3847 self.rotate = value 

3848 self.update() 

3849 

3850 def waterfall_cmap_change(self, cmap): 

3851 self.waterfall_cmap = cmap 

3852 self.update() 

3853 

3854 def waterfall_clip_change(self, clip_min, clip_max): 

3855 self.waterfall_clip_min = clip_min 

3856 self.waterfall_clip_max = clip_max 

3857 self.update() 

3858 

3859 def waterfall_show_absolute_change(self, toggle): 

3860 self.waterfall_show_absolute = toggle 

3861 self.update() 

3862 

3863 def waterfall_set_integrate(self, toggle): 

3864 self.waterfall_integrate = toggle 

3865 self.update() 

3866 

3867 def set_selected_markers(self, markers): 

3868 ''' 

3869 Set a list of markers selected 

3870 

3871 :param markers: list of markers 

3872 ''' 

3873 self.deselect_all() 

3874 for m in markers: 

3875 m.selected = True 

3876 

3877 self.update() 

3878 

3879 def deselect_all(self): 

3880 for marker in self.markers: 

3881 marker.selected = False 

3882 

3883 def animate_picking(self): 

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

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

3886 

3887 def get_nslc_ids_for_track(self, ftrack): 

3888 itrack = int(ftrack) 

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

3890 

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

3892 if self.picking: 

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

3894 self.picking = None 

3895 self.picking_down = None 

3896 self.picking_timer.stop() 

3897 self.picking_timer = None 

3898 if not abort: 

3899 self.add_marker(self.floating_marker) 

3900 self.floating_marker.selected = True 

3901 self.emit_selected_markers() 

3902 

3903 self.floating_marker = None 

3904 

3905 def start_picking(self, ignore): 

3906 

3907 if not self.picking: 

3908 self.deselect_all() 

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

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

3911 

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

3913 self.picking.setGeometry( 

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

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

3916 

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

3918 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3920 self.floating_marker.selected = True 

3921 

3922 self.picking_timer = qc.QTimer() 

3923 self.picking_timer.timeout.connect( 

3924 self.animate_picking) 

3925 

3926 self.picking_timer.setInterval(50) 

3927 self.picking_timer.start() 

3928 

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

3930 if self.picking: 

3931 mouset = self.time_projection.rev(x) 

3932 dt = 0.0 

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

3934 if mouset < self.tmin: 

3935 dt = -(self.tmin - mouset) 

3936 else: 

3937 dt = mouset - self.tmax 

3938 ddt = self.tmax-self.tmin 

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

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

3941 

3942 x0 = x 

3943 if self.picking_down is not None: 

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

3945 

3946 w = abs(x-x0) 

3947 x0 = min(x0, x) 

3948 

3949 tmin, tmax = ( 

3950 self.time_projection.rev(x0), 

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

3952 

3953 tmin, tmax = ( 

3954 max(working_system_time_range[0], tmin), 

3955 min(working_system_time_range[1], tmax)) 

3956 

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

3958 

3959 self.picking.setGeometry( 

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

3961 

3962 ftrack = self.track_to_screen.rev(y) 

3963 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3965 

3966 if dt != 0.0 and doshift: 

3967 self.interrupt_following() 

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

3969 

3970 self.update() 

3971 

3972 def update_status(self): 

3973 

3974 if self.message is None: 

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

3976 

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

3978 if not is_working_time(mouse_t): 

3979 return 

3980 

3981 if self.floating_marker: 

3982 tmi, tma = ( 

3983 self.floating_marker.tmin, 

3984 self.floating_marker.tmax) 

3985 

3986 tt, ms = gmtime_x(tmi) 

3987 

3988 if tmi == tma: 

3989 message = mystrftime( 

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

3991 tt=tt, milliseconds=ms) 

3992 else: 

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

3994 message = mystrftime( 

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

3996 tt=tt, milliseconds=ms) 

3997 else: 

3998 tt, ms = gmtime_x(mouse_t) 

3999 

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

4001 else: 

4002 message = self.message 

4003 

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

4005 sb.clearMessage() 

4006 sb.showMessage(message) 

4007 

4008 def set_sortingmode_change_delay_time(self, dt): 

4009 self.sortingmode_change_delay_time = dt 

4010 

4011 def sortingmode_change_delayed(self): 

4012 now = time.time() 

4013 return ( 

4014 self.sortingmode_change_delay_time is not None 

4015 and now - self.sortingmode_change_time 

4016 < self.sortingmode_change_delay_time) 

4017 

4018 def set_visible_marker_kinds(self, kinds): 

4019 self.deselect_all() 

4020 self.visible_marker_kinds = tuple(kinds) 

4021 self.emit_selected_markers() 

4022 

4023 def following(self): 

4024 return self.follow_timer is not None \ 

4025 and not self.following_interrupted() 

4026 

4027 def interrupt_following(self): 

4028 self.interactive_range_change_time = time.time() 

4029 

4030 def following_interrupted(self, now=None): 

4031 if now is None: 

4032 now = time.time() 

4033 return now - self.interactive_range_change_time \ 

4034 < self.interactive_range_change_delay_time 

4035 

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

4037 if tmax_start is None: 

4038 tmax_start = time.time() 

4039 self.show_all = False 

4040 self.follow_time = tlen 

4041 self.follow_timer = qc.QTimer(self) 

4042 self.follow_timer.timeout.connect( 

4043 self.follow_update) 

4044 self.follow_timer.setInterval(interval) 

4045 self.follow_timer.start() 

4046 self.follow_started = time.time() 

4047 self.follow_lapse = lapse 

4048 self.follow_tshift = self.follow_started - tmax_start 

4049 self.interactive_range_change_time = 0.0 

4050 

4051 def unfollow(self): 

4052 if self.follow_timer is not None: 

4053 self.follow_timer.stop() 

4054 self.follow_timer = None 

4055 self.interactive_range_change_time = 0.0 

4056 

4057 def follow_update(self): 

4058 rnow = time.time() 

4059 if self.follow_lapse is None: 

4060 now = rnow 

4061 else: 

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

4063 * self.follow_lapse 

4064 

4065 if self.following_interrupted(rnow): 

4066 return 

4067 self.set_time_range( 

4068 now-self.follow_time-self.follow_tshift, 

4069 now-self.follow_tshift) 

4070 

4071 self.update() 

4072 

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

4074 self.return_tag = return_tag 

4075 self.window().close() 

4076 

4077 def cleanup(self): 

4078 self.about_to_close.emit() 

4079 self.timer.stop() 

4080 if self.follow_timer is not None: 

4081 self.follow_timer.stop() 

4082 

4083 for snuffling in list(self.snufflings): 

4084 self.remove_snuffling(snuffling) 

4085 

4086 def set_error_message(self, key, value): 

4087 if value is None: 

4088 if key in self.error_messages: 

4089 del self.error_messages[key] 

4090 else: 

4091 self.error_messages[key] = value 

4092 

4093 def inputline_changed(self, text): 

4094 pass 

4095 

4096 def inputline_finished(self, text): 

4097 line = str(text) 

4098 

4099 toks = line.split() 

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

4101 if len(toks) >= 1: 

4102 command = toks[0].lower() 

4103 

4104 try: 

4105 quick_filter_commands = { 

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

4107 's': '*.%s.*.*', 

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

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

4110 

4111 if command in quick_filter_commands: 

4112 if len(toks) >= 2: 

4113 patterns = [ 

4114 quick_filter_commands[toks[0]] % pat 

4115 for pat in toks[1:]] 

4116 self.set_quick_filter_patterns(patterns, line) 

4117 else: 

4118 self.set_quick_filter_patterns(None) 

4119 

4120 self.update() 

4121 

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

4123 if len(toks) >= 2: 

4124 patterns = [] 

4125 if len(toks) == 2: 

4126 patterns = [toks[1]] 

4127 elif len(toks) >= 3: 

4128 x = { 

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

4130 's': '*.%s.*.*', 

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

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

4133 

4134 if toks[1] in x: 

4135 patterns.extend( 

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

4137 

4138 for pattern in patterns: 

4139 if command == 'hide': 

4140 self.add_blacklist_pattern(pattern) 

4141 else: 

4142 self.remove_blacklist_pattern(pattern) 

4143 

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

4145 self.clear_blacklist() 

4146 

4147 clearit = True 

4148 

4149 self.update() 

4150 

4151 elif command == 'markers': 

4152 if len(toks) == 2: 

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

4154 kinds = self.all_marker_kinds 

4155 else: 

4156 kinds = [] 

4157 for x in toks[1]: 

4158 try: 

4159 kinds.append(int(x)) 

4160 except Exception: 

4161 pass 

4162 

4163 self.set_visible_marker_kinds(kinds) 

4164 

4165 elif len(toks) == 1: 

4166 self.set_visible_marker_kinds(()) 

4167 

4168 self.update() 

4169 

4170 elif command == 'scaling': 

4171 if len(toks) == 2: 

4172 hideit = False 

4173 error = 'wrong number of arguments' 

4174 

4175 if len(toks) >= 3: 

4176 vmin, vmax = [ 

4177 pyrocko.model.float_or_none(x) 

4178 for x in toks[-2:]] 

4179 

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

4181 if k in d: 

4182 if vmin is not None: 

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

4184 if vmax is not None: 

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

4186 

4187 if len(toks) == 1: 

4188 self.remove_scaling_hooks() 

4189 

4190 elif len(toks) == 3: 

4191 def hook(data_ranges): 

4192 for k in data_ranges: 

4193 upd(data_ranges, k, vmin, vmax) 

4194 

4195 self.set_scaling_hook('_', hook) 

4196 

4197 elif len(toks) == 4: 

4198 pattern = toks[1] 

4199 

4200 def hook(data_ranges): 

4201 for k in pyrocko.util.match_nslcs( 

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

4203 

4204 upd(data_ranges, k, vmin, vmax) 

4205 

4206 self.set_scaling_hook(pattern, hook) 

4207 

4208 elif command == 'goto': 

4209 toks2 = line.split(None, 1) 

4210 if len(toks2) == 2: 

4211 arg = toks2[1] 

4212 m = re.match( 

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

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

4215 if m: 

4216 tlen = None 

4217 if not m.group(1): 

4218 tlen = 12*32*24*60*60 

4219 elif not m.group(2): 

4220 tlen = 32*24*60*60 

4221 elif not m.group(3): 

4222 tlen = 24*60*60 

4223 elif not m.group(4): 

4224 tlen = 60*60 

4225 elif not m.group(5): 

4226 tlen = 60 

4227 

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

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

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

4231 t = pyrocko.util.str_to_time(arg) 

4232 self.go_to_time(t, tlen=tlen) 

4233 

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

4235 supl = '00:00:00' 

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

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

4238 tmin, tmax = self.get_time_range() 

4239 sdate = pyrocko.util.time_to_str( 

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

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

4242 self.go_to_time(t) 

4243 

4244 elif arg == 'today': 

4245 self.go_to_time( 

4246 day_start( 

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

4248 

4249 elif arg == 'yesterday': 

4250 self.go_to_time( 

4251 day_start( 

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

4253 

4254 else: 

4255 self.go_to_event_by_name(arg) 

4256 

4257 else: 

4258 raise PileViewerMainException( 

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

4260 

4261 except PileViewerMainException as e: 

4262 error = str(e) 

4263 hideit = False 

4264 

4265 return clearit, hideit, error 

4266 

4267 return PileViewerMain 

4268 

4269 

4270PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4271GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget) 

4272 

4273 

4274class LineEditWithAbort(qw.QLineEdit): 

4275 

4276 aborted = qc.pyqtSignal() 

4277 history_down = qc.pyqtSignal() 

4278 history_up = qc.pyqtSignal() 

4279 

4280 def keyPressEvent(self, key_event): 

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

4282 self.aborted.emit() 

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

4284 self.history_down.emit() 

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

4286 self.history_up.emit() 

4287 else: 

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

4289 

4290 

4291class PileViewer(qw.QFrame): 

4292 ''' 

4293 PileViewerMain + Controls + Inputline 

4294 ''' 

4295 

4296 def __init__( 

4297 self, pile, 

4298 ntracks_shown_max=20, 

4299 marker_editor_sortable=True, 

4300 use_opengl=None, 

4301 panel_parent=None, 

4302 *args): 

4303 

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

4305 

4306 layout = qw.QGridLayout() 

4307 layout.setContentsMargins(0, 0, 0, 0) 

4308 layout.setSpacing(0) 

4309 

4310 self.menu = PileViewerMenuBar(self) 

4311 

4312 if use_opengl is None: 

4313 use_opengl = is_macos 

4314 

4315 if use_opengl: 

4316 self.viewer = GLPileViewerMain( 

4317 pile, 

4318 ntracks_shown_max=ntracks_shown_max, 

4319 panel_parent=panel_parent, 

4320 menu=self.menu) 

4321 else: 

4322 self.viewer = PileViewerMain( 

4323 pile, 

4324 ntracks_shown_max=ntracks_shown_max, 

4325 panel_parent=panel_parent, 

4326 menu=self.menu) 

4327 

4328 self.marker_editor_sortable = marker_editor_sortable 

4329 

4330 self.setFrameShape(qw.QFrame.StyledPanel) 

4331 self.setFrameShadow(qw.QFrame.Sunken) 

4332 

4333 self.input_area = qw.QFrame(self) 

4334 ia_layout = qw.QGridLayout() 

4335 ia_layout.setContentsMargins(11, 11, 11, 11) 

4336 self.input_area.setLayout(ia_layout) 

4337 

4338 self.inputline = LineEditWithAbort(self.input_area) 

4339 self.inputline.returnPressed.connect( 

4340 self.inputline_returnpressed) 

4341 self.inputline.editingFinished.connect( 

4342 self.inputline_finished) 

4343 self.inputline.aborted.connect( 

4344 self.inputline_aborted) 

4345 

4346 self.inputline.history_down.connect( 

4347 lambda: self.step_through_history(1)) 

4348 self.inputline.history_up.connect( 

4349 lambda: self.step_through_history(-1)) 

4350 

4351 self.inputline.textEdited.connect( 

4352 self.inputline_changed) 

4353 

4354 self.inputline.setPlaceholderText( 

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

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

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

4358 self.input_area.hide() 

4359 self.history = None 

4360 

4361 self.inputline_error_str = None 

4362 

4363 self.inputline_error = qw.QLabel() 

4364 self.inputline_error.hide() 

4365 

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

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

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

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

4370 

4371 pb = Progressbars(self) 

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

4373 self.progressbars = pb 

4374 

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

4376 self.scrollbar = scrollbar 

4377 layout.addWidget(scrollbar, 1, 1) 

4378 self.scrollbar.valueChanged.connect( 

4379 self.scrollbar_changed) 

4380 

4381 self.block_scrollbar_changes = False 

4382 

4383 self.viewer.want_input.connect( 

4384 self.inputline_show) 

4385 self.viewer.tracks_range_changed.connect( 

4386 self.tracks_range_changed) 

4387 self.viewer.pile_has_changed_signal.connect( 

4388 self.adjust_controls) 

4389 self.viewer.about_to_close.connect( 

4390 self.save_inputline_history) 

4391 

4392 self.setLayout(layout) 

4393 

4394 def cleanup(self): 

4395 self.viewer.cleanup() 

4396 

4397 def get_progressbars(self): 

4398 return self.progressbars 

4399 

4400 def inputline_show(self): 

4401 if not self.history: 

4402 self.load_inputline_history() 

4403 

4404 self.input_area.show() 

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

4406 self.inputline.selectAll() 

4407 

4408 def inputline_set_error(self, string): 

4409 self.inputline_error_str = string 

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

4411 self.inputline.selectAll() 

4412 self.inputline_error.setText(string) 

4413 self.input_area.show() 

4414 self.inputline_error.show() 

4415 

4416 def inputline_clear_error(self): 

4417 if self.inputline_error_str: 

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

4419 self.inputline_error_str = None 

4420 self.inputline_error.clear() 

4421 self.inputline_error.hide() 

4422 

4423 def inputline_changed(self, line): 

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

4425 self.inputline_clear_error() 

4426 

4427 def inputline_returnpressed(self): 

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

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

4430 

4431 if error: 

4432 self.inputline_set_error(error) 

4433 

4434 line = line.strip() 

4435 

4436 if line != '' and not error: 

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

4438 self.history.append(line) 

4439 

4440 if clearit: 

4441 

4442 self.inputline.blockSignals(True) 

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

4444 if qpat is None: 

4445 self.inputline.clear() 

4446 else: 

4447 self.inputline.setText(qinp) 

4448 self.inputline.blockSignals(False) 

4449 

4450 if hideit and not error: 

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

4452 self.input_area.hide() 

4453 

4454 self.hist_ind = len(self.history) 

4455 

4456 def inputline_aborted(self): 

4457 ''' 

4458 Hide the input line. 

4459 ''' 

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

4461 self.hist_ind = len(self.history) 

4462 self.input_area.hide() 

4463 

4464 def save_inputline_history(self): 

4465 ''' 

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

4467 ''' 

4468 if not self.history: 

4469 return 

4470 

4471 conf = pyrocko.config 

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

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

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

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

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

4477 

4478 def load_inputline_history(self): 

4479 ''' 

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

4481 ''' 

4482 conf = pyrocko.config 

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

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

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

4486 f.write('\n') 

4487 

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

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

4490 

4491 self.hist_ind = len(self.history) 

4492 

4493 def step_through_history(self, ud=1): 

4494 ''' 

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

4496 ''' 

4497 n = len(self.history) 

4498 self.hist_ind += ud 

4499 self.hist_ind %= (n + 1) 

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

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

4502 else: 

4503 self.inputline.setText('') 

4504 

4505 def inputline_finished(self): 

4506 pass 

4507 

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

4509 if self.block_scrollbar_changes: 

4510 return 

4511 

4512 self.scrollbar.blockSignals(True) 

4513 self.scrollbar.setPageStep(ihi-ilo) 

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

4515 self.scrollbar.setRange(0, vmax) 

4516 self.scrollbar.setValue(ilo) 

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

4518 self.scrollbar.blockSignals(False) 

4519 

4520 def scrollbar_changed(self, value): 

4521 self.block_scrollbar_changes = True 

4522 ilo = value 

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

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

4525 self.block_scrollbar_changes = False 

4526 self.update_contents() 

4527 

4528 def controls(self): 

4529 frame = qw.QFrame(self) 

4530 layout = qw.QGridLayout() 

4531 frame.setLayout(layout) 

4532 

4533 minfreq = 0.001 

4534 maxfreq = 1000.0 

4535 self.lowpass_control = ValControl(high_is_none=True) 

4536 self.lowpass_control.setup( 

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

4538 self.highpass_control = ValControl(low_is_none=True) 

4539 self.highpass_control.setup( 

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

4541 self.gain_control = ValControl() 

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

4543 self.rot_control = LinValControl() 

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

4545 self.colorbar_control = ColorbarControl(self) 

4546 

4547 self.lowpass_control.valchange.connect( 

4548 self.viewer.lowpass_change) 

4549 self.highpass_control.valchange.connect( 

4550 self.viewer.highpass_change) 

4551 self.gain_control.valchange.connect( 

4552 self.viewer.gain_change) 

4553 self.rot_control.valchange.connect( 

4554 self.viewer.rot_change) 

4555 self.colorbar_control.cmap_changed.connect( 

4556 self.viewer.waterfall_cmap_change 

4557 ) 

4558 self.colorbar_control.clip_changed.connect( 

4559 self.viewer.waterfall_clip_change 

4560 ) 

4561 self.colorbar_control.show_absolute_toggled.connect( 

4562 self.viewer.waterfall_show_absolute_change 

4563 ) 

4564 self.colorbar_control.show_integrate_toggled.connect( 

4565 self.viewer.waterfall_set_integrate 

4566 ) 

4567 

4568 for icontrol, control in enumerate(( 

4569 self.highpass_control, 

4570 self.lowpass_control, 

4571 self.gain_control, 

4572 self.rot_control, 

4573 self.colorbar_control)): 

4574 

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

4576 layout.addWidget(widget, icontrol, iwidget) 

4577 

4578 spacer = qw.QSpacerItem( 

4579 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4581 

4582 self.adjust_controls() 

4583 self.viewer.viewmode_change(ViewMode.Wiggle) 

4584 return frame 

4585 

4586 def marker_editor(self): 

4587 editor = pyrocko.gui.marker_editor.MarkerEditor( 

4588 self, sortable=self.marker_editor_sortable) 

4589 

4590 editor.set_viewer(self.get_view()) 

4591 editor.get_marker_model().dataChanged.connect( 

4592 self.update_contents) 

4593 return editor 

4594 

4595 def adjust_controls(self): 

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

4597 maxfreq = 0.5/dtmin 

4598 minfreq = (0.5/dtmax)*0.001 

4599 self.lowpass_control.set_range(minfreq, maxfreq) 

4600 self.highpass_control.set_range(minfreq, maxfreq) 

4601 

4602 def setup_snufflings(self): 

4603 self.viewer.setup_snufflings() 

4604 

4605 def get_view(self): 

4606 return self.viewer 

4607 

4608 def update_contents(self): 

4609 self.viewer.update() 

4610 

4611 def get_pile(self): 

4612 return self.viewer.get_pile() 

4613 

4614 def show_colorbar_ctrl(self, show): 

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

4616 w.setVisible(show) 

4617 

4618 def show_gain_ctrl(self, show): 

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

4620 w.setVisible(show)