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_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_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 self.cached_chopped_traces = {} 

1162 

1163 self.timer = qc.QTimer(self) 

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

1165 self.timer.setInterval(1000) 

1166 self.timer.start() 

1167 self.pile.add_listener(self) 

1168 self.trace_styles = {} 

1169 if self.get_squirrel() is None: 

1170 self.determine_box_styles() 

1171 

1172 self.setMouseTracking(True) 

1173 

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

1175 self.snuffling_modules = {} 

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

1177 self.default_snufflings = None 

1178 self.snufflings = [] 

1179 

1180 self.stations = {} 

1181 

1182 self.timer_draw = Timer() 

1183 self.timer_cutout = Timer() 

1184 self.time_spent_painting = 0.0 

1185 self.time_last_painted = time.time() 

1186 

1187 self.interactive_range_change_time = 0.0 

1188 self.interactive_range_change_delay_time = 10.0 

1189 self.follow_timer = None 

1190 

1191 self.sortingmode_change_time = 0.0 

1192 self.sortingmode_change_delay_time = None 

1193 

1194 self.old_data_ranges = {} 

1195 

1196 self.error_messages = {} 

1197 self.return_tag = None 

1198 self.wheel_pos = 60 

1199 

1200 self.setAcceptDrops(True) 

1201 self._paths_to_load = [] 

1202 

1203 self.tf_cache = {} 

1204 

1205 self.waterfall = TraceWaterfall() 

1206 self.waterfall_cmap = 'viridis' 

1207 self.waterfall_clip_min = 0. 

1208 self.waterfall_clip_max = 1. 

1209 self.waterfall_show_absolute = False 

1210 self.waterfall_integrate = False 

1211 self.view_mode = ViewMode.Wiggle 

1212 

1213 self.automatic_updates = True 

1214 

1215 self.closing = False 

1216 self.paint_timer = qc.QTimer(self) 

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

1218 self.paint_timer.setInterval(20) 

1219 self.paint_timer.start() 

1220 

1221 @qc.pyqtSlot() 

1222 def reset_updates(self): 

1223 if not self.updatesEnabled(): 

1224 self.setUpdatesEnabled(True) 

1225 

1226 def fail(self, reason): 

1227 box = qw.QMessageBox(self) 

1228 box.setText(reason) 

1229 box.exec_() 

1230 

1231 def set_trace_filter(self, filter_func): 

1232 self.trace_filter = filter_func 

1233 self.sortingmode_change() 

1234 

1235 def update_trace_filter(self): 

1236 if self.blacklist: 

1237 

1238 def blacklist_func(tr): 

1239 return not pyrocko.util.match_nslc( 

1240 self.blacklist, tr.nslc_id) 

1241 

1242 else: 

1243 blacklist_func = None 

1244 

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

1246 self.set_trace_filter(None) 

1247 elif self.quick_filter is None: 

1248 self.set_trace_filter(blacklist_func) 

1249 elif blacklist_func is None: 

1250 self.set_trace_filter(self.quick_filter) 

1251 else: 

1252 self.set_trace_filter( 

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

1254 

1255 def set_quick_filter(self, filter_func): 

1256 self.quick_filter = filter_func 

1257 self.update_trace_filter() 

1258 

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

1260 if patterns is not None: 

1261 self.set_quick_filter( 

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

1263 else: 

1264 self.set_quick_filter(None) 

1265 

1266 self.quick_filter_patterns = patterns, inputline 

1267 

1268 def get_quick_filter_patterns(self): 

1269 return self.quick_filter_patterns 

1270 

1271 def add_blacklist_pattern(self, pattern): 

1272 if pattern == 'empty': 

1273 keys = set(self.pile.nslc_ids) 

1274 trs = self.pile.all( 

1275 tmin=self.tmin, 

1276 tmax=self.tmax, 

1277 load_data=False, 

1278 degap=False) 

1279 

1280 for tr in trs: 

1281 if tr.nslc_id in keys: 

1282 keys.remove(tr.nslc_id) 

1283 

1284 for key in keys: 

1285 xpattern = '.'.join(key) 

1286 if xpattern not in self.blacklist: 

1287 self.blacklist.append(xpattern) 

1288 

1289 else: 

1290 if pattern in self.blacklist: 

1291 self.blacklist.remove(pattern) 

1292 

1293 self.blacklist.append(pattern) 

1294 

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

1296 self.update_trace_filter() 

1297 

1298 def remove_blacklist_pattern(self, pattern): 

1299 if pattern in self.blacklist: 

1300 self.blacklist.remove(pattern) 

1301 else: 

1302 raise PileViewerMainException( 

1303 'Pattern not found in blacklist.') 

1304 

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

1306 self.update_trace_filter() 

1307 

1308 def clear_blacklist(self): 

1309 self.blacklist = [] 

1310 self.update_trace_filter() 

1311 

1312 def ssort(self, tr): 

1313 return self._ssort(tr) 

1314 

1315 def station_key(self, x): 

1316 return x.network, x.station 

1317 

1318 def station_keys(self, x): 

1319 return [ 

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

1321 (x.network, x.station)] 

1322 

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

1324 for sk in self.station_keys(tr): 

1325 if sk in self.stations: 

1326 station = self.stations[sk] 

1327 return getter(station) 

1328 

1329 return default_getter(tr) 

1330 

1331 def get_station(self, sk): 

1332 return self.stations[sk] 

1333 

1334 def has_station(self, station): 

1335 for sk in self.station_keys(station): 

1336 if sk in self.stations: 

1337 return True 

1338 

1339 return False 

1340 

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

1342 return self.station_attrib( 

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

1344 

1345 def set_stations(self, stations): 

1346 self.stations = {} 

1347 self.add_stations(stations) 

1348 

1349 def add_stations(self, stations): 

1350 for station in stations: 

1351 for sk in self.station_keys(station): 

1352 self.stations[sk] = station 

1353 

1354 ev = self.get_active_event() 

1355 if ev: 

1356 self.set_origin(ev) 

1357 

1358 def add_event(self, event): 

1359 marker = EventMarker(event) 

1360 self.add_marker(marker) 

1361 

1362 def add_events(self, events): 

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

1364 self.add_markers(markers) 

1365 

1366 def set_event_marker_as_origin(self, ignore=None): 

1367 selected = self.selected_markers() 

1368 if not selected: 

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

1370 return 

1371 

1372 m = selected[0] 

1373 if not isinstance(m, EventMarker): 

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

1375 return 

1376 

1377 self.set_active_event_marker(m) 

1378 

1379 def deactivate_event_marker(self): 

1380 if self.active_event_marker: 

1381 self.active_event_marker.active = False 

1382 

1383 self.active_event_marker_changed.emit() 

1384 self.active_event_marker = None 

1385 

1386 def set_active_event_marker(self, event_marker): 

1387 if self.active_event_marker: 

1388 self.active_event_marker.active = False 

1389 

1390 self.active_event_marker = event_marker 

1391 event_marker.active = True 

1392 event = event_marker.get_event() 

1393 self.set_origin(event) 

1394 self.active_event_marker_changed.emit() 

1395 

1396 def set_active_event(self, event): 

1397 for marker in self.markers: 

1398 if isinstance(marker, EventMarker): 

1399 if marker.get_event() is event: 

1400 self.set_active_event_marker(marker) 

1401 

1402 def get_active_event_marker(self): 

1403 return self.active_event_marker 

1404 

1405 def get_active_event(self): 

1406 m = self.get_active_event_marker() 

1407 if m is not None: 

1408 return m.get_event() 

1409 else: 

1410 return None 

1411 

1412 def get_active_markers(self): 

1413 emarker = self.get_active_event_marker() 

1414 if emarker is None: 

1415 return None, [] 

1416 

1417 else: 

1418 ev = emarker.get_event() 

1419 pmarkers = [ 

1420 m for m in self.markers 

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

1422 

1423 return emarker, pmarkers 

1424 

1425 def set_origin(self, location): 

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

1427 station.set_event_relative_data( 

1428 location, 

1429 distance_3d=self.menuitem_distances_3d.isChecked()) 

1430 

1431 self.sortingmode_change() 

1432 

1433 def distances_3d_changed(self): 

1434 ignore = self.menuitem_distances_3d.isChecked() 

1435 self.set_event_marker_as_origin(ignore) 

1436 

1437 def iter_snuffling_modules(self): 

1438 pjoin = os.path.join 

1439 for path in self.snuffling_paths: 

1440 

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

1442 os.mkdir(path) 

1443 

1444 for entry in os.listdir(path): 

1445 directory = path 

1446 fn = entry 

1447 d = pjoin(path, entry) 

1448 if os.path.isdir(d): 

1449 directory = d 

1450 if os.path.isfile( 

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

1452 fn = 'snuffling.py' 

1453 

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

1455 continue 

1456 

1457 name = fn[:-3] 

1458 

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

1460 self.snuffling_modules[directory, name] = \ 

1461 pyrocko.gui.snuffling.SnufflingModule( 

1462 directory, name, self) 

1463 

1464 yield self.snuffling_modules[directory, name] 

1465 

1466 def setup_snufflings(self): 

1467 # user snufflings 

1468 for mod in self.iter_snuffling_modules(): 

1469 try: 

1470 mod.load_if_needed() 

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

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

1473 

1474 # load the default snufflings on first run 

1475 if self.default_snufflings is None: 

1476 self.default_snufflings = pyrocko.gui\ 

1477 .snufflings.__snufflings__() 

1478 for snuffling in self.default_snufflings: 

1479 self.add_snuffling(snuffling) 

1480 

1481 def set_panel_parent(self, panel_parent): 

1482 self.panel_parent = panel_parent 

1483 

1484 def get_panel_parent(self): 

1485 return self.panel_parent 

1486 

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

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

1489 snuffling.init_gui( 

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

1491 self.snufflings.append(snuffling) 

1492 self.update() 

1493 

1494 def remove_snuffling(self, snuffling): 

1495 snuffling.delete_gui() 

1496 self.update() 

1497 self.snufflings.remove(snuffling) 

1498 snuffling.pre_destroy() 

1499 

1500 def add_snuffling_menuitem(self, item): 

1501 self.snufflings_menu.addAction(item) 

1502 item.setParent(self.snufflings_menu) 

1503 sort_actions(self.snufflings_menu) 

1504 

1505 def remove_snuffling_menuitem(self, item): 

1506 self.snufflings_menu.removeAction(item) 

1507 

1508 def add_snuffling_help_menuitem(self, item): 

1509 self.snuffling_help.addAction(item) 

1510 item.setParent(self.snuffling_help) 

1511 sort_actions(self.snuffling_help) 

1512 

1513 def remove_snuffling_help_menuitem(self, item): 

1514 self.snuffling_help.removeAction(item) 

1515 

1516 def add_panel_toggler(self, item): 

1517 self.toggle_panel_menu.addAction(item) 

1518 item.setParent(self.toggle_panel_menu) 

1519 sort_actions(self.toggle_panel_menu) 

1520 

1521 def remove_panel_toggler(self, item): 

1522 self.toggle_panel_menu.removeAction(item) 

1523 

1524 def load(self, paths, regex=None, format='from_extension', 

1525 cache_dir=None, force_cache=False): 

1526 

1527 if cache_dir is None: 

1528 cache_dir = pyrocko.config.config().cache_dir 

1529 if isinstance(paths, str): 

1530 paths = [paths] 

1531 

1532 fns = pyrocko.util.select_files( 

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

1534 

1535 if not fns: 

1536 return 

1537 

1538 cache = pyrocko.pile.get_cache(cache_dir) 

1539 

1540 t = [time.time()] 

1541 

1542 def update_bar(label, value): 

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

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

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

1546 else: 

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

1548 

1549 return pbs.set_status(label, value) 

1550 

1551 def update_progress(label, i, n): 

1552 abort = False 

1553 

1554 qw.qApp.processEvents() 

1555 if n != 0: 

1556 perc = i*100/n 

1557 else: 

1558 perc = 100 

1559 abort |= update_bar(label, perc) 

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

1561 

1562 tnow = time.time() 

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

1564 self.update() 

1565 t[0] = tnow 

1566 

1567 return abort 

1568 

1569 self.automatic_updates = False 

1570 

1571 self.pile.load_files( 

1572 sorted(fns), 

1573 filename_attributes=regex, 

1574 cache=cache, 

1575 fileformat=format, 

1576 show_progress=False, 

1577 update_progress=update_progress) 

1578 

1579 self.automatic_updates = True 

1580 self.update() 

1581 

1582 def load_queued(self): 

1583 if not self._paths_to_load: 

1584 return 

1585 paths = self._paths_to_load 

1586 self._paths_to_load = [] 

1587 self.load(paths) 

1588 

1589 def load_soon(self, paths): 

1590 self._paths_to_load.extend(paths) 

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

1592 

1593 def open_waveforms(self): 

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

1595 

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

1597 self, caption, options=qfiledialog_options)) 

1598 

1599 if fns: 

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

1601 

1602 def open_waveform_directory(self): 

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

1604 

1605 dn = qw.QFileDialog.getExistingDirectory( 

1606 self, caption, options=qfiledialog_options) 

1607 

1608 if dn: 

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

1610 

1611 def open_stations(self, fns=None): 

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

1613 

1614 if not fns: 

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

1616 self, caption, options=qfiledialog_options)) 

1617 

1618 try: 

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

1620 for stat in stations: 

1621 self.add_stations(stat) 

1622 

1623 except Exception as e: 

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

1625 

1626 def open_stations_xml(self, fns=None): 

1627 from pyrocko.io import stationxml 

1628 

1629 caption = 'Select one or more StationXML files' 

1630 if not fns: 

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

1632 self, caption, options=qfiledialog_options, 

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

1634 ';;All files (*)')) 

1635 

1636 try: 

1637 stations = [ 

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

1639 for x in fns] 

1640 

1641 for stat in stations: 

1642 self.add_stations(stat) 

1643 

1644 except Exception as e: 

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

1646 

1647 def add_traces(self, traces): 

1648 if traces: 

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

1650 self.pile.add_file(mtf) 

1651 ticket = (self.pile, mtf) 

1652 return ticket 

1653 else: 

1654 return (None, None) 

1655 

1656 def release_data(self, tickets): 

1657 for ticket in tickets: 

1658 pile, mtf = ticket 

1659 if pile is not None: 

1660 pile.remove_file(mtf) 

1661 

1662 def periodical(self): 

1663 if self.menuitem_watch.isChecked(): 

1664 if self.pile.reload_modified(): 

1665 self.update() 

1666 

1667 def get_pile(self): 

1668 return self.pile 

1669 

1670 def pile_changed(self, what): 

1671 self.pile_has_changed = True 

1672 self.pile_has_changed_signal.emit() 

1673 if self.automatic_updates: 

1674 self.update() 

1675 

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

1677 

1678 if gather is None: 

1679 def gather_func(tr): 

1680 return tr.nslc_id 

1681 

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

1683 

1684 else: 

1685 def gather_func(tr): 

1686 return ( 

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

1688 

1689 if color is None: 

1690 def color(tr): 

1691 return tr.location 

1692 

1693 self.gather = gather_func 

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

1695 

1696 self.color_gather = color 

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

1698 previous_ntracks = self.ntracks 

1699 self.set_ntracks(len(keys)) 

1700 

1701 if self.shown_tracks_range is None or \ 

1702 previous_ntracks == 0 or \ 

1703 self.show_all: 

1704 

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

1706 key_at_top = None 

1707 n = high-low 

1708 

1709 else: 

1710 low, high = self.shown_tracks_range 

1711 key_at_top = self.track_keys[low] 

1712 n = high-low 

1713 

1714 self.track_keys = sorted(keys) 

1715 

1716 track_patterns = [] 

1717 for k in self.track_keys: 

1718 pat = ['*', '*', '*', '*'] 

1719 for i, j in enumerate(gather): 

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

1721 

1722 track_patterns.append(pat) 

1723 

1724 self.track_patterns = track_patterns 

1725 

1726 if key_at_top is not None: 

1727 try: 

1728 ind = self.track_keys.index(key_at_top) 

1729 low = ind 

1730 high = low+n 

1731 except Exception: 

1732 pass 

1733 

1734 self.set_tracks_range((low, high)) 

1735 

1736 self.key_to_row = dict( 

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

1738 

1739 def inrange(x, r): 

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

1741 

1742 def trace_selector(trace): 

1743 gt = self.gather(trace) 

1744 return ( 

1745 gt in self.key_to_row and 

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

1747 

1748 if self.trace_filter is not None: 

1749 self.trace_selector = lambda x: \ 

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

1751 else: 

1752 self.trace_selector = trace_selector 

1753 

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

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

1756 self.show_all: 

1757 

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

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

1760 tlen = (tmax - tmin) 

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

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

1763 

1764 def set_time_range(self, tmin, tmax): 

1765 if tmin is None: 

1766 tmin = initial_time_range[0] 

1767 

1768 if tmax is None: 

1769 tmax = initial_time_range[1] 

1770 

1771 if tmin > tmax: 

1772 tmin, tmax = tmax, tmin 

1773 

1774 if tmin == tmax: 

1775 tmin -= 1. 

1776 tmax += 1. 

1777 

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

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

1780 

1781 min_deltat = self.content_deltat_range()[0] 

1782 if (tmax - tmin < min_deltat): 

1783 m = (tmin + tmax) / 2. 

1784 tmin = m - min_deltat/2. 

1785 tmax = m + min_deltat/2. 

1786 

1787 self.time_projection.set_in_range(tmin, tmax) 

1788 self.tmin, self.tmax = tmin, tmax 

1789 

1790 def get_time_range(self): 

1791 return self.tmin, self.tmax 

1792 

1793 def ypart(self, y): 

1794 if y < self.ax_height: 

1795 return -1 

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

1797 return 1 

1798 else: 

1799 return 0 

1800 

1801 def time_fractional_digits(self): 

1802 min_deltat = self.content_deltat_range()[0] 

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

1804 

1805 def write_markers(self, fn=None): 

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

1807 if not fn: 

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

1809 self, caption, options=qfiledialog_options)) 

1810 if fn: 

1811 try: 

1812 Marker.save_markers( 

1813 self.markers, fn, 

1814 fdigits=self.time_fractional_digits()) 

1815 

1816 except Exception as e: 

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

1818 

1819 def write_selected_markers(self, fn=None): 

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

1821 if not fn: 

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

1823 self, caption, options=qfiledialog_options)) 

1824 if fn: 

1825 try: 

1826 Marker.save_markers( 

1827 self.iter_selected_markers(), 

1828 fn, 

1829 fdigits=self.time_fractional_digits()) 

1830 

1831 except Exception as e: 

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

1833 

1834 def read_events(self, fn=None): 

1835 ''' 

1836 Open QFileDialog to open, read and add 

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

1838 representation to the pile viewer. 

1839 ''' 

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

1841 if not fn: 

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

1843 self, caption, options=qfiledialog_options)) 

1844 if fn: 

1845 try: 

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

1847 self.associate_phases_to_events() 

1848 

1849 except Exception as e: 

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

1851 

1852 def read_markers(self, fn=None): 

1853 ''' 

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

1855 ''' 

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

1857 if not fn: 

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

1859 self, caption, options=qfiledialog_options)) 

1860 if fn: 

1861 try: 

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

1863 self.associate_phases_to_events() 

1864 

1865 except Exception as e: 

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

1867 

1868 def associate_phases_to_events(self): 

1869 associate_phases_to_events(self.markers) 

1870 

1871 def add_marker(self, marker): 

1872 # need index to inform QAbstactTableModel about upcoming change, 

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

1874 self.markers.insert(marker) 

1875 i = self.markers.remove(marker) 

1876 

1877 self.begin_markers_add.emit(i, i) 

1878 self.markers.insert(marker) 

1879 self.end_markers_add.emit() 

1880 self.markers_deltat_max = max( 

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

1882 

1883 def add_markers(self, markers): 

1884 if not self.markers: 

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

1886 self.markers.insert_many(markers) 

1887 self.end_markers_add.emit() 

1888 self.update_markers_deltat_max() 

1889 else: 

1890 for marker in markers: 

1891 self.add_marker(marker) 

1892 

1893 def update_markers_deltat_max(self): 

1894 if self.markers: 

1895 self.markers_deltat_max = max( 

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

1897 

1898 def remove_marker(self, marker): 

1899 ''' 

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

1901 

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

1903 ''' 

1904 

1905 if marker is self.active_event_marker: 

1906 self.deactivate_event_marker() 

1907 

1908 try: 

1909 i = self.markers.index(marker) 

1910 self.begin_markers_remove.emit(i, i) 

1911 self.markers.remove_at(i) 

1912 self.end_markers_remove.emit() 

1913 except ValueError: 

1914 pass 

1915 

1916 def remove_markers(self, markers): 

1917 ''' 

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

1919 

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

1921 instances 

1922 ''' 

1923 

1924 if markers is self.markers: 

1925 markers = list(markers) 

1926 

1927 for marker in markers: 

1928 self.remove_marker(marker) 

1929 

1930 self.update_markers_deltat_max() 

1931 

1932 def remove_selected_markers(self): 

1933 def delete_segment(istart, iend): 

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

1935 for _ in range(iend - istart): 

1936 self.markers.remove_at(istart) 

1937 

1938 self.end_markers_remove.emit() 

1939 

1940 istart = None 

1941 ipos = 0 

1942 markers = self.markers 

1943 nmarkers = len(self.markers) 

1944 while ipos < nmarkers: 

1945 marker = markers[ipos] 

1946 if marker.is_selected(): 

1947 if marker is self.active_event_marker: 

1948 self.deactivate_event_marker() 

1949 

1950 if istart is None: 

1951 istart = ipos 

1952 else: 

1953 if istart is not None: 

1954 delete_segment(istart, ipos) 

1955 nmarkers -= ipos - istart 

1956 ipos = istart - 1 

1957 istart = None 

1958 

1959 ipos += 1 

1960 

1961 if istart is not None: 

1962 delete_segment(istart, ipos) 

1963 

1964 self.update_markers_deltat_max() 

1965 

1966 def selected_markers(self): 

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

1968 

1969 def iter_selected_markers(self): 

1970 for marker in self.markers: 

1971 if marker.is_selected(): 

1972 yield marker 

1973 

1974 def get_markers(self): 

1975 return self.markers 

1976 

1977 def mousePressEvent(self, mouse_ev): 

1978 self.show_all = False 

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

1980 

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

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

1983 if self.picking: 

1984 if self.picking_down is None: 

1985 self.picking_down = ( 

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

1987 mouse_ev.y()) 

1988 

1989 elif marker is not None: 

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

1991 self.deselect_all() 

1992 marker.selected = True 

1993 self.emit_selected_markers() 

1994 self.update() 

1995 else: 

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

1997 self.track_trange = self.tmin, self.tmax 

1998 

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

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

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

2002 self.update_status() 

2003 

2004 def mouseReleaseEvent(self, mouse_ev): 

2005 if self.ignore_releases: 

2006 self.ignore_releases -= 1 

2007 return 

2008 

2009 if self.picking: 

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

2011 self.emit_selected_markers() 

2012 

2013 if self.track_start: 

2014 self.update() 

2015 

2016 self.track_start = None 

2017 self.track_trange = None 

2018 self.update_status() 

2019 

2020 def mouseDoubleClickEvent(self, mouse_ev): 

2021 self.show_all = False 

2022 self.start_picking(None) 

2023 self.ignore_releases = 1 

2024 

2025 def mouseMoveEvent(self, mouse_ev): 

2026 self.setUpdatesEnabled(False) 

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

2028 

2029 if self.picking: 

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

2031 

2032 elif self.track_start is not None: 

2033 x0, y0 = self.track_start 

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

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

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

2037 dy = 0 

2038 

2039 tmin0, tmax0 = self.track_trange 

2040 

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

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

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

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

2045 

2046 self.interrupt_following() 

2047 self.set_time_range( 

2048 tmin0 - dt - dtr*frac, 

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

2050 

2051 self.update() 

2052 else: 

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

2054 

2055 self.update_status() 

2056 

2057 def nslc_ids_under_cursor(self, x, y): 

2058 ftrack = self.track_to_screen.rev(y) 

2059 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2060 return nslc_ids 

2061 

2062 def marker_under_cursor(self, x, y): 

2063 mouset = self.time_projection.rev(x) 

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

2065 relevant_nslc_ids = None 

2066 for marker in self.markers: 

2067 if marker.kind not in self.visible_marker_kinds: 

2068 continue 

2069 

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

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

2072 

2073 if relevant_nslc_ids is None: 

2074 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2075 

2076 marker_nslc_ids = marker.get_nslc_ids() 

2077 if not marker_nslc_ids: 

2078 return marker 

2079 

2080 for nslc_id in marker_nslc_ids: 

2081 if nslc_id in relevant_nslc_ids: 

2082 return marker 

2083 

2084 def hoovering(self, x, y): 

2085 mouset = self.time_projection.rev(x) 

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

2087 needupdate = False 

2088 haveone = False 

2089 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2090 for marker in self.markers: 

2091 if marker.kind not in self.visible_marker_kinds: 

2092 continue 

2093 

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

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

2096 

2097 if state: 

2098 xstate = False 

2099 

2100 marker_nslc_ids = marker.get_nslc_ids() 

2101 if not marker_nslc_ids: 

2102 xstate = True 

2103 

2104 for nslc in relevant_nslc_ids: 

2105 if marker.match_nslc(nslc): 

2106 xstate = True 

2107 

2108 state = xstate 

2109 

2110 if state: 

2111 haveone = True 

2112 oldstate = marker.is_alerted() 

2113 if oldstate != state: 

2114 needupdate = True 

2115 marker.set_alerted(state) 

2116 if state: 

2117 self.message = marker.hoover_message() 

2118 

2119 if not haveone: 

2120 self.message = None 

2121 

2122 if needupdate: 

2123 self.update() 

2124 

2125 def event(self, event): 

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

2127 self.keyPressEvent(event) 

2128 return True 

2129 else: 

2130 return base.event(self, event) 

2131 

2132 def keyPressEvent(self, key_event): 

2133 self.show_all = False 

2134 dt = self.tmax - self.tmin 

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

2136 

2137 key = key_event.key() 

2138 try: 

2139 keytext = str(key_event.text()) 

2140 except UnicodeEncodeError: 

2141 return 

2142 

2143 if key == qc.Qt.Key_Space: 

2144 self.interrupt_following() 

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

2146 

2147 elif key == qc.Qt.Key_Up: 

2148 for m in self.selected_markers(): 

2149 if isinstance(m, PhaseMarker): 

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

2151 p = 0 

2152 else: 

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

2154 m.set_polarity(p) 

2155 

2156 elif key == qc.Qt.Key_Down: 

2157 for m in self.selected_markers(): 

2158 if isinstance(m, PhaseMarker): 

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

2160 p = 0 

2161 else: 

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

2163 m.set_polarity(p) 

2164 

2165 elif key == qc.Qt.Key_B: 

2166 dt = self.tmax - self.tmin 

2167 self.interrupt_following() 

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

2169 

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

2171 self.interrupt_following() 

2172 

2173 tgo = None 

2174 

2175 class TraceDummy(object): 

2176 def __init__(self, marker): 

2177 self._marker = marker 

2178 

2179 @property 

2180 def nslc_id(self): 

2181 return self._marker.one_nslc() 

2182 

2183 def marker_to_itrack(marker): 

2184 try: 

2185 return self.key_to_row.get( 

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

2187 

2188 except MarkerOneNSLCRequired: 

2189 return -1 

2190 

2191 emarker, pmarkers = self.get_active_markers() 

2192 pmarkers = [ 

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

2194 pmarkers.sort(key=lambda m: ( 

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

2196 

2197 if key == qc.Qt.Key_Backtab: 

2198 pmarkers.reverse() 

2199 

2200 smarkers = self.selected_markers() 

2201 iselected = [] 

2202 for sm in smarkers: 

2203 try: 

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

2205 except ValueError: 

2206 pass 

2207 

2208 if iselected: 

2209 icurrent = max(iselected) + 1 

2210 else: 

2211 icurrent = 0 

2212 

2213 if icurrent < len(pmarkers): 

2214 self.deselect_all() 

2215 cmarker = pmarkers[icurrent] 

2216 cmarker.selected = True 

2217 tgo = cmarker.tmin 

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

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

2220 

2221 itrack = marker_to_itrack(cmarker) 

2222 if itrack != -1: 

2223 if itrack < self.shown_tracks_range[0]: 

2224 self.scroll_tracks( 

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

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

2227 self.scroll_tracks( 

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

2229 

2230 if itrack not in self.track_to_nslc_ids: 

2231 self.go_to_selection() 

2232 

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

2234 smarkers = self.selected_markers() 

2235 tgo = None 

2236 dir = str(keytext) 

2237 if smarkers: 

2238 tmid = smarkers[0].tmin 

2239 for smarker in smarkers: 

2240 if dir == 'n': 

2241 tmid = max(smarker.tmin, tmid) 

2242 else: 

2243 tmid = min(smarker.tmin, tmid) 

2244 

2245 tgo = tmid 

2246 

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

2248 for marker in sorted( 

2249 self.markers, 

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

2251 

2252 t = marker.tmin 

2253 if t > tmid and \ 

2254 marker.kind in self.visible_marker_kinds and \ 

2255 (dir == 'n' or 

2256 isinstance(marker, EventMarker)): 

2257 

2258 self.deselect_all() 

2259 marker.selected = True 

2260 tgo = t 

2261 break 

2262 else: 

2263 for marker in sorted( 

2264 self.markers, 

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

2266 reverse=True): 

2267 

2268 t = marker.tmin 

2269 if t < tmid and \ 

2270 marker.kind in self.visible_marker_kinds and \ 

2271 (dir == 'p' or 

2272 isinstance(marker, EventMarker)): 

2273 self.deselect_all() 

2274 marker.selected = True 

2275 tgo = t 

2276 break 

2277 

2278 if tgo is not None: 

2279 self.interrupt_following() 

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

2281 

2282 elif keytext == 'r': 

2283 if self.pile.reload_modified(): 

2284 self.reloaded = True 

2285 

2286 elif keytext == 'R': 

2287 self.setup_snufflings() 

2288 

2289 elif key == qc.Qt.Key_Backspace: 

2290 self.remove_selected_markers() 

2291 

2292 elif keytext == 'a': 

2293 for marker in self.markers: 

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

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

2296 marker.kind in self.visible_marker_kinds): 

2297 marker.selected = True 

2298 else: 

2299 marker.selected = False 

2300 

2301 elif keytext == 'A': 

2302 for marker in self.markers: 

2303 if marker.kind in self.visible_marker_kinds: 

2304 marker.selected = True 

2305 

2306 elif keytext == 'd': 

2307 self.deselect_all() 

2308 

2309 elif keytext == 'E': 

2310 self.deactivate_event_marker() 

2311 

2312 elif keytext == 'e': 

2313 markers = self.selected_markers() 

2314 event_markers_in_spe = [ 

2315 marker for marker in markers 

2316 if not isinstance(marker, PhaseMarker)] 

2317 

2318 phase_markers = [ 

2319 marker for marker in markers 

2320 if isinstance(marker, PhaseMarker)] 

2321 

2322 if len(event_markers_in_spe) == 1: 

2323 event_marker = event_markers_in_spe[0] 

2324 if not isinstance(event_marker, EventMarker): 

2325 nslcs = list(event_marker.nslc_ids) 

2326 lat, lon = 0.0, 0.0 

2327 old = self.get_active_event() 

2328 if len(nslcs) == 1: 

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

2330 elif old is not None: 

2331 lat, lon = old.lat, old.lon 

2332 

2333 event_marker.convert_to_event_marker(lat, lon) 

2334 

2335 self.set_active_event_marker(event_marker) 

2336 event = event_marker.get_event() 

2337 for marker in phase_markers: 

2338 marker.set_event(event) 

2339 

2340 else: 

2341 for marker in event_markers_in_spe: 

2342 marker.convert_to_event_marker() 

2343 

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

2345 for marker in self.selected_markers(): 

2346 marker.set_kind(int(keytext)) 

2347 self.emit_selected_markers() 

2348 

2349 elif key in fkey_map: 

2350 self.handle_fkeys(key) 

2351 

2352 elif key == qc.Qt.Key_Escape: 

2353 if self.picking: 

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

2355 

2356 elif key == qc.Qt.Key_PageDown: 

2357 self.scroll_tracks( 

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

2359 

2360 elif key == qc.Qt.Key_PageUp: 

2361 self.scroll_tracks( 

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

2363 

2364 elif key == qc.Qt.Key_Plus: 

2365 self.zoom_tracks(0., 1.) 

2366 

2367 elif key == qc.Qt.Key_Minus: 

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

2369 

2370 elif key == qc.Qt.Key_Equal: 

2371 ntracks_shown = self.shown_tracks_range[1] - \ 

2372 self.shown_tracks_range[0] 

2373 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2374 self.zoom_tracks(0., dtracks) 

2375 

2376 elif key == qc.Qt.Key_Colon: 

2377 self.want_input.emit() 

2378 

2379 elif keytext == 'f': 

2380 self.toggle_fullscreen() 

2381 

2382 elif keytext == 'g': 

2383 self.go_to_selection() 

2384 

2385 elif keytext == 'G': 

2386 self.go_to_selection(tight=True) 

2387 

2388 elif keytext == 'm': 

2389 self.toggle_marker_editor() 

2390 

2391 elif keytext == 'c': 

2392 self.toggle_main_controls() 

2393 

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

2395 dir = 1 

2396 amount = 1 

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

2398 dir = -1 

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

2400 amount = 10 

2401 self.nudge_selected_markers(dir*amount) 

2402 else: 

2403 super().keyPressEvent(key_event) 

2404 

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

2406 self.emit_selected_markers() 

2407 

2408 self.update() 

2409 self.update_status() 

2410 

2411 def handle_fkeys(self, key): 

2412 self.set_phase_kind( 

2413 self.selected_markers(), 

2414 fkey_map[key] + 1) 

2415 self.emit_selected_markers() 

2416 

2417 def emit_selected_markers(self): 

2418 ibounds = [] 

2419 last_selected = False 

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

2421 this_selected = marker.is_selected() 

2422 if this_selected != last_selected: 

2423 ibounds.append(imarker) 

2424 

2425 last_selected = this_selected 

2426 

2427 if last_selected: 

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

2429 

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

2431 self.n_selected_markers = sum( 

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

2433 self.marker_selection_changed.emit(chunks) 

2434 

2435 def toggle_marker_editor(self): 

2436 self.panel_parent.toggle_marker_editor() 

2437 

2438 def toggle_main_controls(self): 

2439 self.panel_parent.toggle_main_controls() 

2440 

2441 def nudge_selected_markers(self, npixels): 

2442 a, b = self.time_projection.ur 

2443 c, d = self.time_projection.xr 

2444 for marker in self.selected_markers(): 

2445 if not isinstance(marker, EventMarker): 

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

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

2448 

2449 def toggle_fullscreen(self): 

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

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

2452 self.window().showNormal() 

2453 else: 

2454 if macosx: 

2455 self.window().showMaximized() 

2456 else: 

2457 self.window().showFullScreen() 

2458 

2459 def about(self): 

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

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

2462 txt = f.read() 

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

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

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

2466 

2467 def help(self): 

2468 class MyScrollArea(qw.QScrollArea): 

2469 

2470 def sizeHint(self): 

2471 s = qc.QSize() 

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

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

2474 return s 

2475 

2476 with open(pyrocko.util.data_file( 

2477 'snuffler_help.html')) as f: 

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

2479 

2480 with open(pyrocko.util.data_file( 

2481 'snuffler_help_epilog.html')) as f: 

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

2483 

2484 for h in [hcheat, hepilog]: 

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

2486 h.setWordWrap(True) 

2487 

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

2489 

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

2491 scroller = qw.QScrollArea() 

2492 frame = qw.QFrame(scroller) 

2493 frame.setLineWidth(0) 

2494 layout = qw.QVBoxLayout() 

2495 layout.setContentsMargins(0, 0, 0, 0) 

2496 layout.setSpacing(0) 

2497 frame.setLayout(layout) 

2498 scroller.setWidget(frame) 

2499 scroller.setWidgetResizable(True) 

2500 frame.setBackgroundRole(qg.QPalette.Base) 

2501 for h in labels: 

2502 h.setParent(frame) 

2503 h.setMargin(3) 

2504 h.setTextInteractionFlags( 

2505 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2506 h.setBackgroundRole(qg.QPalette.Base) 

2507 layout.addWidget(h) 

2508 h.linkActivated.connect( 

2509 self.open_link) 

2510 

2511 if self.panel_parent is not None: 

2512 if target == 'panel': 

2513 self.panel_parent.add_panel( 

2514 name, scroller, True, volatile=False) 

2515 else: 

2516 self.panel_parent.add_tab(name, scroller) 

2517 

2518 def open_link(self, link): 

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

2520 

2521 def wheelEvent(self, wheel_event): 

2522 if use_pyqt5: 

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

2524 else: 

2525 self.wheel_pos += wheel_event.delta() 

2526 

2527 n = self.wheel_pos // 120 

2528 self.wheel_pos = self.wheel_pos % 120 

2529 if n == 0: 

2530 return 

2531 

2532 amount = max( 

2533 1., 

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

2535 wdelta = amount * n 

2536 

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

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

2539 / (trmax-trmin) 

2540 

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

2542 self.zoom_tracks(anchor, wdelta) 

2543 else: 

2544 self.scroll_tracks(-wdelta) 

2545 

2546 def dragEnterEvent(self, event): 

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

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

2549 event.setDropAction(qc.Qt.LinkAction) 

2550 event.accept() 

2551 

2552 def dropEvent(self, event): 

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

2554 paths = list( 

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

2556 event.acceptProposedAction() 

2557 self.load(paths) 

2558 

2559 def get_phase_name(self, kind): 

2560 return self.config.get_phase_name(kind) 

2561 

2562 def set_phase_kind(self, markers, kind): 

2563 phasename = self.get_phase_name(kind) 

2564 

2565 for marker in markers: 

2566 if isinstance(marker, PhaseMarker): 

2567 if kind == 10: 

2568 marker.convert_to_marker() 

2569 else: 

2570 marker.set_phasename(phasename) 

2571 marker.set_event(self.get_active_event()) 

2572 

2573 elif isinstance(marker, EventMarker): 

2574 pass 

2575 

2576 else: 

2577 if kind != 10: 

2578 event = self.get_active_event() 

2579 marker.convert_to_phase_marker( 

2580 event, phasename, None, False) 

2581 

2582 def set_ntracks(self, ntracks): 

2583 if self.ntracks != ntracks: 

2584 self.ntracks = ntracks 

2585 if self.shown_tracks_range is not None: 

2586 l, h = self.shown_tracks_range 

2587 else: 

2588 l, h = 0, self.ntracks 

2589 

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

2591 

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

2593 

2594 low, high = range 

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

2596 high = min(self.ntracks, high) 

2597 low = max(0, low) 

2598 high = max(1, high) 

2599 

2600 if start is None: 

2601 start = float(low) 

2602 

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

2604 self.shown_tracks_range = low, high 

2605 self.shown_tracks_start = start 

2606 

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

2608 

2609 def scroll_tracks(self, shift): 

2610 shown = self.shown_tracks_range 

2611 shiftmin = -shown[0] 

2612 shiftmax = self.ntracks-shown[1] 

2613 shift = max(shiftmin, shift) 

2614 shift = min(shiftmax, shift) 

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

2616 

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

2618 

2619 self.update() 

2620 

2621 def zoom_tracks(self, anchor, delta): 

2622 ntracks_shown = self.shown_tracks_range[1] \ 

2623 - self.shown_tracks_range[0] 

2624 

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

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

2627 return 

2628 

2629 ntracks_shown += int(round(delta)) 

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

2631 

2632 u = self.shown_tracks_start 

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

2634 nv = nu + ntracks_shown 

2635 if nv > self.ntracks: 

2636 nu -= nv - self.ntracks 

2637 nv -= nv - self.ntracks 

2638 

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

2640 

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

2642 - self.shown_tracks_range[0] 

2643 

2644 self.update() 

2645 

2646 def content_time_range(self): 

2647 pile = self.get_pile() 

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

2649 if tmin is None: 

2650 tmin = initial_time_range[0] 

2651 if tmax is None: 

2652 tmax = initial_time_range[1] 

2653 

2654 return tmin, tmax 

2655 

2656 def content_deltat_range(self): 

2657 pile = self.get_pile() 

2658 

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

2660 

2661 if deltatmin is None: 

2662 deltatmin = 0.001 

2663 

2664 if deltatmax is None: 

2665 deltatmax = 1000.0 

2666 

2667 return deltatmin, deltatmax 

2668 

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

2670 if tmax < tmin: 

2671 tmin, tmax = tmax, tmin 

2672 

2673 deltatmin = self.content_deltat_range()[0] 

2674 dt = deltatmin * self.visible_length * 0.95 

2675 

2676 if dt == 0.0: 

2677 dt = 1.0 

2678 

2679 if tight: 

2680 if tmax != tmin: 

2681 dtm = tmax - tmin 

2682 tmin -= dtm*0.1 

2683 tmax += dtm*0.1 

2684 return tmin, tmax 

2685 else: 

2686 tcenter = (tmin + tmax) / 2. 

2687 tmin = tcenter - 0.5*dt 

2688 tmax = tcenter + 0.5*dt 

2689 return tmin, tmax 

2690 

2691 if tmax-tmin < dt: 

2692 vmin, vmax = self.get_time_range() 

2693 dt = min(vmax - vmin, dt) 

2694 

2695 tcenter = (tmin+tmax)/2. 

2696 etmin, etmax = tmin, tmax 

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

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

2699 dtm = tmax-tmin 

2700 if etmin == tmin: 

2701 tmin -= dtm*0.1 

2702 if etmax == tmax: 

2703 tmax += dtm*0.1 

2704 

2705 else: 

2706 dtm = tmax-tmin 

2707 tmin -= dtm*0.1 

2708 tmax += dtm*0.1 

2709 

2710 return tmin, tmax 

2711 

2712 def go_to_selection(self, tight=False): 

2713 markers = self.selected_markers() 

2714 if markers: 

2715 tmax, tmin = self.content_time_range() 

2716 for marker in markers: 

2717 tmin = min(tmin, marker.tmin) 

2718 tmax = max(tmax, marker.tmax) 

2719 

2720 else: 

2721 if tight: 

2722 vmin, vmax = self.get_time_range() 

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

2724 else: 

2725 tmin, tmax = self.content_time_range() 

2726 

2727 tmin, tmax = self.make_good_looking_time_range( 

2728 tmin, tmax, tight=tight) 

2729 

2730 self.interrupt_following() 

2731 self.set_time_range(tmin, tmax) 

2732 self.update() 

2733 

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

2735 tmax = t 

2736 if tlen is not None: 

2737 tmax = t+tlen 

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

2739 self.interrupt_following() 

2740 self.set_time_range(tmin, tmax) 

2741 self.update() 

2742 

2743 def go_to_event_by_name(self, name): 

2744 for marker in self.markers: 

2745 if isinstance(marker, EventMarker): 

2746 event = marker.get_event() 

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

2748 tmin, tmax = self.make_good_looking_time_range( 

2749 event.time, event.time) 

2750 

2751 self.interrupt_following() 

2752 self.set_time_range(tmin, tmax) 

2753 

2754 def printit(self): 

2755 from .qt_compat import qprint 

2756 printer = qprint.QPrinter() 

2757 printer.setOrientation(qprint.QPrinter.Landscape) 

2758 

2759 dialog = qprint.QPrintDialog(printer, self) 

2760 dialog.setWindowTitle('Print') 

2761 

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

2763 return 

2764 

2765 painter = qg.QPainter() 

2766 painter.begin(printer) 

2767 page = printer.pageRect() 

2768 self.drawit( 

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

2770 

2771 painter.end() 

2772 

2773 def savesvg(self, fn=None): 

2774 

2775 if not fn: 

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

2777 self, 

2778 'Save as SVG|PNG', 

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

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

2781 options=qfiledialog_options)) 

2782 

2783 if fn == '': 

2784 return 

2785 

2786 fn = str(fn) 

2787 

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

2789 try: 

2790 w, h = 842, 595 

2791 margin = 0.025 

2792 m = max(w, h)*margin 

2793 

2794 generator = qsvg.QSvgGenerator() 

2795 generator.setFileName(fn) 

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

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

2798 

2799 painter = qg.QPainter() 

2800 painter.begin(generator) 

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

2802 painter.end() 

2803 

2804 except Exception as e: 

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

2806 

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

2808 if use_pyqt5: 

2809 pixmap = self.grab() 

2810 else: 

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

2812 

2813 try: 

2814 pixmap.save(fn) 

2815 

2816 except Exception as e: 

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

2818 

2819 else: 

2820 self.fail( 

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

2822 '".png".') 

2823 

2824 def paintEvent(self, paint_ev): 

2825 ''' 

2826 Called by QT whenever widget needs to be painted. 

2827 ''' 

2828 painter = qg.QPainter(self) 

2829 

2830 if self.menuitem_antialias.isChecked(): 

2831 painter.setRenderHint(qg.QPainter.Antialiasing) 

2832 

2833 self.drawit(painter) 

2834 

2835 logger.debug( 

2836 'Time spent drawing: ' 

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

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

2839 (self.timer_draw - self.timer_cutout)) 

2840 

2841 logger.debug( 

2842 'Time spent processing:' 

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

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

2845 self.timer_cutout.get()) 

2846 

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

2848 self.time_last_painted = time.time() 

2849 

2850 def determine_box_styles(self): 

2851 

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

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

2854 istyle = 0 

2855 trace_styles = {} 

2856 for itr, tr in enumerate(traces): 

2857 if itr > 0: 

2858 other = traces[itr-1] 

2859 if not ( 

2860 other.nslc_id == tr.nslc_id 

2861 and other.deltat == tr.deltat 

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

2863 < gap_lap_tolerance*tr.deltat): 

2864 

2865 istyle += 1 

2866 

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

2868 

2869 self.trace_styles = trace_styles 

2870 

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

2872 

2873 for v_projection in track_projections.values(): 

2874 v_projection.set_in_range(0., 1.) 

2875 

2876 def selector(x): 

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

2878 

2879 if self.trace_filter is not None: 

2880 def tselector(x): 

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

2882 

2883 else: 

2884 tselector = selector 

2885 

2886 traces = list(self.pile.iter_traces( 

2887 group_selector=selector, trace_selector=tselector)) 

2888 

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

2890 

2891 def drawbox(itrack, istyle, traces): 

2892 v_projection = track_projections[itrack] 

2893 dvmin = v_projection(0.) 

2894 dvmax = v_projection(1.) 

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

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

2897 

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

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

2900 p.fillRect(rect, style.fill_brush) 

2901 p.setPen(style.frame_pen) 

2902 p.drawRect(rect) 

2903 

2904 traces_by_style = {} 

2905 for itr, tr in enumerate(traces): 

2906 gt = self.gather(tr) 

2907 if gt not in self.key_to_row: 

2908 continue 

2909 

2910 itrack = self.key_to_row[gt] 

2911 if itrack not in track_projections: 

2912 continue 

2913 

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

2915 

2916 if len(traces) < 500: 

2917 drawbox(itrack, istyle, [tr]) 

2918 else: 

2919 if (itrack, istyle) not in traces_by_style: 

2920 traces_by_style[itrack, istyle] = [] 

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

2922 

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

2924 drawbox(itrack, istyle, traces) 

2925 

2926 def draw_visible_markers( 

2927 self, p, vcenter_projection, primary_pen): 

2928 

2929 try: 

2930 markers = self.markers.with_key_in_limited( 

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

2932 

2933 except pyrocko.pile.TooMany: 

2934 tmin = self.markers[0].tmin 

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

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

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

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

2939 v0, _ = vcenter_projection.get_out_range() 

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

2941 

2942 p.save() 

2943 

2944 pen = qg.QPen(primary_pen) 

2945 pen.setWidth(2) 

2946 pen.setStyle(qc.Qt.DotLine) 

2947 # pat = [5., 3.] 

2948 # pen.setDashPattern(pat) 

2949 p.setPen(pen) 

2950 

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

2952 s_selected = ' (all selected)' 

2953 elif self.n_selected_markers > 0: 

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

2955 else: 

2956 s_selected = '' 

2957 

2958 draw_label( 

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

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

2961 label_bg, 'LB') 

2962 

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

2964 p.drawLine(line) 

2965 p.restore() 

2966 

2967 return 

2968 

2969 for marker in markers: 

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

2971 and marker.kind in self.visible_marker_kinds: 

2972 

2973 marker.draw( 

2974 p, self.time_projection, vcenter_projection, 

2975 with_label=True) 

2976 

2977 def get_squirrel(self): 

2978 try: 

2979 return self.pile._squirrel 

2980 except AttributeError: 

2981 return None 

2982 

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

2984 sq = self.get_squirrel() 

2985 if sq is None: 

2986 return 

2987 

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

2989 v_projection = track_projections[itrack] 

2990 dvmin = v_projection(0.) 

2991 dvmax = v_projection(1.) 

2992 dtmin = time_projection.clipped(tmin) 

2993 dtmax = time_projection.clipped(tmax) 

2994 

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

2996 p.fillRect(rect, style.fill_brush) 

2997 p.setPen(style.frame_pen) 

2998 p.drawRect(rect) 

2999 

3000 pattern_list = [] 

3001 pattern_to_itrack = {} 

3002 for key in self.track_keys: 

3003 itrack = self.key_to_row[key] 

3004 if itrack not in track_projections: 

3005 continue 

3006 

3007 pattern = self.track_patterns[itrack] 

3008 pattern_to_itrack[tuple(pattern)] = itrack 

3009 pattern_list.append(pattern) 

3010 

3011 vmin, vmax = self.get_time_range() 

3012 

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

3014 for entry in sq.get_coverage( 

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

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

3017 itrack = pattern_to_itrack[tuple(pattern)] 

3018 

3019 if cover_data is None: 

3020 drawbox( 

3021 itrack, tmin, tmax, 

3022 box_styles_coverage[kind][0]) 

3023 else: 

3024 t = None 

3025 pcount = 0 

3026 for tb, count in cover_data: 

3027 if t is not None and tb > t: 

3028 if pcount > 0: 

3029 drawbox( 

3030 itrack, t, tb, 

3031 box_styles_coverage[kind][ 

3032 min(len(box_styles_coverage)-1, 

3033 pcount)]) 

3034 

3035 t = tb 

3036 pcount = count 

3037 

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

3039 ''' 

3040 This performs the actual drawing. 

3041 ''' 

3042 

3043 self.timer_draw.start() 

3044 show_boxes = self.menuitem_showboxes.isChecked() 

3045 sq = self.get_squirrel() 

3046 

3047 if self.gather is None: 

3048 self.set_gathering() 

3049 

3050 if self.pile_has_changed: 

3051 

3052 if not self.sortingmode_change_delayed(): 

3053 self.sortingmode_change() 

3054 

3055 if show_boxes and sq is None: 

3056 self.determine_box_styles() 

3057 

3058 self.pile_has_changed = False 

3059 

3060 if h is None: 

3061 h = float(self.height()) 

3062 if w is None: 

3063 w = float(self.width()) 

3064 

3065 if printmode: 

3066 primary_color = (0, 0, 0) 

3067 else: 

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

3069 

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

3071 

3072 ax_h = float(self.ax_height) 

3073 

3074 vbottom_ax_projection = Projection() 

3075 vtop_ax_projection = Projection() 

3076 vcenter_projection = Projection() 

3077 

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

3079 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3080 vtop_ax_projection.set_out_range(0., ax_h) 

3081 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3082 vcenter_projection.set_in_range(0., 1.) 

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

3084 

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

3086 track_projections = {} 

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

3088 proj = Projection() 

3089 proj.set_out_range( 

3090 self.track_to_screen(i+0.05), 

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

3092 

3093 track_projections[i] = proj 

3094 

3095 if self.tmin > self.tmax: 

3096 return 

3097 

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

3099 vbottom_ax_projection.set_in_range(0, ax_h) 

3100 

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

3102 

3103 yscaler = pyrocko.plot.AutoScaler() 

3104 

3105 p.setPen(primary_pen) 

3106 

3107 font = qg.QFont() 

3108 font.setBold(True) 

3109 

3110 axannotfont = qg.QFont() 

3111 axannotfont.setBold(True) 

3112 axannotfont.setPointSize(8) 

3113 

3114 processed_traces = self.prepare_cutout2( 

3115 self.tmin, self.tmax, 

3116 trace_selector=self.trace_selector, 

3117 degap=self.menuitem_degap.isChecked(), 

3118 demean=self.menuitem_demean.isChecked()) 

3119 

3120 if not printmode and show_boxes: 

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

3122 or (self.view_mode is ViewMode.Waterfall 

3123 and not processed_traces): 

3124 

3125 if sq is None: 

3126 self.draw_trace_boxes( 

3127 p, self.time_projection, track_projections) 

3128 

3129 else: 

3130 self.draw_coverage( 

3131 p, self.time_projection, track_projections) 

3132 

3133 p.setFont(font) 

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

3135 

3136 color_lookup = dict( 

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

3138 

3139 self.track_to_nslc_ids = {} 

3140 nticks = 0 

3141 annot_labels = [] 

3142 

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

3144 waterfall = self.waterfall 

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

3146 waterfall.set_traces(processed_traces) 

3147 waterfall.set_cmap(self.waterfall_cmap) 

3148 waterfall.set_integrate(self.waterfall_integrate) 

3149 waterfall.set_clip( 

3150 self.waterfall_clip_min, self.waterfall_clip_max) 

3151 waterfall.show_absolute_values( 

3152 self.waterfall_show_absolute) 

3153 

3154 rect = qc.QRectF( 

3155 0, self.ax_height, 

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

3157 ) 

3158 waterfall.draw_waterfall(p, rect=rect) 

3159 

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

3161 show_scales = self.menuitem_showscalerange.isChecked() \ 

3162 or self.menuitem_showscaleaxis.isChecked() 

3163 

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

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

3166 - self.track_to_screen(0.05) 

3167 

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

3169 

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

3171 if self.menuitem_showscaleaxis.isChecked() \ 

3172 else 15 

3173 

3174 yscaler = pyrocko.plot.AutoScaler( 

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

3176 snap=show_scales 

3177 and not self.menuitem_showscaleaxis.isChecked()) 

3178 

3179 data_ranges = pyrocko.trace.minmax( 

3180 processed_traces, 

3181 key=self.scaling_key, 

3182 mode=self.scaling_base) 

3183 

3184 if not self.menuitem_fixscalerange.isChecked(): 

3185 self.old_data_ranges = data_ranges 

3186 else: 

3187 data_ranges.update(self.old_data_ranges) 

3188 

3189 self.apply_scaling_hooks(data_ranges) 

3190 

3191 trace_to_itrack = {} 

3192 track_scaling_keys = {} 

3193 track_scaling_colors = {} 

3194 for trace in processed_traces: 

3195 gt = self.gather(trace) 

3196 if gt not in self.key_to_row: 

3197 continue 

3198 

3199 itrack = self.key_to_row[gt] 

3200 if itrack not in track_projections: 

3201 continue 

3202 

3203 trace_to_itrack[trace] = itrack 

3204 

3205 if itrack not in self.track_to_nslc_ids: 

3206 self.track_to_nslc_ids[itrack] = set() 

3207 

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

3209 

3210 if itrack not in track_scaling_keys: 

3211 track_scaling_keys[itrack] = set() 

3212 

3213 scaling_key = self.scaling_key(trace) 

3214 track_scaling_keys[itrack].add(scaling_key) 

3215 

3216 color = pyrocko.plot.color( 

3217 color_lookup[self.color_gather(trace)]) 

3218 

3219 k = itrack, scaling_key 

3220 if k not in track_scaling_colors \ 

3221 and self.menuitem_colortraces.isChecked(): 

3222 track_scaling_colors[k] = color 

3223 else: 

3224 track_scaling_colors[k] = primary_color 

3225 

3226 # y axes, zero lines 

3227 trace_projections = {} 

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

3229 if itrack not in track_scaling_keys: 

3230 continue 

3231 uoff = 0 

3232 for scaling_key in track_scaling_keys[itrack]: 

3233 data_range = data_ranges[scaling_key] 

3234 dymin, dymax = data_range 

3235 ymin, ymax, yinc = yscaler.make_scale( 

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

3237 iexp = yscaler.make_exp(yinc) 

3238 factor = 10**iexp 

3239 trace_projection = track_projections[itrack].copy() 

3240 trace_projection.set_in_range(ymax, ymin) 

3241 trace_projections[itrack, scaling_key] = \ 

3242 trace_projection 

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

3244 vmin, vmax = trace_projection.get_out_range() 

3245 umax_zeroline = umax 

3246 uoffnext = uoff 

3247 

3248 if show_scales: 

3249 pen = qg.QPen(primary_pen) 

3250 k = itrack, scaling_key 

3251 if k in track_scaling_colors: 

3252 c = qg.QColor(*track_scaling_colors[ 

3253 itrack, scaling_key]) 

3254 

3255 pen.setColor(c) 

3256 

3257 p.setPen(pen) 

3258 if nlinesavail > 3: 

3259 if self.menuitem_showscaleaxis.isChecked(): 

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

3261 ny_annot = int( 

3262 math.floor(ymax/yinc) 

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

3264 

3265 for iy_annot in range(ny_annot): 

3266 y = ymin_annot + iy_annot*yinc 

3267 v = trace_projection(y) 

3268 line = qc.QLineF( 

3269 umax-10-uoff, v, umax-uoff, v) 

3270 

3271 p.drawLine(line) 

3272 if iy_annot == ny_annot - 1 \ 

3273 and iexp != 0: 

3274 sexp = ' &times; ' \ 

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

3276 else: 

3277 sexp = '' 

3278 

3279 snum = num_to_html(y/factor) 

3280 lab = Label( 

3281 p, 

3282 umax-20-uoff, 

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

3284 label_bg=None, 

3285 anchor='MR', 

3286 font=axannotfont, 

3287 color=c) 

3288 

3289 uoffnext = max( 

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

3291 

3292 annot_labels.append(lab) 

3293 if y == 0.: 

3294 umax_zeroline = \ 

3295 umax - 20 \ 

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

3297 - uoff 

3298 else: 

3299 if not show_boxes: 

3300 qpoints = make_QPolygonF( 

3301 [umax-20-uoff, 

3302 umax-10-uoff, 

3303 umax-10-uoff, 

3304 umax-20-uoff], 

3305 [vmax, vmax, vmin, vmin]) 

3306 p.drawPolyline(qpoints) 

3307 

3308 snum = num_to_html(ymin) 

3309 labmin = Label( 

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

3311 label_bg=None, 

3312 anchor='BR', 

3313 font=axannotfont, 

3314 color=c) 

3315 

3316 annot_labels.append(labmin) 

3317 snum = num_to_html(ymax) 

3318 labmax = Label( 

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

3320 label_bg=None, 

3321 anchor='TR', 

3322 font=axannotfont, 

3323 color=c) 

3324 

3325 annot_labels.append(labmax) 

3326 

3327 for lab in (labmin, labmax): 

3328 uoffnext = max( 

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

3330 

3331 if self.menuitem_showzeroline.isChecked(): 

3332 v = trace_projection(0.) 

3333 if vmin <= v <= vmax: 

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

3335 p.drawLine(line) 

3336 

3337 uoff = uoffnext 

3338 

3339 p.setFont(font) 

3340 p.setPen(primary_pen) 

3341 for trace in processed_traces: 

3342 if self.view_mode is not ViewMode.Wiggle: 

3343 break 

3344 

3345 if trace not in trace_to_itrack: 

3346 continue 

3347 

3348 itrack = trace_to_itrack[trace] 

3349 scaling_key = self.scaling_key(trace) 

3350 trace_projection = trace_projections[ 

3351 itrack, scaling_key] 

3352 

3353 vdata = trace_projection(trace.get_ydata()) 

3354 

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

3356 udata_max = float(self.time_projection( 

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

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

3359 

3360 qpoints = make_QPolygonF(udata, vdata) 

3361 

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

3363 vmin, vmax = trace_projection.get_out_range() 

3364 

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

3366 

3367 if self.menuitem_cliptraces.isChecked(): 

3368 p.setClipRect(trackrect) 

3369 

3370 if self.menuitem_colortraces.isChecked(): 

3371 color = pyrocko.plot.color( 

3372 color_lookup[self.color_gather(trace)]) 

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

3374 p.setPen(pen) 

3375 

3376 p.drawPolyline(qpoints) 

3377 

3378 if self.floating_marker: 

3379 self.floating_marker.draw_trace( 

3380 self, p, trace, 

3381 self.time_projection, trace_projection, 1.0) 

3382 

3383 for marker in self.markers.with_key_in( 

3384 self.tmin - self.markers_deltat_max, 

3385 self.tmax): 

3386 

3387 if marker.tmin < self.tmax \ 

3388 and self.tmin < marker.tmax \ 

3389 and marker.kind \ 

3390 in self.visible_marker_kinds: 

3391 marker.draw_trace( 

3392 self, p, trace, self.time_projection, 

3393 trace_projection, 1.0) 

3394 

3395 p.setPen(primary_pen) 

3396 

3397 if self.menuitem_cliptraces.isChecked(): 

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

3399 

3400 if self.floating_marker: 

3401 self.floating_marker.draw( 

3402 p, self.time_projection, vcenter_projection) 

3403 

3404 self.draw_visible_markers( 

3405 p, vcenter_projection, primary_pen) 

3406 

3407 p.setPen(primary_pen) 

3408 while font.pointSize() > 2: 

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

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

3411 - self.track_to_screen(0.05) 

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

3413 if nlinesavail > 1: 

3414 break 

3415 

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

3417 

3418 p.setFont(font) 

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

3420 

3421 for key in self.track_keys: 

3422 itrack = self.key_to_row[key] 

3423 if itrack in track_projections: 

3424 plabel = ' '.join( 

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

3426 lx = 10 

3427 ly = self.track_to_screen(itrack+0.5) 

3428 

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

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

3431 continue 

3432 

3433 contains_cursor = \ 

3434 self.track_to_screen(itrack) \ 

3435 < mouse_pos.y() \ 

3436 < self.track_to_screen(itrack+1) 

3437 

3438 if not contains_cursor: 

3439 continue 

3440 

3441 font_large = p.font() 

3442 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3443 p.setFont(font_large) 

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

3445 p.setFont(font) 

3446 

3447 for lab in annot_labels: 

3448 lab.draw() 

3449 

3450 self.timer_draw.stop() 

3451 

3452 def see_data_params(self): 

3453 

3454 min_deltat = self.content_deltat_range()[0] 

3455 

3456 # determine padding and downampling requirements 

3457 if self.lowpass is not None: 

3458 deltat_target = 1./self.lowpass * 0.25 

3459 ndecimate = min( 

3460 50, 

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

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

3463 else: 

3464 ndecimate = 1 

3465 tpad = min_deltat*5. 

3466 

3467 if self.highpass is not None: 

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

3469 

3470 nsee_points_per_trace = 5000*10 

3471 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3472 

3473 return ndecimate, tpad, tsee 

3474 

3475 def clean_update(self): 

3476 self.cached_processed_traces = None 

3477 self.cached_chopped_traces = {} 

3478 self.update() 

3479 

3480 def get_adequate_tpad(self): 

3481 tpad = 0. 

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

3483 if f is not None: 

3484 tpad = max(tpad, 1.0/f) 

3485 

3486 for snuffling in self.snufflings: 

3487 if snuffling._post_process_hook_enabled \ 

3488 or snuffling._pre_process_hook_enabled: 

3489 

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

3491 

3492 return tpad 

3493 

3494 def prepare_cutout2( 

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

3496 demean=True, nmax=6000): 

3497 

3498 if self.pile.is_empty(): 

3499 return [] 

3500 

3501 nmax = self.visible_length 

3502 

3503 self.timer_cutout.start() 

3504 

3505 tsee = tmax-tmin 

3506 min_deltat_wo_decimate = tsee/nmax 

3507 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3508 

3509 min_deltat_allow = min_deltat_wo_decimate 

3510 if self.lowpass is not None: 

3511 target_deltat_lp = 0.25/self.lowpass 

3512 if target_deltat_lp > min_deltat_wo_decimate: 

3513 min_deltat_allow = min_deltat_w_decimate 

3514 

3515 min_deltat_allow = math.exp( 

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

3517 

3518 tmin_ = tmin 

3519 tmax_ = tmax 

3520 

3521 # fetch more than needed? 

3522 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3526 

3527 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3528 lphp = self.menuitem_lphp.isChecked() 

3529 ads = self.menuitem_allowdownsampling.isChecked() 

3530 

3531 tpad = self.get_adequate_tpad() 

3532 tpad = max(tpad, tsee) 

3533 

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

3535 vec = ( 

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

3537 self.highpass, fft_filtering, lphp, 

3538 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3539 ads, self.pile.get_update_count()) 

3540 

3541 dtmin = 0. if not self.cached_vec else tmin_ - self.cached_vec[0] 

3542 dtmax = 0. if not self.cached_vec else tmax_ - self.cached_vec[1] 

3543 

3544 if (self.cached_vec 

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

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

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

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

3549 and self.cached_processed_traces is not None): 

3550 

3551 logger.debug('Using cached traces') 

3552 processed_traces = self.cached_processed_traces 

3553 

3554 else: 

3555 processed_traces = [] 

3556 if self.pile.deltatmax >= min_deltat_allow: 

3557 

3558 def group_selector(gr): 

3559 return gr.deltatmax >= min_deltat_allow 

3560 

3561 if trace_selector is not None: 

3562 def trace_selectorx(tr): 

3563 return tr.deltat >= min_deltat_allow \ 

3564 and trace_selector(tr) 

3565 else: 

3566 def trace_selectorx(tr): 

3567 return tr.deltat >= min_deltat_allow 

3568 

3569 for traces in self.pile.chopper( 

3570 tmin=tmin, tmax=tmax, tpad=tpad, 

3571 want_incomplete=True, 

3572 degap=degap, 

3573 maxgap=gap_lap_tolerance, 

3574 maxlap=gap_lap_tolerance, 

3575 keep_current_files_open=True, 

3576 group_selector=group_selector, 

3577 trace_selector=trace_selectorx, 

3578 accessor_id=id(self), 

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

3580 include_last=True): 

3581 

3582 if demean: 

3583 for tr in traces: 

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

3585 continue 

3586 y = tr.get_ydata() 

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

3588 

3589 traces = self.pre_process_hooks(traces) 

3590 

3591 for trace in traces: 

3592 

3593 if not (trace.meta 

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

3595 

3596 if fft_filtering: 

3597 but = pyrocko.response.ButterworthResponse 

3598 multres = pyrocko.response.MultiplyResponse 

3599 if self.lowpass is not None \ 

3600 or self.highpass is not None: 

3601 

3602 it = num.arange( 

3603 trace.data_len(), dtype=float) 

3604 detr_data, m, b = detrend( 

3605 it, trace.get_ydata()) 

3606 

3607 trace.set_ydata(detr_data) 

3608 

3609 freqs, fdata = trace.spectrum( 

3610 pad_to_pow2=True, tfade=None) 

3611 

3612 nfreqs = fdata.size 

3613 

3614 key = (trace.deltat, nfreqs) 

3615 

3616 if key not in self.tf_cache: 

3617 resps = [] 

3618 if self.lowpass is not None: 

3619 resps.append(but( 

3620 order=4, 

3621 corner=self.lowpass, 

3622 type='low')) 

3623 

3624 if self.highpass is not None: 

3625 resps.append(but( 

3626 order=4, 

3627 corner=self.highpass, 

3628 type='high')) 

3629 

3630 resp = multres(resps) 

3631 self.tf_cache[key] = \ 

3632 resp.evaluate(freqs) 

3633 

3634 filtered_data = num.fft.irfft( 

3635 fdata*self.tf_cache[key] 

3636 )[:trace.data_len()] 

3637 

3638 retrended_data = retrend( 

3639 it, filtered_data, m, b) 

3640 

3641 trace.set_ydata(retrended_data) 

3642 

3643 else: 

3644 

3645 if ads and self.lowpass is not None: 

3646 while trace.deltat \ 

3647 < min_deltat_wo_decimate: 

3648 

3649 trace.downsample(2, demean=False) 

3650 

3651 fmax = 0.5/trace.deltat 

3652 if not lphp and ( 

3653 self.lowpass is not None 

3654 and self.highpass is not None 

3655 and self.lowpass < fmax 

3656 and self.highpass < fmax 

3657 and self.highpass < self.lowpass): 

3658 

3659 trace.bandpass( 

3660 2, self.highpass, self.lowpass) 

3661 else: 

3662 if self.lowpass is not None: 

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

3664 trace.lowpass( 

3665 4, self.lowpass, 

3666 demean=False) 

3667 

3668 if self.highpass is not None: 

3669 if self.lowpass is None \ 

3670 or self.highpass \ 

3671 < self.lowpass: 

3672 

3673 if self.highpass < \ 

3674 0.5/trace.deltat: 

3675 trace.highpass( 

3676 4, self.highpass, 

3677 demean=False) 

3678 

3679 processed_traces.append(trace) 

3680 

3681 if self.rotate != 0.0: 

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

3683 cphi = math.cos(phi) 

3684 sphi = math.sin(phi) 

3685 for a in processed_traces: 

3686 for b in processed_traces: 

3687 if (a.network == b.network 

3688 and a.station == b.station 

3689 and a.location == b.location 

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

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

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

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

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

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

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

3697 

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

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

3700 a.set_ydata(aydata) 

3701 b.set_ydata(bydata) 

3702 

3703 processed_traces = self.post_process_hooks(processed_traces) 

3704 

3705 self.cached_processed_traces = processed_traces 

3706 self.cached_vec = vec 

3707 

3708 chopped_traces = [] 

3709 for trace in processed_traces: 

3710 chop_tmin = tmin_ - trace.deltat*4 

3711 chop_tmax = tmax_ + trace.deltat*4 

3712 trace_hash = trace.hash(unsafe=True) 

3713 

3714 # Use cache if tmin and tmax have not changed 

3715 if dtmin == 0. and dtmax == 0. \ 

3716 and trace_hash in self.cached_chopped_traces: 

3717 ctrace = self.cached_chopped_traces[trace_hash] 

3718 

3719 else: 

3720 try: 

3721 ctrace = trace.chop( 

3722 chop_tmin, chop_tmax, 

3723 inplace=False) 

3724 

3725 except pyrocko.trace.NoData: 

3726 continue 

3727 

3728 if ctrace.data_len() < 2: 

3729 continue 

3730 

3731 self.cached_chopped_traces[trace_hash] = ctrace 

3732 chopped_traces.append(ctrace) 

3733 

3734 self.timer_cutout.stop() 

3735 return chopped_traces 

3736 

3737 def pre_process_hooks(self, traces): 

3738 for snuffling in self.snufflings: 

3739 if snuffling._pre_process_hook_enabled: 

3740 traces = snuffling.pre_process_hook(traces) 

3741 

3742 return traces 

3743 

3744 def post_process_hooks(self, traces): 

3745 for snuffling in self.snufflings: 

3746 if snuffling._post_process_hook_enabled: 

3747 traces = snuffling.post_process_hook(traces) 

3748 

3749 return traces 

3750 

3751 def visible_length_change(self, ignore=None): 

3752 for menuitem, vlen in self.menuitems_visible_length: 

3753 if menuitem.isChecked(): 

3754 self.visible_length = vlen 

3755 

3756 def scaling_base_change(self, ignore=None): 

3757 for menuitem, scaling_base in self.menuitems_scaling_base: 

3758 if menuitem.isChecked(): 

3759 self.scaling_base = scaling_base 

3760 

3761 def scalingmode_change(self, ignore=None): 

3762 for menuitem, scaling_key in self.menuitems_scaling: 

3763 if menuitem.isChecked(): 

3764 self.scaling_key = scaling_key 

3765 self.update() 

3766 

3767 def apply_scaling_hooks(self, data_ranges): 

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

3769 hook = self.scaling_hooks[k] 

3770 hook(data_ranges) 

3771 

3772 def viewmode_change(self, ignore=True): 

3773 for item, mode in self.menuitems_viewmode: 

3774 if item.isChecked(): 

3775 self.view_mode = mode 

3776 break 

3777 else: 

3778 raise AttributeError('unknown view mode') 

3779 

3780 items_waterfall_disabled = ( 

3781 self.menuitem_showscaleaxis, 

3782 self.menuitem_showscalerange, 

3783 self.menuitem_showzeroline, 

3784 self.menuitem_colortraces, 

3785 self.menuitem_cliptraces, 

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

3787 ) 

3788 

3789 if self.view_mode is ViewMode.Waterfall: 

3790 self.parent().show_colorbar_ctrl(True) 

3791 self.parent().show_gain_ctrl(False) 

3792 

3793 for item in items_waterfall_disabled: 

3794 item.setDisabled(True) 

3795 

3796 self.visible_length = 180. 

3797 else: 

3798 self.parent().show_colorbar_ctrl(False) 

3799 self.parent().show_gain_ctrl(True) 

3800 

3801 for item in items_waterfall_disabled: 

3802 item.setDisabled(False) 

3803 

3804 self.visible_length_change() 

3805 self.update() 

3806 

3807 def set_scaling_hook(self, k, hook): 

3808 self.scaling_hooks[k] = hook 

3809 

3810 def remove_scaling_hook(self, k): 

3811 del self.scaling_hooks[k] 

3812 

3813 def remove_scaling_hooks(self): 

3814 self.scaling_hooks = {} 

3815 

3816 def s_sortingmode_change(self, ignore=None): 

3817 for menuitem, valfunc in self.menuitems_ssorting: 

3818 if menuitem.isChecked(): 

3819 self._ssort = valfunc 

3820 

3821 self.sortingmode_change() 

3822 

3823 def sortingmode_change(self, ignore=None): 

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

3825 if menuitem.isChecked(): 

3826 self.set_gathering(gather, color) 

3827 

3828 self.sortingmode_change_time = time.time() 

3829 

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

3831 self.lowpass = value 

3832 self.passband_check() 

3833 self.tf_cache = {} 

3834 self.update() 

3835 

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

3837 self.highpass = value 

3838 self.passband_check() 

3839 self.tf_cache = {} 

3840 self.update() 

3841 

3842 def passband_check(self): 

3843 if self.highpass and self.lowpass \ 

3844 and self.highpass >= self.lowpass: 

3845 

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

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

3848 'deactivate the highpass.' 

3849 

3850 self.update_status() 

3851 else: 

3852 oldmess = self.message 

3853 self.message = None 

3854 if oldmess is not None: 

3855 self.update_status() 

3856 

3857 def gain_change(self, value, ignore): 

3858 self.gain = value 

3859 self.update() 

3860 

3861 def rot_change(self, value, ignore): 

3862 self.rotate = value 

3863 self.update() 

3864 

3865 def waterfall_cmap_change(self, cmap): 

3866 self.waterfall_cmap = cmap 

3867 self.update() 

3868 

3869 def waterfall_clip_change(self, clip_min, clip_max): 

3870 self.waterfall_clip_min = clip_min 

3871 self.waterfall_clip_max = clip_max 

3872 self.update() 

3873 

3874 def waterfall_show_absolute_change(self, toggle): 

3875 self.waterfall_show_absolute = toggle 

3876 self.update() 

3877 

3878 def waterfall_set_integrate(self, toggle): 

3879 self.waterfall_integrate = toggle 

3880 self.update() 

3881 

3882 def set_selected_markers(self, markers): 

3883 ''' 

3884 Set a list of markers selected 

3885 

3886 :param markers: list of markers 

3887 ''' 

3888 self.deselect_all() 

3889 for m in markers: 

3890 m.selected = True 

3891 

3892 self.update() 

3893 

3894 def deselect_all(self): 

3895 for marker in self.markers: 

3896 marker.selected = False 

3897 

3898 def animate_picking(self): 

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

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

3901 

3902 def get_nslc_ids_for_track(self, ftrack): 

3903 itrack = int(ftrack) 

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

3905 

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

3907 if self.picking: 

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

3909 self.picking = None 

3910 self.picking_down = None 

3911 self.picking_timer.stop() 

3912 self.picking_timer = None 

3913 if not abort: 

3914 self.add_marker(self.floating_marker) 

3915 self.floating_marker.selected = True 

3916 self.emit_selected_markers() 

3917 

3918 self.floating_marker = None 

3919 

3920 def start_picking(self, ignore): 

3921 

3922 if not self.picking: 

3923 self.deselect_all() 

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

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

3926 

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

3928 self.picking.setGeometry( 

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

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

3931 

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

3933 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3935 self.floating_marker.selected = True 

3936 

3937 self.picking_timer = qc.QTimer() 

3938 self.picking_timer.timeout.connect( 

3939 self.animate_picking) 

3940 

3941 self.picking_timer.setInterval(50) 

3942 self.picking_timer.start() 

3943 

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

3945 if self.picking: 

3946 mouset = self.time_projection.rev(x) 

3947 dt = 0.0 

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

3949 if mouset < self.tmin: 

3950 dt = -(self.tmin - mouset) 

3951 else: 

3952 dt = mouset - self.tmax 

3953 ddt = self.tmax-self.tmin 

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

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

3956 

3957 x0 = x 

3958 if self.picking_down is not None: 

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

3960 

3961 w = abs(x-x0) 

3962 x0 = min(x0, x) 

3963 

3964 tmin, tmax = ( 

3965 self.time_projection.rev(x0), 

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

3967 

3968 tmin, tmax = ( 

3969 max(working_system_time_range[0], tmin), 

3970 min(working_system_time_range[1], tmax)) 

3971 

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

3973 

3974 self.picking.setGeometry( 

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

3976 

3977 ftrack = self.track_to_screen.rev(y) 

3978 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3980 

3981 if dt != 0.0 and doshift: 

3982 self.interrupt_following() 

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

3984 

3985 self.update() 

3986 

3987 def update_status(self): 

3988 

3989 if self.message is None: 

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

3991 

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

3993 if not is_working_time(mouse_t): 

3994 return 

3995 

3996 if self.floating_marker: 

3997 tmi, tma = ( 

3998 self.floating_marker.tmin, 

3999 self.floating_marker.tmax) 

4000 

4001 tt, ms = gmtime_x(tmi) 

4002 

4003 if tmi == tma: 

4004 message = mystrftime( 

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

4006 tt=tt, milliseconds=ms) 

4007 else: 

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

4009 message = mystrftime( 

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

4011 tt=tt, milliseconds=ms) 

4012 else: 

4013 tt, ms = gmtime_x(mouse_t) 

4014 

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

4016 else: 

4017 message = self.message 

4018 

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

4020 sb.clearMessage() 

4021 sb.showMessage(message) 

4022 

4023 def set_sortingmode_change_delay_time(self, dt): 

4024 self.sortingmode_change_delay_time = dt 

4025 

4026 def sortingmode_change_delayed(self): 

4027 now = time.time() 

4028 return ( 

4029 self.sortingmode_change_delay_time is not None 

4030 and now - self.sortingmode_change_time 

4031 < self.sortingmode_change_delay_time) 

4032 

4033 def set_visible_marker_kinds(self, kinds): 

4034 self.deselect_all() 

4035 self.visible_marker_kinds = tuple(kinds) 

4036 self.emit_selected_markers() 

4037 

4038 def following(self): 

4039 return self.follow_timer is not None \ 

4040 and not self.following_interrupted() 

4041 

4042 def interrupt_following(self): 

4043 self.interactive_range_change_time = time.time() 

4044 

4045 def following_interrupted(self, now=None): 

4046 if now is None: 

4047 now = time.time() 

4048 return now - self.interactive_range_change_time \ 

4049 < self.interactive_range_change_delay_time 

4050 

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

4052 if tmax_start is None: 

4053 tmax_start = time.time() 

4054 self.show_all = False 

4055 self.follow_time = tlen 

4056 self.follow_timer = qc.QTimer(self) 

4057 self.follow_timer.timeout.connect( 

4058 self.follow_update) 

4059 self.follow_timer.setInterval(interval) 

4060 self.follow_timer.start() 

4061 self.follow_started = time.time() 

4062 self.follow_lapse = lapse 

4063 self.follow_tshift = self.follow_started - tmax_start 

4064 self.interactive_range_change_time = 0.0 

4065 

4066 def unfollow(self): 

4067 if self.follow_timer is not None: 

4068 self.follow_timer.stop() 

4069 self.follow_timer = None 

4070 self.interactive_range_change_time = 0.0 

4071 

4072 def follow_update(self): 

4073 rnow = time.time() 

4074 if self.follow_lapse is None: 

4075 now = rnow 

4076 else: 

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

4078 * self.follow_lapse 

4079 

4080 if self.following_interrupted(rnow): 

4081 return 

4082 self.set_time_range( 

4083 now-self.follow_time-self.follow_tshift, 

4084 now-self.follow_tshift) 

4085 

4086 self.update() 

4087 

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

4089 self.return_tag = return_tag 

4090 self.window().close() 

4091 

4092 def cleanup(self): 

4093 self.about_to_close.emit() 

4094 self.timer.stop() 

4095 if self.follow_timer is not None: 

4096 self.follow_timer.stop() 

4097 

4098 for snuffling in list(self.snufflings): 

4099 self.remove_snuffling(snuffling) 

4100 

4101 def set_error_message(self, key, value): 

4102 if value is None: 

4103 if key in self.error_messages: 

4104 del self.error_messages[key] 

4105 else: 

4106 self.error_messages[key] = value 

4107 

4108 def inputline_changed(self, text): 

4109 pass 

4110 

4111 def inputline_finished(self, text): 

4112 line = str(text) 

4113 

4114 toks = line.split() 

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

4116 if len(toks) >= 1: 

4117 command = toks[0].lower() 

4118 

4119 try: 

4120 quick_filter_commands = { 

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

4122 's': '*.%s.*.*', 

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

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

4125 

4126 if command in quick_filter_commands: 

4127 if len(toks) >= 2: 

4128 patterns = [ 

4129 quick_filter_commands[toks[0]] % pat 

4130 for pat in toks[1:]] 

4131 self.set_quick_filter_patterns(patterns, line) 

4132 else: 

4133 self.set_quick_filter_patterns(None) 

4134 

4135 self.update() 

4136 

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

4138 if len(toks) >= 2: 

4139 patterns = [] 

4140 if len(toks) == 2: 

4141 patterns = [toks[1]] 

4142 elif len(toks) >= 3: 

4143 x = { 

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

4145 's': '*.%s.*.*', 

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

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

4148 

4149 if toks[1] in x: 

4150 patterns.extend( 

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

4152 

4153 for pattern in patterns: 

4154 if command == 'hide': 

4155 self.add_blacklist_pattern(pattern) 

4156 else: 

4157 self.remove_blacklist_pattern(pattern) 

4158 

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

4160 self.clear_blacklist() 

4161 

4162 clearit = True 

4163 

4164 self.update() 

4165 

4166 elif command == 'markers': 

4167 if len(toks) == 2: 

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

4169 kinds = self.all_marker_kinds 

4170 else: 

4171 kinds = [] 

4172 for x in toks[1]: 

4173 try: 

4174 kinds.append(int(x)) 

4175 except Exception: 

4176 pass 

4177 

4178 self.set_visible_marker_kinds(kinds) 

4179 

4180 elif len(toks) == 1: 

4181 self.set_visible_marker_kinds(()) 

4182 

4183 self.update() 

4184 

4185 elif command == 'scaling': 

4186 if len(toks) == 2: 

4187 hideit = False 

4188 error = 'wrong number of arguments' 

4189 

4190 if len(toks) >= 3: 

4191 vmin, vmax = [ 

4192 pyrocko.model.float_or_none(x) 

4193 for x in toks[-2:]] 

4194 

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

4196 if k in d: 

4197 if vmin is not None: 

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

4199 if vmax is not None: 

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

4201 

4202 if len(toks) == 1: 

4203 self.remove_scaling_hooks() 

4204 

4205 elif len(toks) == 3: 

4206 def hook(data_ranges): 

4207 for k in data_ranges: 

4208 upd(data_ranges, k, vmin, vmax) 

4209 

4210 self.set_scaling_hook('_', hook) 

4211 

4212 elif len(toks) == 4: 

4213 pattern = toks[1] 

4214 

4215 def hook(data_ranges): 

4216 for k in pyrocko.util.match_nslcs( 

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

4218 

4219 upd(data_ranges, k, vmin, vmax) 

4220 

4221 self.set_scaling_hook(pattern, hook) 

4222 

4223 elif command == 'goto': 

4224 toks2 = line.split(None, 1) 

4225 if len(toks2) == 2: 

4226 arg = toks2[1] 

4227 m = re.match( 

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

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

4230 if m: 

4231 tlen = None 

4232 if not m.group(1): 

4233 tlen = 12*32*24*60*60 

4234 elif not m.group(2): 

4235 tlen = 32*24*60*60 

4236 elif not m.group(3): 

4237 tlen = 24*60*60 

4238 elif not m.group(4): 

4239 tlen = 60*60 

4240 elif not m.group(5): 

4241 tlen = 60 

4242 

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

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

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

4246 t = pyrocko.util.str_to_time(arg) 

4247 self.go_to_time(t, tlen=tlen) 

4248 

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

4250 supl = '00:00:00' 

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

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

4253 tmin, tmax = self.get_time_range() 

4254 sdate = pyrocko.util.time_to_str( 

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

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

4257 self.go_to_time(t) 

4258 

4259 elif arg == 'today': 

4260 self.go_to_time( 

4261 day_start( 

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

4263 

4264 elif arg == 'yesterday': 

4265 self.go_to_time( 

4266 day_start( 

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

4268 

4269 else: 

4270 self.go_to_event_by_name(arg) 

4271 

4272 else: 

4273 raise PileViewerMainException( 

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

4275 

4276 except PileViewerMainException as e: 

4277 error = str(e) 

4278 hideit = False 

4279 

4280 return clearit, hideit, error 

4281 

4282 return PileViewerMain 

4283 

4284 

4285PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4286GLPileViewerMain = MakePileViewerMainClass(qgl.QGLWidget) 

4287 

4288 

4289class LineEditWithAbort(qw.QLineEdit): 

4290 

4291 aborted = qc.pyqtSignal() 

4292 history_down = qc.pyqtSignal() 

4293 history_up = qc.pyqtSignal() 

4294 

4295 def keyPressEvent(self, key_event): 

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

4297 self.aborted.emit() 

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

4299 self.history_down.emit() 

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

4301 self.history_up.emit() 

4302 else: 

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

4304 

4305 

4306class PileViewer(qw.QFrame): 

4307 ''' 

4308 PileViewerMain + Controls + Inputline 

4309 ''' 

4310 

4311 def __init__( 

4312 self, pile, 

4313 ntracks_shown_max=20, 

4314 marker_editor_sortable=True, 

4315 use_opengl=False, 

4316 panel_parent=None, 

4317 *args): 

4318 

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

4320 

4321 layout = qw.QGridLayout() 

4322 layout.setContentsMargins(0, 0, 0, 0) 

4323 layout.setSpacing(0) 

4324 

4325 self.menu = PileViewerMenuBar(self) 

4326 

4327 if use_opengl: 

4328 self.viewer = GLPileViewerMain( 

4329 pile, 

4330 ntracks_shown_max=ntracks_shown_max, 

4331 panel_parent=panel_parent, 

4332 menu=self.menu) 

4333 else: 

4334 self.viewer = PileViewerMain( 

4335 pile, 

4336 ntracks_shown_max=ntracks_shown_max, 

4337 panel_parent=panel_parent, 

4338 menu=self.menu) 

4339 

4340 self.marker_editor_sortable = marker_editor_sortable 

4341 

4342 self.setFrameShape(qw.QFrame.StyledPanel) 

4343 self.setFrameShadow(qw.QFrame.Sunken) 

4344 

4345 self.input_area = qw.QFrame(self) 

4346 ia_layout = qw.QGridLayout() 

4347 ia_layout.setContentsMargins(11, 11, 11, 11) 

4348 self.input_area.setLayout(ia_layout) 

4349 

4350 self.inputline = LineEditWithAbort(self.input_area) 

4351 self.inputline.returnPressed.connect( 

4352 self.inputline_returnpressed) 

4353 self.inputline.editingFinished.connect( 

4354 self.inputline_finished) 

4355 self.inputline.aborted.connect( 

4356 self.inputline_aborted) 

4357 

4358 self.inputline.history_down.connect( 

4359 lambda: self.step_through_history(1)) 

4360 self.inputline.history_up.connect( 

4361 lambda: self.step_through_history(-1)) 

4362 

4363 self.inputline.textEdited.connect( 

4364 self.inputline_changed) 

4365 

4366 self.inputline.setPlaceholderText( 

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

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

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

4370 self.input_area.hide() 

4371 self.history = None 

4372 

4373 self.inputline_error_str = None 

4374 

4375 self.inputline_error = qw.QLabel() 

4376 self.inputline_error.hide() 

4377 

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

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

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

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

4382 

4383 pb = Progressbars(self) 

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

4385 self.progressbars = pb 

4386 

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

4388 self.scrollbar = scrollbar 

4389 layout.addWidget(scrollbar, 1, 1) 

4390 self.scrollbar.valueChanged.connect( 

4391 self.scrollbar_changed) 

4392 

4393 self.block_scrollbar_changes = False 

4394 

4395 self.viewer.want_input.connect( 

4396 self.inputline_show) 

4397 self.viewer.tracks_range_changed.connect( 

4398 self.tracks_range_changed) 

4399 self.viewer.pile_has_changed_signal.connect( 

4400 self.adjust_controls) 

4401 self.viewer.about_to_close.connect( 

4402 self.save_inputline_history) 

4403 

4404 self.setLayout(layout) 

4405 

4406 def cleanup(self): 

4407 self.viewer.cleanup() 

4408 

4409 def get_progressbars(self): 

4410 return self.progressbars 

4411 

4412 def inputline_show(self): 

4413 if not self.history: 

4414 self.load_inputline_history() 

4415 

4416 self.input_area.show() 

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

4418 self.inputline.selectAll() 

4419 

4420 def inputline_set_error(self, string): 

4421 self.inputline_error_str = string 

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

4423 self.inputline.selectAll() 

4424 self.inputline_error.setText(string) 

4425 self.input_area.show() 

4426 self.inputline_error.show() 

4427 

4428 def inputline_clear_error(self): 

4429 if self.inputline_error_str: 

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

4431 self.inputline_error_str = None 

4432 self.inputline_error.clear() 

4433 self.inputline_error.hide() 

4434 

4435 def inputline_changed(self, line): 

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

4437 self.inputline_clear_error() 

4438 

4439 def inputline_returnpressed(self): 

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

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

4442 

4443 if error: 

4444 self.inputline_set_error(error) 

4445 

4446 line = line.strip() 

4447 

4448 if line != '' and not error: 

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

4450 self.history.append(line) 

4451 

4452 if clearit: 

4453 

4454 self.inputline.blockSignals(True) 

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

4456 if qpat is None: 

4457 self.inputline.clear() 

4458 else: 

4459 self.inputline.setText(qinp) 

4460 self.inputline.blockSignals(False) 

4461 

4462 if hideit and not error: 

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

4464 self.input_area.hide() 

4465 

4466 self.hist_ind = len(self.history) 

4467 

4468 def inputline_aborted(self): 

4469 ''' 

4470 Hide the input line. 

4471 ''' 

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

4473 self.hist_ind = len(self.history) 

4474 self.input_area.hide() 

4475 

4476 def save_inputline_history(self): 

4477 ''' 

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

4479 ''' 

4480 if not self.history: 

4481 return 

4482 

4483 conf = pyrocko.config 

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

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

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

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

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

4489 

4490 def load_inputline_history(self): 

4491 ''' 

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

4493 ''' 

4494 conf = pyrocko.config 

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

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

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

4498 f.write('\n') 

4499 

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

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

4502 

4503 self.hist_ind = len(self.history) 

4504 

4505 def step_through_history(self, ud=1): 

4506 ''' 

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

4508 ''' 

4509 n = len(self.history) 

4510 self.hist_ind += ud 

4511 self.hist_ind %= (n + 1) 

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

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

4514 else: 

4515 self.inputline.setText('') 

4516 

4517 def inputline_finished(self): 

4518 pass 

4519 

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

4521 if self.block_scrollbar_changes: 

4522 return 

4523 

4524 self.scrollbar.blockSignals(True) 

4525 self.scrollbar.setPageStep(ihi-ilo) 

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

4527 self.scrollbar.setRange(0, vmax) 

4528 self.scrollbar.setValue(ilo) 

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

4530 self.scrollbar.blockSignals(False) 

4531 

4532 def scrollbar_changed(self, value): 

4533 self.block_scrollbar_changes = True 

4534 ilo = value 

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

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

4537 self.block_scrollbar_changes = False 

4538 self.update_contents() 

4539 

4540 def controls(self): 

4541 frame = qw.QFrame(self) 

4542 layout = qw.QGridLayout() 

4543 frame.setLayout(layout) 

4544 

4545 minfreq = 0.001 

4546 maxfreq = 1000.0 

4547 self.lowpass_control = ValControl(high_is_none=True) 

4548 self.lowpass_control.setup( 

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

4550 self.highpass_control = ValControl(low_is_none=True) 

4551 self.highpass_control.setup( 

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

4553 self.gain_control = ValControl() 

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

4555 self.rot_control = LinValControl() 

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

4557 self.colorbar_control = ColorbarControl(self) 

4558 

4559 self.lowpass_control.valchange.connect( 

4560 self.viewer.lowpass_change) 

4561 self.highpass_control.valchange.connect( 

4562 self.viewer.highpass_change) 

4563 self.gain_control.valchange.connect( 

4564 self.viewer.gain_change) 

4565 self.rot_control.valchange.connect( 

4566 self.viewer.rot_change) 

4567 self.colorbar_control.cmap_changed.connect( 

4568 self.viewer.waterfall_cmap_change 

4569 ) 

4570 self.colorbar_control.clip_changed.connect( 

4571 self.viewer.waterfall_clip_change 

4572 ) 

4573 self.colorbar_control.show_absolute_toggled.connect( 

4574 self.viewer.waterfall_show_absolute_change 

4575 ) 

4576 self.colorbar_control.show_integrate_toggled.connect( 

4577 self.viewer.waterfall_set_integrate 

4578 ) 

4579 

4580 for icontrol, control in enumerate(( 

4581 self.highpass_control, 

4582 self.lowpass_control, 

4583 self.gain_control, 

4584 self.rot_control, 

4585 self.colorbar_control)): 

4586 

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

4588 layout.addWidget(widget, icontrol, iwidget) 

4589 

4590 spacer = qw.QSpacerItem( 

4591 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4593 

4594 self.adjust_controls() 

4595 self.viewer.viewmode_change(ViewMode.Wiggle) 

4596 return frame 

4597 

4598 def marker_editor(self): 

4599 editor = pyrocko.gui.marker_editor.MarkerEditor( 

4600 self, sortable=self.marker_editor_sortable) 

4601 

4602 editor.set_viewer(self.get_view()) 

4603 editor.get_marker_model().dataChanged.connect( 

4604 self.update_contents) 

4605 return editor 

4606 

4607 def adjust_controls(self): 

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

4609 maxfreq = 0.5/dtmin 

4610 minfreq = (0.5/dtmax)*0.001 

4611 self.lowpass_control.set_range(minfreq, maxfreq) 

4612 self.highpass_control.set_range(minfreq, maxfreq) 

4613 

4614 def setup_snufflings(self): 

4615 self.viewer.setup_snufflings() 

4616 

4617 def get_view(self): 

4618 return self.viewer 

4619 

4620 def update_contents(self): 

4621 self.viewer.update() 

4622 

4623 def get_pile(self): 

4624 return self.viewer.get_pile() 

4625 

4626 def show_colorbar_ctrl(self, show): 

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

4628 w.setVisible(show) 

4629 

4630 def show_gain_ctrl(self, show): 

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

4632 w.setVisible(show)