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 sys 

8import os 

9import time 

10import calendar 

11import datetime 

12import re 

13import math 

14import logging 

15import operator 

16import copy 

17import enum 

18from itertools import groupby 

19 

20import numpy as num 

21import pyrocko.model 

22import pyrocko.pile 

23import pyrocko.trace 

24import pyrocko.response 

25import pyrocko.util 

26import pyrocko.plot 

27import pyrocko.gui.snuffling 

28import pyrocko.gui.snufflings 

29import pyrocko.gui.marker_editor 

30 

31from pyrocko.util import hpfloat, gmtime_x, mystrftime 

32 

33from .marker import associate_phases_to_events, MarkerOneNSLCRequired 

34 

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

36 PhaseMarker, make_QPolygonF, draw_label, Label, 

37 Progressbars, ColorbarControl) 

38 

39from .qt_compat import qc, qg, qw, qgl, qsvg, use_pyqt5 

40 

41from .pile_viewer_waterfall import TraceWaterfall 

42 

43import scipy.stats as sstats 

44import platform 

45 

46MIN_LABEL_SIZE_PT = 6 

47 

48try: 

49 newstr = unicode 

50except NameError: 

51 newstr = str 

52 

53 

54def fnpatch(x): 

55 if use_pyqt5: 

56 return x 

57 else: 

58 return x, None 

59 

60 

61if sys.version_info[0] >= 3: 

62 qc.QString = str 

63 

64qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \ 

65 qw.QFileDialog.DontUseSheet 

66 

67if platform.mac_ver() != ('', ('', '', ''), ''): 

68 macosx = True 

69else: 

70 macosx = False 

71 

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

73 

74 

75def detrend(x, y): 

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

77 y_detrended = y - slope * x - offset 

78 return y_detrended, slope, offset 

79 

80 

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

82 return x * slope + y_detrended + offset 

83 

84 

85class Global(object): 

86 appOnDemand = None 

87 

88 

89class NSLC(object): 

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

91 self.network = n 

92 self.station = s 

93 self.location = l 

94 self.channel = c 

95 

96 

97class m_float(float): 

98 

99 def __str__(self): 

100 if abs(self) >= 10000.: 

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

102 elif abs(self) >= 1000.: 

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

104 else: 

105 return '%.5g m' % self 

106 

107 def __lt__(self, other): 

108 if other is None: 

109 return True 

110 return float(self) < float(other) 

111 

112 def __gt__(self, other): 

113 if other is None: 

114 return False 

115 return float(self) > float(other) 

116 

117 

118def m_float_or_none(x): 

119 if x is None: 

120 return None 

121 else: 

122 return m_float(x) 

123 

124 

125def make_chunks(items): 

126 ''' 

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

128 ''' 

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

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

131 

132 

133class deg_float(float): 

134 

135 def __str__(self): 

136 return '%4.0f' % self 

137 

138 

139def deg_float_or_none(x): 

140 if x is None: 

141 return None 

142 else: 

143 return deg_float(x) 

144 

145 

146class sector_int(int): 

147 

148 def __str__(self): 

149 return '[%i]' % self 

150 

151 

152def num_to_html(num): 

153 snum = '%g' % num 

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

155 if m: 

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

157 

158 return snum 

159 

160 

161gap_lap_tolerance = 5. 

162 

163 

164class ViewMode(enum.Enum): 

165 Wiggle = 1 

166 Waterfall = 2 

167 

168 

169class Timer(object): 

170 def __init__(self): 

171 self._start = None 

172 self._stop = None 

173 

174 def start(self): 

175 self._start = os.times() 

176 

177 def stop(self): 

178 self._stop = os.times() 

179 

180 def get(self): 

181 a = self._start 

182 b = self._stop 

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

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

185 else: 

186 return tuple([0.] * 5) 

187 

188 def __sub__(self, other): 

189 a = self.get() 

190 b = other.get() 

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

192 

193 

194class ObjectStyle(object): 

195 def __init__(self, frame_pen, fill_brush): 

196 self.frame_pen = frame_pen 

197 self.fill_brush = fill_brush 

198 

199 

200box_styles = [] 

201box_alpha = 100 

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

203 'scarletred'.split(): 

204 

205 box_styles.append(ObjectStyle( 

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

207 qg.QBrush(qg.QColor( 

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

209 )) 

210 

211box_styles_coverage = {} 

212 

213box_styles_coverage['waveform'] = [ 

214 ObjectStyle( 

215 qg.QPen( 

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

217 1, qc.Qt.DashLine), 

218 qg.QBrush(qg.QColor( 

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

220 ), 

221 ObjectStyle( 

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

223 qg.QBrush(qg.QColor( 

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

225 ), 

226 ObjectStyle( 

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

228 qg.QBrush(qg.QColor( 

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

230 )] 

231 

232box_styles_coverage['waveform_promise'] = [ 

233 ObjectStyle( 

234 qg.QPen( 

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

236 1, qc.Qt.DashLine), 

237 qg.QBrush(qg.QColor( 

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

239 ), 

240 ObjectStyle( 

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

242 qg.QBrush(qg.QColor( 

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

244 ), 

245 ObjectStyle( 

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

247 qg.QBrush(qg.QColor( 

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

249 )] 

250 

251sday = 60*60*24. # \ 

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

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

254 

255acceptable_tincs = num.array([ 

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

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

258 

259 

260working_system_time_range = \ 

261 pyrocko.util.working_system_time_range() 

262 

263initial_time_range = [] 

264 

265try: 

266 initial_time_range.append( 

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

268except Exception: 

269 initial_time_range.append(working_system_time_range[0]) 

270 

271try: 

272 initial_time_range.append( 

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

274except Exception: 

275 initial_time_range.append(working_system_time_range[1]) 

276 

277 

278def is_working_time(t): 

279 return working_system_time_range[0] <= t and \ 

280 t <= working_system_time_range[1] 

281 

282 

283def fancy_time_ax_format(inc): 

284 l0_fmt_brief = '' 

285 l2_fmt = '' 

286 l2_trig = 0 

287 if inc < 0.000001: 

288 l0_fmt = '.%n' 

289 l0_center = False 

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

291 l1_trig = 6 

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

293 l2_trig = 3 

294 elif inc < 0.001: 

295 l0_fmt = '.%u' 

296 l0_center = False 

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

298 l1_trig = 6 

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

300 l2_trig = 3 

301 elif inc < 1: 

302 l0_fmt = '.%r' 

303 l0_center = False 

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

305 l1_trig = 6 

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

307 l2_trig = 3 

308 elif inc < 60: 

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

310 l0_center = False 

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

312 l1_trig = 3 

313 elif inc < 3600: 

314 l0_fmt = '%H:%M' 

315 l0_center = False 

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

317 l1_trig = 3 

318 elif inc < sday: 

319 l0_fmt = '%H:%M' 

320 l0_center = False 

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

322 l1_trig = 3 

323 elif inc < smonth: 

324 l0_fmt = '%a %d' 

325 l0_fmt_brief = '%d' 

326 l0_center = True 

327 l1_fmt = '%b, %Y' 

328 l1_trig = 2 

329 elif inc < syear: 

330 l0_fmt = '%b' 

331 l0_center = True 

332 l1_fmt = '%Y' 

333 l1_trig = 1 

334 else: 

335 l0_fmt = '%Y' 

336 l0_center = False 

337 l1_fmt = '' 

338 l1_trig = 0 

339 

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

341 

342 

343def day_start(timestamp): 

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

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

346 return calendar.timegm(tts) 

347 

348 

349def month_start(timestamp): 

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

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

352 return calendar.timegm(tts) 

353 

354 

355def year_start(timestamp): 

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

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

358 return calendar.timegm(tts) 

359 

360 

361def time_nice_value(inc0): 

362 if inc0 < acceptable_tincs[0]: 

363 return pyrocko.plot.nice_value(inc0) 

364 elif inc0 > acceptable_tincs[-1]: 

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

366 else: 

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

368 return acceptable_tincs[i] 

369 

370 

371class TimeScaler(pyrocko.plot.AutoScaler): 

372 def __init__(self): 

373 pyrocko.plot.AutoScaler.__init__(self) 

374 self.mode = 'min-max' 

375 

376 def make_scale(self, data_range): 

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

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

379 

380 data_min = min(data_range) 

381 data_max = max(data_range) 

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

383 

384 mi, ma = data_min, data_max 

385 nmi = mi 

386 if self.mode != 'off': 

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

388 

389 nma = ma 

390 if self.mode != 'off': 

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

392 

393 mi, ma = nmi, nma 

394 

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

396 mi -= 1.0 

397 ma += 1.0 

398 

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

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

401 

402 # make nice tick increment 

403 if self.inc is not None: 

404 inc = self.inc 

405 else: 

406 if self.approx_ticks > 0.: 

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

408 else: 

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

410 

411 if inc == 0.0: 

412 inc = 1.0 

413 

414 if is_reverse: 

415 return ma, mi, -inc 

416 else: 

417 return mi, ma, inc 

418 

419 def make_ticks(self, data_range): 

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

421 

422 is_reverse = False 

423 if inc < 0: 

424 mi, ma, inc = ma, mi, -inc 

425 is_reverse = True 

426 

427 ticks = [] 

428 

429 if inc < sday: 

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

431 if inc < 0.001: 

432 mi_day = hpfloat(mi_day) 

433 

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

435 if inc < 0.001: 

436 base = hpfloat(base) 

437 

438 base_day = mi_day 

439 i = 0 

440 while True: 

441 tick = base+i*inc 

442 if tick > ma: 

443 break 

444 

445 tick_day = day_start(tick) 

446 if tick_day > base_day: 

447 base_day = tick_day 

448 base = base_day 

449 i = 0 

450 else: 

451 ticks.append(tick) 

452 i += 1 

453 

454 elif inc < smonth: 

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

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

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

458 if mi_day == mi: 

459 dt_base += delta 

460 i = 0 

461 while True: 

462 current = dt_base + i*delta 

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

464 if tick > ma: 

465 break 

466 ticks.append(tick) 

467 i += 1 

468 

469 elif inc < syear: 

470 mi_month = month_start(max( 

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

472 

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

474 while True: 

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

476 m += 1 

477 if m > 12: 

478 y, m = y+1, 1 

479 

480 if tick > ma: 

481 break 

482 

483 if tick >= mi: 

484 ticks.append(tick) 

485 

486 else: 

487 mi_year = year_start(max( 

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

489 

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

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

492 

493 while True: 

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

495 y += incy 

496 if tick > ma: 

497 break 

498 if tick >= mi: 

499 ticks.append(tick) 

500 

501 if is_reverse: 

502 ticks.reverse() 

503 

504 return ticks, inc 

505 

506 

507def need_l1_tick(tt, ms, l1_trig): 

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

509 

510 

511def tick_to_labels(tick, inc): 

512 tt, ms = gmtime_x(tick) 

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

514 fancy_time_ax_format(inc) 

515 

516 l0 = mystrftime(l0_fmt, tt, ms) 

517 l0_brief = mystrftime(l0_fmt_brief, tt, ms) 

518 l1, l2 = None, None 

519 if need_l1_tick(tt, ms, l1_trig): 

520 l1 = mystrftime(l1_fmt, tt, ms) 

521 if need_l1_tick(tt, ms, l2_trig): 

522 l2 = mystrftime(l2_fmt, tt, ms) 

523 

524 return l0, l0_brief, l0_center, l1, l2 

525 

526 

527def l1_l2_tick(tick, inc): 

528 tt, ms = gmtime_x(tick) 

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

530 fancy_time_ax_format(inc) 

531 

532 l1 = mystrftime(l1_fmt, tt, ms) 

533 l2 = mystrftime(l2_fmt, tt, ms) 

534 return l1, l2 

535 

536 

537class TimeAx(TimeScaler): 

538 def __init__(self, *args): 

539 TimeScaler.__init__(self, *args) 

540 

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

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

543 p.setPen(pen) 

544 font = qg.QFont() 

545 font.setBold(True) 

546 p.setFont(font) 

547 fm = p.fontMetrics() 

548 ticklen = 10 

549 pad = 10 

550 tmin, tmax = xprojection.get_in_range() 

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

552 l1_hits = 0 

553 l2_hits = 0 

554 

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

556 uumin, uumax = xprojection.get_out_range() 

557 first_tick_with_label = None 

558 for tick in ticks: 

559 umin = xprojection(tick) 

560 

561 umin_approx_next = xprojection(tick+inc) 

562 umax = xprojection(tick) 

563 

564 pinc_approx = umin_approx_next - umin 

565 

566 p.drawLine(qc.QPointF(umin, vmin), qc.QPointF(umax, vmax)) 

567 l0, l0_brief, l0_center, l1, l2 = tick_to_labels(tick, inc) 

568 

569 if tick == 0.0 and tmax - tmin < 3600*24: 

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

571 if l2: 

572 l2 = None 

573 elif l1: 

574 l1 = None 

575 

576 if l0_center: 

577 ushift = (umin_approx_next-umin)/2. 

578 else: 

579 ushift = 0. 

580 

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

582 label0 = l0x 

583 rect0 = fm.boundingRect(label0) 

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

585 break 

586 

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

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

589 

590 if first_tick_with_label is None: 

591 first_tick_with_label = tick 

592 p.drawText(qc.QPointF( 

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

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

595 

596 if l1: 

597 label1 = l1 

598 rect1 = fm.boundingRect(label1) 

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

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

601 

602 p.drawText(qc.QPointF( 

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

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

605 label1) 

606 

607 l1_hits += 1 

608 

609 if l2: 

610 label2 = l2 

611 rect2 = fm.boundingRect(label2) 

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

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

614 

615 p.drawText(qc.QPointF( 

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

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

618 ticklen), label2) 

619 

620 l2_hits += 1 

621 

622 if first_tick_with_label is None: 

623 first_tick_with_label = tmin 

624 

625 l1, l2 = l1_l2_tick(first_tick_with_label, inc) 

626 

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

628 tmax - tmin < 3600*24: 

629 

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

631 if l2: 

632 l2 = None 

633 elif l1: 

634 l1 = None 

635 

636 if l1_hits == 0 and l1: 

637 label1 = l1 

638 rect1 = fm.boundingRect(label1) 

639 p.drawText(qc.QPointF( 

640 uumin+pad, 

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

642 label1) 

643 

644 l1_hits += 1 

645 

646 if l2_hits == 0 and l2: 

647 label2 = l2 

648 rect2 = fm.boundingRect(label2) 

649 p.drawText(qc.QPointF( 

650 uumin+pad, 

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

652 label2) 

653 

654 v = yprojection(0) 

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

656 

657 

658class Projection(object): 

659 def __init__(self): 

660 self.xr = 0., 1. 

661 self.ur = 0., 1. 

662 

663 def set_in_range(self, xmin, xmax): 

664 if xmax == xmin: 

665 xmax = xmin + 1. 

666 

667 self.xr = xmin, xmax 

668 

669 def get_in_range(self): 

670 return self.xr 

671 

672 def set_out_range(self, umin, umax): 

673 if umax == umin: 

674 umax = umin + 1. 

675 

676 self.ur = umin, umax 

677 

678 def get_out_range(self): 

679 return self.ur 

680 

681 def __call__(self, x): 

682 umin, umax = self.ur 

683 xmin, xmax = self.xr 

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

685 

686 def clipped(self, x): 

687 umin, umax = self.ur 

688 xmin, xmax = self.xr 

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

690 

691 def rev(self, u): 

692 umin, umax = self.ur 

693 xmin, xmax = self.xr 

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

695 

696 def copy(self): 

697 return copy.copy(self) 

698 

699 

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

701 group = qw.QActionGroup(menu) 

702 group.setExclusive(True) 

703 menuitems = [] 

704 

705 for name, value, *shortcut in menudef: 

706 action = menu.addAction(name) 

707 action.setCheckable(True) 

708 action.setActionGroup(group) 

709 if shortcut: 

710 action.setShortcut(shortcut[0]) 

711 

712 menuitems.append((action, value)) 

713 if default is not None and ( 

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

715 value == default): 

716 action.setChecked(True) 

717 

718 group.triggered.connect(target) 

719 

720 if default is None: 

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

722 

723 return menuitems 

724 

725 

726def sort_actions(menu): 

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

728 for action in actions: 

729 menu.removeAction(action) 

730 actions.sort(key=lambda x: newstr(x.text())) 

731 

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

733 if help_action: 

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

735 for action in actions: 

736 menu.addAction(action) 

737 

738 

739fkey_map = dict(zip( 

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

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

742 range(10))) 

743 

744 

745class PileViewerMainException(Exception): 

746 pass 

747 

748 

749class PileViewerMenuBar(qw.QMenuBar): 

750 ... 

751 

752 

753class PileViewerMenu(qw.QMenu): 

754 ... 

755 

756 

757def MakePileViewerMainClass(base): 

758 

759 class PileViewerMain(base): 

760 

761 want_input = qc.pyqtSignal() 

762 about_to_close = qc.pyqtSignal() 

763 pile_has_changed_signal = qc.pyqtSignal() 

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

765 

766 begin_markers_add = qc.pyqtSignal(int, int) 

767 end_markers_add = qc.pyqtSignal() 

768 begin_markers_remove = qc.pyqtSignal(int, int) 

769 end_markers_remove = qc.pyqtSignal() 

770 

771 marker_selection_changed = qc.pyqtSignal(list) 

772 active_event_marker_changed = qc.pyqtSignal() 

773 

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

775 menu=None): 

776 if base == qgl.QGLWidget: 

777 from OpenGL import GL # noqa 

778 

779 base.__init__( 

780 self, qgl.QGLFormat(qgl.QGL.SampleBuffers), *args) 

781 else: 

782 base.__init__(self, *args) 

783 

784 self.pile = pile 

785 self.ax_height = 80 

786 self.panel_parent = panel_parent 

787 

788 self.click_tolerance = 5 

789 

790 self.ntracks_shown_max = ntracks_shown_max 

791 self.initial_ntracks_shown_max = ntracks_shown_max 

792 self.ntracks = 0 

793 self.show_all = True 

794 self.shown_tracks_range = None 

795 self.track_start = None 

796 self.track_trange = None 

797 

798 self.lowpass = None 

799 self.highpass = None 

800 self.gain = 1.0 

801 self.rotate = 0.0 

802 self.picking_down = None 

803 self.picking = None 

804 self.floating_marker = None 

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

806 self.markers_deltat_max = 0. 

807 self.n_selected_markers = 0 

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

809 self.visible_marker_kinds = self.all_marker_kinds 

810 self.active_event_marker = None 

811 self.ignore_releases = 0 

812 self.message = None 

813 self.reloaded = False 

814 self.pile_has_changed = False 

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

816 

817 self.tax = TimeAx() 

818 self.setBackgroundRole(qg.QPalette.Base) 

819 self.setAutoFillBackground(True) 

820 poli = qw.QSizePolicy( 

821 qw.QSizePolicy.Expanding, 

822 qw.QSizePolicy.Expanding) 

823 

824 self.setSizePolicy(poli) 

825 self.setMinimumSize(300, 200) 

826 self.setFocusPolicy(qc.Qt.ClickFocus) 

827 

828 self.menu = menu or PileViewerMenu(self) 

829 

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

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

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

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

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

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

836 

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

838 

839 self.snufflings_menu = self.toggle_panel_menu.addMenu( 

840 'Run Snuffling') 

841 self.toggle_panel_menu.addSeparator() 

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

843 help_menu.addSeparator() 

844 

845 file_menu.addAction( 

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

847 'Open waveform files...', 

848 self.open_waveforms, 

849 qg.QKeySequence.Open) 

850 

851 file_menu.addAction( 

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

853 'Open waveform directory...', 

854 self.open_waveform_directory) 

855 

856 file_menu.addAction( 

857 'Open station files...', 

858 self.open_stations) 

859 

860 file_menu.addAction( 

861 'Open StationXML files...', 

862 self.open_stations_xml) 

863 

864 file_menu.addAction( 

865 'Open event file...', 

866 self.read_events) 

867 

868 file_menu.addSeparator() 

869 file_menu.addAction( 

870 'Open marker file...', 

871 self.read_markers) 

872 

873 file_menu.addAction( 

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

875 'Save markers...', 

876 self.write_markers, 

877 qg.QKeySequence.Save) 

878 

879 file_menu.addAction( 

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

881 'Save selected markers...', 

882 self.write_selected_markers, 

883 qg.QKeySequence.SaveAs) 

884 

885 file_menu.addSeparator() 

886 file_menu.addAction( 

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

888 'Print', 

889 self.printit, 

890 qg.QKeySequence.Print) 

891 

892 file_menu.addAction( 

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

894 'Save as SVG or PNG', 

895 self.savesvg, 

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

897 

898 file_menu.addSeparator() 

899 close = file_menu.addAction( 

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

901 'Close', 

902 self.myclose) 

903 close.setShortcuts( 

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

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

906 

907 # Scale Menu 

908 menudef = [ 

909 ('Individual Scale', 

910 lambda tr: tr.nslc_id, 

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

912 ('Common Scale', 

913 lambda tr: None, 

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

915 ('Common Scale per Station', 

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

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

918 ('Common Scale per Station Location', 

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

920 ('Common Scale per Component', 

921 lambda tr: (tr.channel)), 

922 ] 

923 

924 self.menuitems_scaling = add_radiobuttongroup( 

925 scale_menu, menudef, self.scalingmode_change, 

926 default=self.config.trace_scale) 

927 scale_menu.addSeparator() 

928 

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

930 self.scaling_hooks = {} 

931 self.scalingmode_change() 

932 

933 menudef = [ 

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

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

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

937 ] 

938 

939 self.menuitems_scaling_base = add_radiobuttongroup( 

940 scale_menu, menudef, self.scaling_base_change) 

941 

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

943 scale_menu.addSeparator() 

944 

945 self.menuitem_fixscalerange = scale_menu.addAction( 

946 'Fix Scale Ranges') 

947 self.menuitem_fixscalerange.setCheckable(True) 

948 

949 # Sort Menu 

950 def sector_dist(sta): 

951 if sta.dist_m is None: 

952 return None, None 

953 else: 

954 return ( 

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

956 m_float(sta.dist_m)) 

957 

958 menudef = [ 

959 ('Sort by Names', 

960 lambda tr: (), 

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

962 ('Sort by Distance', 

963 lambda tr: self.station_attrib( 

964 tr, 

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

966 lambda tr: (None,)), 

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

968 ('Sort by Azimuth', 

969 lambda tr: self.station_attrib( 

970 tr, 

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

972 lambda tr: (None,))), 

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

974 lambda tr: self.station_attrib( 

975 tr, 

976 sector_dist, 

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

978 ('Sort by Backazimuth', 

979 lambda tr: self.station_attrib( 

980 tr, 

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

982 lambda tr: (None,))), 

983 ] 

984 self.menuitems_ssorting = add_radiobuttongroup( 

985 sort_menu, menudef, self.s_sortingmode_change) 

986 sort_menu.addSeparator() 

987 

988 self._ssort = lambda tr: () 

989 

990 self.menu.addSeparator() 

991 

992 menudef = [ 

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

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

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

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

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

998 lambda tr: tr.channel)), 

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

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

1001 lambda tr: tr.channel)), 

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

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

1004 lambda tr: tr.channel)), 

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

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

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

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

1009 ((0, 1, 3), 

1010 lambda tr: tr.location)), 

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

1012 ((1, 0, 3), 

1013 lambda tr: tr.location)), 

1014 ] 

1015 

1016 self.menuitems_sorting = add_radiobuttongroup( 

1017 sort_menu, menudef, self.sortingmode_change) 

1018 

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

1020 self.config.visible_length_setting] 

1021 

1022 # View menu 

1023 self.menuitems_visible_length = add_radiobuttongroup( 

1024 view_menu, menudef, 

1025 self.visible_length_change) 

1026 view_menu.addSeparator() 

1027 

1028 view_modes = [ 

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

1030 ('Waterfall', ViewMode.Waterfall) 

1031 ] 

1032 

1033 self.menuitems_viewmode = add_radiobuttongroup( 

1034 view_menu, view_modes, 

1035 self.viewmode_change, default=ViewMode.Wiggle) 

1036 view_menu.addSeparator() 

1037 

1038 self.menuitem_cliptraces = view_menu.addAction( 

1039 'Clip Traces') 

1040 self.menuitem_cliptraces.setCheckable(True) 

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

1042 

1043 self.menuitem_showboxes = view_menu.addAction( 

1044 'Show Boxes') 

1045 self.menuitem_showboxes.setCheckable(True) 

1046 self.menuitem_showboxes.setChecked( 

1047 self.config.show_boxes) 

1048 

1049 self.menuitem_colortraces = view_menu.addAction( 

1050 'Color Traces') 

1051 self.menuitem_colortraces.setCheckable(True) 

1052 self.menuitem_antialias = view_menu.addAction( 

1053 'Antialiasing') 

1054 self.menuitem_antialias.setCheckable(True) 

1055 

1056 view_menu.addSeparator() 

1057 self.menuitem_showscalerange = view_menu.addAction( 

1058 'Show Scale Ranges') 

1059 self.menuitem_showscalerange.setCheckable(True) 

1060 self.menuitem_showscalerange.setChecked( 

1061 self.config.show_scale_ranges) 

1062 

1063 self.menuitem_showscaleaxis = view_menu.addAction( 

1064 'Show Scale Axes') 

1065 self.menuitem_showscaleaxis.setCheckable(True) 

1066 self.menuitem_showscaleaxis.setChecked( 

1067 self.config.show_scale_axes) 

1068 

1069 self.menuitem_showzeroline = view_menu.addAction( 

1070 'Show Zero Lines') 

1071 self.menuitem_showzeroline.setCheckable(True) 

1072 

1073 view_menu.addSeparator() 

1074 view_menu.addAction( 

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

1076 'Fullscreen', 

1077 self.toggle_fullscreen, 

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

1079 

1080 # Options Menu 

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

1082 self.menuitem_demean.setCheckable(True) 

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

1084 self.menuitem_demean.setShortcut( 

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

1086 

1087 self.menuitem_distances_3d = options_menu.addAction( 

1088 '3D distances', 

1089 self.distances_3d_changed) 

1090 self.menuitem_distances_3d.setCheckable(True) 

1091 

1092 self.menuitem_allowdownsampling = options_menu.addAction( 

1093 'Allow Downsampling') 

1094 self.menuitem_allowdownsampling.setCheckable(True) 

1095 self.menuitem_allowdownsampling.setChecked(True) 

1096 

1097 self.menuitem_degap = options_menu.addAction( 

1098 'Allow Degapping') 

1099 self.menuitem_degap.setCheckable(True) 

1100 self.menuitem_degap.setChecked(True) 

1101 

1102 options_menu.addSeparator() 

1103 

1104 self.menuitem_fft_filtering = options_menu.addAction( 

1105 'FFT Filtering') 

1106 self.menuitem_fft_filtering.setCheckable(True) 

1107 

1108 self.menuitem_lphp = options_menu.addAction( 

1109 'Bandpass is Low- + Highpass') 

1110 self.menuitem_lphp.setCheckable(True) 

1111 self.menuitem_lphp.setChecked(True) 

1112 

1113 options_menu.addSeparator() 

1114 self.menuitem_watch = options_menu.addAction( 

1115 'Watch Files') 

1116 self.menuitem_watch.setCheckable(True) 

1117 

1118 self.menuitem_liberal_fetch = options_menu.addAction( 

1119 'Liberal Fetch Optimization') 

1120 self.menuitem_liberal_fetch.setCheckable(True) 

1121 

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

1123 

1124 self.snufflings_menu.addAction( 

1125 'Reload Snufflings', 

1126 self.setup_snufflings) 

1127 

1128 # Disable ShadowPileTest 

1129 if False: 

1130 test_action = self.menu.addAction( 

1131 'Test', 

1132 self.toggletest) 

1133 test_action.setCheckable(True) 

1134 

1135 help_menu.addAction( 

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

1137 'Snuffler Controls', 

1138 self.help, 

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

1140 

1141 help_menu.addAction( 

1142 'About', 

1143 self.about) 

1144 

1145 self.time_projection = Projection() 

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

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

1148 

1149 self.gather = None 

1150 

1151 self.trace_filter = None 

1152 self.quick_filter = None 

1153 self.quick_filter_patterns = None, None 

1154 self.blacklist = [] 

1155 

1156 self.track_to_screen = Projection() 

1157 self.track_to_nslc_ids = {} 

1158 

1159 self.cached_vec = None 

1160 self.cached_processed_traces = None 

1161 

1162 self.timer = qc.QTimer(self) 

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

1164 self.timer.setInterval(1000) 

1165 self.timer.start() 

1166 self.pile.add_listener(self) 

1167 self.trace_styles = {} 

1168 if self.get_squirrel() is None: 

1169 self.determine_box_styles() 

1170 

1171 self.setMouseTracking(True) 

1172 

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

1174 self.snuffling_modules = {} 

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

1176 self.default_snufflings = None 

1177 self.snufflings = [] 

1178 

1179 self.stations = {} 

1180 

1181 self.timer_draw = Timer() 

1182 self.timer_cutout = Timer() 

1183 self.time_spent_painting = 0.0 

1184 self.time_last_painted = time.time() 

1185 

1186 self.interactive_range_change_time = 0.0 

1187 self.interactive_range_change_delay_time = 10.0 

1188 self.follow_timer = None 

1189 

1190 self.sortingmode_change_time = 0.0 

1191 self.sortingmode_change_delay_time = None 

1192 

1193 self.old_data_ranges = {} 

1194 

1195 self.error_messages = {} 

1196 self.return_tag = None 

1197 self.wheel_pos = 60 

1198 

1199 self.setAcceptDrops(True) 

1200 self._paths_to_load = [] 

1201 

1202 self.tf_cache = {} 

1203 

1204 self.waterfall = TraceWaterfall() 

1205 self.waterfall_cmap = 'viridis' 

1206 self.waterfall_clip_min = 0. 

1207 self.waterfall_clip_max = 1. 

1208 self.waterfall_show_absolute = False 

1209 self.waterfall_integrate = False 

1210 self.view_mode = ViewMode.Wiggle 

1211 

1212 self.automatic_updates = True 

1213 

1214 self.closing = False 

1215 self.paint_timer = qc.QTimer(self) 

1216 self.paint_timer.timeout.connect(self.reset_updates) 

1217 self.paint_timer.setInterval(20) 

1218 self.paint_timer.start() 

1219 

1220 @qc.pyqtSlot() 

1221 def reset_updates(self): 

1222 if not self.updatesEnabled(): 

1223 self.setUpdatesEnabled(True) 

1224 

1225 def fail(self, reason): 

1226 box = qw.QMessageBox(self) 

1227 box.setText(reason) 

1228 box.exec_() 

1229 

1230 def set_trace_filter(self, filter_func): 

1231 self.trace_filter = filter_func 

1232 self.sortingmode_change() 

1233 

1234 def update_trace_filter(self): 

1235 if self.blacklist: 

1236 

1237 def blacklist_func(tr): 

1238 return not pyrocko.util.match_nslc( 

1239 self.blacklist, tr.nslc_id) 

1240 

1241 else: 

1242 blacklist_func = None 

1243 

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

1245 self.set_trace_filter(None) 

1246 elif self.quick_filter is None: 

1247 self.set_trace_filter(blacklist_func) 

1248 elif blacklist_func is None: 

1249 self.set_trace_filter(self.quick_filter) 

1250 else: 

1251 self.set_trace_filter( 

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

1253 

1254 def set_quick_filter(self, filter_func): 

1255 self.quick_filter = filter_func 

1256 self.update_trace_filter() 

1257 

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

1259 if patterns is not None: 

1260 self.set_quick_filter( 

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

1262 else: 

1263 self.set_quick_filter(None) 

1264 

1265 self.quick_filter_patterns = patterns, inputline 

1266 

1267 def get_quick_filter_patterns(self): 

1268 return self.quick_filter_patterns 

1269 

1270 def add_blacklist_pattern(self, pattern): 

1271 if pattern == 'empty': 

1272 keys = set(self.pile.nslc_ids) 

1273 trs = self.pile.all( 

1274 tmin=self.tmin, 

1275 tmax=self.tmax, 

1276 load_data=False, 

1277 degap=False) 

1278 

1279 for tr in trs: 

1280 if tr.nslc_id in keys: 

1281 keys.remove(tr.nslc_id) 

1282 

1283 for key in keys: 

1284 xpattern = '.'.join(key) 

1285 if xpattern not in self.blacklist: 

1286 self.blacklist.append(xpattern) 

1287 

1288 else: 

1289 if pattern in self.blacklist: 

1290 self.blacklist.remove(pattern) 

1291 

1292 self.blacklist.append(pattern) 

1293 

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

1295 self.update_trace_filter() 

1296 

1297 def remove_blacklist_pattern(self, pattern): 

1298 if pattern in self.blacklist: 

1299 self.blacklist.remove(pattern) 

1300 else: 

1301 raise PileViewerMainException( 

1302 'Pattern not found in blacklist.') 

1303 

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

1305 self.update_trace_filter() 

1306 

1307 def clear_blacklist(self): 

1308 self.blacklist = [] 

1309 self.update_trace_filter() 

1310 

1311 def ssort(self, tr): 

1312 return self._ssort(tr) 

1313 

1314 def station_key(self, x): 

1315 return x.network, x.station 

1316 

1317 def station_keys(self, x): 

1318 return [ 

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

1320 (x.network, x.station)] 

1321 

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

1323 for sk in self.station_keys(tr): 

1324 if sk in self.stations: 

1325 station = self.stations[sk] 

1326 return getter(station) 

1327 

1328 return default_getter(tr) 

1329 

1330 def get_station(self, sk): 

1331 return self.stations[sk] 

1332 

1333 def has_station(self, station): 

1334 for sk in self.station_keys(station): 

1335 if sk in self.stations: 

1336 return True 

1337 

1338 return False 

1339 

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

1341 return self.station_attrib( 

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

1343 

1344 def set_stations(self, stations): 

1345 self.stations = {} 

1346 self.add_stations(stations) 

1347 

1348 def add_stations(self, stations): 

1349 for station in stations: 

1350 for sk in self.station_keys(station): 

1351 self.stations[sk] = station 

1352 

1353 ev = self.get_active_event() 

1354 if ev: 

1355 self.set_origin(ev) 

1356 

1357 def add_event(self, event): 

1358 marker = EventMarker(event) 

1359 self.add_marker(marker) 

1360 

1361 def add_events(self, events): 

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

1363 self.add_markers(markers) 

1364 

1365 def set_event_marker_as_origin(self, ignore=None): 

1366 selected = self.selected_markers() 

1367 if not selected: 

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

1369 return 

1370 

1371 m = selected[0] 

1372 if not isinstance(m, EventMarker): 

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

1374 return 

1375 

1376 self.set_active_event_marker(m) 

1377 

1378 def deactivate_event_marker(self): 

1379 if self.active_event_marker: 

1380 self.active_event_marker.active = False 

1381 

1382 self.active_event_marker_changed.emit() 

1383 self.active_event_marker = None 

1384 

1385 def set_active_event_marker(self, event_marker): 

1386 if self.active_event_marker: 

1387 self.active_event_marker.active = False 

1388 

1389 self.active_event_marker = event_marker 

1390 event_marker.active = True 

1391 event = event_marker.get_event() 

1392 self.set_origin(event) 

1393 self.active_event_marker_changed.emit() 

1394 

1395 def set_active_event(self, event): 

1396 for marker in self.markers: 

1397 if isinstance(marker, EventMarker): 

1398 if marker.get_event() is event: 

1399 self.set_active_event_marker(marker) 

1400 

1401 def get_active_event_marker(self): 

1402 return self.active_event_marker 

1403 

1404 def get_active_event(self): 

1405 m = self.get_active_event_marker() 

1406 if m is not None: 

1407 return m.get_event() 

1408 else: 

1409 return None 

1410 

1411 def get_active_markers(self): 

1412 emarker = self.get_active_event_marker() 

1413 if emarker is None: 

1414 return None, [] 

1415 

1416 else: 

1417 ev = emarker.get_event() 

1418 pmarkers = [ 

1419 m for m in self.markers 

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

1421 

1422 return emarker, pmarkers 

1423 

1424 def set_origin(self, location): 

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

1426 station.set_event_relative_data( 

1427 location, 

1428 distance_3d=self.menuitem_distances_3d.isChecked()) 

1429 

1430 self.sortingmode_change() 

1431 

1432 def distances_3d_changed(self): 

1433 ignore = self.menuitem_distances_3d.isChecked() 

1434 self.set_event_marker_as_origin(ignore) 

1435 

1436 def iter_snuffling_modules(self): 

1437 pjoin = os.path.join 

1438 for path in self.snuffling_paths: 

1439 

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

1441 os.mkdir(path) 

1442 

1443 for entry in os.listdir(path): 

1444 directory = path 

1445 fn = entry 

1446 d = pjoin(path, entry) 

1447 if os.path.isdir(d): 

1448 directory = d 

1449 if os.path.isfile( 

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

1451 fn = 'snuffling.py' 

1452 

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

1454 continue 

1455 

1456 name = fn[:-3] 

1457 

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

1459 self.snuffling_modules[directory, name] = \ 

1460 pyrocko.gui.snuffling.SnufflingModule( 

1461 directory, name, self) 

1462 

1463 yield self.snuffling_modules[directory, name] 

1464 

1465 def setup_snufflings(self): 

1466 # user snufflings 

1467 for mod in self.iter_snuffling_modules(): 

1468 try: 

1469 mod.load_if_needed() 

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

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

1472 

1473 # load the default snufflings on first run 

1474 if self.default_snufflings is None: 

1475 self.default_snufflings = pyrocko.gui\ 

1476 .snufflings.__snufflings__() 

1477 for snuffling in self.default_snufflings: 

1478 self.add_snuffling(snuffling) 

1479 

1480 def set_panel_parent(self, panel_parent): 

1481 self.panel_parent = panel_parent 

1482 

1483 def get_panel_parent(self): 

1484 return self.panel_parent 

1485 

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

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

1488 snuffling.init_gui( 

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

1490 self.snufflings.append(snuffling) 

1491 self.update() 

1492 

1493 def remove_snuffling(self, snuffling): 

1494 snuffling.delete_gui() 

1495 self.update() 

1496 self.snufflings.remove(snuffling) 

1497 snuffling.pre_destroy() 

1498 

1499 def add_snuffling_menuitem(self, item): 

1500 self.snufflings_menu.addAction(item) 

1501 item.setParent(self.snufflings_menu) 

1502 sort_actions(self.snufflings_menu) 

1503 

1504 def remove_snuffling_menuitem(self, item): 

1505 self.snufflings_menu.removeAction(item) 

1506 

1507 def add_snuffling_help_menuitem(self, item): 

1508 self.snuffling_help.addAction(item) 

1509 item.setParent(self.snuffling_help) 

1510 sort_actions(self.snuffling_help) 

1511 

1512 def remove_snuffling_help_menuitem(self, item): 

1513 self.snuffling_help.removeAction(item) 

1514 

1515 def add_panel_toggler(self, item): 

1516 self.toggle_panel_menu.addAction(item) 

1517 item.setParent(self.toggle_panel_menu) 

1518 sort_actions(self.toggle_panel_menu) 

1519 

1520 def remove_panel_toggler(self, item): 

1521 self.toggle_panel_menu.removeAction(item) 

1522 

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

1524 cache_dir=None, force_cache=False): 

1525 

1526 if cache_dir is None: 

1527 cache_dir = pyrocko.config.config().cache_dir 

1528 if isinstance(paths, str): 

1529 paths = [paths] 

1530 

1531 fns = pyrocko.util.select_files( 

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

1533 

1534 if not fns: 

1535 return 

1536 

1537 cache = pyrocko.pile.get_cache(cache_dir) 

1538 

1539 t = [time.time()] 

1540 

1541 def update_bar(label, value): 

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

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

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

1545 else: 

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

1547 

1548 return pbs.set_status(label, value) 

1549 

1550 def update_progress(label, i, n): 

1551 abort = False 

1552 

1553 qw.qApp.processEvents() 

1554 if n != 0: 

1555 perc = i*100/n 

1556 else: 

1557 perc = 100 

1558 abort |= update_bar(label, perc) 

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

1560 

1561 tnow = time.time() 

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

1563 self.update() 

1564 t[0] = tnow 

1565 

1566 return abort 

1567 

1568 self.automatic_updates = False 

1569 

1570 self.pile.load_files( 

1571 sorted(fns), 

1572 filename_attributes=regex, 

1573 cache=cache, 

1574 fileformat=format, 

1575 show_progress=False, 

1576 update_progress=update_progress) 

1577 

1578 self.automatic_updates = True 

1579 self.update() 

1580 

1581 def load_queued(self): 

1582 if not self._paths_to_load: 

1583 return 

1584 paths = self._paths_to_load 

1585 self._paths_to_load = [] 

1586 self.load(paths) 

1587 

1588 def load_soon(self, paths): 

1589 self._paths_to_load.extend(paths) 

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

1591 

1592 def open_waveforms(self): 

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

1594 

1595 fns, _ = fnpatch(qw.QFileDialog.getOpenFileNames( 

1596 self, caption, options=qfiledialog_options)) 

1597 

1598 if fns: 

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

1600 

1601 def open_waveform_directory(self): 

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

1603 

1604 dn = qw.QFileDialog.getExistingDirectory( 

1605 self, caption, options=qfiledialog_options) 

1606 

1607 if dn: 

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

1609 

1610 def open_stations(self, fns=None): 

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

1612 

1613 if not fns: 

1614 fns, _ = fnpatch(qw.QFileDialog.getOpenFileNames( 

1615 self, caption, options=qfiledialog_options)) 

1616 

1617 try: 

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

1619 for stat in stations: 

1620 self.add_stations(stat) 

1621 

1622 except Exception as e: 

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

1624 

1625 def open_stations_xml(self, fns=None): 

1626 from pyrocko.io import stationxml 

1627 

1628 caption = 'Select one or more StationXML files' 

1629 if not fns: 

1630 fns, _ = fnpatch(qw.QFileDialog.getOpenFileNames( 

1631 self, caption, options=qfiledialog_options, 

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

1633 ';;All files (*)')) 

1634 

1635 try: 

1636 stations = [ 

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

1638 for x in fns] 

1639 

1640 for stat in stations: 

1641 self.add_stations(stat) 

1642 

1643 except Exception as e: 

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

1645 

1646 def add_traces(self, traces): 

1647 if traces: 

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

1649 self.pile.add_file(mtf) 

1650 ticket = (self.pile, mtf) 

1651 return ticket 

1652 else: 

1653 return (None, None) 

1654 

1655 def release_data(self, tickets): 

1656 for ticket in tickets: 

1657 pile, mtf = ticket 

1658 if pile is not None: 

1659 pile.remove_file(mtf) 

1660 

1661 def periodical(self): 

1662 if self.menuitem_watch.isChecked(): 

1663 if self.pile.reload_modified(): 

1664 self.update() 

1665 

1666 def get_pile(self): 

1667 return self.pile 

1668 

1669 def pile_changed(self, what): 

1670 self.pile_has_changed = True 

1671 self.pile_has_changed_signal.emit() 

1672 if self.automatic_updates: 

1673 self.update() 

1674 

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

1676 

1677 if gather is None: 

1678 def gather_func(tr): 

1679 return tr.nslc_id 

1680 

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

1682 

1683 else: 

1684 def gather_func(tr): 

1685 return ( 

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

1687 

1688 if color is None: 

1689 def color(tr): 

1690 return tr.location 

1691 

1692 self.gather = gather_func 

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

1694 

1695 self.color_gather = color 

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

1697 previous_ntracks = self.ntracks 

1698 self.set_ntracks(len(keys)) 

1699 

1700 if self.shown_tracks_range is None or \ 

1701 previous_ntracks == 0 or \ 

1702 self.show_all: 

1703 

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

1705 key_at_top = None 

1706 n = high-low 

1707 

1708 else: 

1709 low, high = self.shown_tracks_range 

1710 key_at_top = self.track_keys[low] 

1711 n = high-low 

1712 

1713 self.track_keys = sorted(keys) 

1714 

1715 track_patterns = [] 

1716 for k in self.track_keys: 

1717 pat = ['*', '*', '*', '*'] 

1718 for i, j in enumerate(gather): 

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

1720 

1721 track_patterns.append(pat) 

1722 

1723 self.track_patterns = track_patterns 

1724 

1725 if key_at_top is not None: 

1726 try: 

1727 ind = self.track_keys.index(key_at_top) 

1728 low = ind 

1729 high = low+n 

1730 except Exception: 

1731 pass 

1732 

1733 self.set_tracks_range((low, high)) 

1734 

1735 self.key_to_row = dict( 

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

1737 

1738 def inrange(x, r): 

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

1740 

1741 def trace_selector(trace): 

1742 gt = self.gather(trace) 

1743 return ( 

1744 gt in self.key_to_row and 

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

1746 

1747 if self.trace_filter is not None: 

1748 self.trace_selector = lambda x: \ 

1749 self.trace_filter(x) and trace_selector(x) 

1750 else: 

1751 self.trace_selector = trace_selector 

1752 

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

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

1755 self.show_all: 

1756 

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

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

1759 tlen = (tmax - tmin) 

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

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

1762 

1763 def set_time_range(self, tmin, tmax): 

1764 if tmin is None: 

1765 tmin = initial_time_range[0] 

1766 

1767 if tmax is None: 

1768 tmax = initial_time_range[1] 

1769 

1770 if tmin > tmax: 

1771 tmin, tmax = tmax, tmin 

1772 

1773 if tmin == tmax: 

1774 tmin -= 1. 

1775 tmax += 1. 

1776 

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

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

1779 

1780 min_deltat = self.content_deltat_range()[0] 

1781 if (tmax - tmin < min_deltat): 

1782 m = (tmin + tmax) / 2. 

1783 tmin = m - min_deltat/2. 

1784 tmax = m + min_deltat/2. 

1785 

1786 self.time_projection.set_in_range(tmin, tmax) 

1787 self.tmin, self.tmax = tmin, tmax 

1788 

1789 def get_time_range(self): 

1790 return self.tmin, self.tmax 

1791 

1792 def ypart(self, y): 

1793 if y < self.ax_height: 

1794 return -1 

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

1796 return 1 

1797 else: 

1798 return 0 

1799 

1800 def time_fractional_digits(self): 

1801 min_deltat = self.content_deltat_range()[0] 

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

1803 

1804 def write_markers(self, fn=None): 

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

1806 if not fn: 

1807 fn, _ = fnpatch(qw.QFileDialog.getSaveFileName( 

1808 self, caption, options=qfiledialog_options)) 

1809 if fn: 

1810 try: 

1811 Marker.save_markers( 

1812 self.markers, fn, 

1813 fdigits=self.time_fractional_digits()) 

1814 

1815 except Exception as e: 

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

1817 

1818 def write_selected_markers(self, fn=None): 

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

1820 if not fn: 

1821 fn, _ = fnpatch(qw.QFileDialog.getSaveFileName( 

1822 self, caption, options=qfiledialog_options)) 

1823 if fn: 

1824 try: 

1825 Marker.save_markers( 

1826 self.iter_selected_markers(), 

1827 fn, 

1828 fdigits=self.time_fractional_digits()) 

1829 

1830 except Exception as e: 

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

1832 

1833 def read_events(self, fn=None): 

1834 ''' 

1835 Open QFileDialog to open, read and add 

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

1837 representation to the pile viewer. 

1838 ''' 

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

1840 if not fn: 

1841 fn, _ = fnpatch(qw.QFileDialog.getOpenFileName( 

1842 self, caption, options=qfiledialog_options)) 

1843 if fn: 

1844 try: 

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

1846 self.associate_phases_to_events() 

1847 

1848 except Exception as e: 

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

1850 

1851 def read_markers(self, fn=None): 

1852 ''' 

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

1854 ''' 

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

1856 if not fn: 

1857 fn, _ = fnpatch(qw.QFileDialog.getOpenFileName( 

1858 self, caption, options=qfiledialog_options)) 

1859 if fn: 

1860 try: 

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

1862 self.associate_phases_to_events() 

1863 

1864 except Exception as e: 

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

1866 

1867 def associate_phases_to_events(self): 

1868 associate_phases_to_events(self.markers) 

1869 

1870 def add_marker(self, marker): 

1871 # need index to inform QAbstactTableModel about upcoming change, 

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

1873 self.markers.insert(marker) 

1874 i = self.markers.remove(marker) 

1875 

1876 self.begin_markers_add.emit(i, i) 

1877 self.markers.insert(marker) 

1878 self.end_markers_add.emit() 

1879 self.markers_deltat_max = max( 

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

1881 

1882 def add_markers(self, markers): 

1883 if not self.markers: 

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

1885 self.markers.insert_many(markers) 

1886 self.end_markers_add.emit() 

1887 self.update_markers_deltat_max() 

1888 else: 

1889 for marker in markers: 

1890 self.add_marker(marker) 

1891 

1892 def update_markers_deltat_max(self): 

1893 if self.markers: 

1894 self.markers_deltat_max = max( 

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

1896 

1897 def remove_marker(self, marker): 

1898 ''' 

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

1900 

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

1902 ''' 

1903 

1904 if marker is self.active_event_marker: 

1905 self.deactivate_event_marker() 

1906 

1907 try: 

1908 i = self.markers.index(marker) 

1909 self.begin_markers_remove.emit(i, i) 

1910 self.markers.remove_at(i) 

1911 self.end_markers_remove.emit() 

1912 except ValueError: 

1913 pass 

1914 

1915 def remove_markers(self, markers): 

1916 ''' 

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

1918 

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

1920 instances 

1921 ''' 

1922 

1923 if markers is self.markers: 

1924 markers = list(markers) 

1925 

1926 for marker in markers: 

1927 self.remove_marker(marker) 

1928 

1929 self.update_markers_deltat_max() 

1930 

1931 def remove_selected_markers(self): 

1932 def delete_segment(istart, iend): 

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

1934 for _ in range(iend - istart): 

1935 self.markers.remove_at(istart) 

1936 

1937 self.end_markers_remove.emit() 

1938 

1939 istart = None 

1940 ipos = 0 

1941 markers = self.markers 

1942 nmarkers = len(self.markers) 

1943 while ipos < nmarkers: 

1944 marker = markers[ipos] 

1945 if marker.is_selected(): 

1946 if marker is self.active_event_marker: 

1947 self.deactivate_event_marker() 

1948 

1949 if istart is None: 

1950 istart = ipos 

1951 else: 

1952 if istart is not None: 

1953 delete_segment(istart, ipos) 

1954 nmarkers -= ipos - istart 

1955 ipos = istart - 1 

1956 istart = None 

1957 

1958 ipos += 1 

1959 

1960 if istart is not None: 

1961 delete_segment(istart, ipos) 

1962 

1963 self.update_markers_deltat_max() 

1964 

1965 def selected_markers(self): 

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

1967 

1968 def iter_selected_markers(self): 

1969 for marker in self.markers: 

1970 if marker.is_selected(): 

1971 yield marker 

1972 

1973 def get_markers(self): 

1974 return self.markers 

1975 

1976 def mousePressEvent(self, mouse_ev): 

1977 self.show_all = False 

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

1979 

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

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

1982 if self.picking: 

1983 if self.picking_down is None: 

1984 self.picking_down = ( 

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

1986 mouse_ev.y()) 

1987 

1988 elif marker is not None: 

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

1990 self.deselect_all() 

1991 marker.selected = True 

1992 self.emit_selected_markers() 

1993 self.update() 

1994 else: 

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

1996 self.track_trange = self.tmin, self.tmax 

1997 

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

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

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

2001 self.update_status() 

2002 

2003 def mouseReleaseEvent(self, mouse_ev): 

2004 if self.ignore_releases: 

2005 self.ignore_releases -= 1 

2006 return 

2007 

2008 if self.picking: 

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

2010 self.emit_selected_markers() 

2011 

2012 if self.track_start: 

2013 self.update() 

2014 

2015 self.track_start = None 

2016 self.track_trange = None 

2017 self.update_status() 

2018 

2019 def mouseDoubleClickEvent(self, mouse_ev): 

2020 self.show_all = False 

2021 self.start_picking(None) 

2022 self.ignore_releases = 1 

2023 

2024 def mouseMoveEvent(self, mouse_ev): 

2025 self.setUpdatesEnabled(False) 

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

2027 

2028 if self.picking: 

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

2030 

2031 elif self.track_start is not None: 

2032 x0, y0 = self.track_start 

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

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

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

2036 dy = 0 

2037 

2038 tmin0, tmax0 = self.track_trange 

2039 

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

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

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

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

2044 

2045 self.interrupt_following() 

2046 self.set_time_range( 

2047 tmin0 - dt - dtr*frac, 

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

2049 

2050 self.update() 

2051 else: 

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

2053 

2054 self.update_status() 

2055 

2056 def nslc_ids_under_cursor(self, x, y): 

2057 ftrack = self.track_to_screen.rev(y) 

2058 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2059 return nslc_ids 

2060 

2061 def marker_under_cursor(self, x, y): 

2062 mouset = self.time_projection.rev(x) 

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

2064 relevant_nslc_ids = None 

2065 for marker in self.markers: 

2066 if marker.kind not in self.visible_marker_kinds: 

2067 continue 

2068 

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

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

2071 

2072 if relevant_nslc_ids is None: 

2073 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2074 

2075 marker_nslc_ids = marker.get_nslc_ids() 

2076 if not marker_nslc_ids: 

2077 return marker 

2078 

2079 for nslc_id in marker_nslc_ids: 

2080 if nslc_id in relevant_nslc_ids: 

2081 return marker 

2082 

2083 def hoovering(self, x, y): 

2084 mouset = self.time_projection.rev(x) 

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

2086 needupdate = False 

2087 haveone = False 

2088 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2089 for marker in self.markers: 

2090 if marker.kind not in self.visible_marker_kinds: 

2091 continue 

2092 

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

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

2095 

2096 if state: 

2097 xstate = False 

2098 

2099 marker_nslc_ids = marker.get_nslc_ids() 

2100 if not marker_nslc_ids: 

2101 xstate = True 

2102 

2103 for nslc in relevant_nslc_ids: 

2104 if marker.match_nslc(nslc): 

2105 xstate = True 

2106 

2107 state = xstate 

2108 

2109 if state: 

2110 haveone = True 

2111 oldstate = marker.is_alerted() 

2112 if oldstate != state: 

2113 needupdate = True 

2114 marker.set_alerted(state) 

2115 if state: 

2116 self.message = marker.hoover_message() 

2117 

2118 if not haveone: 

2119 self.message = None 

2120 

2121 if needupdate: 

2122 self.update() 

2123 

2124 def event(self, event): 

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

2126 self.keyPressEvent(event) 

2127 return True 

2128 else: 

2129 return base.event(self, event) 

2130 

2131 def keyPressEvent(self, key_event): 

2132 self.show_all = False 

2133 dt = self.tmax - self.tmin 

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

2135 

2136 key = key_event.key() 

2137 try: 

2138 keytext = str(key_event.text()) 

2139 except UnicodeEncodeError: 

2140 return 

2141 

2142 if key == qc.Qt.Key_Space: 

2143 self.interrupt_following() 

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

2145 

2146 elif key == qc.Qt.Key_Up: 

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_Down: 

2156 for m in self.selected_markers(): 

2157 if isinstance(m, PhaseMarker): 

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

2159 p = 0 

2160 else: 

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

2162 m.set_polarity(p) 

2163 

2164 elif key == qc.Qt.Key_B: 

2165 dt = self.tmax - self.tmin 

2166 self.interrupt_following() 

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

2168 

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

2170 self.interrupt_following() 

2171 

2172 tgo = None 

2173 

2174 class TraceDummy(object): 

2175 def __init__(self, marker): 

2176 self._marker = marker 

2177 

2178 @property 

2179 def nslc_id(self): 

2180 return self._marker.one_nslc() 

2181 

2182 def marker_to_itrack(marker): 

2183 try: 

2184 return self.key_to_row.get( 

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

2186 

2187 except MarkerOneNSLCRequired: 

2188 return -1 

2189 

2190 emarker, pmarkers = self.get_active_markers() 

2191 pmarkers = [ 

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

2193 pmarkers.sort(key=lambda m: ( 

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

2195 

2196 if key == qc.Qt.Key_Backtab: 

2197 pmarkers.reverse() 

2198 

2199 smarkers = self.selected_markers() 

2200 iselected = [] 

2201 for sm in smarkers: 

2202 try: 

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

2204 except ValueError: 

2205 pass 

2206 

2207 if iselected: 

2208 icurrent = max(iselected) + 1 

2209 else: 

2210 icurrent = 0 

2211 

2212 if icurrent < len(pmarkers): 

2213 self.deselect_all() 

2214 cmarker = pmarkers[icurrent] 

2215 cmarker.selected = True 

2216 tgo = cmarker.tmin 

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

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

2219 

2220 itrack = marker_to_itrack(cmarker) 

2221 if itrack != -1: 

2222 if itrack < self.shown_tracks_range[0]: 

2223 self.scroll_tracks( 

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

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

2226 self.scroll_tracks( 

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

2228 

2229 if itrack not in self.track_to_nslc_ids: 

2230 self.go_to_selection() 

2231 

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

2233 smarkers = self.selected_markers() 

2234 tgo = None 

2235 dir = str(keytext) 

2236 if smarkers: 

2237 tmid = smarkers[0].tmin 

2238 for smarker in smarkers: 

2239 if dir == 'n': 

2240 tmid = max(smarker.tmin, tmid) 

2241 else: 

2242 tmid = min(smarker.tmin, tmid) 

2243 

2244 tgo = tmid 

2245 

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

2247 for marker in sorted( 

2248 self.markers, 

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

2250 

2251 t = marker.tmin 

2252 if t > tmid and \ 

2253 marker.kind in self.visible_marker_kinds and \ 

2254 (dir == 'n' or 

2255 isinstance(marker, EventMarker)): 

2256 

2257 self.deselect_all() 

2258 marker.selected = True 

2259 tgo = t 

2260 break 

2261 else: 

2262 for marker in sorted( 

2263 self.markers, 

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

2265 reverse=True): 

2266 

2267 t = marker.tmin 

2268 if t < tmid and \ 

2269 marker.kind in self.visible_marker_kinds and \ 

2270 (dir == 'p' or 

2271 isinstance(marker, EventMarker)): 

2272 self.deselect_all() 

2273 marker.selected = True 

2274 tgo = t 

2275 break 

2276 

2277 if tgo is not None: 

2278 self.interrupt_following() 

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

2280 

2281 elif keytext == 'r': 

2282 if self.pile.reload_modified(): 

2283 self.reloaded = True 

2284 

2285 elif keytext == 'R': 

2286 self.setup_snufflings() 

2287 

2288 elif key == qc.Qt.Key_Backspace: 

2289 self.remove_selected_markers() 

2290 

2291 elif keytext == 'a': 

2292 for marker in self.markers: 

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

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

2295 marker.kind in self.visible_marker_kinds): 

2296 marker.selected = True 

2297 else: 

2298 marker.selected = False 

2299 

2300 elif keytext == 'A': 

2301 for marker in self.markers: 

2302 if marker.kind in self.visible_marker_kinds: 

2303 marker.selected = True 

2304 

2305 elif keytext == 'd': 

2306 self.deselect_all() 

2307 

2308 elif keytext == 'E': 

2309 self.deactivate_event_marker() 

2310 

2311 elif keytext == 'e': 

2312 markers = self.selected_markers() 

2313 event_markers_in_spe = [ 

2314 marker for marker in markers 

2315 if not isinstance(marker, PhaseMarker)] 

2316 

2317 phase_markers = [ 

2318 marker for marker in markers 

2319 if isinstance(marker, PhaseMarker)] 

2320 

2321 if len(event_markers_in_spe) == 1: 

2322 event_marker = event_markers_in_spe[0] 

2323 if not isinstance(event_marker, EventMarker): 

2324 nslcs = list(event_marker.nslc_ids) 

2325 lat, lon = 0.0, 0.0 

2326 old = self.get_active_event() 

2327 if len(nslcs) == 1: 

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

2329 elif old is not None: 

2330 lat, lon = old.lat, old.lon 

2331 

2332 event_marker.convert_to_event_marker(lat, lon) 

2333 

2334 self.set_active_event_marker(event_marker) 

2335 event = event_marker.get_event() 

2336 for marker in phase_markers: 

2337 marker.set_event(event) 

2338 

2339 else: 

2340 for marker in event_markers_in_spe: 

2341 marker.convert_to_event_marker() 

2342 

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

2344 for marker in self.selected_markers(): 

2345 marker.set_kind(int(keytext)) 

2346 self.emit_selected_markers() 

2347 

2348 elif key in fkey_map: 

2349 self.handle_fkeys(key) 

2350 

2351 elif key == qc.Qt.Key_Escape: 

2352 if self.picking: 

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

2354 

2355 elif key == qc.Qt.Key_PageDown: 

2356 self.scroll_tracks( 

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

2358 

2359 elif key == qc.Qt.Key_PageUp: 

2360 self.scroll_tracks( 

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

2362 

2363 elif key == qc.Qt.Key_Plus: 

2364 self.zoom_tracks(0., 1.) 

2365 

2366 elif key == qc.Qt.Key_Minus: 

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

2368 

2369 elif key == qc.Qt.Key_Equal: 

2370 ntracks_shown = self.shown_tracks_range[1] - \ 

2371 self.shown_tracks_range[0] 

2372 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2373 self.zoom_tracks(0., dtracks) 

2374 

2375 elif key == qc.Qt.Key_Colon: 

2376 self.want_input.emit() 

2377 

2378 elif keytext == 'f': 

2379 self.toggle_fullscreen() 

2380 

2381 elif keytext == 'g': 

2382 self.go_to_selection() 

2383 

2384 elif keytext == 'G': 

2385 self.go_to_selection(tight=True) 

2386 

2387 elif keytext == 'm': 

2388 self.toggle_marker_editor() 

2389 

2390 elif keytext == 'c': 

2391 self.toggle_main_controls() 

2392 

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

2394 dir = 1 

2395 amount = 1 

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

2397 dir = -1 

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

2399 amount = 10 

2400 self.nudge_selected_markers(dir*amount) 

2401 else: 

2402 super().keyPressEvent(key_event) 

2403 

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

2405 self.emit_selected_markers() 

2406 

2407 self.update() 

2408 self.update_status() 

2409 

2410 def handle_fkeys(self, key): 

2411 self.set_phase_kind( 

2412 self.selected_markers(), 

2413 fkey_map[key] + 1) 

2414 self.emit_selected_markers() 

2415 

2416 def emit_selected_markers(self): 

2417 ibounds = [] 

2418 last_selected = False 

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

2420 this_selected = marker.is_selected() 

2421 if this_selected != last_selected: 

2422 ibounds.append(imarker) 

2423 

2424 last_selected = this_selected 

2425 

2426 if last_selected: 

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

2428 

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

2430 self.n_selected_markers = sum( 

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

2432 self.marker_selection_changed.emit(chunks) 

2433 

2434 def toggle_marker_editor(self): 

2435 self.panel_parent.toggle_marker_editor() 

2436 

2437 def toggle_main_controls(self): 

2438 self.panel_parent.toggle_main_controls() 

2439 

2440 def nudge_selected_markers(self, npixels): 

2441 a, b = self.time_projection.ur 

2442 c, d = self.time_projection.xr 

2443 for marker in self.selected_markers(): 

2444 if not isinstance(marker, EventMarker): 

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

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

2447 

2448 def toggle_fullscreen(self): 

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

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

2451 self.window().showNormal() 

2452 else: 

2453 if macosx: 

2454 self.window().showMaximized() 

2455 else: 

2456 self.window().showFullScreen() 

2457 

2458 def about(self): 

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

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

2461 txt = f.read() 

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

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

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

2465 

2466 def help(self): 

2467 class MyScrollArea(qw.QScrollArea): 

2468 

2469 def sizeHint(self): 

2470 s = qc.QSize() 

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

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

2473 return s 

2474 

2475 with open(pyrocko.util.data_file( 

2476 'snuffler_help.html')) as f: 

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

2478 

2479 with open(pyrocko.util.data_file( 

2480 'snuffler_help_epilog.html')) as f: 

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

2482 

2483 for h in [hcheat, hepilog]: 

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

2485 h.setWordWrap(True) 

2486 

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

2488 

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

2490 scroller = qw.QScrollArea() 

2491 frame = qw.QFrame(scroller) 

2492 frame.setLineWidth(0) 

2493 layout = qw.QVBoxLayout() 

2494 layout.setContentsMargins(0, 0, 0, 0) 

2495 layout.setSpacing(0) 

2496 frame.setLayout(layout) 

2497 scroller.setWidget(frame) 

2498 scroller.setWidgetResizable(True) 

2499 frame.setBackgroundRole(qg.QPalette.Base) 

2500 for h in labels: 

2501 h.setParent(frame) 

2502 h.setMargin(3) 

2503 h.setTextInteractionFlags( 

2504 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2505 h.setBackgroundRole(qg.QPalette.Base) 

2506 layout.addWidget(h) 

2507 h.linkActivated.connect( 

2508 self.open_link) 

2509 

2510 if self.panel_parent is not None: 

2511 if target == 'panel': 

2512 self.panel_parent.add_panel( 

2513 name, scroller, True, volatile=False) 

2514 else: 

2515 self.panel_parent.add_tab(name, scroller) 

2516 

2517 def open_link(self, link): 

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

2519 

2520 def wheelEvent(self, wheel_event): 

2521 if use_pyqt5: 

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

2523 else: 

2524 self.wheel_pos += wheel_event.delta() 

2525 

2526 n = self.wheel_pos // 120 

2527 self.wheel_pos = self.wheel_pos % 120 

2528 if n == 0: 

2529 return 

2530 

2531 amount = max( 

2532 1., 

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

2534 wdelta = amount * n 

2535 

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

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

2538 / (trmax-trmin) 

2539 

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

2541 self.zoom_tracks(anchor, wdelta) 

2542 else: 

2543 self.scroll_tracks(-wdelta) 

2544 

2545 def dragEnterEvent(self, event): 

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

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

2548 event.setDropAction(qc.Qt.LinkAction) 

2549 event.accept() 

2550 

2551 def dropEvent(self, event): 

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

2553 paths = list( 

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

2555 event.acceptProposedAction() 

2556 self.load(paths) 

2557 

2558 def get_phase_name(self, kind): 

2559 return self.config.get_phase_name(kind) 

2560 

2561 def set_phase_kind(self, markers, kind): 

2562 phasename = self.get_phase_name(kind) 

2563 

2564 for marker in markers: 

2565 if isinstance(marker, PhaseMarker): 

2566 if kind == 10: 

2567 marker.convert_to_marker() 

2568 else: 

2569 marker.set_phasename(phasename) 

2570 marker.set_event(self.get_active_event()) 

2571 

2572 elif isinstance(marker, EventMarker): 

2573 pass 

2574 

2575 else: 

2576 if kind != 10: 

2577 event = self.get_active_event() 

2578 marker.convert_to_phase_marker( 

2579 event, phasename, None, False) 

2580 

2581 def set_ntracks(self, ntracks): 

2582 if self.ntracks != ntracks: 

2583 self.ntracks = ntracks 

2584 if self.shown_tracks_range is not None: 

2585 l, h = self.shown_tracks_range 

2586 else: 

2587 l, h = 0, self.ntracks 

2588 

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

2590 

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

2592 

2593 low, high = range 

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

2595 high = min(self.ntracks, high) 

2596 low = max(0, low) 

2597 high = max(1, high) 

2598 

2599 if start is None: 

2600 start = float(low) 

2601 

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

2603 self.shown_tracks_range = low, high 

2604 self.shown_tracks_start = start 

2605 

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

2607 

2608 def scroll_tracks(self, shift): 

2609 shown = self.shown_tracks_range 

2610 shiftmin = -shown[0] 

2611 shiftmax = self.ntracks-shown[1] 

2612 shift = max(shiftmin, shift) 

2613 shift = min(shiftmax, shift) 

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

2615 

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

2617 

2618 self.update() 

2619 

2620 def zoom_tracks(self, anchor, delta): 

2621 ntracks_shown = self.shown_tracks_range[1] \ 

2622 - self.shown_tracks_range[0] 

2623 

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

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

2626 return 

2627 

2628 ntracks_shown += int(round(delta)) 

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

2630 

2631 u = self.shown_tracks_start 

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

2633 nv = nu + ntracks_shown 

2634 if nv > self.ntracks: 

2635 nu -= nv - self.ntracks 

2636 nv -= nv - self.ntracks 

2637 

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

2639 

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

2641 - self.shown_tracks_range[0] 

2642 

2643 self.update() 

2644 

2645 def content_time_range(self): 

2646 pile = self.get_pile() 

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

2648 if tmin is None: 

2649 tmin = initial_time_range[0] 

2650 if tmax is None: 

2651 tmax = initial_time_range[1] 

2652 

2653 return tmin, tmax 

2654 

2655 def content_deltat_range(self): 

2656 pile = self.get_pile() 

2657 

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

2659 

2660 if deltatmin is None: 

2661 deltatmin = 0.001 

2662 

2663 if deltatmax is None: 

2664 deltatmax = 1000.0 

2665 

2666 return deltatmin, deltatmax 

2667 

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

2669 if tmax < tmin: 

2670 tmin, tmax = tmax, tmin 

2671 

2672 deltatmin = self.content_deltat_range()[0] 

2673 dt = deltatmin * self.visible_length * 0.95 

2674 

2675 if dt == 0.0: 

2676 dt = 1.0 

2677 

2678 if tight: 

2679 if tmax != tmin: 

2680 dtm = tmax - tmin 

2681 tmin -= dtm*0.1 

2682 tmax += dtm*0.1 

2683 return tmin, tmax 

2684 else: 

2685 tcenter = (tmin + tmax) / 2. 

2686 tmin = tcenter - 0.5*dt 

2687 tmax = tcenter + 0.5*dt 

2688 return tmin, tmax 

2689 

2690 if tmax-tmin < dt: 

2691 vmin, vmax = self.get_time_range() 

2692 dt = min(vmax - vmin, dt) 

2693 

2694 tcenter = (tmin+tmax)/2. 

2695 etmin, etmax = tmin, tmax 

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

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

2698 dtm = tmax-tmin 

2699 if etmin == tmin: 

2700 tmin -= dtm*0.1 

2701 if etmax == tmax: 

2702 tmax += dtm*0.1 

2703 

2704 else: 

2705 dtm = tmax-tmin 

2706 tmin -= dtm*0.1 

2707 tmax += dtm*0.1 

2708 

2709 return tmin, tmax 

2710 

2711 def go_to_selection(self, tight=False): 

2712 markers = self.selected_markers() 

2713 if markers: 

2714 tmax, tmin = self.content_time_range() 

2715 for marker in markers: 

2716 tmin = min(tmin, marker.tmin) 

2717 tmax = max(tmax, marker.tmax) 

2718 

2719 else: 

2720 if tight: 

2721 vmin, vmax = self.get_time_range() 

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

2723 else: 

2724 tmin, tmax = self.content_time_range() 

2725 

2726 tmin, tmax = self.make_good_looking_time_range( 

2727 tmin, tmax, tight=tight) 

2728 

2729 self.interrupt_following() 

2730 self.set_time_range(tmin, tmax) 

2731 self.update() 

2732 

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

2734 tmax = t 

2735 if tlen is not None: 

2736 tmax = t+tlen 

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

2738 self.interrupt_following() 

2739 self.set_time_range(tmin, tmax) 

2740 self.update() 

2741 

2742 def go_to_event_by_name(self, name): 

2743 for marker in self.markers: 

2744 if isinstance(marker, EventMarker): 

2745 event = marker.get_event() 

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

2747 tmin, tmax = self.make_good_looking_time_range( 

2748 event.time, event.time) 

2749 

2750 self.interrupt_following() 

2751 self.set_time_range(tmin, tmax) 

2752 

2753 def printit(self): 

2754 from .qt_compat import qprint 

2755 printer = qprint.QPrinter() 

2756 printer.setOrientation(qprint.QPrinter.Landscape) 

2757 

2758 dialog = qprint.QPrintDialog(printer, self) 

2759 dialog.setWindowTitle('Print') 

2760 

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

2762 return 

2763 

2764 painter = qg.QPainter() 

2765 painter.begin(printer) 

2766 page = printer.pageRect() 

2767 self.drawit( 

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

2769 

2770 painter.end() 

2771 

2772 def savesvg(self, fn=None): 

2773 

2774 if not fn: 

2775 fn, _ = fnpatch(qw.QFileDialog.getSaveFileName( 

2776 self, 

2777 'Save as SVG|PNG', 

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

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

2780 options=qfiledialog_options)) 

2781 

2782 if fn == '': 

2783 return 

2784 

2785 fn = str(fn) 

2786 

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

2788 try: 

2789 w, h = 842, 595 

2790 margin = 0.025 

2791 m = max(w, h)*margin 

2792 

2793 generator = qsvg.QSvgGenerator() 

2794 generator.setFileName(fn) 

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

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

2797 

2798 painter = qg.QPainter() 

2799 painter.begin(generator) 

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

2801 painter.end() 

2802 

2803 except Exception as e: 

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

2805 

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

2807 if use_pyqt5: 

2808 pixmap = self.grab() 

2809 else: 

2810 pixmap = qg.QPixmap().grabWidget(self) 

2811 

2812 try: 

2813 pixmap.save(fn) 

2814 

2815 except Exception as e: 

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

2817 

2818 else: 

2819 self.fail( 

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

2821 '".png".') 

2822 

2823 def paintEvent(self, paint_ev): 

2824 ''' 

2825 Called by QT whenever widget needs to be painted. 

2826 ''' 

2827 painter = qg.QPainter(self) 

2828 

2829 if self.menuitem_antialias.isChecked(): 

2830 painter.setRenderHint(qg.QPainter.Antialiasing) 

2831 

2832 self.drawit(painter) 

2833 

2834 logger.debug( 

2835 'Time spent drawing: ' 

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

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

2838 (self.timer_draw - self.timer_cutout)) 

2839 

2840 logger.debug( 

2841 'Time spent processing:' 

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

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

2844 self.timer_cutout.get()) 

2845 

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

2847 self.time_last_painted = time.time() 

2848 

2849 def determine_box_styles(self): 

2850 

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

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

2853 istyle = 0 

2854 trace_styles = {} 

2855 for itr, tr in enumerate(traces): 

2856 if itr > 0: 

2857 other = traces[itr-1] 

2858 if not ( 

2859 other.nslc_id == tr.nslc_id 

2860 and other.deltat == tr.deltat 

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

2862 < gap_lap_tolerance*tr.deltat): 

2863 

2864 istyle += 1 

2865 

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

2867 

2868 self.trace_styles = trace_styles 

2869 

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

2871 

2872 for v_projection in track_projections.values(): 

2873 v_projection.set_in_range(0., 1.) 

2874 

2875 def selector(x): 

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

2877 

2878 if self.trace_filter is not None: 

2879 def tselector(x): 

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

2881 

2882 else: 

2883 tselector = selector 

2884 

2885 traces = list(self.pile.iter_traces( 

2886 group_selector=selector, trace_selector=tselector)) 

2887 

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

2889 

2890 def drawbox(itrack, istyle, traces): 

2891 v_projection = track_projections[itrack] 

2892 dvmin = v_projection(0.) 

2893 dvmax = v_projection(1.) 

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

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

2896 

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

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

2899 p.fillRect(rect, style.fill_brush) 

2900 p.setPen(style.frame_pen) 

2901 p.drawRect(rect) 

2902 

2903 traces_by_style = {} 

2904 for itr, tr in enumerate(traces): 

2905 gt = self.gather(tr) 

2906 if gt not in self.key_to_row: 

2907 continue 

2908 

2909 itrack = self.key_to_row[gt] 

2910 if itrack not in track_projections: 

2911 continue 

2912 

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

2914 

2915 if len(traces) < 500: 

2916 drawbox(itrack, istyle, [tr]) 

2917 else: 

2918 if (itrack, istyle) not in traces_by_style: 

2919 traces_by_style[itrack, istyle] = [] 

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

2921 

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

2923 drawbox(itrack, istyle, traces) 

2924 

2925 def draw_visible_markers( 

2926 self, p, vcenter_projection, primary_pen): 

2927 

2928 try: 

2929 markers = self.markers.with_key_in_limited( 

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

2931 

2932 except pyrocko.pile.TooMany: 

2933 tmin = self.markers[0].tmin 

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

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

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

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

2938 v0, _ = vcenter_projection.get_out_range() 

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

2940 

2941 p.save() 

2942 

2943 pen = qg.QPen(primary_pen) 

2944 pen.setWidth(2) 

2945 pen.setStyle(qc.Qt.DotLine) 

2946 # pat = [5., 3.] 

2947 # pen.setDashPattern(pat) 

2948 p.setPen(pen) 

2949 

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

2951 s_selected = ' (all selected)' 

2952 elif self.n_selected_markers > 0: 

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

2954 else: 

2955 s_selected = '' 

2956 

2957 draw_label( 

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

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

2960 label_bg, 'LB') 

2961 

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

2963 p.drawLine(line) 

2964 p.restore() 

2965 

2966 return 

2967 

2968 for marker in markers: 

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

2970 and marker.kind in self.visible_marker_kinds: 

2971 

2972 marker.draw( 

2973 p, self.time_projection, vcenter_projection, 

2974 with_label=True) 

2975 

2976 def get_squirrel(self): 

2977 try: 

2978 return self.pile._squirrel 

2979 except AttributeError: 

2980 return None 

2981 

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

2983 sq = self.get_squirrel() 

2984 if sq is None: 

2985 return 

2986 

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

2988 v_projection = track_projections[itrack] 

2989 dvmin = v_projection(0.) 

2990 dvmax = v_projection(1.) 

2991 dtmin = time_projection.clipped(tmin) 

2992 dtmax = time_projection.clipped(tmax) 

2993 

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

2995 p.fillRect(rect, style.fill_brush) 

2996 p.setPen(style.frame_pen) 

2997 p.drawRect(rect) 

2998 

2999 pattern_list = [] 

3000 pattern_to_itrack = {} 

3001 for key in self.track_keys: 

3002 itrack = self.key_to_row[key] 

3003 if itrack not in track_projections: 

3004 continue 

3005 

3006 pattern = self.track_patterns[itrack] 

3007 pattern_to_itrack[tuple(pattern)] = itrack 

3008 pattern_list.append(pattern) 

3009 

3010 vmin, vmax = self.get_time_range() 

3011 

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

3013 for entry in sq.get_coverage( 

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

3015 pattern, codes, deltat, tmin, tmax, cover_data = entry 

3016 itrack = pattern_to_itrack[tuple(pattern)] 

3017 

3018 if cover_data is None: 

3019 drawbox( 

3020 itrack, tmin, tmax, 

3021 box_styles_coverage[kind][0]) 

3022 else: 

3023 t = None 

3024 pcount = 0 

3025 for tb, count in cover_data: 

3026 if t is not None and tb > t: 

3027 if pcount > 0: 

3028 drawbox( 

3029 itrack, t, tb, 

3030 box_styles_coverage[kind][ 

3031 min(len(box_styles_coverage)-1, 

3032 pcount)]) 

3033 

3034 t = tb 

3035 pcount = count 

3036 

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

3038 ''' 

3039 This performs the actual drawing. 

3040 ''' 

3041 

3042 self.timer_draw.start() 

3043 show_boxes = self.menuitem_showboxes.isChecked() 

3044 sq = self.get_squirrel() 

3045 

3046 if self.gather is None: 

3047 self.set_gathering() 

3048 

3049 if self.pile_has_changed: 

3050 

3051 if not self.sortingmode_change_delayed(): 

3052 self.sortingmode_change() 

3053 

3054 if show_boxes and sq is None: 

3055 self.determine_box_styles() 

3056 

3057 self.pile_has_changed = False 

3058 

3059 if h is None: 

3060 h = float(self.height()) 

3061 if w is None: 

3062 w = float(self.width()) 

3063 

3064 if printmode: 

3065 primary_color = (0, 0, 0) 

3066 else: 

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

3068 

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

3070 

3071 ax_h = float(self.ax_height) 

3072 

3073 vbottom_ax_projection = Projection() 

3074 vtop_ax_projection = Projection() 

3075 vcenter_projection = Projection() 

3076 

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

3078 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3079 vtop_ax_projection.set_out_range(0., ax_h) 

3080 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3081 vcenter_projection.set_in_range(0., 1.) 

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

3083 

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

3085 track_projections = {} 

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

3087 proj = Projection() 

3088 proj.set_out_range( 

3089 self.track_to_screen(i+0.05), 

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

3091 

3092 track_projections[i] = proj 

3093 

3094 if self.tmin > self.tmax: 

3095 return 

3096 

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

3098 vbottom_ax_projection.set_in_range(0, ax_h) 

3099 

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

3101 

3102 yscaler = pyrocko.plot.AutoScaler() 

3103 

3104 p.setPen(primary_pen) 

3105 

3106 font = qg.QFont() 

3107 font.setBold(True) 

3108 

3109 axannotfont = qg.QFont() 

3110 axannotfont.setBold(True) 

3111 axannotfont.setPointSize(8) 

3112 

3113 processed_traces = self.prepare_cutout2( 

3114 self.tmin, self.tmax, 

3115 trace_selector=self.trace_selector, 

3116 degap=self.menuitem_degap.isChecked(), 

3117 demean=self.menuitem_demean.isChecked()) 

3118 

3119 if not printmode and show_boxes: 

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

3121 or (self.view_mode is ViewMode.Waterfall 

3122 and not processed_traces): 

3123 

3124 if sq is None: 

3125 self.draw_trace_boxes( 

3126 p, self.time_projection, track_projections) 

3127 

3128 else: 

3129 self.draw_coverage( 

3130 p, self.time_projection, track_projections) 

3131 

3132 p.setFont(font) 

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

3134 

3135 color_lookup = dict( 

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

3137 

3138 self.track_to_nslc_ids = {} 

3139 nticks = 0 

3140 annot_labels = [] 

3141 

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

3143 waterfall = self.waterfall 

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

3145 waterfall.set_traces(processed_traces) 

3146 waterfall.set_cmap(self.waterfall_cmap) 

3147 waterfall.set_integrate(self.waterfall_integrate) 

3148 waterfall.set_clip( 

3149 self.waterfall_clip_min, self.waterfall_clip_max) 

3150 waterfall.show_absolute_values( 

3151 self.waterfall_show_absolute) 

3152 

3153 rect = qc.QRectF( 

3154 0, self.ax_height, 

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

3156 ) 

3157 waterfall.draw_waterfall(p, rect=rect) 

3158 

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

3160 show_scales = self.menuitem_showscalerange.isChecked() \ 

3161 or self.menuitem_showscaleaxis.isChecked() 

3162 

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

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

3165 - self.track_to_screen(0.05) 

3166 

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

3168 

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

3170 if self.menuitem_showscaleaxis.isChecked() \ 

3171 else 15 

3172 

3173 yscaler = pyrocko.plot.AutoScaler( 

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

3175 snap=show_scales 

3176 and not self.menuitem_showscaleaxis.isChecked()) 

3177 

3178 data_ranges = pyrocko.trace.minmax( 

3179 processed_traces, 

3180 key=self.scaling_key, 

3181 mode=self.scaling_base) 

3182 

3183 if not self.menuitem_fixscalerange.isChecked(): 

3184 self.old_data_ranges = data_ranges 

3185 else: 

3186 data_ranges.update(self.old_data_ranges) 

3187 

3188 self.apply_scaling_hooks(data_ranges) 

3189 

3190 trace_to_itrack = {} 

3191 track_scaling_keys = {} 

3192 track_scaling_colors = {} 

3193 for trace in processed_traces: 

3194 gt = self.gather(trace) 

3195 if gt not in self.key_to_row: 

3196 continue 

3197 

3198 itrack = self.key_to_row[gt] 

3199 if itrack not in track_projections: 

3200 continue 

3201 

3202 trace_to_itrack[trace] = itrack 

3203 

3204 if itrack not in self.track_to_nslc_ids: 

3205 self.track_to_nslc_ids[itrack] = set() 

3206 

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

3208 

3209 if itrack not in track_scaling_keys: 

3210 track_scaling_keys[itrack] = set() 

3211 

3212 scaling_key = self.scaling_key(trace) 

3213 track_scaling_keys[itrack].add(scaling_key) 

3214 

3215 color = pyrocko.plot.color( 

3216 color_lookup[self.color_gather(trace)]) 

3217 

3218 k = itrack, scaling_key 

3219 if k not in track_scaling_colors \ 

3220 and self.menuitem_colortraces.isChecked(): 

3221 track_scaling_colors[k] = color 

3222 else: 

3223 track_scaling_colors[k] = primary_color 

3224 

3225 # y axes, zero lines 

3226 trace_projections = {} 

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

3228 if itrack not in track_scaling_keys: 

3229 continue 

3230 uoff = 0 

3231 for scaling_key in track_scaling_keys[itrack]: 

3232 data_range = data_ranges[scaling_key] 

3233 dymin, dymax = data_range 

3234 ymin, ymax, yinc = yscaler.make_scale( 

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

3236 iexp = yscaler.make_exp(yinc) 

3237 factor = 10**iexp 

3238 trace_projection = track_projections[itrack].copy() 

3239 trace_projection.set_in_range(ymax, ymin) 

3240 trace_projections[itrack, scaling_key] = \ 

3241 trace_projection 

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

3243 vmin, vmax = trace_projection.get_out_range() 

3244 umax_zeroline = umax 

3245 uoffnext = uoff 

3246 

3247 if show_scales: 

3248 pen = qg.QPen(primary_pen) 

3249 k = itrack, scaling_key 

3250 if k in track_scaling_colors: 

3251 c = qg.QColor(*track_scaling_colors[ 

3252 itrack, scaling_key]) 

3253 

3254 pen.setColor(c) 

3255 

3256 p.setPen(pen) 

3257 if nlinesavail > 3: 

3258 if self.menuitem_showscaleaxis.isChecked(): 

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

3260 ny_annot = int( 

3261 math.floor(ymax/yinc) 

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

3263 

3264 for iy_annot in range(ny_annot): 

3265 y = ymin_annot + iy_annot*yinc 

3266 v = trace_projection(y) 

3267 line = qc.QLineF( 

3268 umax-10-uoff, v, umax-uoff, v) 

3269 

3270 p.drawLine(line) 

3271 if iy_annot == ny_annot - 1 \ 

3272 and iexp != 0: 

3273 sexp = ' &times; ' \ 

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

3275 else: 

3276 sexp = '' 

3277 

3278 snum = num_to_html(y/factor) 

3279 lab = Label( 

3280 p, 

3281 umax-20-uoff, 

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

3283 label_bg=None, 

3284 anchor='MR', 

3285 font=axannotfont, 

3286 color=c) 

3287 

3288 uoffnext = max( 

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

3290 

3291 annot_labels.append(lab) 

3292 if y == 0.: 

3293 umax_zeroline = \ 

3294 umax - 20 \ 

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

3296 - uoff 

3297 else: 

3298 if not show_boxes: 

3299 qpoints = make_QPolygonF( 

3300 [umax-20-uoff, 

3301 umax-10-uoff, 

3302 umax-10-uoff, 

3303 umax-20-uoff], 

3304 [vmax, vmax, vmin, vmin]) 

3305 p.drawPolyline(qpoints) 

3306 

3307 snum = num_to_html(ymin) 

3308 labmin = Label( 

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

3310 label_bg=None, 

3311 anchor='BR', 

3312 font=axannotfont, 

3313 color=c) 

3314 

3315 annot_labels.append(labmin) 

3316 snum = num_to_html(ymax) 

3317 labmax = Label( 

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

3319 label_bg=None, 

3320 anchor='TR', 

3321 font=axannotfont, 

3322 color=c) 

3323 

3324 annot_labels.append(labmax) 

3325 

3326 for lab in (labmin, labmax): 

3327 uoffnext = max( 

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

3329 

3330 if self.menuitem_showzeroline.isChecked(): 

3331 v = trace_projection(0.) 

3332 if vmin <= v <= vmax: 

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

3334 p.drawLine(line) 

3335 

3336 uoff = uoffnext 

3337 

3338 p.setFont(font) 

3339 p.setPen(primary_pen) 

3340 for trace in processed_traces: 

3341 if self.view_mode is not ViewMode.Wiggle: 

3342 break 

3343 

3344 if trace not in trace_to_itrack: 

3345 continue 

3346 

3347 itrack = trace_to_itrack[trace] 

3348 scaling_key = self.scaling_key(trace) 

3349 trace_projection = trace_projections[ 

3350 itrack, scaling_key] 

3351 

3352 vdata = trace_projection(trace.get_ydata()) 

3353 

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

3355 udata_max = float(self.time_projection( 

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

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

3358 

3359 qpoints = make_QPolygonF(udata, vdata) 

3360 

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

3362 vmin, vmax = trace_projection.get_out_range() 

3363 

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

3365 

3366 if self.menuitem_cliptraces.isChecked(): 

3367 p.setClipRect(trackrect) 

3368 

3369 if self.menuitem_colortraces.isChecked(): 

3370 color = pyrocko.plot.color( 

3371 color_lookup[self.color_gather(trace)]) 

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

3373 p.setPen(pen) 

3374 

3375 p.drawPolyline(qpoints) 

3376 

3377 if self.floating_marker: 

3378 self.floating_marker.draw_trace( 

3379 self, p, trace, 

3380 self.time_projection, trace_projection, 1.0) 

3381 

3382 for marker in self.markers.with_key_in( 

3383 self.tmin - self.markers_deltat_max, 

3384 self.tmax): 

3385 

3386 if marker.tmin < self.tmax \ 

3387 and self.tmin < marker.tmax \ 

3388 and marker.kind \ 

3389 in self.visible_marker_kinds: 

3390 marker.draw_trace( 

3391 self, p, trace, self.time_projection, 

3392 trace_projection, 1.0) 

3393 

3394 p.setPen(primary_pen) 

3395 

3396 if self.menuitem_cliptraces.isChecked(): 

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

3398 

3399 if self.floating_marker: 

3400 self.floating_marker.draw( 

3401 p, self.time_projection, vcenter_projection) 

3402 

3403 self.draw_visible_markers( 

3404 p, vcenter_projection, primary_pen) 

3405 

3406 p.setPen(primary_pen) 

3407 while font.pointSize() > 2: 

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

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

3410 - self.track_to_screen(0.05) 

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

3412 if nlinesavail > 1: 

3413 break 

3414 

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

3416 

3417 p.setFont(font) 

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

3419 

3420 for key in self.track_keys: 

3421 itrack = self.key_to_row[key] 

3422 if itrack in track_projections: 

3423 plabel = ' '.join( 

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

3425 lx = 10 

3426 ly = self.track_to_screen(itrack+0.5) 

3427 

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

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

3430 continue 

3431 

3432 contains_cursor = \ 

3433 self.track_to_screen(itrack) \ 

3434 < mouse_pos.y() \ 

3435 < self.track_to_screen(itrack+1) 

3436 

3437 if not contains_cursor: 

3438 continue 

3439 

3440 font_large = p.font() 

3441 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3442 p.setFont(font_large) 

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

3444 p.setFont(font) 

3445 

3446 for lab in annot_labels: 

3447 lab.draw() 

3448 

3449 self.timer_draw.stop() 

3450 

3451 def see_data_params(self): 

3452 

3453 min_deltat = self.content_deltat_range()[0] 

3454 

3455 # determine padding and downampling requirements 

3456 if self.lowpass is not None: 

3457 deltat_target = 1./self.lowpass * 0.25 

3458 ndecimate = min( 

3459 50, 

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

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

3462 else: 

3463 ndecimate = 1 

3464 tpad = min_deltat*5. 

3465 

3466 if self.highpass is not None: 

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

3468 

3469 nsee_points_per_trace = 5000*10 

3470 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3471 

3472 return ndecimate, tpad, tsee 

3473 

3474 def clean_update(self): 

3475 self.cached_processed_traces = None 

3476 self.update() 

3477 

3478 def get_adequate_tpad(self): 

3479 tpad = 0. 

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

3481 if f is not None: 

3482 tpad = max(tpad, 1.0/f) 

3483 

3484 for snuffling in self.snufflings: 

3485 if snuffling._post_process_hook_enabled \ 

3486 or snuffling._pre_process_hook_enabled: 

3487 

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

3489 

3490 return tpad 

3491 

3492 def prepare_cutout2( 

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

3494 demean=True, nmax=6000): 

3495 

3496 if self.pile.is_empty(): 

3497 return [] 

3498 

3499 nmax = self.visible_length 

3500 

3501 self.timer_cutout.start() 

3502 

3503 tsee = tmax-tmin 

3504 min_deltat_wo_decimate = tsee/nmax 

3505 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3506 

3507 min_deltat_allow = min_deltat_wo_decimate 

3508 if self.lowpass is not None: 

3509 target_deltat_lp = 0.25/self.lowpass 

3510 if target_deltat_lp > min_deltat_wo_decimate: 

3511 min_deltat_allow = min_deltat_w_decimate 

3512 

3513 min_deltat_allow = math.exp( 

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

3515 

3516 tmin_ = tmin 

3517 tmax_ = tmax 

3518 

3519 # fetch more than needed? 

3520 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3524 

3525 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3526 lphp = self.menuitem_lphp.isChecked() 

3527 ads = self.menuitem_allowdownsampling.isChecked() 

3528 

3529 tpad = self.get_adequate_tpad() 

3530 tpad = max(tpad, tsee) 

3531 

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

3533 vec = ( 

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

3535 self.highpass, fft_filtering, lphp, 

3536 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3537 ads, self.pile.get_update_count()) 

3538 

3539 if (self.cached_vec 

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

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

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

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

3544 and self.cached_processed_traces is not None): 

3545 

3546 logger.debug('Using cached traces') 

3547 processed_traces = self.cached_processed_traces 

3548 

3549 else: 

3550 processed_traces = [] 

3551 if self.pile.deltatmax >= min_deltat_allow: 

3552 

3553 def group_selector(gr): 

3554 return gr.deltatmax >= min_deltat_allow 

3555 

3556 if trace_selector is not None: 

3557 def trace_selectorx(tr): 

3558 return tr.deltat >= min_deltat_allow \ 

3559 and trace_selector(tr) 

3560 else: 

3561 def trace_selectorx(tr): 

3562 return tr.deltat >= min_deltat_allow 

3563 

3564 for traces in self.pile.chopper( 

3565 tmin=tmin, tmax=tmax, tpad=tpad, 

3566 want_incomplete=True, 

3567 degap=degap, 

3568 maxgap=gap_lap_tolerance, 

3569 maxlap=gap_lap_tolerance, 

3570 keep_current_files_open=True, 

3571 group_selector=group_selector, 

3572 trace_selector=trace_selectorx, 

3573 accessor_id=id(self), 

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

3575 include_last=True): 

3576 

3577 if demean: 

3578 for tr in traces: 

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

3580 continue 

3581 y = tr.get_ydata() 

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

3583 

3584 traces = self.pre_process_hooks(traces) 

3585 

3586 for trace in traces: 

3587 

3588 if not (trace.meta 

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

3590 

3591 if fft_filtering: 

3592 but = pyrocko.response.ButterworthResponse 

3593 multres = pyrocko.response.MultiplyResponse 

3594 if self.lowpass is not None \ 

3595 or self.highpass is not None: 

3596 

3597 it = num.arange( 

3598 trace.data_len(), dtype=float) 

3599 detr_data, m, b = detrend( 

3600 it, trace.get_ydata()) 

3601 

3602 trace.set_ydata(detr_data) 

3603 

3604 freqs, fdata = trace.spectrum( 

3605 pad_to_pow2=True, tfade=None) 

3606 

3607 nfreqs = fdata.size 

3608 

3609 key = (trace.deltat, nfreqs) 

3610 

3611 if key not in self.tf_cache: 

3612 resps = [] 

3613 if self.lowpass is not None: 

3614 resps.append(but( 

3615 order=4, 

3616 corner=self.lowpass, 

3617 type='low')) 

3618 

3619 if self.highpass is not None: 

3620 resps.append(but( 

3621 order=4, 

3622 corner=self.highpass, 

3623 type='high')) 

3624 

3625 resp = multres(resps) 

3626 self.tf_cache[key] = \ 

3627 resp.evaluate(freqs) 

3628 

3629 filtered_data = num.fft.irfft( 

3630 fdata*self.tf_cache[key] 

3631 )[:trace.data_len()] 

3632 

3633 retrended_data = retrend( 

3634 it, filtered_data, m, b) 

3635 

3636 trace.set_ydata(retrended_data) 

3637 

3638 else: 

3639 

3640 if ads and self.lowpass is not None: 

3641 while trace.deltat \ 

3642 < min_deltat_wo_decimate: 

3643 

3644 trace.downsample(2, demean=False) 

3645 

3646 fmax = 0.5/trace.deltat 

3647 if not lphp and ( 

3648 self.lowpass is not None 

3649 and self.highpass is not None 

3650 and self.lowpass < fmax 

3651 and self.highpass < fmax 

3652 and self.highpass < self.lowpass): 

3653 

3654 trace.bandpass( 

3655 2, self.highpass, self.lowpass) 

3656 else: 

3657 if self.lowpass is not None: 

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

3659 trace.lowpass( 

3660 4, self.lowpass, 

3661 demean=False) 

3662 

3663 if self.highpass is not None: 

3664 if self.lowpass is None \ 

3665 or self.highpass \ 

3666 < self.lowpass: 

3667 

3668 if self.highpass < \ 

3669 0.5/trace.deltat: 

3670 trace.highpass( 

3671 4, self.highpass, 

3672 demean=False) 

3673 

3674 processed_traces.append(trace) 

3675 

3676 if self.rotate != 0.0: 

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

3678 cphi = math.cos(phi) 

3679 sphi = math.sin(phi) 

3680 for a in processed_traces: 

3681 for b in processed_traces: 

3682 if (a.network == b.network 

3683 and a.station == b.station 

3684 and a.location == b.location 

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

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

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

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

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

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

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

3692 

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

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

3695 a.set_ydata(aydata) 

3696 b.set_ydata(bydata) 

3697 

3698 processed_traces = self.post_process_hooks(processed_traces) 

3699 

3700 self.cached_processed_traces = processed_traces 

3701 self.cached_vec = vec 

3702 

3703 chopped_traces = [] 

3704 for trace in processed_traces: 

3705 chop_tmin = tmin_ - trace.deltat*4 

3706 chop_tmax = tmax_ + trace.deltat*4 

3707 

3708 try: 

3709 ctrace = trace.chop( 

3710 chop_tmin, chop_tmax, 

3711 inplace=False) 

3712 

3713 except pyrocko.trace.NoData: 

3714 continue 

3715 

3716 if ctrace.data_len() < 2: 

3717 continue 

3718 

3719 chopped_traces.append(ctrace) 

3720 

3721 self.timer_cutout.stop() 

3722 return chopped_traces 

3723 

3724 def pre_process_hooks(self, traces): 

3725 for snuffling in self.snufflings: 

3726 if snuffling._pre_process_hook_enabled: 

3727 traces = snuffling.pre_process_hook(traces) 

3728 

3729 return traces 

3730 

3731 def post_process_hooks(self, traces): 

3732 for snuffling in self.snufflings: 

3733 if snuffling._post_process_hook_enabled: 

3734 traces = snuffling.post_process_hook(traces) 

3735 

3736 return traces 

3737 

3738 def visible_length_change(self, ignore=None): 

3739 for menuitem, vlen in self.menuitems_visible_length: 

3740 if menuitem.isChecked(): 

3741 self.visible_length = vlen 

3742 

3743 def scaling_base_change(self, ignore=None): 

3744 for menuitem, scaling_base in self.menuitems_scaling_base: 

3745 if menuitem.isChecked(): 

3746 self.scaling_base = scaling_base 

3747 

3748 def scalingmode_change(self, ignore=None): 

3749 for menuitem, scaling_key in self.menuitems_scaling: 

3750 if menuitem.isChecked(): 

3751 self.scaling_key = scaling_key 

3752 self.update() 

3753 

3754 def apply_scaling_hooks(self, data_ranges): 

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

3756 hook = self.scaling_hooks[k] 

3757 hook(data_ranges) 

3758 

3759 def viewmode_change(self, ignore=True): 

3760 for item, mode in self.menuitems_viewmode: 

3761 if item.isChecked(): 

3762 self.view_mode = mode 

3763 break 

3764 else: 

3765 raise AttributeError('unknown view mode') 

3766 

3767 items_waterfall_disabled = ( 

3768 self.menuitem_showscaleaxis, 

3769 self.menuitem_showscalerange, 

3770 self.menuitem_showzeroline, 

3771 self.menuitem_colortraces, 

3772 self.menuitem_cliptraces, 

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

3774 ) 

3775 

3776 if self.view_mode is ViewMode.Waterfall: 

3777 self.parent().show_colorbar_ctrl(True) 

3778 self.parent().show_gain_ctrl(False) 

3779 

3780 for item in items_waterfall_disabled: 

3781 item.setDisabled(True) 

3782 

3783 self.visible_length = 180. 

3784 else: 

3785 self.parent().show_colorbar_ctrl(False) 

3786 self.parent().show_gain_ctrl(True) 

3787 

3788 for item in items_waterfall_disabled: 

3789 item.setDisabled(False) 

3790 

3791 self.visible_length_change() 

3792 self.update() 

3793 

3794 def set_scaling_hook(self, k, hook): 

3795 self.scaling_hooks[k] = hook 

3796 

3797 def remove_scaling_hook(self, k): 

3798 del self.scaling_hooks[k] 

3799 

3800 def remove_scaling_hooks(self): 

3801 self.scaling_hooks = {} 

3802 

3803 def s_sortingmode_change(self, ignore=None): 

3804 for menuitem, valfunc in self.menuitems_ssorting: 

3805 if menuitem.isChecked(): 

3806 self._ssort = valfunc 

3807 

3808 self.sortingmode_change() 

3809 

3810 def sortingmode_change(self, ignore=None): 

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

3812 if menuitem.isChecked(): 

3813 self.set_gathering(gather, color) 

3814 

3815 self.sortingmode_change_time = time.time() 

3816 

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

3818 self.lowpass = value 

3819 self.passband_check() 

3820 self.tf_cache = {} 

3821 self.update() 

3822 

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

3824 self.highpass = value 

3825 self.passband_check() 

3826 self.tf_cache = {} 

3827 self.update() 

3828 

3829 def passband_check(self): 

3830 if self.highpass and self.lowpass \ 

3831 and self.highpass >= self.lowpass: 

3832 

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

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

3835 'deactivate the highpass.' 

3836 

3837 self.update_status() 

3838 else: 

3839 oldmess = self.message 

3840 self.message = None 

3841 if oldmess is not None: 

3842 self.update_status() 

3843 

3844 def gain_change(self, value, ignore): 

3845 self.gain = value 

3846 self.update() 

3847 

3848 def rot_change(self, value, ignore): 

3849 self.rotate = value 

3850 self.update() 

3851 

3852 def waterfall_cmap_change(self, cmap): 

3853 self.waterfall_cmap = cmap 

3854 self.update() 

3855 

3856 def waterfall_clip_change(self, clip_min, clip_max): 

3857 self.waterfall_clip_min = clip_min 

3858 self.waterfall_clip_max = clip_max 

3859 self.update() 

3860 

3861 def waterfall_show_absolute_change(self, toggle): 

3862 self.waterfall_show_absolute = toggle 

3863 self.update() 

3864 

3865 def waterfall_set_integrate(self, toggle): 

3866 self.waterfall_integrate = toggle 

3867 self.update() 

3868 

3869 def set_selected_markers(self, markers): 

3870 ''' 

3871 Set a list of markers selected 

3872 

3873 :param markers: list of markers 

3874 ''' 

3875 self.deselect_all() 

3876 for m in markers: 

3877 m.selected = True 

3878 

3879 self.update() 

3880 

3881 def deselect_all(self): 

3882 for marker in self.markers: 

3883 marker.selected = False 

3884 

3885 def animate_picking(self): 

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

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

3888 

3889 def get_nslc_ids_for_track(self, ftrack): 

3890 itrack = int(ftrack) 

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

3892 

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

3894 if self.picking: 

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

3896 self.picking = None 

3897 self.picking_down = None 

3898 self.picking_timer.stop() 

3899 self.picking_timer = None 

3900 if not abort: 

3901 self.add_marker(self.floating_marker) 

3902 self.floating_marker.selected = True 

3903 self.emit_selected_markers() 

3904 

3905 self.floating_marker = None 

3906 

3907 def start_picking(self, ignore): 

3908 

3909 if not self.picking: 

3910 self.deselect_all() 

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

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

3913 

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

3915 self.picking.setGeometry( 

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

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

3918 

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

3920 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3922 self.floating_marker.selected = True 

3923 

3924 self.picking_timer = qc.QTimer() 

3925 self.picking_timer.timeout.connect( 

3926 self.animate_picking) 

3927 

3928 self.picking_timer.setInterval(50) 

3929 self.picking_timer.start() 

3930 

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

3932 if self.picking: 

3933 mouset = self.time_projection.rev(x) 

3934 dt = 0.0 

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

3936 if mouset < self.tmin: 

3937 dt = -(self.tmin - mouset) 

3938 else: 

3939 dt = mouset - self.tmax 

3940 ddt = self.tmax-self.tmin 

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

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

3943 

3944 x0 = x 

3945 if self.picking_down is not None: 

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

3947 

3948 w = abs(x-x0) 

3949 x0 = min(x0, x) 

3950 

3951 tmin, tmax = ( 

3952 self.time_projection.rev(x0), 

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

3954 

3955 tmin, tmax = ( 

3956 max(working_system_time_range[0], tmin), 

3957 min(working_system_time_range[1], tmax)) 

3958 

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

3960 

3961 self.picking.setGeometry( 

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

3963 

3964 ftrack = self.track_to_screen.rev(y) 

3965 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3967 

3968 if dt != 0.0 and doshift: 

3969 self.interrupt_following() 

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

3971 

3972 self.update() 

3973 

3974 def update_status(self): 

3975 

3976 if self.message is None: 

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

3978 

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

3980 if not is_working_time(mouse_t): 

3981 return 

3982 

3983 if self.floating_marker: 

3984 tmi, tma = ( 

3985 self.floating_marker.tmin, 

3986 self.floating_marker.tmax) 

3987 

3988 tt, ms = gmtime_x(tmi) 

3989 

3990 if tmi == tma: 

3991 message = mystrftime( 

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

3993 tt=tt, milliseconds=ms) 

3994 else: 

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

3996 message = mystrftime( 

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

3998 tt=tt, milliseconds=ms) 

3999 else: 

4000 tt, ms = gmtime_x(mouse_t) 

4001 

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

4003 else: 

4004 message = self.message 

4005 

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

4007 sb.clearMessage() 

4008 sb.showMessage(message) 

4009 

4010 def set_sortingmode_change_delay_time(self, dt): 

4011 self.sortingmode_change_delay_time = dt 

4012 

4013 def sortingmode_change_delayed(self): 

4014 now = time.time() 

4015 return ( 

4016 self.sortingmode_change_delay_time is not None 

4017 and now - self.sortingmode_change_time 

4018 < self.sortingmode_change_delay_time) 

4019 

4020 def set_visible_marker_kinds(self, kinds): 

4021 self.deselect_all() 

4022 self.visible_marker_kinds = tuple(kinds) 

4023 self.emit_selected_markers() 

4024 

4025 def following(self): 

4026 return self.follow_timer is not None \ 

4027 and not self.following_interrupted() 

4028 

4029 def interrupt_following(self): 

4030 self.interactive_range_change_time = time.time() 

4031 

4032 def following_interrupted(self, now=None): 

4033 if now is None: 

4034 now = time.time() 

4035 return now - self.interactive_range_change_time \ 

4036 < self.interactive_range_change_delay_time 

4037 

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

4039 if tmax_start is None: 

4040 tmax_start = time.time() 

4041 self.show_all = False 

4042 self.follow_time = tlen 

4043 self.follow_timer = qc.QTimer(self) 

4044 self.follow_timer.timeout.connect( 

4045 self.follow_update) 

4046 self.follow_timer.setInterval(interval) 

4047 self.follow_timer.start() 

4048 self.follow_started = time.time() 

4049 self.follow_lapse = lapse 

4050 self.follow_tshift = self.follow_started - tmax_start 

4051 self.interactive_range_change_time = 0.0 

4052 

4053 def unfollow(self): 

4054 if self.follow_timer is not None: 

4055 self.follow_timer.stop() 

4056 self.follow_timer = None 

4057 self.interactive_range_change_time = 0.0 

4058 

4059 def follow_update(self): 

4060 rnow = time.time() 

4061 if self.follow_lapse is None: 

4062 now = rnow 

4063 else: 

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

4065 * self.follow_lapse 

4066 

4067 if self.following_interrupted(rnow): 

4068 return 

4069 self.set_time_range( 

4070 now-self.follow_time-self.follow_tshift, 

4071 now-self.follow_tshift) 

4072 

4073 self.update() 

4074 

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

4076 self.return_tag = return_tag 

4077 self.window().close() 

4078 

4079 def cleanup(self): 

4080 self.about_to_close.emit() 

4081 self.timer.stop() 

4082 if self.follow_timer is not None: 

4083 self.follow_timer.stop() 

4084 

4085 for snuffling in list(self.snufflings): 

4086 self.remove_snuffling(snuffling) 

4087 

4088 def set_error_message(self, key, value): 

4089 if value is None: 

4090 if key in self.error_messages: 

4091 del self.error_messages[key] 

4092 else: 

4093 self.error_messages[key] = value 

4094 

4095 def inputline_changed(self, text): 

4096 pass 

4097 

4098 def inputline_finished(self, text): 

4099 line = str(text) 

4100 

4101 toks = line.split() 

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

4103 if len(toks) >= 1: 

4104 command = toks[0].lower() 

4105 

4106 try: 

4107 quick_filter_commands = { 

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

4109 's': '*.%s.*.*', 

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

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

4112 

4113 if command in quick_filter_commands: 

4114 if len(toks) >= 2: 

4115 patterns = [ 

4116 quick_filter_commands[toks[0]] % pat 

4117 for pat in toks[1:]] 

4118 self.set_quick_filter_patterns(patterns, line) 

4119 else: 

4120 self.set_quick_filter_patterns(None) 

4121 

4122 self.update() 

4123 

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

4125 if len(toks) >= 2: 

4126 patterns = [] 

4127 if len(toks) == 2: 

4128 patterns = [toks[1]] 

4129 elif len(toks) >= 3: 

4130 x = { 

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

4132 's': '*.%s.*.*', 

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

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

4135 

4136 if toks[1] in x: 

4137 patterns.extend( 

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

4139 

4140 for pattern in patterns: 

4141 if command == 'hide': 

4142 self.add_blacklist_pattern(pattern) 

4143 else: 

4144 self.remove_blacklist_pattern(pattern) 

4145 

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

4147 self.clear_blacklist() 

4148 

4149 clearit = True 

4150 

4151 self.update() 

4152 

4153 elif command == 'markers': 

4154 if len(toks) == 2: 

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

4156 kinds = self.all_marker_kinds 

4157 else: 

4158 kinds = [] 

4159 for x in toks[1]: 

4160 try: 

4161 kinds.append(int(x)) 

4162 except Exception: 

4163 pass 

4164 

4165 self.set_visible_marker_kinds(kinds) 

4166 

4167 elif len(toks) == 1: 

4168 self.set_visible_marker_kinds(()) 

4169 

4170 self.update() 

4171 

4172 elif command == 'scaling': 

4173 if len(toks) == 2: 

4174 hideit = False 

4175 error = 'wrong number of arguments' 

4176 

4177 if len(toks) >= 3: 

4178 vmin, vmax = [ 

4179 pyrocko.model.float_or_none(x) 

4180 for x in toks[-2:]] 

4181 

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

4183 if k in d: 

4184 if vmin is not None: 

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

4186 if vmax is not None: 

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

4188 

4189 if len(toks) == 1: 

4190 self.remove_scaling_hooks() 

4191 

4192 elif len(toks) == 3: 

4193 def hook(data_ranges): 

4194 for k in data_ranges: 

4195 upd(data_ranges, k, vmin, vmax) 

4196 

4197 self.set_scaling_hook('_', hook) 

4198 

4199 elif len(toks) == 4: 

4200 pattern = toks[1] 

4201 

4202 def hook(data_ranges): 

4203 for k in pyrocko.util.match_nslcs( 

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

4205 

4206 upd(data_ranges, k, vmin, vmax) 

4207 

4208 self.set_scaling_hook(pattern, hook) 

4209 

4210 elif command == 'goto': 

4211 toks2 = line.split(None, 1) 

4212 if len(toks2) == 2: 

4213 arg = toks2[1] 

4214 m = re.match( 

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

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

4217 if m: 

4218 tlen = None 

4219 if not m.group(1): 

4220 tlen = 12*32*24*60*60 

4221 elif not m.group(2): 

4222 tlen = 32*24*60*60 

4223 elif not m.group(3): 

4224 tlen = 24*60*60 

4225 elif not m.group(4): 

4226 tlen = 60*60 

4227 elif not m.group(5): 

4228 tlen = 60 

4229 

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

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

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

4233 t = pyrocko.util.str_to_time(arg) 

4234 self.go_to_time(t, tlen=tlen) 

4235 

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

4237 supl = '00:00:00' 

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

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

4240 tmin, tmax = self.get_time_range() 

4241 sdate = pyrocko.util.time_to_str( 

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

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

4244 self.go_to_time(t) 

4245 

4246 elif arg == 'today': 

4247 self.go_to_time( 

4248 day_start( 

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

4250 

4251 elif arg == 'yesterday': 

4252 self.go_to_time( 

4253 day_start( 

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

4255 

4256 else: 

4257 self.go_to_event_by_name(arg) 

4258 

4259 else: 

4260 raise PileViewerMainException( 

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

4262 

4263 except PileViewerMainException as e: 

4264 error = str(e) 

4265 hideit = False 

4266 

4267 return clearit, hideit, error 

4268 

4269 return PileViewerMain 

4270 

4271 

4272PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4273GLPileViewerMain = MakePileViewerMainClass(qgl.QGLWidget) 

4274 

4275 

4276class LineEditWithAbort(qw.QLineEdit): 

4277 

4278 aborted = qc.pyqtSignal() 

4279 history_down = qc.pyqtSignal() 

4280 history_up = qc.pyqtSignal() 

4281 

4282 def keyPressEvent(self, key_event): 

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

4284 self.aborted.emit() 

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

4286 self.history_down.emit() 

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

4288 self.history_up.emit() 

4289 else: 

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

4291 

4292 

4293class PileViewer(qw.QFrame): 

4294 ''' 

4295 PileViewerMain + Controls + Inputline 

4296 ''' 

4297 

4298 def __init__( 

4299 self, pile, 

4300 ntracks_shown_max=20, 

4301 marker_editor_sortable=True, 

4302 use_opengl=False, 

4303 panel_parent=None, 

4304 *args): 

4305 

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

4307 

4308 layout = qw.QGridLayout() 

4309 layout.setContentsMargins(0, 0, 0, 0) 

4310 layout.setSpacing(0) 

4311 

4312 self.menu = PileViewerMenuBar(self) 

4313 

4314 if use_opengl: 

4315 self.viewer = GLPileViewerMain( 

4316 pile, 

4317 ntracks_shown_max=ntracks_shown_max, 

4318 panel_parent=panel_parent, 

4319 menu=self.menu) 

4320 else: 

4321 self.viewer = PileViewerMain( 

4322 pile, 

4323 ntracks_shown_max=ntracks_shown_max, 

4324 panel_parent=panel_parent, 

4325 menu=self.menu) 

4326 

4327 self.marker_editor_sortable = marker_editor_sortable 

4328 

4329 self.setFrameShape(qw.QFrame.StyledPanel) 

4330 self.setFrameShadow(qw.QFrame.Sunken) 

4331 

4332 self.input_area = qw.QFrame(self) 

4333 ia_layout = qw.QGridLayout() 

4334 ia_layout.setContentsMargins(11, 11, 11, 11) 

4335 self.input_area.setLayout(ia_layout) 

4336 

4337 self.inputline = LineEditWithAbort(self.input_area) 

4338 self.inputline.returnPressed.connect( 

4339 self.inputline_returnpressed) 

4340 self.inputline.editingFinished.connect( 

4341 self.inputline_finished) 

4342 self.inputline.aborted.connect( 

4343 self.inputline_aborted) 

4344 

4345 self.inputline.history_down.connect( 

4346 lambda: self.step_through_history(1)) 

4347 self.inputline.history_up.connect( 

4348 lambda: self.step_through_history(-1)) 

4349 

4350 self.inputline.textEdited.connect( 

4351 self.inputline_changed) 

4352 

4353 self.inputline.setPlaceholderText( 

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

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

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

4357 self.input_area.hide() 

4358 self.history = None 

4359 

4360 self.inputline_error_str = None 

4361 

4362 self.inputline_error = qw.QLabel() 

4363 self.inputline_error.hide() 

4364 

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

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

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

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

4369 

4370 pb = Progressbars(self) 

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

4372 self.progressbars = pb 

4373 

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

4375 self.scrollbar = scrollbar 

4376 layout.addWidget(scrollbar, 1, 1) 

4377 self.scrollbar.valueChanged.connect( 

4378 self.scrollbar_changed) 

4379 

4380 self.block_scrollbar_changes = False 

4381 

4382 self.viewer.want_input.connect( 

4383 self.inputline_show) 

4384 self.viewer.tracks_range_changed.connect( 

4385 self.tracks_range_changed) 

4386 self.viewer.pile_has_changed_signal.connect( 

4387 self.adjust_controls) 

4388 self.viewer.about_to_close.connect( 

4389 self.save_inputline_history) 

4390 

4391 self.setLayout(layout) 

4392 

4393 def cleanup(self): 

4394 self.viewer.cleanup() 

4395 

4396 def get_progressbars(self): 

4397 return self.progressbars 

4398 

4399 def inputline_show(self): 

4400 if not self.history: 

4401 self.load_inputline_history() 

4402 

4403 self.input_area.show() 

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

4405 self.inputline.selectAll() 

4406 

4407 def inputline_set_error(self, string): 

4408 self.inputline_error_str = string 

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

4410 self.inputline.selectAll() 

4411 self.inputline_error.setText(string) 

4412 self.input_area.show() 

4413 self.inputline_error.show() 

4414 

4415 def inputline_clear_error(self): 

4416 if self.inputline_error_str: 

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

4418 self.inputline_error_str = None 

4419 self.inputline_error.clear() 

4420 self.inputline_error.hide() 

4421 

4422 def inputline_changed(self, line): 

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

4424 self.inputline_clear_error() 

4425 

4426 def inputline_returnpressed(self): 

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

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

4429 

4430 if error: 

4431 self.inputline_set_error(error) 

4432 

4433 line = line.strip() 

4434 

4435 if line != '' and not error: 

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

4437 self.history.append(line) 

4438 

4439 if clearit: 

4440 

4441 self.inputline.blockSignals(True) 

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

4443 if qpat is None: 

4444 self.inputline.clear() 

4445 else: 

4446 self.inputline.setText(qinp) 

4447 self.inputline.blockSignals(False) 

4448 

4449 if hideit and not error: 

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

4451 self.input_area.hide() 

4452 

4453 self.hist_ind = len(self.history) 

4454 

4455 def inputline_aborted(self): 

4456 ''' 

4457 Hide the input line. 

4458 ''' 

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

4460 self.hist_ind = len(self.history) 

4461 self.input_area.hide() 

4462 

4463 def save_inputline_history(self): 

4464 ''' 

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

4466 ''' 

4467 if not self.history: 

4468 return 

4469 

4470 conf = pyrocko.config 

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

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

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

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

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

4476 

4477 def load_inputline_history(self): 

4478 ''' 

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

4480 ''' 

4481 conf = pyrocko.config 

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

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

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

4485 f.write('\n') 

4486 

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

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

4489 

4490 self.hist_ind = len(self.history) 

4491 

4492 def step_through_history(self, ud=1): 

4493 ''' 

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

4495 ''' 

4496 n = len(self.history) 

4497 self.hist_ind += ud 

4498 self.hist_ind %= (n + 1) 

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

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

4501 else: 

4502 self.inputline.setText('') 

4503 

4504 def inputline_finished(self): 

4505 pass 

4506 

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

4508 if self.block_scrollbar_changes: 

4509 return 

4510 

4511 self.scrollbar.blockSignals(True) 

4512 self.scrollbar.setPageStep(ihi-ilo) 

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

4514 self.scrollbar.setRange(0, vmax) 

4515 self.scrollbar.setValue(ilo) 

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

4517 self.scrollbar.blockSignals(False) 

4518 

4519 def scrollbar_changed(self, value): 

4520 self.block_scrollbar_changes = True 

4521 ilo = value 

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

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

4524 self.block_scrollbar_changes = False 

4525 self.update_contents() 

4526 

4527 def controls(self): 

4528 frame = qw.QFrame(self) 

4529 layout = qw.QGridLayout() 

4530 frame.setLayout(layout) 

4531 

4532 minfreq = 0.001 

4533 maxfreq = 1000.0 

4534 self.lowpass_control = ValControl(high_is_none=True) 

4535 self.lowpass_control.setup( 

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

4537 self.highpass_control = ValControl(low_is_none=True) 

4538 self.highpass_control.setup( 

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

4540 self.gain_control = ValControl() 

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

4542 self.rot_control = LinValControl() 

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

4544 self.colorbar_control = ColorbarControl(self) 

4545 

4546 self.lowpass_control.valchange.connect( 

4547 self.viewer.lowpass_change) 

4548 self.highpass_control.valchange.connect( 

4549 self.viewer.highpass_change) 

4550 self.gain_control.valchange.connect( 

4551 self.viewer.gain_change) 

4552 self.rot_control.valchange.connect( 

4553 self.viewer.rot_change) 

4554 self.colorbar_control.cmap_changed.connect( 

4555 self.viewer.waterfall_cmap_change 

4556 ) 

4557 self.colorbar_control.clip_changed.connect( 

4558 self.viewer.waterfall_clip_change 

4559 ) 

4560 self.colorbar_control.show_absolute_toggled.connect( 

4561 self.viewer.waterfall_show_absolute_change 

4562 ) 

4563 self.colorbar_control.show_integrate_toggled.connect( 

4564 self.viewer.waterfall_set_integrate 

4565 ) 

4566 

4567 for icontrol, control in enumerate(( 

4568 self.highpass_control, 

4569 self.lowpass_control, 

4570 self.gain_control, 

4571 self.rot_control, 

4572 self.colorbar_control)): 

4573 

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

4575 layout.addWidget(widget, icontrol, iwidget) 

4576 

4577 spacer = qw.QSpacerItem( 

4578 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4580 

4581 self.adjust_controls() 

4582 self.viewer.viewmode_change(ViewMode.Wiggle) 

4583 return frame 

4584 

4585 def marker_editor(self): 

4586 editor = pyrocko.gui.marker_editor.MarkerEditor( 

4587 self, sortable=self.marker_editor_sortable) 

4588 

4589 editor.set_viewer(self.get_view()) 

4590 editor.get_marker_model().dataChanged.connect( 

4591 self.update_contents) 

4592 return editor 

4593 

4594 def adjust_controls(self): 

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

4596 maxfreq = 0.5/dtmin 

4597 minfreq = (0.5/dtmax)*0.001 

4598 self.lowpass_control.set_range(minfreq, maxfreq) 

4599 self.highpass_control.set_range(minfreq, maxfreq) 

4600 

4601 def setup_snufflings(self): 

4602 self.viewer.setup_snufflings() 

4603 

4604 def get_view(self): 

4605 return self.viewer 

4606 

4607 def update_contents(self): 

4608 self.viewer.update() 

4609 

4610 def get_pile(self): 

4611 return self.viewer.get_pile() 

4612 

4613 def show_colorbar_ctrl(self, show): 

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

4615 w.setVisible(show) 

4616 

4617 def show_gain_ctrl(self, show): 

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

4619 w.setVisible(show)