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 def __lt__(self, other): 

139 if other is None: 

140 return True 

141 return float(self) < float(other) 

142 

143 def __gt__(self, other): 

144 if other is None: 

145 return False 

146 return float(self) > float(other) 

147 

148 

149def deg_float_or_none(x): 

150 if x is None: 

151 return None 

152 else: 

153 return deg_float(x) 

154 

155 

156class sector_int(int): 

157 

158 def __str__(self): 

159 return '[%i]' % self 

160 

161 def __lt__(self, other): 

162 if other is None: 

163 return True 

164 return int(self) < int(other) 

165 

166 def __gt__(self, other): 

167 if other is None: 

168 return False 

169 return int(self) > int(other) 

170 

171 

172def num_to_html(num): 

173 snum = '%g' % num 

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

175 if m: 

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

177 

178 return snum 

179 

180 

181gap_lap_tolerance = 5. 

182 

183 

184class ViewMode(enum.Enum): 

185 Wiggle = 1 

186 Waterfall = 2 

187 

188 

189class Timer(object): 

190 def __init__(self): 

191 self._start = None 

192 self._stop = None 

193 

194 def start(self): 

195 self._start = os.times() 

196 

197 def stop(self): 

198 self._stop = os.times() 

199 

200 def get(self): 

201 a = self._start 

202 b = self._stop 

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

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

205 else: 

206 return tuple([0.] * 5) 

207 

208 def __sub__(self, other): 

209 a = self.get() 

210 b = other.get() 

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

212 

213 

214class ObjectStyle(object): 

215 def __init__(self, frame_pen, fill_brush): 

216 self.frame_pen = frame_pen 

217 self.fill_brush = fill_brush 

218 

219 

220box_styles = [] 

221box_alpha = 100 

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

223 'scarletred'.split(): 

224 

225 box_styles.append(ObjectStyle( 

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

227 qg.QBrush(qg.QColor( 

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

229 )) 

230 

231box_styles_coverage = {} 

232 

233box_styles_coverage['waveform'] = [ 

234 ObjectStyle( 

235 qg.QPen( 

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

237 1, qc.Qt.DashLine), 

238 qg.QBrush(qg.QColor( 

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

240 ), 

241 ObjectStyle( 

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

243 qg.QBrush(qg.QColor( 

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

245 ), 

246 ObjectStyle( 

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

248 qg.QBrush(qg.QColor( 

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

250 )] 

251 

252box_styles_coverage['waveform_promise'] = [ 

253 ObjectStyle( 

254 qg.QPen( 

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

256 1, qc.Qt.DashLine), 

257 qg.QBrush(qg.QColor( 

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

259 ), 

260 ObjectStyle( 

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

262 qg.QBrush(qg.QColor( 

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

264 ), 

265 ObjectStyle( 

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

267 qg.QBrush(qg.QColor( 

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

269 )] 

270 

271sday = 60*60*24. # \ 

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

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

274 

275acceptable_tincs = num.array([ 

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

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

278 

279 

280working_system_time_range = \ 

281 pyrocko.util.working_system_time_range() 

282 

283initial_time_range = [] 

284 

285try: 

286 initial_time_range.append( 

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

288except Exception: 

289 initial_time_range.append(working_system_time_range[0]) 

290 

291try: 

292 initial_time_range.append( 

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

294except Exception: 

295 initial_time_range.append(working_system_time_range[1]) 

296 

297 

298def is_working_time(t): 

299 return working_system_time_range[0] <= t and \ 

300 t <= working_system_time_range[1] 

301 

302 

303def fancy_time_ax_format(inc): 

304 l0_fmt_brief = '' 

305 l2_fmt = '' 

306 l2_trig = 0 

307 if inc < 0.000001: 

308 l0_fmt = '.%n' 

309 l0_center = False 

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

311 l1_trig = 6 

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

313 l2_trig = 3 

314 elif inc < 0.001: 

315 l0_fmt = '.%u' 

316 l0_center = False 

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

318 l1_trig = 6 

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

320 l2_trig = 3 

321 elif inc < 1: 

322 l0_fmt = '.%r' 

323 l0_center = False 

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

325 l1_trig = 6 

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

327 l2_trig = 3 

328 elif inc < 60: 

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

330 l0_center = False 

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

332 l1_trig = 3 

333 elif inc < 3600: 

334 l0_fmt = '%H:%M' 

335 l0_center = False 

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

337 l1_trig = 3 

338 elif inc < sday: 

339 l0_fmt = '%H:%M' 

340 l0_center = False 

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

342 l1_trig = 3 

343 elif inc < smonth: 

344 l0_fmt = '%a %d' 

345 l0_fmt_brief = '%d' 

346 l0_center = True 

347 l1_fmt = '%b, %Y' 

348 l1_trig = 2 

349 elif inc < syear: 

350 l0_fmt = '%b' 

351 l0_center = True 

352 l1_fmt = '%Y' 

353 l1_trig = 1 

354 else: 

355 l0_fmt = '%Y' 

356 l0_center = False 

357 l1_fmt = '' 

358 l1_trig = 0 

359 

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

361 

362 

363def day_start(timestamp): 

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

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

366 return calendar.timegm(tts) 

367 

368 

369def month_start(timestamp): 

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

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

372 return calendar.timegm(tts) 

373 

374 

375def year_start(timestamp): 

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

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

378 return calendar.timegm(tts) 

379 

380 

381def time_nice_value(inc0): 

382 if inc0 < acceptable_tincs[0]: 

383 return pyrocko.plot.nice_value(inc0) 

384 elif inc0 > acceptable_tincs[-1]: 

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

386 else: 

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

388 return acceptable_tincs[i] 

389 

390 

391class TimeScaler(pyrocko.plot.AutoScaler): 

392 def __init__(self): 

393 pyrocko.plot.AutoScaler.__init__(self) 

394 self.mode = 'min-max' 

395 

396 def make_scale(self, data_range): 

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

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

399 

400 data_min = min(data_range) 

401 data_max = max(data_range) 

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

403 

404 mi, ma = data_min, data_max 

405 nmi = mi 

406 if self.mode != 'off': 

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

408 

409 nma = ma 

410 if self.mode != 'off': 

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

412 

413 mi, ma = nmi, nma 

414 

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

416 mi -= 1.0 

417 ma += 1.0 

418 

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

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

421 

422 # make nice tick increment 

423 if self.inc is not None: 

424 inc = self.inc 

425 else: 

426 if self.approx_ticks > 0.: 

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

428 else: 

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

430 

431 if inc == 0.0: 

432 inc = 1.0 

433 

434 if is_reverse: 

435 return ma, mi, -inc 

436 else: 

437 return mi, ma, inc 

438 

439 def make_ticks(self, data_range): 

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

441 

442 is_reverse = False 

443 if inc < 0: 

444 mi, ma, inc = ma, mi, -inc 

445 is_reverse = True 

446 

447 ticks = [] 

448 

449 if inc < sday: 

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

451 if inc < 0.001: 

452 mi_day = hpfloat(mi_day) 

453 

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

455 if inc < 0.001: 

456 base = hpfloat(base) 

457 

458 base_day = mi_day 

459 i = 0 

460 while True: 

461 tick = base+i*inc 

462 if tick > ma: 

463 break 

464 

465 tick_day = day_start(tick) 

466 if tick_day > base_day: 

467 base_day = tick_day 

468 base = base_day 

469 i = 0 

470 else: 

471 ticks.append(tick) 

472 i += 1 

473 

474 elif inc < smonth: 

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

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

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

478 if mi_day == mi: 

479 dt_base += delta 

480 i = 0 

481 while True: 

482 current = dt_base + i*delta 

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

484 if tick > ma: 

485 break 

486 ticks.append(tick) 

487 i += 1 

488 

489 elif inc < syear: 

490 mi_month = month_start(max( 

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

492 

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

494 while True: 

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

496 m += 1 

497 if m > 12: 

498 y, m = y+1, 1 

499 

500 if tick > ma: 

501 break 

502 

503 if tick >= mi: 

504 ticks.append(tick) 

505 

506 else: 

507 mi_year = year_start(max( 

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

509 

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

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

512 

513 while True: 

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

515 y += incy 

516 if tick > ma: 

517 break 

518 if tick >= mi: 

519 ticks.append(tick) 

520 

521 if is_reverse: 

522 ticks.reverse() 

523 

524 return ticks, inc 

525 

526 

527def need_l1_tick(tt, ms, l1_trig): 

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

529 

530 

531def tick_to_labels(tick, inc): 

532 tt, ms = gmtime_x(tick) 

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

534 fancy_time_ax_format(inc) 

535 

536 l0 = mystrftime(l0_fmt, tt, ms) 

537 l0_brief = mystrftime(l0_fmt_brief, tt, ms) 

538 l1, l2 = None, None 

539 if need_l1_tick(tt, ms, l1_trig): 

540 l1 = mystrftime(l1_fmt, tt, ms) 

541 if need_l1_tick(tt, ms, l2_trig): 

542 l2 = mystrftime(l2_fmt, tt, ms) 

543 

544 return l0, l0_brief, l0_center, l1, l2 

545 

546 

547def l1_l2_tick(tick, inc): 

548 tt, ms = gmtime_x(tick) 

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

550 fancy_time_ax_format(inc) 

551 

552 l1 = mystrftime(l1_fmt, tt, ms) 

553 l2 = mystrftime(l2_fmt, tt, ms) 

554 return l1, l2 

555 

556 

557class TimeAx(TimeScaler): 

558 def __init__(self, *args): 

559 TimeScaler.__init__(self, *args) 

560 

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

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

563 p.setPen(pen) 

564 font = qg.QFont() 

565 font.setBold(True) 

566 p.setFont(font) 

567 fm = p.fontMetrics() 

568 ticklen = 10 

569 pad = 10 

570 tmin, tmax = xprojection.get_in_range() 

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

572 l1_hits = 0 

573 l2_hits = 0 

574 

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

576 uumin, uumax = xprojection.get_out_range() 

577 first_tick_with_label = None 

578 for tick in ticks: 

579 umin = xprojection(tick) 

580 

581 umin_approx_next = xprojection(tick+inc) 

582 umax = xprojection(tick) 

583 

584 pinc_approx = umin_approx_next - umin 

585 

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

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

588 

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

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

591 if l2: 

592 l2 = None 

593 elif l1: 

594 l1 = None 

595 

596 if l0_center: 

597 ushift = (umin_approx_next-umin)/2. 

598 else: 

599 ushift = 0. 

600 

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

602 label0 = l0x 

603 rect0 = fm.boundingRect(label0) 

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

605 break 

606 

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

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

609 

610 if first_tick_with_label is None: 

611 first_tick_with_label = tick 

612 p.drawText(qc.QPointF( 

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

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

615 

616 if l1: 

617 label1 = l1 

618 rect1 = fm.boundingRect(label1) 

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

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

621 

622 p.drawText(qc.QPointF( 

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

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

625 label1) 

626 

627 l1_hits += 1 

628 

629 if l2: 

630 label2 = l2 

631 rect2 = fm.boundingRect(label2) 

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

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

634 

635 p.drawText(qc.QPointF( 

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

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

638 ticklen), label2) 

639 

640 l2_hits += 1 

641 

642 if first_tick_with_label is None: 

643 first_tick_with_label = tmin 

644 

645 l1, l2 = l1_l2_tick(first_tick_with_label, inc) 

646 

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

648 tmax - tmin < 3600*24: 

649 

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

651 if l2: 

652 l2 = None 

653 elif l1: 

654 l1 = None 

655 

656 if l1_hits == 0 and l1: 

657 label1 = l1 

658 rect1 = fm.boundingRect(label1) 

659 p.drawText(qc.QPointF( 

660 uumin+pad, 

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

662 label1) 

663 

664 l1_hits += 1 

665 

666 if l2_hits == 0 and l2: 

667 label2 = l2 

668 rect2 = fm.boundingRect(label2) 

669 p.drawText(qc.QPointF( 

670 uumin+pad, 

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

672 label2) 

673 

674 v = yprojection(0) 

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

676 

677 

678class Projection(object): 

679 def __init__(self): 

680 self.xr = 0., 1. 

681 self.ur = 0., 1. 

682 

683 def set_in_range(self, xmin, xmax): 

684 if xmax == xmin: 

685 xmax = xmin + 1. 

686 

687 self.xr = xmin, xmax 

688 

689 def get_in_range(self): 

690 return self.xr 

691 

692 def set_out_range(self, umin, umax): 

693 if umax == umin: 

694 umax = umin + 1. 

695 

696 self.ur = umin, umax 

697 

698 def get_out_range(self): 

699 return self.ur 

700 

701 def __call__(self, x): 

702 umin, umax = self.ur 

703 xmin, xmax = self.xr 

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

705 

706 def clipped(self, x): 

707 umin, umax = self.ur 

708 xmin, xmax = self.xr 

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

710 

711 def rev(self, u): 

712 umin, umax = self.ur 

713 xmin, xmax = self.xr 

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

715 

716 def copy(self): 

717 return copy.copy(self) 

718 

719 

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

721 group = qw.QActionGroup(menu) 

722 group.setExclusive(True) 

723 menuitems = [] 

724 

725 for name, value, *shortcut in menudef: 

726 action = menu.addAction(name) 

727 action.setCheckable(True) 

728 action.setActionGroup(group) 

729 if shortcut: 

730 action.setShortcut(shortcut[0]) 

731 

732 menuitems.append((action, value)) 

733 if default is not None and ( 

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

735 value == default): 

736 action.setChecked(True) 

737 

738 group.triggered.connect(target) 

739 

740 if default is None: 

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

742 

743 return menuitems 

744 

745 

746def sort_actions(menu): 

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

748 for action in actions: 

749 menu.removeAction(action) 

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

751 

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

753 if help_action: 

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

755 for action in actions: 

756 menu.addAction(action) 

757 

758 

759fkey_map = dict(zip( 

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

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

762 range(10))) 

763 

764 

765class PileViewerMainException(Exception): 

766 pass 

767 

768 

769class PileViewerMenuBar(qw.QMenuBar): 

770 ... 

771 

772 

773class PileViewerMenu(qw.QMenu): 

774 ... 

775 

776 

777def MakePileViewerMainClass(base): 

778 

779 class PileViewerMain(base): 

780 

781 want_input = qc.pyqtSignal() 

782 about_to_close = qc.pyqtSignal() 

783 pile_has_changed_signal = qc.pyqtSignal() 

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

785 

786 begin_markers_add = qc.pyqtSignal(int, int) 

787 end_markers_add = qc.pyqtSignal() 

788 begin_markers_remove = qc.pyqtSignal(int, int) 

789 end_markers_remove = qc.pyqtSignal() 

790 

791 marker_selection_changed = qc.pyqtSignal(list) 

792 active_event_marker_changed = qc.pyqtSignal() 

793 

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

795 menu=None): 

796 if base == qgl.QGLWidget: 

797 from OpenGL import GL # noqa 

798 

799 base.__init__( 

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

801 else: 

802 base.__init__(self, *args) 

803 

804 self.pile = pile 

805 self.ax_height = 80 

806 self.panel_parent = panel_parent 

807 

808 self.click_tolerance = 5 

809 

810 self.ntracks_shown_max = ntracks_shown_max 

811 self.initial_ntracks_shown_max = ntracks_shown_max 

812 self.ntracks = 0 

813 self.show_all = True 

814 self.shown_tracks_range = None 

815 self.track_start = None 

816 self.track_trange = None 

817 

818 self.lowpass = None 

819 self.highpass = None 

820 self.gain = 1.0 

821 self.rotate = 0.0 

822 self.picking_down = None 

823 self.picking = None 

824 self.floating_marker = None 

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

826 self.markers_deltat_max = 0. 

827 self.n_selected_markers = 0 

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

829 self.visible_marker_kinds = self.all_marker_kinds 

830 self.active_event_marker = None 

831 self.ignore_releases = 0 

832 self.message = None 

833 self.reloaded = False 

834 self.pile_has_changed = False 

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

836 

837 self.tax = TimeAx() 

838 self.setBackgroundRole(qg.QPalette.Base) 

839 self.setAutoFillBackground(True) 

840 poli = qw.QSizePolicy( 

841 qw.QSizePolicy.Expanding, 

842 qw.QSizePolicy.Expanding) 

843 

844 self.setSizePolicy(poli) 

845 self.setMinimumSize(300, 200) 

846 self.setFocusPolicy(qc.Qt.ClickFocus) 

847 

848 self.menu = menu or PileViewerMenu(self) 

849 

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

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

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

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

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

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

856 

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

858 

859 self.snufflings_menu = self.toggle_panel_menu.addMenu( 

860 'Run Snuffling') 

861 self.toggle_panel_menu.addSeparator() 

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

863 help_menu.addSeparator() 

864 

865 file_menu.addAction( 

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

867 'Open waveform files...', 

868 self.open_waveforms, 

869 qg.QKeySequence.Open) 

870 

871 file_menu.addAction( 

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

873 'Open waveform directory...', 

874 self.open_waveform_directory) 

875 

876 file_menu.addAction( 

877 'Open station files...', 

878 self.open_stations) 

879 

880 file_menu.addAction( 

881 'Open StationXML files...', 

882 self.open_stations_xml) 

883 

884 file_menu.addAction( 

885 'Open event file...', 

886 self.read_events) 

887 

888 file_menu.addSeparator() 

889 file_menu.addAction( 

890 'Open marker file...', 

891 self.read_markers) 

892 

893 file_menu.addAction( 

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

895 'Save markers...', 

896 self.write_markers, 

897 qg.QKeySequence.Save) 

898 

899 file_menu.addAction( 

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

901 'Save selected markers...', 

902 self.write_selected_markers, 

903 qg.QKeySequence.SaveAs) 

904 

905 file_menu.addSeparator() 

906 file_menu.addAction( 

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

908 'Print', 

909 self.printit, 

910 qg.QKeySequence.Print) 

911 

912 file_menu.addAction( 

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

914 'Save as SVG or PNG', 

915 self.savesvg, 

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

917 

918 file_menu.addSeparator() 

919 close = file_menu.addAction( 

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

921 'Close', 

922 self.myclose) 

923 close.setShortcuts( 

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

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

926 

927 # Scale Menu 

928 menudef = [ 

929 ('Individual Scale', 

930 lambda tr: tr.nslc_id, 

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

932 ('Common Scale', 

933 lambda tr: None, 

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

935 ('Common Scale per Station', 

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

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

938 ('Common Scale per Station Location', 

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

940 ('Common Scale per Component', 

941 lambda tr: (tr.channel)), 

942 ] 

943 

944 self.menuitems_scaling = add_radiobuttongroup( 

945 scale_menu, menudef, self.scalingmode_change, 

946 default=self.config.trace_scale) 

947 scale_menu.addSeparator() 

948 

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

950 self.scaling_hooks = {} 

951 self.scalingmode_change() 

952 

953 menudef = [ 

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

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

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

957 ] 

958 

959 self.menuitems_scaling_base = add_radiobuttongroup( 

960 scale_menu, menudef, self.scaling_base_change) 

961 

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

963 scale_menu.addSeparator() 

964 

965 self.menuitem_fixscalerange = scale_menu.addAction( 

966 'Fix Scale Ranges') 

967 self.menuitem_fixscalerange.setCheckable(True) 

968 

969 # Sort Menu 

970 def sector_dist(sta): 

971 if sta.dist_m is None: 

972 return None, None 

973 else: 

974 return ( 

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

976 m_float(sta.dist_m)) 

977 

978 menudef = [ 

979 ('Sort by Names', 

980 lambda tr: (), 

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

982 ('Sort by Distance', 

983 lambda tr: self.station_attrib( 

984 tr, 

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

986 lambda tr: (None,)), 

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

988 ('Sort by Azimuth', 

989 lambda tr: self.station_attrib( 

990 tr, 

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

992 lambda tr: (None,))), 

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

994 lambda tr: self.station_attrib( 

995 tr, 

996 sector_dist, 

997 lambda tr: (None, None))), 

998 ('Sort by Backazimuth', 

999 lambda tr: self.station_attrib( 

1000 tr, 

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

1002 lambda tr: (None,))), 

1003 ] 

1004 self.menuitems_ssorting = add_radiobuttongroup( 

1005 sort_menu, menudef, self.s_sortingmode_change) 

1006 sort_menu.addSeparator() 

1007 

1008 self._ssort = lambda tr: () 

1009 

1010 self.menu.addSeparator() 

1011 

1012 menudef = [ 

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

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

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

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

1017 ((0, 1, 3, 2), 

1018 lambda tr: tr.channel)), 

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

1020 ((1, 0, 3, 2), 

1021 lambda tr: tr.channel)), 

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

1023 ((2, 0, 1, 3), 

1024 lambda tr: tr.channel)), 

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

1026 ((3, 0, 1, 2), 

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

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

1029 ((0, 1, 3), 

1030 lambda tr: tr.location)), 

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

1032 ((1, 0, 3), 

1033 lambda tr: tr.location)), 

1034 ] 

1035 

1036 self.menuitems_sorting = add_radiobuttongroup( 

1037 sort_menu, menudef, self.sortingmode_change) 

1038 

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

1040 self.config.visible_length_setting] 

1041 

1042 # View menu 

1043 self.menuitems_visible_length = add_radiobuttongroup( 

1044 view_menu, menudef, 

1045 self.visible_length_change) 

1046 view_menu.addSeparator() 

1047 

1048 view_modes = [ 

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

1050 ('Waterfall', ViewMode.Waterfall) 

1051 ] 

1052 

1053 self.menuitems_viewmode = add_radiobuttongroup( 

1054 view_menu, view_modes, 

1055 self.viewmode_change, default=ViewMode.Wiggle) 

1056 view_menu.addSeparator() 

1057 

1058 self.menuitem_cliptraces = view_menu.addAction( 

1059 'Clip Traces') 

1060 self.menuitem_cliptraces.setCheckable(True) 

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

1062 

1063 self.menuitem_showboxes = view_menu.addAction( 

1064 'Show Boxes') 

1065 self.menuitem_showboxes.setCheckable(True) 

1066 self.menuitem_showboxes.setChecked( 

1067 self.config.show_boxes) 

1068 

1069 self.menuitem_colortraces = view_menu.addAction( 

1070 'Color Traces') 

1071 self.menuitem_colortraces.setCheckable(True) 

1072 self.menuitem_antialias = view_menu.addAction( 

1073 'Antialiasing') 

1074 self.menuitem_antialias.setCheckable(True) 

1075 

1076 view_menu.addSeparator() 

1077 self.menuitem_showscalerange = view_menu.addAction( 

1078 'Show Scale Ranges') 

1079 self.menuitem_showscalerange.setCheckable(True) 

1080 self.menuitem_showscalerange.setChecked( 

1081 self.config.show_scale_ranges) 

1082 

1083 self.menuitem_showscaleaxis = view_menu.addAction( 

1084 'Show Scale Axes') 

1085 self.menuitem_showscaleaxis.setCheckable(True) 

1086 self.menuitem_showscaleaxis.setChecked( 

1087 self.config.show_scale_axes) 

1088 

1089 self.menuitem_showzeroline = view_menu.addAction( 

1090 'Show Zero Lines') 

1091 self.menuitem_showzeroline.setCheckable(True) 

1092 

1093 view_menu.addSeparator() 

1094 view_menu.addAction( 

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

1096 'Fullscreen', 

1097 self.toggle_fullscreen, 

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

1099 

1100 # Options Menu 

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

1102 self.menuitem_demean.setCheckable(True) 

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

1104 self.menuitem_demean.setShortcut( 

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

1106 

1107 self.menuitem_distances_3d = options_menu.addAction( 

1108 '3D distances', 

1109 self.distances_3d_changed) 

1110 self.menuitem_distances_3d.setCheckable(True) 

1111 

1112 self.menuitem_allowdownsampling = options_menu.addAction( 

1113 'Allow Downsampling') 

1114 self.menuitem_allowdownsampling.setCheckable(True) 

1115 self.menuitem_allowdownsampling.setChecked(True) 

1116 

1117 self.menuitem_degap = options_menu.addAction( 

1118 'Allow Degapping') 

1119 self.menuitem_degap.setCheckable(True) 

1120 self.menuitem_degap.setChecked(True) 

1121 

1122 options_menu.addSeparator() 

1123 

1124 self.menuitem_fft_filtering = options_menu.addAction( 

1125 'FFT Filtering') 

1126 self.menuitem_fft_filtering.setCheckable(True) 

1127 

1128 self.menuitem_lphp = options_menu.addAction( 

1129 'Bandpass is Low- + Highpass') 

1130 self.menuitem_lphp.setCheckable(True) 

1131 self.menuitem_lphp.setChecked(True) 

1132 

1133 options_menu.addSeparator() 

1134 self.menuitem_watch = options_menu.addAction( 

1135 'Watch Files') 

1136 self.menuitem_watch.setCheckable(True) 

1137 

1138 self.menuitem_liberal_fetch = options_menu.addAction( 

1139 'Liberal Fetch Optimization') 

1140 self.menuitem_liberal_fetch.setCheckable(True) 

1141 

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

1143 

1144 self.snufflings_menu.addAction( 

1145 'Reload Snufflings', 

1146 self.setup_snufflings) 

1147 

1148 # Disable ShadowPileTest 

1149 if False: 

1150 test_action = self.menu.addAction( 

1151 'Test', 

1152 self.toggletest) 

1153 test_action.setCheckable(True) 

1154 

1155 help_menu.addAction( 

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

1157 'Snuffler Controls', 

1158 self.help, 

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

1160 

1161 help_menu.addAction( 

1162 'About', 

1163 self.about) 

1164 

1165 self.time_projection = Projection() 

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

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

1168 

1169 self.gather = None 

1170 

1171 self.trace_filter = None 

1172 self.quick_filter = None 

1173 self.quick_filter_patterns = None, None 

1174 self.blacklist = [] 

1175 

1176 self.track_to_screen = Projection() 

1177 self.track_to_nslc_ids = {} 

1178 

1179 self.cached_vec = None 

1180 self.cached_processed_traces = None 

1181 

1182 self.timer = qc.QTimer(self) 

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

1184 self.timer.setInterval(1000) 

1185 self.timer.start() 

1186 self.pile.add_listener(self) 

1187 self.trace_styles = {} 

1188 if self.get_squirrel() is None: 

1189 self.determine_box_styles() 

1190 

1191 self.setMouseTracking(True) 

1192 

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

1194 self.snuffling_modules = {} 

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

1196 self.default_snufflings = None 

1197 self.snufflings = [] 

1198 

1199 self.stations = {} 

1200 

1201 self.timer_draw = Timer() 

1202 self.timer_cutout = Timer() 

1203 self.time_spent_painting = 0.0 

1204 self.time_last_painted = time.time() 

1205 

1206 self.interactive_range_change_time = 0.0 

1207 self.interactive_range_change_delay_time = 10.0 

1208 self.follow_timer = None 

1209 

1210 self.sortingmode_change_time = 0.0 

1211 self.sortingmode_change_delay_time = None 

1212 

1213 self.old_data_ranges = {} 

1214 

1215 self.error_messages = {} 

1216 self.return_tag = None 

1217 self.wheel_pos = 60 

1218 

1219 self.setAcceptDrops(True) 

1220 self._paths_to_load = [] 

1221 

1222 self.tf_cache = {} 

1223 

1224 self.waterfall = TraceWaterfall() 

1225 self.waterfall_cmap = 'viridis' 

1226 self.waterfall_clip_min = 0. 

1227 self.waterfall_clip_max = 1. 

1228 self.waterfall_show_absolute = False 

1229 self.waterfall_integrate = False 

1230 self.view_mode = ViewMode.Wiggle 

1231 

1232 self.automatic_updates = True 

1233 

1234 self.closing = False 

1235 self.paint_timer = qc.QTimer(self) 

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

1237 self.paint_timer.setInterval(20) 

1238 self.paint_timer.start() 

1239 

1240 @qc.pyqtSlot() 

1241 def reset_updates(self): 

1242 if not self.updatesEnabled(): 

1243 self.setUpdatesEnabled(True) 

1244 

1245 def fail(self, reason): 

1246 box = qw.QMessageBox(self) 

1247 box.setText(reason) 

1248 box.exec_() 

1249 

1250 def set_trace_filter(self, filter_func): 

1251 self.trace_filter = filter_func 

1252 self.sortingmode_change() 

1253 

1254 def update_trace_filter(self): 

1255 if self.blacklist: 

1256 

1257 def blacklist_func(tr): 

1258 return not pyrocko.util.match_nslc( 

1259 self.blacklist, tr.nslc_id) 

1260 

1261 else: 

1262 blacklist_func = None 

1263 

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

1265 self.set_trace_filter(None) 

1266 elif self.quick_filter is None: 

1267 self.set_trace_filter(blacklist_func) 

1268 elif blacklist_func is None: 

1269 self.set_trace_filter(self.quick_filter) 

1270 else: 

1271 self.set_trace_filter( 

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

1273 

1274 def set_quick_filter(self, filter_func): 

1275 self.quick_filter = filter_func 

1276 self.update_trace_filter() 

1277 

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

1279 if patterns is not None: 

1280 self.set_quick_filter( 

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

1282 else: 

1283 self.set_quick_filter(None) 

1284 

1285 self.quick_filter_patterns = patterns, inputline 

1286 

1287 def get_quick_filter_patterns(self): 

1288 return self.quick_filter_patterns 

1289 

1290 def add_blacklist_pattern(self, pattern): 

1291 if pattern == 'empty': 

1292 keys = set(self.pile.nslc_ids) 

1293 trs = self.pile.all( 

1294 tmin=self.tmin, 

1295 tmax=self.tmax, 

1296 load_data=False, 

1297 degap=False) 

1298 

1299 for tr in trs: 

1300 if tr.nslc_id in keys: 

1301 keys.remove(tr.nslc_id) 

1302 

1303 for key in keys: 

1304 xpattern = '.'.join(key) 

1305 if xpattern not in self.blacklist: 

1306 self.blacklist.append(xpattern) 

1307 

1308 else: 

1309 if pattern in self.blacklist: 

1310 self.blacklist.remove(pattern) 

1311 

1312 self.blacklist.append(pattern) 

1313 

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

1315 self.update_trace_filter() 

1316 

1317 def remove_blacklist_pattern(self, pattern): 

1318 if pattern in self.blacklist: 

1319 self.blacklist.remove(pattern) 

1320 else: 

1321 raise PileViewerMainException( 

1322 'Pattern not found in blacklist.') 

1323 

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

1325 self.update_trace_filter() 

1326 

1327 def clear_blacklist(self): 

1328 self.blacklist = [] 

1329 self.update_trace_filter() 

1330 

1331 def ssort(self, tr): 

1332 return self._ssort(tr) 

1333 

1334 def station_key(self, x): 

1335 return x.network, x.station 

1336 

1337 def station_keys(self, x): 

1338 return [ 

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

1340 (x.network, x.station)] 

1341 

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

1343 for sk in self.station_keys(tr): 

1344 if sk in self.stations: 

1345 station = self.stations[sk] 

1346 return getter(station) 

1347 

1348 return default_getter(tr) 

1349 

1350 def get_station(self, sk): 

1351 return self.stations[sk] 

1352 

1353 def has_station(self, station): 

1354 for sk in self.station_keys(station): 

1355 if sk in self.stations: 

1356 return True 

1357 

1358 return False 

1359 

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

1361 return self.station_attrib( 

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

1363 

1364 def set_stations(self, stations): 

1365 self.stations = {} 

1366 self.add_stations(stations) 

1367 

1368 def add_stations(self, stations): 

1369 for station in stations: 

1370 for sk in self.station_keys(station): 

1371 self.stations[sk] = station 

1372 

1373 ev = self.get_active_event() 

1374 if ev: 

1375 self.set_origin(ev) 

1376 

1377 def add_event(self, event): 

1378 marker = EventMarker(event) 

1379 self.add_marker(marker) 

1380 

1381 def add_events(self, events): 

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

1383 self.add_markers(markers) 

1384 

1385 def set_event_marker_as_origin(self, ignore=None): 

1386 selected = self.selected_markers() 

1387 if not selected: 

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

1389 return 

1390 

1391 m = selected[0] 

1392 if not isinstance(m, EventMarker): 

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

1394 return 

1395 

1396 self.set_active_event_marker(m) 

1397 

1398 def deactivate_event_marker(self): 

1399 if self.active_event_marker: 

1400 self.active_event_marker.active = False 

1401 

1402 self.active_event_marker_changed.emit() 

1403 self.active_event_marker = None 

1404 

1405 def set_active_event_marker(self, event_marker): 

1406 if self.active_event_marker: 

1407 self.active_event_marker.active = False 

1408 

1409 self.active_event_marker = event_marker 

1410 event_marker.active = True 

1411 event = event_marker.get_event() 

1412 self.set_origin(event) 

1413 self.active_event_marker_changed.emit() 

1414 

1415 def set_active_event(self, event): 

1416 for marker in self.markers: 

1417 if isinstance(marker, EventMarker): 

1418 if marker.get_event() is event: 

1419 self.set_active_event_marker(marker) 

1420 

1421 def get_active_event_marker(self): 

1422 return self.active_event_marker 

1423 

1424 def get_active_event(self): 

1425 m = self.get_active_event_marker() 

1426 if m is not None: 

1427 return m.get_event() 

1428 else: 

1429 return None 

1430 

1431 def get_active_markers(self): 

1432 emarker = self.get_active_event_marker() 

1433 if emarker is None: 

1434 return None, [] 

1435 

1436 else: 

1437 ev = emarker.get_event() 

1438 pmarkers = [ 

1439 m for m in self.markers 

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

1441 

1442 return emarker, pmarkers 

1443 

1444 def set_origin(self, location): 

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

1446 station.set_event_relative_data( 

1447 location, 

1448 distance_3d=self.menuitem_distances_3d.isChecked()) 

1449 

1450 self.sortingmode_change() 

1451 

1452 def distances_3d_changed(self): 

1453 ignore = self.menuitem_distances_3d.isChecked() 

1454 self.set_event_marker_as_origin(ignore) 

1455 

1456 def iter_snuffling_modules(self): 

1457 pjoin = os.path.join 

1458 for path in self.snuffling_paths: 

1459 

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

1461 os.mkdir(path) 

1462 

1463 for entry in os.listdir(path): 

1464 directory = path 

1465 fn = entry 

1466 d = pjoin(path, entry) 

1467 if os.path.isdir(d): 

1468 directory = d 

1469 if os.path.isfile( 

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

1471 fn = 'snuffling.py' 

1472 

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

1474 continue 

1475 

1476 name = fn[:-3] 

1477 

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

1479 self.snuffling_modules[directory, name] = \ 

1480 pyrocko.gui.snuffling.SnufflingModule( 

1481 directory, name, self) 

1482 

1483 yield self.snuffling_modules[directory, name] 

1484 

1485 def setup_snufflings(self): 

1486 # user snufflings 

1487 for mod in self.iter_snuffling_modules(): 

1488 try: 

1489 mod.load_if_needed() 

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

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

1492 

1493 # load the default snufflings on first run 

1494 if self.default_snufflings is None: 

1495 self.default_snufflings = pyrocko.gui\ 

1496 .snufflings.__snufflings__() 

1497 for snuffling in self.default_snufflings: 

1498 self.add_snuffling(snuffling) 

1499 

1500 def set_panel_parent(self, panel_parent): 

1501 self.panel_parent = panel_parent 

1502 

1503 def get_panel_parent(self): 

1504 return self.panel_parent 

1505 

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

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

1508 snuffling.init_gui( 

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

1510 self.snufflings.append(snuffling) 

1511 self.update() 

1512 

1513 def remove_snuffling(self, snuffling): 

1514 snuffling.delete_gui() 

1515 self.update() 

1516 self.snufflings.remove(snuffling) 

1517 snuffling.pre_destroy() 

1518 

1519 def add_snuffling_menuitem(self, item): 

1520 self.snufflings_menu.addAction(item) 

1521 item.setParent(self.snufflings_menu) 

1522 sort_actions(self.snufflings_menu) 

1523 

1524 def remove_snuffling_menuitem(self, item): 

1525 self.snufflings_menu.removeAction(item) 

1526 

1527 def add_snuffling_help_menuitem(self, item): 

1528 self.snuffling_help.addAction(item) 

1529 item.setParent(self.snuffling_help) 

1530 sort_actions(self.snuffling_help) 

1531 

1532 def remove_snuffling_help_menuitem(self, item): 

1533 self.snuffling_help.removeAction(item) 

1534 

1535 def add_panel_toggler(self, item): 

1536 self.toggle_panel_menu.addAction(item) 

1537 item.setParent(self.toggle_panel_menu) 

1538 sort_actions(self.toggle_panel_menu) 

1539 

1540 def remove_panel_toggler(self, item): 

1541 self.toggle_panel_menu.removeAction(item) 

1542 

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

1544 cache_dir=None, force_cache=False): 

1545 

1546 if cache_dir is None: 

1547 cache_dir = pyrocko.config.config().cache_dir 

1548 if isinstance(paths, str): 

1549 paths = [paths] 

1550 

1551 fns = pyrocko.util.select_files( 

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

1553 

1554 if not fns: 

1555 return 

1556 

1557 cache = pyrocko.pile.get_cache(cache_dir) 

1558 

1559 t = [time.time()] 

1560 

1561 def update_bar(label, value): 

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

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

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

1565 else: 

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

1567 

1568 return pbs.set_status(label, value) 

1569 

1570 def update_progress(label, i, n): 

1571 abort = False 

1572 

1573 qw.qApp.processEvents() 

1574 if n != 0: 

1575 perc = i*100/n 

1576 else: 

1577 perc = 100 

1578 abort |= update_bar(label, perc) 

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

1580 

1581 tnow = time.time() 

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

1583 self.update() 

1584 t[0] = tnow 

1585 

1586 return abort 

1587 

1588 self.automatic_updates = False 

1589 

1590 self.pile.load_files( 

1591 sorted(fns), 

1592 filename_attributes=regex, 

1593 cache=cache, 

1594 fileformat=format, 

1595 show_progress=False, 

1596 update_progress=update_progress) 

1597 

1598 self.automatic_updates = True 

1599 self.update() 

1600 

1601 def load_queued(self): 

1602 if not self._paths_to_load: 

1603 return 

1604 paths = self._paths_to_load 

1605 self._paths_to_load = [] 

1606 self.load(paths) 

1607 

1608 def load_soon(self, paths): 

1609 self._paths_to_load.extend(paths) 

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

1611 

1612 def open_waveforms(self): 

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

1614 

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

1616 self, caption, options=qfiledialog_options)) 

1617 

1618 if fns: 

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

1620 

1621 def open_waveform_directory(self): 

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

1623 

1624 dn = qw.QFileDialog.getExistingDirectory( 

1625 self, caption, options=qfiledialog_options) 

1626 

1627 if dn: 

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

1629 

1630 def open_stations(self, fns=None): 

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

1632 

1633 if not fns: 

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

1635 self, caption, options=qfiledialog_options)) 

1636 

1637 try: 

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

1639 for stat in stations: 

1640 self.add_stations(stat) 

1641 

1642 except Exception as e: 

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

1644 

1645 def open_stations_xml(self, fns=None): 

1646 from pyrocko.io import stationxml 

1647 

1648 caption = 'Select one or more StationXML files' 

1649 if not fns: 

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

1651 self, caption, options=qfiledialog_options, 

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

1653 ';;All files (*)')) 

1654 

1655 try: 

1656 stations = [ 

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

1658 for x in fns] 

1659 

1660 for stat in stations: 

1661 self.add_stations(stat) 

1662 

1663 except Exception as e: 

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

1665 

1666 def add_traces(self, traces): 

1667 if traces: 

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

1669 self.pile.add_file(mtf) 

1670 ticket = (self.pile, mtf) 

1671 return ticket 

1672 else: 

1673 return (None, None) 

1674 

1675 def release_data(self, tickets): 

1676 for ticket in tickets: 

1677 pile, mtf = ticket 

1678 if pile is not None: 

1679 pile.remove_file(mtf) 

1680 

1681 def periodical(self): 

1682 if self.menuitem_watch.isChecked(): 

1683 if self.pile.reload_modified(): 

1684 self.update() 

1685 

1686 def get_pile(self): 

1687 return self.pile 

1688 

1689 def pile_changed(self, what): 

1690 self.pile_has_changed = True 

1691 self.pile_has_changed_signal.emit() 

1692 if self.automatic_updates: 

1693 self.update() 

1694 

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

1696 

1697 if gather is None: 

1698 def gather_func(tr): 

1699 return tr.nslc_id 

1700 

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

1702 

1703 else: 

1704 def gather_func(tr): 

1705 return ( 

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

1707 

1708 if color is None: 

1709 def color(tr): 

1710 return tr.location 

1711 

1712 self.gather = gather_func 

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

1714 

1715 self.color_gather = color 

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

1717 previous_ntracks = self.ntracks 

1718 self.set_ntracks(len(keys)) 

1719 

1720 if self.shown_tracks_range is None or \ 

1721 previous_ntracks == 0 or \ 

1722 self.show_all: 

1723 

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

1725 key_at_top = None 

1726 n = high-low 

1727 

1728 else: 

1729 low, high = self.shown_tracks_range 

1730 key_at_top = self.track_keys[low] 

1731 n = high-low 

1732 

1733 self.track_keys = sorted(keys) 

1734 

1735 track_patterns = [] 

1736 for k in self.track_keys: 

1737 pat = ['*', '*', '*', '*'] 

1738 for i, j in enumerate(gather): 

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

1740 

1741 track_patterns.append(pat) 

1742 

1743 self.track_patterns = track_patterns 

1744 

1745 if key_at_top is not None: 

1746 try: 

1747 ind = self.track_keys.index(key_at_top) 

1748 low = ind 

1749 high = low+n 

1750 except Exception: 

1751 pass 

1752 

1753 self.set_tracks_range((low, high)) 

1754 

1755 self.key_to_row = dict( 

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

1757 

1758 def inrange(x, r): 

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

1760 

1761 def trace_selector(trace): 

1762 gt = self.gather(trace) 

1763 return ( 

1764 gt in self.key_to_row and 

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

1766 

1767 if self.trace_filter is not None: 

1768 self.trace_selector = lambda x: \ 

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

1770 else: 

1771 self.trace_selector = trace_selector 

1772 

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

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

1775 self.show_all: 

1776 

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

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

1779 tlen = (tmax - tmin) 

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

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

1782 

1783 def set_time_range(self, tmin, tmax): 

1784 if tmin is None: 

1785 tmin = initial_time_range[0] 

1786 

1787 if tmax is None: 

1788 tmax = initial_time_range[1] 

1789 

1790 if tmin > tmax: 

1791 tmin, tmax = tmax, tmin 

1792 

1793 if tmin == tmax: 

1794 tmin -= 1. 

1795 tmax += 1. 

1796 

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

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

1799 

1800 min_deltat = self.content_deltat_range()[0] 

1801 if (tmax - tmin < min_deltat): 

1802 m = (tmin + tmax) / 2. 

1803 tmin = m - min_deltat/2. 

1804 tmax = m + min_deltat/2. 

1805 

1806 self.time_projection.set_in_range(tmin, tmax) 

1807 self.tmin, self.tmax = tmin, tmax 

1808 

1809 def get_time_range(self): 

1810 return self.tmin, self.tmax 

1811 

1812 def ypart(self, y): 

1813 if y < self.ax_height: 

1814 return -1 

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

1816 return 1 

1817 else: 

1818 return 0 

1819 

1820 def time_fractional_digits(self): 

1821 min_deltat = self.content_deltat_range()[0] 

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

1823 

1824 def write_markers(self, fn=None): 

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

1826 if not fn: 

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

1828 self, caption, options=qfiledialog_options)) 

1829 if fn: 

1830 try: 

1831 Marker.save_markers( 

1832 self.markers, fn, 

1833 fdigits=self.time_fractional_digits()) 

1834 

1835 except Exception as e: 

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

1837 

1838 def write_selected_markers(self, fn=None): 

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

1840 if not fn: 

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

1842 self, caption, options=qfiledialog_options)) 

1843 if fn: 

1844 try: 

1845 Marker.save_markers( 

1846 self.iter_selected_markers(), 

1847 fn, 

1848 fdigits=self.time_fractional_digits()) 

1849 

1850 except Exception as e: 

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

1852 

1853 def read_events(self, fn=None): 

1854 ''' 

1855 Open QFileDialog to open, read and add 

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

1857 representation to the pile viewer. 

1858 ''' 

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

1860 if not fn: 

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

1862 self, caption, options=qfiledialog_options)) 

1863 if fn: 

1864 try: 

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

1866 self.associate_phases_to_events() 

1867 

1868 except Exception as e: 

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

1870 

1871 def read_markers(self, fn=None): 

1872 ''' 

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

1874 ''' 

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

1876 if not fn: 

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

1878 self, caption, options=qfiledialog_options)) 

1879 if fn: 

1880 try: 

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

1882 self.associate_phases_to_events() 

1883 

1884 except Exception as e: 

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

1886 

1887 def associate_phases_to_events(self): 

1888 associate_phases_to_events(self.markers) 

1889 

1890 def add_marker(self, marker): 

1891 # need index to inform QAbstactTableModel about upcoming change, 

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

1893 self.markers.insert(marker) 

1894 i = self.markers.remove(marker) 

1895 

1896 self.begin_markers_add.emit(i, i) 

1897 self.markers.insert(marker) 

1898 self.end_markers_add.emit() 

1899 self.markers_deltat_max = max( 

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

1901 

1902 def add_markers(self, markers): 

1903 if not self.markers: 

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

1905 self.markers.insert_many(markers) 

1906 self.end_markers_add.emit() 

1907 self.update_markers_deltat_max() 

1908 else: 

1909 for marker in markers: 

1910 self.add_marker(marker) 

1911 

1912 def update_markers_deltat_max(self): 

1913 if self.markers: 

1914 self.markers_deltat_max = max( 

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

1916 

1917 def remove_marker(self, marker): 

1918 ''' 

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

1920 

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

1922 ''' 

1923 

1924 if marker is self.active_event_marker: 

1925 self.deactivate_event_marker() 

1926 

1927 try: 

1928 i = self.markers.index(marker) 

1929 self.begin_markers_remove.emit(i, i) 

1930 self.markers.remove_at(i) 

1931 self.end_markers_remove.emit() 

1932 except ValueError: 

1933 pass 

1934 

1935 def remove_markers(self, markers): 

1936 ''' 

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

1938 

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

1940 instances 

1941 ''' 

1942 

1943 if markers is self.markers: 

1944 markers = list(markers) 

1945 

1946 for marker in markers: 

1947 self.remove_marker(marker) 

1948 

1949 self.update_markers_deltat_max() 

1950 

1951 def remove_selected_markers(self): 

1952 def delete_segment(istart, iend): 

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

1954 for _ in range(iend - istart): 

1955 self.markers.remove_at(istart) 

1956 

1957 self.end_markers_remove.emit() 

1958 

1959 istart = None 

1960 ipos = 0 

1961 markers = self.markers 

1962 nmarkers = len(self.markers) 

1963 while ipos < nmarkers: 

1964 marker = markers[ipos] 

1965 if marker.is_selected(): 

1966 if marker is self.active_event_marker: 

1967 self.deactivate_event_marker() 

1968 

1969 if istart is None: 

1970 istart = ipos 

1971 else: 

1972 if istart is not None: 

1973 delete_segment(istart, ipos) 

1974 nmarkers -= ipos - istart 

1975 ipos = istart - 1 

1976 istart = None 

1977 

1978 ipos += 1 

1979 

1980 if istart is not None: 

1981 delete_segment(istart, ipos) 

1982 

1983 self.update_markers_deltat_max() 

1984 

1985 def selected_markers(self): 

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

1987 

1988 def iter_selected_markers(self): 

1989 for marker in self.markers: 

1990 if marker.is_selected(): 

1991 yield marker 

1992 

1993 def get_markers(self): 

1994 return self.markers 

1995 

1996 def mousePressEvent(self, mouse_ev): 

1997 self.show_all = False 

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

1999 

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

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

2002 if self.picking: 

2003 if self.picking_down is None: 

2004 self.picking_down = ( 

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

2006 mouse_ev.y()) 

2007 

2008 elif marker is not None: 

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

2010 self.deselect_all() 

2011 marker.selected = True 

2012 self.emit_selected_markers() 

2013 self.update() 

2014 else: 

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

2016 self.track_trange = self.tmin, self.tmax 

2017 

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

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

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

2021 self.update_status() 

2022 

2023 def mouseReleaseEvent(self, mouse_ev): 

2024 if self.ignore_releases: 

2025 self.ignore_releases -= 1 

2026 return 

2027 

2028 if self.picking: 

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

2030 self.emit_selected_markers() 

2031 

2032 if self.track_start: 

2033 self.update() 

2034 

2035 self.track_start = None 

2036 self.track_trange = None 

2037 self.update_status() 

2038 

2039 def mouseDoubleClickEvent(self, mouse_ev): 

2040 self.show_all = False 

2041 self.start_picking(None) 

2042 self.ignore_releases = 1 

2043 

2044 def mouseMoveEvent(self, mouse_ev): 

2045 self.setUpdatesEnabled(False) 

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

2047 

2048 if self.picking: 

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

2050 

2051 elif self.track_start is not None: 

2052 x0, y0 = self.track_start 

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

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

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

2056 dy = 0 

2057 

2058 tmin0, tmax0 = self.track_trange 

2059 

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

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

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

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

2064 

2065 self.interrupt_following() 

2066 self.set_time_range( 

2067 tmin0 - dt - dtr*frac, 

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

2069 

2070 self.update() 

2071 else: 

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

2073 

2074 self.update_status() 

2075 

2076 def nslc_ids_under_cursor(self, x, y): 

2077 ftrack = self.track_to_screen.rev(y) 

2078 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2079 return nslc_ids 

2080 

2081 def marker_under_cursor(self, x, y): 

2082 mouset = self.time_projection.rev(x) 

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

2084 relevant_nslc_ids = None 

2085 for marker in self.markers: 

2086 if marker.kind not in self.visible_marker_kinds: 

2087 continue 

2088 

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

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

2091 

2092 if relevant_nslc_ids is None: 

2093 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2094 

2095 marker_nslc_ids = marker.get_nslc_ids() 

2096 if not marker_nslc_ids: 

2097 return marker 

2098 

2099 for nslc_id in marker_nslc_ids: 

2100 if nslc_id in relevant_nslc_ids: 

2101 return marker 

2102 

2103 def hoovering(self, x, y): 

2104 mouset = self.time_projection.rev(x) 

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

2106 needupdate = False 

2107 haveone = False 

2108 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2109 for marker in self.markers: 

2110 if marker.kind not in self.visible_marker_kinds: 

2111 continue 

2112 

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

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

2115 

2116 if state: 

2117 xstate = False 

2118 

2119 marker_nslc_ids = marker.get_nslc_ids() 

2120 if not marker_nslc_ids: 

2121 xstate = True 

2122 

2123 for nslc in relevant_nslc_ids: 

2124 if marker.match_nslc(nslc): 

2125 xstate = True 

2126 

2127 state = xstate 

2128 

2129 if state: 

2130 haveone = True 

2131 oldstate = marker.is_alerted() 

2132 if oldstate != state: 

2133 needupdate = True 

2134 marker.set_alerted(state) 

2135 if state: 

2136 self.message = marker.hoover_message() 

2137 

2138 if not haveone: 

2139 self.message = None 

2140 

2141 if needupdate: 

2142 self.update() 

2143 

2144 def event(self, event): 

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

2146 self.keyPressEvent(event) 

2147 return True 

2148 else: 

2149 return base.event(self, event) 

2150 

2151 def keyPressEvent(self, key_event): 

2152 self.show_all = False 

2153 dt = self.tmax - self.tmin 

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

2155 

2156 key = key_event.key() 

2157 try: 

2158 keytext = str(key_event.text()) 

2159 except UnicodeEncodeError: 

2160 return 

2161 

2162 if key == qc.Qt.Key_Space: 

2163 self.interrupt_following() 

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

2165 

2166 elif key == qc.Qt.Key_Up: 

2167 for m in self.selected_markers(): 

2168 if isinstance(m, PhaseMarker): 

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

2170 p = 0 

2171 else: 

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

2173 m.set_polarity(p) 

2174 

2175 elif key == qc.Qt.Key_Down: 

2176 for m in self.selected_markers(): 

2177 if isinstance(m, PhaseMarker): 

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

2179 p = 0 

2180 else: 

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

2182 m.set_polarity(p) 

2183 

2184 elif key == qc.Qt.Key_B: 

2185 dt = self.tmax - self.tmin 

2186 self.interrupt_following() 

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

2188 

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

2190 self.interrupt_following() 

2191 

2192 tgo = None 

2193 

2194 class TraceDummy(object): 

2195 def __init__(self, marker): 

2196 self._marker = marker 

2197 

2198 @property 

2199 def nslc_id(self): 

2200 return self._marker.one_nslc() 

2201 

2202 def marker_to_itrack(marker): 

2203 try: 

2204 return self.key_to_row.get( 

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

2206 

2207 except MarkerOneNSLCRequired: 

2208 return -1 

2209 

2210 emarker, pmarkers = self.get_active_markers() 

2211 pmarkers = [ 

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

2213 pmarkers.sort(key=lambda m: ( 

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

2215 

2216 if key == qc.Qt.Key_Backtab: 

2217 pmarkers.reverse() 

2218 

2219 smarkers = self.selected_markers() 

2220 iselected = [] 

2221 for sm in smarkers: 

2222 try: 

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

2224 except ValueError: 

2225 pass 

2226 

2227 if iselected: 

2228 icurrent = max(iselected) + 1 

2229 else: 

2230 icurrent = 0 

2231 

2232 if icurrent < len(pmarkers): 

2233 self.deselect_all() 

2234 cmarker = pmarkers[icurrent] 

2235 cmarker.selected = True 

2236 tgo = cmarker.tmin 

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

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

2239 

2240 itrack = marker_to_itrack(cmarker) 

2241 if itrack != -1: 

2242 if itrack < self.shown_tracks_range[0]: 

2243 self.scroll_tracks( 

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

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

2246 self.scroll_tracks( 

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

2248 

2249 if itrack not in self.track_to_nslc_ids: 

2250 self.go_to_selection() 

2251 

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

2253 smarkers = self.selected_markers() 

2254 tgo = None 

2255 dir = str(keytext) 

2256 if smarkers: 

2257 tmid = smarkers[0].tmin 

2258 for smarker in smarkers: 

2259 if dir == 'n': 

2260 tmid = max(smarker.tmin, tmid) 

2261 else: 

2262 tmid = min(smarker.tmin, tmid) 

2263 

2264 tgo = tmid 

2265 

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

2267 for marker in sorted( 

2268 self.markers, 

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

2270 

2271 t = marker.tmin 

2272 if t > tmid and \ 

2273 marker.kind in self.visible_marker_kinds and \ 

2274 (dir == 'n' or 

2275 isinstance(marker, EventMarker)): 

2276 

2277 self.deselect_all() 

2278 marker.selected = True 

2279 tgo = t 

2280 break 

2281 else: 

2282 for marker in sorted( 

2283 self.markers, 

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

2285 reverse=True): 

2286 

2287 t = marker.tmin 

2288 if t < tmid and \ 

2289 marker.kind in self.visible_marker_kinds and \ 

2290 (dir == 'p' or 

2291 isinstance(marker, EventMarker)): 

2292 self.deselect_all() 

2293 marker.selected = True 

2294 tgo = t 

2295 break 

2296 

2297 if tgo is not None: 

2298 self.interrupt_following() 

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

2300 

2301 elif keytext == 'r': 

2302 if self.pile.reload_modified(): 

2303 self.reloaded = True 

2304 

2305 elif keytext == 'R': 

2306 self.setup_snufflings() 

2307 

2308 elif key == qc.Qt.Key_Backspace: 

2309 self.remove_selected_markers() 

2310 

2311 elif keytext == 'a': 

2312 for marker in self.markers: 

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

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

2315 marker.kind in self.visible_marker_kinds): 

2316 marker.selected = True 

2317 else: 

2318 marker.selected = False 

2319 

2320 elif keytext == 'A': 

2321 for marker in self.markers: 

2322 if marker.kind in self.visible_marker_kinds: 

2323 marker.selected = True 

2324 

2325 elif keytext == 'd': 

2326 self.deselect_all() 

2327 

2328 elif keytext == 'E': 

2329 self.deactivate_event_marker() 

2330 

2331 elif keytext == 'e': 

2332 markers = self.selected_markers() 

2333 event_markers_in_spe = [ 

2334 marker for marker in markers 

2335 if not isinstance(marker, PhaseMarker)] 

2336 

2337 phase_markers = [ 

2338 marker for marker in markers 

2339 if isinstance(marker, PhaseMarker)] 

2340 

2341 if len(event_markers_in_spe) == 1: 

2342 event_marker = event_markers_in_spe[0] 

2343 if not isinstance(event_marker, EventMarker): 

2344 nslcs = list(event_marker.nslc_ids) 

2345 lat, lon = 0.0, 0.0 

2346 old = self.get_active_event() 

2347 if len(nslcs) == 1: 

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

2349 elif old is not None: 

2350 lat, lon = old.lat, old.lon 

2351 

2352 event_marker.convert_to_event_marker(lat, lon) 

2353 

2354 self.set_active_event_marker(event_marker) 

2355 event = event_marker.get_event() 

2356 for marker in phase_markers: 

2357 marker.set_event(event) 

2358 

2359 else: 

2360 for marker in event_markers_in_spe: 

2361 marker.convert_to_event_marker() 

2362 

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

2364 for marker in self.selected_markers(): 

2365 marker.set_kind(int(keytext)) 

2366 self.emit_selected_markers() 

2367 

2368 elif key in fkey_map: 

2369 self.handle_fkeys(key) 

2370 

2371 elif key == qc.Qt.Key_Escape: 

2372 if self.picking: 

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

2374 

2375 elif key == qc.Qt.Key_PageDown: 

2376 self.scroll_tracks( 

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

2378 

2379 elif key == qc.Qt.Key_PageUp: 

2380 self.scroll_tracks( 

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

2382 

2383 elif key == qc.Qt.Key_Plus: 

2384 self.zoom_tracks(0., 1.) 

2385 

2386 elif key == qc.Qt.Key_Minus: 

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

2388 

2389 elif key == qc.Qt.Key_Equal: 

2390 ntracks_shown = self.shown_tracks_range[1] - \ 

2391 self.shown_tracks_range[0] 

2392 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2393 self.zoom_tracks(0., dtracks) 

2394 

2395 elif key == qc.Qt.Key_Colon: 

2396 self.want_input.emit() 

2397 

2398 elif keytext == 'f': 

2399 self.toggle_fullscreen() 

2400 

2401 elif keytext == 'g': 

2402 self.go_to_selection() 

2403 

2404 elif keytext == 'G': 

2405 self.go_to_selection(tight=True) 

2406 

2407 elif keytext == 'm': 

2408 self.toggle_marker_editor() 

2409 

2410 elif keytext == 'c': 

2411 self.toggle_main_controls() 

2412 

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

2414 dir = 1 

2415 amount = 1 

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

2417 dir = -1 

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

2419 amount = 10 

2420 self.nudge_selected_markers(dir*amount) 

2421 else: 

2422 super().keyPressEvent(key_event) 

2423 

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

2425 self.emit_selected_markers() 

2426 

2427 self.update() 

2428 self.update_status() 

2429 

2430 def handle_fkeys(self, key): 

2431 self.set_phase_kind( 

2432 self.selected_markers(), 

2433 fkey_map[key] + 1) 

2434 self.emit_selected_markers() 

2435 

2436 def emit_selected_markers(self): 

2437 ibounds = [] 

2438 last_selected = False 

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

2440 this_selected = marker.is_selected() 

2441 if this_selected != last_selected: 

2442 ibounds.append(imarker) 

2443 

2444 last_selected = this_selected 

2445 

2446 if last_selected: 

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

2448 

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

2450 self.n_selected_markers = sum( 

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

2452 self.marker_selection_changed.emit(chunks) 

2453 

2454 def toggle_marker_editor(self): 

2455 self.panel_parent.toggle_marker_editor() 

2456 

2457 def toggle_main_controls(self): 

2458 self.panel_parent.toggle_main_controls() 

2459 

2460 def nudge_selected_markers(self, npixels): 

2461 a, b = self.time_projection.ur 

2462 c, d = self.time_projection.xr 

2463 for marker in self.selected_markers(): 

2464 if not isinstance(marker, EventMarker): 

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

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

2467 

2468 def toggle_fullscreen(self): 

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

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

2471 self.window().showNormal() 

2472 else: 

2473 if macosx: 

2474 self.window().showMaximized() 

2475 else: 

2476 self.window().showFullScreen() 

2477 

2478 def about(self): 

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

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

2481 txt = f.read() 

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

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

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

2485 

2486 def help(self): 

2487 class MyScrollArea(qw.QScrollArea): 

2488 

2489 def sizeHint(self): 

2490 s = qc.QSize() 

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

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

2493 return s 

2494 

2495 with open(pyrocko.util.data_file( 

2496 'snuffler_help.html')) as f: 

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

2498 

2499 with open(pyrocko.util.data_file( 

2500 'snuffler_help_epilog.html')) as f: 

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

2502 

2503 for h in [hcheat, hepilog]: 

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

2505 h.setWordWrap(True) 

2506 

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

2508 

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

2510 scroller = qw.QScrollArea() 

2511 frame = qw.QFrame(scroller) 

2512 frame.setLineWidth(0) 

2513 layout = qw.QVBoxLayout() 

2514 layout.setContentsMargins(0, 0, 0, 0) 

2515 layout.setSpacing(0) 

2516 frame.setLayout(layout) 

2517 scroller.setWidget(frame) 

2518 scroller.setWidgetResizable(True) 

2519 frame.setBackgroundRole(qg.QPalette.Base) 

2520 for h in labels: 

2521 h.setParent(frame) 

2522 h.setMargin(3) 

2523 h.setTextInteractionFlags( 

2524 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2525 h.setBackgroundRole(qg.QPalette.Base) 

2526 layout.addWidget(h) 

2527 h.linkActivated.connect( 

2528 self.open_link) 

2529 

2530 if self.panel_parent is not None: 

2531 if target == 'panel': 

2532 self.panel_parent.add_panel( 

2533 name, scroller, True, volatile=False) 

2534 else: 

2535 self.panel_parent.add_tab(name, scroller) 

2536 

2537 def open_link(self, link): 

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

2539 

2540 def wheelEvent(self, wheel_event): 

2541 if use_pyqt5: 

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

2543 else: 

2544 self.wheel_pos += wheel_event.delta() 

2545 

2546 n = self.wheel_pos // 120 

2547 self.wheel_pos = self.wheel_pos % 120 

2548 if n == 0: 

2549 return 

2550 

2551 amount = max( 

2552 1., 

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

2554 wdelta = amount * n 

2555 

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

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

2558 / (trmax-trmin) 

2559 

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

2561 self.zoom_tracks(anchor, wdelta) 

2562 else: 

2563 self.scroll_tracks(-wdelta) 

2564 

2565 def dragEnterEvent(self, event): 

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

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

2568 event.setDropAction(qc.Qt.LinkAction) 

2569 event.accept() 

2570 

2571 def dropEvent(self, event): 

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

2573 paths = list( 

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

2575 event.acceptProposedAction() 

2576 self.load(paths) 

2577 

2578 def get_phase_name(self, kind): 

2579 return self.config.get_phase_name(kind) 

2580 

2581 def set_phase_kind(self, markers, kind): 

2582 phasename = self.get_phase_name(kind) 

2583 

2584 for marker in markers: 

2585 if isinstance(marker, PhaseMarker): 

2586 if kind == 10: 

2587 marker.convert_to_marker() 

2588 else: 

2589 marker.set_phasename(phasename) 

2590 marker.set_event(self.get_active_event()) 

2591 

2592 elif isinstance(marker, EventMarker): 

2593 pass 

2594 

2595 else: 

2596 if kind != 10: 

2597 event = self.get_active_event() 

2598 marker.convert_to_phase_marker( 

2599 event, phasename, None, False) 

2600 

2601 def set_ntracks(self, ntracks): 

2602 if self.ntracks != ntracks: 

2603 self.ntracks = ntracks 

2604 if self.shown_tracks_range is not None: 

2605 l, h = self.shown_tracks_range 

2606 else: 

2607 l, h = 0, self.ntracks 

2608 

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

2610 

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

2612 

2613 low, high = range 

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

2615 high = min(self.ntracks, high) 

2616 low = max(0, low) 

2617 high = max(1, high) 

2618 

2619 if start is None: 

2620 start = float(low) 

2621 

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

2623 self.shown_tracks_range = low, high 

2624 self.shown_tracks_start = start 

2625 

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

2627 

2628 def scroll_tracks(self, shift): 

2629 shown = self.shown_tracks_range 

2630 shiftmin = -shown[0] 

2631 shiftmax = self.ntracks-shown[1] 

2632 shift = max(shiftmin, shift) 

2633 shift = min(shiftmax, shift) 

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

2635 

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

2637 

2638 self.update() 

2639 

2640 def zoom_tracks(self, anchor, delta): 

2641 ntracks_shown = self.shown_tracks_range[1] \ 

2642 - self.shown_tracks_range[0] 

2643 

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

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

2646 return 

2647 

2648 ntracks_shown += int(round(delta)) 

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

2650 

2651 u = self.shown_tracks_start 

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

2653 nv = nu + ntracks_shown 

2654 if nv > self.ntracks: 

2655 nu -= nv - self.ntracks 

2656 nv -= nv - self.ntracks 

2657 

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

2659 

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

2661 - self.shown_tracks_range[0] 

2662 

2663 self.update() 

2664 

2665 def content_time_range(self): 

2666 pile = self.get_pile() 

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

2668 if tmin is None: 

2669 tmin = initial_time_range[0] 

2670 if tmax is None: 

2671 tmax = initial_time_range[1] 

2672 

2673 return tmin, tmax 

2674 

2675 def content_deltat_range(self): 

2676 pile = self.get_pile() 

2677 

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

2679 

2680 if deltatmin is None: 

2681 deltatmin = 0.001 

2682 

2683 if deltatmax is None: 

2684 deltatmax = 1000.0 

2685 

2686 return deltatmin, deltatmax 

2687 

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

2689 if tmax < tmin: 

2690 tmin, tmax = tmax, tmin 

2691 

2692 deltatmin = self.content_deltat_range()[0] 

2693 dt = deltatmin * self.visible_length * 0.95 

2694 

2695 if dt == 0.0: 

2696 dt = 1.0 

2697 

2698 if tight: 

2699 if tmax != tmin: 

2700 dtm = tmax - tmin 

2701 tmin -= dtm*0.1 

2702 tmax += dtm*0.1 

2703 return tmin, tmax 

2704 else: 

2705 tcenter = (tmin + tmax) / 2. 

2706 tmin = tcenter - 0.5*dt 

2707 tmax = tcenter + 0.5*dt 

2708 return tmin, tmax 

2709 

2710 if tmax-tmin < dt: 

2711 vmin, vmax = self.get_time_range() 

2712 dt = min(vmax - vmin, dt) 

2713 

2714 tcenter = (tmin+tmax)/2. 

2715 etmin, etmax = tmin, tmax 

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

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

2718 dtm = tmax-tmin 

2719 if etmin == tmin: 

2720 tmin -= dtm*0.1 

2721 if etmax == tmax: 

2722 tmax += dtm*0.1 

2723 

2724 else: 

2725 dtm = tmax-tmin 

2726 tmin -= dtm*0.1 

2727 tmax += dtm*0.1 

2728 

2729 return tmin, tmax 

2730 

2731 def go_to_selection(self, tight=False): 

2732 markers = self.selected_markers() 

2733 if markers: 

2734 tmax, tmin = self.content_time_range() 

2735 for marker in markers: 

2736 tmin = min(tmin, marker.tmin) 

2737 tmax = max(tmax, marker.tmax) 

2738 

2739 else: 

2740 if tight: 

2741 vmin, vmax = self.get_time_range() 

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

2743 else: 

2744 tmin, tmax = self.content_time_range() 

2745 

2746 tmin, tmax = self.make_good_looking_time_range( 

2747 tmin, tmax, tight=tight) 

2748 

2749 self.interrupt_following() 

2750 self.set_time_range(tmin, tmax) 

2751 self.update() 

2752 

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

2754 tmax = t 

2755 if tlen is not None: 

2756 tmax = t+tlen 

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

2758 self.interrupt_following() 

2759 self.set_time_range(tmin, tmax) 

2760 self.update() 

2761 

2762 def go_to_event_by_name(self, name): 

2763 for marker in self.markers: 

2764 if isinstance(marker, EventMarker): 

2765 event = marker.get_event() 

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

2767 tmin, tmax = self.make_good_looking_time_range( 

2768 event.time, event.time) 

2769 

2770 self.interrupt_following() 

2771 self.set_time_range(tmin, tmax) 

2772 

2773 def printit(self): 

2774 from .qt_compat import qprint 

2775 printer = qprint.QPrinter() 

2776 printer.setOrientation(qprint.QPrinter.Landscape) 

2777 

2778 dialog = qprint.QPrintDialog(printer, self) 

2779 dialog.setWindowTitle('Print') 

2780 

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

2782 return 

2783 

2784 painter = qg.QPainter() 

2785 painter.begin(printer) 

2786 page = printer.pageRect() 

2787 self.drawit( 

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

2789 

2790 painter.end() 

2791 

2792 def savesvg(self, fn=None): 

2793 

2794 if not fn: 

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

2796 self, 

2797 'Save as SVG|PNG', 

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

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

2800 options=qfiledialog_options)) 

2801 

2802 if fn == '': 

2803 return 

2804 

2805 fn = str(fn) 

2806 

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

2808 try: 

2809 w, h = 842, 595 

2810 margin = 0.025 

2811 m = max(w, h)*margin 

2812 

2813 generator = qsvg.QSvgGenerator() 

2814 generator.setFileName(fn) 

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

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

2817 

2818 painter = qg.QPainter() 

2819 painter.begin(generator) 

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

2821 painter.end() 

2822 

2823 except Exception as e: 

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

2825 

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

2827 if use_pyqt5: 

2828 pixmap = self.grab() 

2829 else: 

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

2831 

2832 try: 

2833 pixmap.save(fn) 

2834 

2835 except Exception as e: 

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

2837 

2838 else: 

2839 self.fail( 

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

2841 '".png".') 

2842 

2843 def paintEvent(self, paint_ev): 

2844 ''' 

2845 Called by QT whenever widget needs to be painted. 

2846 ''' 

2847 painter = qg.QPainter(self) 

2848 

2849 if self.menuitem_antialias.isChecked(): 

2850 painter.setRenderHint(qg.QPainter.Antialiasing) 

2851 

2852 self.drawit(painter) 

2853 

2854 logger.debug( 

2855 'Time spent drawing: ' 

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

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

2858 (self.timer_draw - self.timer_cutout)) 

2859 

2860 logger.debug( 

2861 'Time spent processing:' 

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

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

2864 self.timer_cutout.get()) 

2865 

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

2867 self.time_last_painted = time.time() 

2868 

2869 def determine_box_styles(self): 

2870 

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

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

2873 istyle = 0 

2874 trace_styles = {} 

2875 for itr, tr in enumerate(traces): 

2876 if itr > 0: 

2877 other = traces[itr-1] 

2878 if not ( 

2879 other.nslc_id == tr.nslc_id 

2880 and other.deltat == tr.deltat 

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

2882 < gap_lap_tolerance*tr.deltat): 

2883 

2884 istyle += 1 

2885 

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

2887 

2888 self.trace_styles = trace_styles 

2889 

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

2891 

2892 for v_projection in track_projections.values(): 

2893 v_projection.set_in_range(0., 1.) 

2894 

2895 def selector(x): 

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

2897 

2898 if self.trace_filter is not None: 

2899 def tselector(x): 

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

2901 

2902 else: 

2903 tselector = selector 

2904 

2905 traces = list(self.pile.iter_traces( 

2906 group_selector=selector, trace_selector=tselector)) 

2907 

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

2909 

2910 def drawbox(itrack, istyle, traces): 

2911 v_projection = track_projections[itrack] 

2912 dvmin = v_projection(0.) 

2913 dvmax = v_projection(1.) 

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

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

2916 

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

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

2919 p.fillRect(rect, style.fill_brush) 

2920 p.setPen(style.frame_pen) 

2921 p.drawRect(rect) 

2922 

2923 traces_by_style = {} 

2924 for itr, tr in enumerate(traces): 

2925 gt = self.gather(tr) 

2926 if gt not in self.key_to_row: 

2927 continue 

2928 

2929 itrack = self.key_to_row[gt] 

2930 if itrack not in track_projections: 

2931 continue 

2932 

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

2934 

2935 if len(traces) < 500: 

2936 drawbox(itrack, istyle, [tr]) 

2937 else: 

2938 if (itrack, istyle) not in traces_by_style: 

2939 traces_by_style[itrack, istyle] = [] 

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

2941 

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

2943 drawbox(itrack, istyle, traces) 

2944 

2945 def draw_visible_markers( 

2946 self, p, vcenter_projection, primary_pen): 

2947 

2948 try: 

2949 markers = self.markers.with_key_in_limited( 

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

2951 

2952 except pyrocko.pile.TooMany: 

2953 tmin = self.markers[0].tmin 

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

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

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

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

2958 v0, _ = vcenter_projection.get_out_range() 

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

2960 

2961 p.save() 

2962 

2963 pen = qg.QPen(primary_pen) 

2964 pen.setWidth(2) 

2965 pen.setStyle(qc.Qt.DotLine) 

2966 # pat = [5., 3.] 

2967 # pen.setDashPattern(pat) 

2968 p.setPen(pen) 

2969 

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

2971 s_selected = ' (all selected)' 

2972 elif self.n_selected_markers > 0: 

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

2974 else: 

2975 s_selected = '' 

2976 

2977 draw_label( 

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

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

2980 label_bg, 'LB') 

2981 

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

2983 p.drawLine(line) 

2984 p.restore() 

2985 

2986 return 

2987 

2988 for marker in markers: 

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

2990 and marker.kind in self.visible_marker_kinds: 

2991 

2992 marker.draw( 

2993 p, self.time_projection, vcenter_projection, 

2994 with_label=True) 

2995 

2996 def get_squirrel(self): 

2997 try: 

2998 return self.pile._squirrel 

2999 except AttributeError: 

3000 return None 

3001 

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

3003 sq = self.get_squirrel() 

3004 if sq is None: 

3005 return 

3006 

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

3008 v_projection = track_projections[itrack] 

3009 dvmin = v_projection(0.) 

3010 dvmax = v_projection(1.) 

3011 dtmin = time_projection.clipped(tmin) 

3012 dtmax = time_projection.clipped(tmax) 

3013 

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

3015 p.fillRect(rect, style.fill_brush) 

3016 p.setPen(style.frame_pen) 

3017 p.drawRect(rect) 

3018 

3019 pattern_list = [] 

3020 pattern_to_itrack = {} 

3021 for key in self.track_keys: 

3022 itrack = self.key_to_row[key] 

3023 if itrack not in track_projections: 

3024 continue 

3025 

3026 pattern = self.track_patterns[itrack] 

3027 pattern_to_itrack[tuple(pattern)] = itrack 

3028 pattern_list.append(tuple(pattern)) 

3029 

3030 vmin, vmax = self.get_time_range() 

3031 

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

3033 for coverage in sq.get_coverage( 

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

3035 itrack = pattern_to_itrack[coverage.pattern.nslc] 

3036 

3037 if coverage.changes is None: 

3038 drawbox( 

3039 itrack, coverage.tmin, coverage.tmax, 

3040 box_styles_coverage[kind][0]) 

3041 else: 

3042 t = None 

3043 pcount = 0 

3044 for tb, count in coverage.changes: 

3045 if t is not None and tb > t: 

3046 if pcount > 0: 

3047 drawbox( 

3048 itrack, t, tb, 

3049 box_styles_coverage[kind][ 

3050 min(len(box_styles_coverage)-1, 

3051 pcount)]) 

3052 

3053 t = tb 

3054 pcount = count 

3055 

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

3057 ''' 

3058 This performs the actual drawing. 

3059 ''' 

3060 

3061 self.timer_draw.start() 

3062 show_boxes = self.menuitem_showboxes.isChecked() 

3063 sq = self.get_squirrel() 

3064 

3065 if self.gather is None: 

3066 self.set_gathering() 

3067 

3068 if self.pile_has_changed: 

3069 

3070 if not self.sortingmode_change_delayed(): 

3071 self.sortingmode_change() 

3072 

3073 if show_boxes and sq is None: 

3074 self.determine_box_styles() 

3075 

3076 self.pile_has_changed = False 

3077 

3078 if h is None: 

3079 h = float(self.height()) 

3080 if w is None: 

3081 w = float(self.width()) 

3082 

3083 if printmode: 

3084 primary_color = (0, 0, 0) 

3085 else: 

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

3087 

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

3089 

3090 ax_h = float(self.ax_height) 

3091 

3092 vbottom_ax_projection = Projection() 

3093 vtop_ax_projection = Projection() 

3094 vcenter_projection = Projection() 

3095 

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

3097 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3098 vtop_ax_projection.set_out_range(0., ax_h) 

3099 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3100 vcenter_projection.set_in_range(0., 1.) 

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

3102 

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

3104 track_projections = {} 

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

3106 proj = Projection() 

3107 proj.set_out_range( 

3108 self.track_to_screen(i+0.05), 

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

3110 

3111 track_projections[i] = proj 

3112 

3113 if self.tmin > self.tmax: 

3114 return 

3115 

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

3117 vbottom_ax_projection.set_in_range(0, ax_h) 

3118 

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

3120 

3121 yscaler = pyrocko.plot.AutoScaler() 

3122 

3123 p.setPen(primary_pen) 

3124 

3125 font = qg.QFont() 

3126 font.setBold(True) 

3127 

3128 axannotfont = qg.QFont() 

3129 axannotfont.setBold(True) 

3130 axannotfont.setPointSize(8) 

3131 

3132 processed_traces = self.prepare_cutout2( 

3133 self.tmin, self.tmax, 

3134 trace_selector=self.trace_selector, 

3135 degap=self.menuitem_degap.isChecked(), 

3136 demean=self.menuitem_demean.isChecked()) 

3137 

3138 if not printmode and show_boxes: 

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

3140 or (self.view_mode is ViewMode.Waterfall 

3141 and not processed_traces): 

3142 

3143 if sq is None: 

3144 self.draw_trace_boxes( 

3145 p, self.time_projection, track_projections) 

3146 

3147 else: 

3148 self.draw_coverage( 

3149 p, self.time_projection, track_projections) 

3150 

3151 p.setFont(font) 

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

3153 

3154 color_lookup = dict( 

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

3156 

3157 self.track_to_nslc_ids = {} 

3158 nticks = 0 

3159 annot_labels = [] 

3160 

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

3162 waterfall = self.waterfall 

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

3164 waterfall.set_traces(processed_traces) 

3165 waterfall.set_cmap(self.waterfall_cmap) 

3166 waterfall.set_integrate(self.waterfall_integrate) 

3167 waterfall.set_clip( 

3168 self.waterfall_clip_min, self.waterfall_clip_max) 

3169 waterfall.show_absolute_values( 

3170 self.waterfall_show_absolute) 

3171 

3172 rect = qc.QRectF( 

3173 0, self.ax_height, 

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

3175 ) 

3176 waterfall.draw_waterfall(p, rect=rect) 

3177 

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

3179 show_scales = self.menuitem_showscalerange.isChecked() \ 

3180 or self.menuitem_showscaleaxis.isChecked() 

3181 

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

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

3184 - self.track_to_screen(0.05) 

3185 

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

3187 

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

3189 if self.menuitem_showscaleaxis.isChecked() \ 

3190 else 15 

3191 

3192 yscaler = pyrocko.plot.AutoScaler( 

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

3194 snap=show_scales 

3195 and not self.menuitem_showscaleaxis.isChecked()) 

3196 

3197 data_ranges = pyrocko.trace.minmax( 

3198 processed_traces, 

3199 key=self.scaling_key, 

3200 mode=self.scaling_base) 

3201 

3202 if not self.menuitem_fixscalerange.isChecked(): 

3203 self.old_data_ranges = data_ranges 

3204 else: 

3205 data_ranges.update(self.old_data_ranges) 

3206 

3207 self.apply_scaling_hooks(data_ranges) 

3208 

3209 trace_to_itrack = {} 

3210 track_scaling_keys = {} 

3211 track_scaling_colors = {} 

3212 for trace in processed_traces: 

3213 gt = self.gather(trace) 

3214 if gt not in self.key_to_row: 

3215 continue 

3216 

3217 itrack = self.key_to_row[gt] 

3218 if itrack not in track_projections: 

3219 continue 

3220 

3221 trace_to_itrack[trace] = itrack 

3222 

3223 if itrack not in self.track_to_nslc_ids: 

3224 self.track_to_nslc_ids[itrack] = set() 

3225 

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

3227 

3228 if itrack not in track_scaling_keys: 

3229 track_scaling_keys[itrack] = set() 

3230 

3231 scaling_key = self.scaling_key(trace) 

3232 track_scaling_keys[itrack].add(scaling_key) 

3233 

3234 color = pyrocko.plot.color( 

3235 color_lookup[self.color_gather(trace)]) 

3236 

3237 k = itrack, scaling_key 

3238 if k not in track_scaling_colors \ 

3239 and self.menuitem_colortraces.isChecked(): 

3240 track_scaling_colors[k] = color 

3241 else: 

3242 track_scaling_colors[k] = primary_color 

3243 

3244 # y axes, zero lines 

3245 trace_projections = {} 

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

3247 if itrack not in track_scaling_keys: 

3248 continue 

3249 uoff = 0 

3250 for scaling_key in track_scaling_keys[itrack]: 

3251 data_range = data_ranges[scaling_key] 

3252 dymin, dymax = data_range 

3253 ymin, ymax, yinc = yscaler.make_scale( 

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

3255 iexp = yscaler.make_exp(yinc) 

3256 factor = 10**iexp 

3257 trace_projection = track_projections[itrack].copy() 

3258 trace_projection.set_in_range(ymax, ymin) 

3259 trace_projections[itrack, scaling_key] = \ 

3260 trace_projection 

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

3262 vmin, vmax = trace_projection.get_out_range() 

3263 umax_zeroline = umax 

3264 uoffnext = uoff 

3265 

3266 if show_scales: 

3267 pen = qg.QPen(primary_pen) 

3268 k = itrack, scaling_key 

3269 if k in track_scaling_colors: 

3270 c = qg.QColor(*track_scaling_colors[ 

3271 itrack, scaling_key]) 

3272 

3273 pen.setColor(c) 

3274 

3275 p.setPen(pen) 

3276 if nlinesavail > 3: 

3277 if self.menuitem_showscaleaxis.isChecked(): 

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

3279 ny_annot = int( 

3280 math.floor(ymax/yinc) 

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

3282 

3283 for iy_annot in range(ny_annot): 

3284 y = ymin_annot + iy_annot*yinc 

3285 v = trace_projection(y) 

3286 line = qc.QLineF( 

3287 umax-10-uoff, v, umax-uoff, v) 

3288 

3289 p.drawLine(line) 

3290 if iy_annot == ny_annot - 1 \ 

3291 and iexp != 0: 

3292 sexp = ' &times; ' \ 

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

3294 else: 

3295 sexp = '' 

3296 

3297 snum = num_to_html(y/factor) 

3298 lab = Label( 

3299 p, 

3300 umax-20-uoff, 

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

3302 label_bg=None, 

3303 anchor='MR', 

3304 font=axannotfont, 

3305 color=c) 

3306 

3307 uoffnext = max( 

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

3309 

3310 annot_labels.append(lab) 

3311 if y == 0.: 

3312 umax_zeroline = \ 

3313 umax - 20 \ 

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

3315 - uoff 

3316 else: 

3317 if not show_boxes: 

3318 qpoints = make_QPolygonF( 

3319 [umax-20-uoff, 

3320 umax-10-uoff, 

3321 umax-10-uoff, 

3322 umax-20-uoff], 

3323 [vmax, vmax, vmin, vmin]) 

3324 p.drawPolyline(qpoints) 

3325 

3326 snum = num_to_html(ymin) 

3327 labmin = Label( 

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

3329 label_bg=None, 

3330 anchor='BR', 

3331 font=axannotfont, 

3332 color=c) 

3333 

3334 annot_labels.append(labmin) 

3335 snum = num_to_html(ymax) 

3336 labmax = Label( 

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

3338 label_bg=None, 

3339 anchor='TR', 

3340 font=axannotfont, 

3341 color=c) 

3342 

3343 annot_labels.append(labmax) 

3344 

3345 for lab in (labmin, labmax): 

3346 uoffnext = max( 

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

3348 

3349 if self.menuitem_showzeroline.isChecked(): 

3350 v = trace_projection(0.) 

3351 if vmin <= v <= vmax: 

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

3353 p.drawLine(line) 

3354 

3355 uoff = uoffnext 

3356 

3357 p.setFont(font) 

3358 p.setPen(primary_pen) 

3359 for trace in processed_traces: 

3360 if self.view_mode is not ViewMode.Wiggle: 

3361 break 

3362 

3363 if trace not in trace_to_itrack: 

3364 continue 

3365 

3366 itrack = trace_to_itrack[trace] 

3367 scaling_key = self.scaling_key(trace) 

3368 trace_projection = trace_projections[ 

3369 itrack, scaling_key] 

3370 

3371 vdata = trace_projection(trace.get_ydata()) 

3372 

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

3374 udata_max = float(self.time_projection( 

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

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

3377 

3378 qpoints = make_QPolygonF(udata, vdata) 

3379 

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

3381 vmin, vmax = trace_projection.get_out_range() 

3382 

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

3384 

3385 if self.menuitem_cliptraces.isChecked(): 

3386 p.setClipRect(trackrect) 

3387 

3388 if self.menuitem_colortraces.isChecked(): 

3389 color = pyrocko.plot.color( 

3390 color_lookup[self.color_gather(trace)]) 

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

3392 p.setPen(pen) 

3393 

3394 p.drawPolyline(qpoints) 

3395 

3396 if self.floating_marker: 

3397 self.floating_marker.draw_trace( 

3398 self, p, trace, 

3399 self.time_projection, trace_projection, 1.0) 

3400 

3401 for marker in self.markers.with_key_in( 

3402 self.tmin - self.markers_deltat_max, 

3403 self.tmax): 

3404 

3405 if marker.tmin < self.tmax \ 

3406 and self.tmin < marker.tmax \ 

3407 and marker.kind \ 

3408 in self.visible_marker_kinds: 

3409 marker.draw_trace( 

3410 self, p, trace, self.time_projection, 

3411 trace_projection, 1.0) 

3412 

3413 p.setPen(primary_pen) 

3414 

3415 if self.menuitem_cliptraces.isChecked(): 

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

3417 

3418 if self.floating_marker: 

3419 self.floating_marker.draw( 

3420 p, self.time_projection, vcenter_projection) 

3421 

3422 self.draw_visible_markers( 

3423 p, vcenter_projection, primary_pen) 

3424 

3425 p.setPen(primary_pen) 

3426 while font.pointSize() > 2: 

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

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

3429 - self.track_to_screen(0.05) 

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

3431 if nlinesavail > 1: 

3432 break 

3433 

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

3435 

3436 p.setFont(font) 

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

3438 

3439 for key in self.track_keys: 

3440 itrack = self.key_to_row[key] 

3441 if itrack in track_projections: 

3442 plabel = ' '.join( 

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

3444 lx = 10 

3445 ly = self.track_to_screen(itrack+0.5) 

3446 

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

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

3449 continue 

3450 

3451 contains_cursor = \ 

3452 self.track_to_screen(itrack) \ 

3453 < mouse_pos.y() \ 

3454 < self.track_to_screen(itrack+1) 

3455 

3456 if not contains_cursor: 

3457 continue 

3458 

3459 font_large = p.font() 

3460 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3461 p.setFont(font_large) 

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

3463 p.setFont(font) 

3464 

3465 for lab in annot_labels: 

3466 lab.draw() 

3467 

3468 self.timer_draw.stop() 

3469 

3470 def see_data_params(self): 

3471 

3472 min_deltat = self.content_deltat_range()[0] 

3473 

3474 # determine padding and downampling requirements 

3475 if self.lowpass is not None: 

3476 deltat_target = 1./self.lowpass * 0.25 

3477 ndecimate = min( 

3478 50, 

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

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

3481 else: 

3482 ndecimate = 1 

3483 tpad = min_deltat*5. 

3484 

3485 if self.highpass is not None: 

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

3487 

3488 nsee_points_per_trace = 5000*10 

3489 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3490 

3491 return ndecimate, tpad, tsee 

3492 

3493 def clean_update(self): 

3494 self.cached_processed_traces = None 

3495 self.update() 

3496 

3497 def get_adequate_tpad(self): 

3498 tpad = 0. 

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

3500 if f is not None: 

3501 tpad = max(tpad, 1.0/f) 

3502 

3503 for snuffling in self.snufflings: 

3504 if snuffling._post_process_hook_enabled \ 

3505 or snuffling._pre_process_hook_enabled: 

3506 

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

3508 

3509 return tpad 

3510 

3511 def prepare_cutout2( 

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

3513 demean=True, nmax=6000): 

3514 

3515 if self.pile.is_empty(): 

3516 return [] 

3517 

3518 nmax = self.visible_length 

3519 

3520 self.timer_cutout.start() 

3521 

3522 tsee = tmax-tmin 

3523 min_deltat_wo_decimate = tsee/nmax 

3524 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3525 

3526 min_deltat_allow = min_deltat_wo_decimate 

3527 if self.lowpass is not None: 

3528 target_deltat_lp = 0.25/self.lowpass 

3529 if target_deltat_lp > min_deltat_wo_decimate: 

3530 min_deltat_allow = min_deltat_w_decimate 

3531 

3532 min_deltat_allow = math.exp( 

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

3534 

3535 tmin_ = tmin 

3536 tmax_ = tmax 

3537 

3538 # fetch more than needed? 

3539 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3543 

3544 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3545 lphp = self.menuitem_lphp.isChecked() 

3546 ads = self.menuitem_allowdownsampling.isChecked() 

3547 

3548 tpad = self.get_adequate_tpad() 

3549 tpad = max(tpad, tsee) 

3550 

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

3552 vec = ( 

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

3554 self.highpass, fft_filtering, lphp, 

3555 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3556 ads, self.pile.get_update_count()) 

3557 

3558 if (self.cached_vec 

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

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

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

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

3563 and self.cached_processed_traces is not None): 

3564 

3565 logger.debug('Using cached traces') 

3566 processed_traces = self.cached_processed_traces 

3567 

3568 else: 

3569 processed_traces = [] 

3570 if self.pile.deltatmax >= min_deltat_allow: 

3571 

3572 def group_selector(gr): 

3573 return gr.deltatmax >= min_deltat_allow 

3574 

3575 if trace_selector is not None: 

3576 def trace_selectorx(tr): 

3577 return tr.deltat >= min_deltat_allow \ 

3578 and trace_selector(tr) 

3579 else: 

3580 def trace_selectorx(tr): 

3581 return tr.deltat >= min_deltat_allow 

3582 

3583 for traces in self.pile.chopper( 

3584 tmin=tmin, tmax=tmax, tpad=tpad, 

3585 want_incomplete=True, 

3586 degap=degap, 

3587 maxgap=gap_lap_tolerance, 

3588 maxlap=gap_lap_tolerance, 

3589 keep_current_files_open=True, 

3590 group_selector=group_selector, 

3591 trace_selector=trace_selectorx, 

3592 accessor_id=id(self), 

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

3594 include_last=True): 

3595 

3596 if demean: 

3597 for tr in traces: 

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

3599 continue 

3600 y = tr.get_ydata() 

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

3602 

3603 traces = self.pre_process_hooks(traces) 

3604 

3605 for trace in traces: 

3606 

3607 if not (trace.meta 

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

3609 

3610 if fft_filtering: 

3611 but = pyrocko.response.ButterworthResponse 

3612 multres = pyrocko.response.MultiplyResponse 

3613 if self.lowpass is not None \ 

3614 or self.highpass is not None: 

3615 

3616 it = num.arange( 

3617 trace.data_len(), dtype=float) 

3618 detr_data, m, b = detrend( 

3619 it, trace.get_ydata()) 

3620 

3621 trace.set_ydata(detr_data) 

3622 

3623 freqs, fdata = trace.spectrum( 

3624 pad_to_pow2=True, tfade=None) 

3625 

3626 nfreqs = fdata.size 

3627 

3628 key = (trace.deltat, nfreqs) 

3629 

3630 if key not in self.tf_cache: 

3631 resps = [] 

3632 if self.lowpass is not None: 

3633 resps.append(but( 

3634 order=4, 

3635 corner=self.lowpass, 

3636 type='low')) 

3637 

3638 if self.highpass is not None: 

3639 resps.append(but( 

3640 order=4, 

3641 corner=self.highpass, 

3642 type='high')) 

3643 

3644 resp = multres(resps) 

3645 self.tf_cache[key] = \ 

3646 resp.evaluate(freqs) 

3647 

3648 filtered_data = num.fft.irfft( 

3649 fdata*self.tf_cache[key] 

3650 )[:trace.data_len()] 

3651 

3652 retrended_data = retrend( 

3653 it, filtered_data, m, b) 

3654 

3655 trace.set_ydata(retrended_data) 

3656 

3657 else: 

3658 

3659 if ads and self.lowpass is not None: 

3660 while trace.deltat \ 

3661 < min_deltat_wo_decimate: 

3662 

3663 trace.downsample(2, demean=False) 

3664 

3665 fmax = 0.5/trace.deltat 

3666 if not lphp and ( 

3667 self.lowpass is not None 

3668 and self.highpass is not None 

3669 and self.lowpass < fmax 

3670 and self.highpass < fmax 

3671 and self.highpass < self.lowpass): 

3672 

3673 trace.bandpass( 

3674 2, self.highpass, self.lowpass) 

3675 else: 

3676 if self.lowpass is not None: 

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

3678 trace.lowpass( 

3679 4, self.lowpass, 

3680 demean=False) 

3681 

3682 if self.highpass is not None: 

3683 if self.lowpass is None \ 

3684 or self.highpass \ 

3685 < self.lowpass: 

3686 

3687 if self.highpass < \ 

3688 0.5/trace.deltat: 

3689 trace.highpass( 

3690 4, self.highpass, 

3691 demean=False) 

3692 

3693 processed_traces.append(trace) 

3694 

3695 if self.rotate != 0.0: 

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

3697 cphi = math.cos(phi) 

3698 sphi = math.sin(phi) 

3699 for a in processed_traces: 

3700 for b in processed_traces: 

3701 if (a.network == b.network 

3702 and a.station == b.station 

3703 and a.location == b.location 

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

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

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

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

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

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

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

3711 

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

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

3714 a.set_ydata(aydata) 

3715 b.set_ydata(bydata) 

3716 

3717 processed_traces = self.post_process_hooks(processed_traces) 

3718 

3719 self.cached_processed_traces = processed_traces 

3720 self.cached_vec = vec 

3721 

3722 chopped_traces = [] 

3723 for trace in processed_traces: 

3724 chop_tmin = tmin_ - trace.deltat*4 

3725 chop_tmax = tmax_ + trace.deltat*4 

3726 

3727 try: 

3728 ctrace = trace.chop( 

3729 chop_tmin, chop_tmax, 

3730 inplace=False) 

3731 

3732 except pyrocko.trace.NoData: 

3733 continue 

3734 

3735 if ctrace.data_len() < 2: 

3736 continue 

3737 

3738 chopped_traces.append(ctrace) 

3739 

3740 self.timer_cutout.stop() 

3741 return chopped_traces 

3742 

3743 def pre_process_hooks(self, traces): 

3744 for snuffling in self.snufflings: 

3745 if snuffling._pre_process_hook_enabled: 

3746 traces = snuffling.pre_process_hook(traces) 

3747 

3748 return traces 

3749 

3750 def post_process_hooks(self, traces): 

3751 for snuffling in self.snufflings: 

3752 if snuffling._post_process_hook_enabled: 

3753 traces = snuffling.post_process_hook(traces) 

3754 

3755 return traces 

3756 

3757 def visible_length_change(self, ignore=None): 

3758 for menuitem, vlen in self.menuitems_visible_length: 

3759 if menuitem.isChecked(): 

3760 self.visible_length = vlen 

3761 

3762 def scaling_base_change(self, ignore=None): 

3763 for menuitem, scaling_base in self.menuitems_scaling_base: 

3764 if menuitem.isChecked(): 

3765 self.scaling_base = scaling_base 

3766 

3767 def scalingmode_change(self, ignore=None): 

3768 for menuitem, scaling_key in self.menuitems_scaling: 

3769 if menuitem.isChecked(): 

3770 self.scaling_key = scaling_key 

3771 self.update() 

3772 

3773 def apply_scaling_hooks(self, data_ranges): 

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

3775 hook = self.scaling_hooks[k] 

3776 hook(data_ranges) 

3777 

3778 def viewmode_change(self, ignore=True): 

3779 for item, mode in self.menuitems_viewmode: 

3780 if item.isChecked(): 

3781 self.view_mode = mode 

3782 break 

3783 else: 

3784 raise AttributeError('unknown view mode') 

3785 

3786 items_waterfall_disabled = ( 

3787 self.menuitem_showscaleaxis, 

3788 self.menuitem_showscalerange, 

3789 self.menuitem_showzeroline, 

3790 self.menuitem_colortraces, 

3791 self.menuitem_cliptraces, 

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

3793 ) 

3794 

3795 if self.view_mode is ViewMode.Waterfall: 

3796 self.parent().show_colorbar_ctrl(True) 

3797 self.parent().show_gain_ctrl(False) 

3798 

3799 for item in items_waterfall_disabled: 

3800 item.setDisabled(True) 

3801 

3802 self.visible_length = 180. 

3803 else: 

3804 self.parent().show_colorbar_ctrl(False) 

3805 self.parent().show_gain_ctrl(True) 

3806 

3807 for item in items_waterfall_disabled: 

3808 item.setDisabled(False) 

3809 

3810 self.visible_length_change() 

3811 self.update() 

3812 

3813 def set_scaling_hook(self, k, hook): 

3814 self.scaling_hooks[k] = hook 

3815 

3816 def remove_scaling_hook(self, k): 

3817 del self.scaling_hooks[k] 

3818 

3819 def remove_scaling_hooks(self): 

3820 self.scaling_hooks = {} 

3821 

3822 def s_sortingmode_change(self, ignore=None): 

3823 for menuitem, valfunc in self.menuitems_ssorting: 

3824 if menuitem.isChecked(): 

3825 self._ssort = valfunc 

3826 

3827 self.sortingmode_change() 

3828 

3829 def sortingmode_change(self, ignore=None): 

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

3831 if menuitem.isChecked(): 

3832 self.set_gathering(gather, color) 

3833 

3834 self.sortingmode_change_time = time.time() 

3835 

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

3837 self.lowpass = value 

3838 self.passband_check() 

3839 self.tf_cache = {} 

3840 self.update() 

3841 

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

3843 self.highpass = value 

3844 self.passband_check() 

3845 self.tf_cache = {} 

3846 self.update() 

3847 

3848 def passband_check(self): 

3849 if self.highpass and self.lowpass \ 

3850 and self.highpass >= self.lowpass: 

3851 

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

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

3854 'deactivate the highpass.' 

3855 

3856 self.update_status() 

3857 else: 

3858 oldmess = self.message 

3859 self.message = None 

3860 if oldmess is not None: 

3861 self.update_status() 

3862 

3863 def gain_change(self, value, ignore): 

3864 self.gain = value 

3865 self.update() 

3866 

3867 def rot_change(self, value, ignore): 

3868 self.rotate = value 

3869 self.update() 

3870 

3871 def waterfall_cmap_change(self, cmap): 

3872 self.waterfall_cmap = cmap 

3873 self.update() 

3874 

3875 def waterfall_clip_change(self, clip_min, clip_max): 

3876 self.waterfall_clip_min = clip_min 

3877 self.waterfall_clip_max = clip_max 

3878 self.update() 

3879 

3880 def waterfall_show_absolute_change(self, toggle): 

3881 self.waterfall_show_absolute = toggle 

3882 self.update() 

3883 

3884 def waterfall_set_integrate(self, toggle): 

3885 self.waterfall_integrate = toggle 

3886 self.update() 

3887 

3888 def set_selected_markers(self, markers): 

3889 ''' 

3890 Set a list of markers selected 

3891 

3892 :param markers: list of markers 

3893 ''' 

3894 self.deselect_all() 

3895 for m in markers: 

3896 m.selected = True 

3897 

3898 self.update() 

3899 

3900 def deselect_all(self): 

3901 for marker in self.markers: 

3902 marker.selected = False 

3903 

3904 def animate_picking(self): 

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

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

3907 

3908 def get_nslc_ids_for_track(self, ftrack): 

3909 itrack = int(ftrack) 

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

3911 

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

3913 if self.picking: 

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

3915 self.picking = None 

3916 self.picking_down = None 

3917 self.picking_timer.stop() 

3918 self.picking_timer = None 

3919 if not abort: 

3920 self.add_marker(self.floating_marker) 

3921 self.floating_marker.selected = True 

3922 self.emit_selected_markers() 

3923 

3924 self.floating_marker = None 

3925 

3926 def start_picking(self, ignore): 

3927 

3928 if not self.picking: 

3929 self.deselect_all() 

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

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

3932 

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

3934 self.picking.setGeometry( 

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

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

3937 

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

3939 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3941 self.floating_marker.selected = True 

3942 

3943 self.picking_timer = qc.QTimer() 

3944 self.picking_timer.timeout.connect( 

3945 self.animate_picking) 

3946 

3947 self.picking_timer.setInterval(50) 

3948 self.picking_timer.start() 

3949 

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

3951 if self.picking: 

3952 mouset = self.time_projection.rev(x) 

3953 dt = 0.0 

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

3955 if mouset < self.tmin: 

3956 dt = -(self.tmin - mouset) 

3957 else: 

3958 dt = mouset - self.tmax 

3959 ddt = self.tmax-self.tmin 

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

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

3962 

3963 x0 = x 

3964 if self.picking_down is not None: 

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

3966 

3967 w = abs(x-x0) 

3968 x0 = min(x0, x) 

3969 

3970 tmin, tmax = ( 

3971 self.time_projection.rev(x0), 

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

3973 

3974 tmin, tmax = ( 

3975 max(working_system_time_range[0], tmin), 

3976 min(working_system_time_range[1], tmax)) 

3977 

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

3979 

3980 self.picking.setGeometry( 

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

3982 

3983 ftrack = self.track_to_screen.rev(y) 

3984 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3986 

3987 if dt != 0.0 and doshift: 

3988 self.interrupt_following() 

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

3990 

3991 self.update() 

3992 

3993 def update_status(self): 

3994 

3995 if self.message is None: 

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

3997 

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

3999 if not is_working_time(mouse_t): 

4000 return 

4001 

4002 if self.floating_marker: 

4003 tmi, tma = ( 

4004 self.floating_marker.tmin, 

4005 self.floating_marker.tmax) 

4006 

4007 tt, ms = gmtime_x(tmi) 

4008 

4009 if tmi == tma: 

4010 message = mystrftime( 

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

4012 tt=tt, milliseconds=ms) 

4013 else: 

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

4015 message = mystrftime( 

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

4017 tt=tt, milliseconds=ms) 

4018 else: 

4019 tt, ms = gmtime_x(mouse_t) 

4020 

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

4022 else: 

4023 message = self.message 

4024 

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

4026 sb.clearMessage() 

4027 sb.showMessage(message) 

4028 

4029 def set_sortingmode_change_delay_time(self, dt): 

4030 self.sortingmode_change_delay_time = dt 

4031 

4032 def sortingmode_change_delayed(self): 

4033 now = time.time() 

4034 return ( 

4035 self.sortingmode_change_delay_time is not None 

4036 and now - self.sortingmode_change_time 

4037 < self.sortingmode_change_delay_time) 

4038 

4039 def set_visible_marker_kinds(self, kinds): 

4040 self.deselect_all() 

4041 self.visible_marker_kinds = tuple(kinds) 

4042 self.emit_selected_markers() 

4043 

4044 def following(self): 

4045 return self.follow_timer is not None \ 

4046 and not self.following_interrupted() 

4047 

4048 def interrupt_following(self): 

4049 self.interactive_range_change_time = time.time() 

4050 

4051 def following_interrupted(self, now=None): 

4052 if now is None: 

4053 now = time.time() 

4054 return now - self.interactive_range_change_time \ 

4055 < self.interactive_range_change_delay_time 

4056 

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

4058 if tmax_start is None: 

4059 tmax_start = time.time() 

4060 self.show_all = False 

4061 self.follow_time = tlen 

4062 self.follow_timer = qc.QTimer(self) 

4063 self.follow_timer.timeout.connect( 

4064 self.follow_update) 

4065 self.follow_timer.setInterval(interval) 

4066 self.follow_timer.start() 

4067 self.follow_started = time.time() 

4068 self.follow_lapse = lapse 

4069 self.follow_tshift = self.follow_started - tmax_start 

4070 self.interactive_range_change_time = 0.0 

4071 

4072 def unfollow(self): 

4073 if self.follow_timer is not None: 

4074 self.follow_timer.stop() 

4075 self.follow_timer = None 

4076 self.interactive_range_change_time = 0.0 

4077 

4078 def follow_update(self): 

4079 rnow = time.time() 

4080 if self.follow_lapse is None: 

4081 now = rnow 

4082 else: 

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

4084 * self.follow_lapse 

4085 

4086 if self.following_interrupted(rnow): 

4087 return 

4088 self.set_time_range( 

4089 now-self.follow_time-self.follow_tshift, 

4090 now-self.follow_tshift) 

4091 

4092 self.update() 

4093 

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

4095 self.return_tag = return_tag 

4096 self.window().close() 

4097 

4098 def cleanup(self): 

4099 self.about_to_close.emit() 

4100 self.timer.stop() 

4101 if self.follow_timer is not None: 

4102 self.follow_timer.stop() 

4103 

4104 for snuffling in list(self.snufflings): 

4105 self.remove_snuffling(snuffling) 

4106 

4107 def set_error_message(self, key, value): 

4108 if value is None: 

4109 if key in self.error_messages: 

4110 del self.error_messages[key] 

4111 else: 

4112 self.error_messages[key] = value 

4113 

4114 def inputline_changed(self, text): 

4115 pass 

4116 

4117 def inputline_finished(self, text): 

4118 line = str(text) 

4119 

4120 toks = line.split() 

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

4122 if len(toks) >= 1: 

4123 command = toks[0].lower() 

4124 

4125 try: 

4126 quick_filter_commands = { 

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

4128 's': '*.%s.*.*', 

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

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

4131 

4132 if command in quick_filter_commands: 

4133 if len(toks) >= 2: 

4134 patterns = [ 

4135 quick_filter_commands[toks[0]] % pat 

4136 for pat in toks[1:]] 

4137 self.set_quick_filter_patterns(patterns, line) 

4138 else: 

4139 self.set_quick_filter_patterns(None) 

4140 

4141 self.update() 

4142 

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

4144 if len(toks) >= 2: 

4145 patterns = [] 

4146 if len(toks) == 2: 

4147 patterns = [toks[1]] 

4148 elif len(toks) >= 3: 

4149 x = { 

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

4151 's': '*.%s.*.*', 

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

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

4154 

4155 if toks[1] in x: 

4156 patterns.extend( 

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

4158 

4159 for pattern in patterns: 

4160 if command == 'hide': 

4161 self.add_blacklist_pattern(pattern) 

4162 else: 

4163 self.remove_blacklist_pattern(pattern) 

4164 

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

4166 self.clear_blacklist() 

4167 

4168 clearit = True 

4169 

4170 self.update() 

4171 

4172 elif command == 'markers': 

4173 if len(toks) == 2: 

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

4175 kinds = self.all_marker_kinds 

4176 else: 

4177 kinds = [] 

4178 for x in toks[1]: 

4179 try: 

4180 kinds.append(int(x)) 

4181 except Exception: 

4182 pass 

4183 

4184 self.set_visible_marker_kinds(kinds) 

4185 

4186 elif len(toks) == 1: 

4187 self.set_visible_marker_kinds(()) 

4188 

4189 self.update() 

4190 

4191 elif command == 'scaling': 

4192 if len(toks) == 2: 

4193 hideit = False 

4194 error = 'wrong number of arguments' 

4195 

4196 if len(toks) >= 3: 

4197 vmin, vmax = [ 

4198 pyrocko.model.float_or_none(x) 

4199 for x in toks[-2:]] 

4200 

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

4202 if k in d: 

4203 if vmin is not None: 

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

4205 if vmax is not None: 

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

4207 

4208 if len(toks) == 1: 

4209 self.remove_scaling_hooks() 

4210 

4211 elif len(toks) == 3: 

4212 def hook(data_ranges): 

4213 for k in data_ranges: 

4214 upd(data_ranges, k, vmin, vmax) 

4215 

4216 self.set_scaling_hook('_', hook) 

4217 

4218 elif len(toks) == 4: 

4219 pattern = toks[1] 

4220 

4221 def hook(data_ranges): 

4222 for k in pyrocko.util.match_nslcs( 

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

4224 

4225 upd(data_ranges, k, vmin, vmax) 

4226 

4227 self.set_scaling_hook(pattern, hook) 

4228 

4229 elif command == 'goto': 

4230 toks2 = line.split(None, 1) 

4231 if len(toks2) == 2: 

4232 arg = toks2[1] 

4233 m = re.match( 

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

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

4236 if m: 

4237 tlen = None 

4238 if not m.group(1): 

4239 tlen = 12*32*24*60*60 

4240 elif not m.group(2): 

4241 tlen = 32*24*60*60 

4242 elif not m.group(3): 

4243 tlen = 24*60*60 

4244 elif not m.group(4): 

4245 tlen = 60*60 

4246 elif not m.group(5): 

4247 tlen = 60 

4248 

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

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

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

4252 t = pyrocko.util.str_to_time(arg) 

4253 self.go_to_time(t, tlen=tlen) 

4254 

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

4256 supl = '00:00:00' 

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

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

4259 tmin, tmax = self.get_time_range() 

4260 sdate = pyrocko.util.time_to_str( 

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

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

4263 self.go_to_time(t) 

4264 

4265 elif arg == 'today': 

4266 self.go_to_time( 

4267 day_start( 

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

4269 

4270 elif arg == 'yesterday': 

4271 self.go_to_time( 

4272 day_start( 

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

4274 

4275 else: 

4276 self.go_to_event_by_name(arg) 

4277 

4278 else: 

4279 raise PileViewerMainException( 

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

4281 

4282 except PileViewerMainException as e: 

4283 error = str(e) 

4284 hideit = False 

4285 

4286 return clearit, hideit, error 

4287 

4288 return PileViewerMain 

4289 

4290 

4291PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4292GLPileViewerMain = MakePileViewerMainClass(qgl.QGLWidget) 

4293 

4294 

4295class LineEditWithAbort(qw.QLineEdit): 

4296 

4297 aborted = qc.pyqtSignal() 

4298 history_down = qc.pyqtSignal() 

4299 history_up = qc.pyqtSignal() 

4300 

4301 def keyPressEvent(self, key_event): 

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

4303 self.aborted.emit() 

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

4305 self.history_down.emit() 

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

4307 self.history_up.emit() 

4308 else: 

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

4310 

4311 

4312class PileViewer(qw.QFrame): 

4313 ''' 

4314 PileViewerMain + Controls + Inputline 

4315 ''' 

4316 

4317 def __init__( 

4318 self, pile, 

4319 ntracks_shown_max=20, 

4320 marker_editor_sortable=True, 

4321 use_opengl=False, 

4322 panel_parent=None, 

4323 *args): 

4324 

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

4326 

4327 layout = qw.QGridLayout() 

4328 layout.setContentsMargins(0, 0, 0, 0) 

4329 layout.setSpacing(0) 

4330 

4331 self.menu = PileViewerMenuBar(self) 

4332 

4333 if use_opengl: 

4334 self.viewer = GLPileViewerMain( 

4335 pile, 

4336 ntracks_shown_max=ntracks_shown_max, 

4337 panel_parent=panel_parent, 

4338 menu=self.menu) 

4339 else: 

4340 self.viewer = PileViewerMain( 

4341 pile, 

4342 ntracks_shown_max=ntracks_shown_max, 

4343 panel_parent=panel_parent, 

4344 menu=self.menu) 

4345 

4346 self.marker_editor_sortable = marker_editor_sortable 

4347 

4348 self.setFrameShape(qw.QFrame.StyledPanel) 

4349 self.setFrameShadow(qw.QFrame.Sunken) 

4350 

4351 self.input_area = qw.QFrame(self) 

4352 ia_layout = qw.QGridLayout() 

4353 ia_layout.setContentsMargins(11, 11, 11, 11) 

4354 self.input_area.setLayout(ia_layout) 

4355 

4356 self.inputline = LineEditWithAbort(self.input_area) 

4357 self.inputline.returnPressed.connect( 

4358 self.inputline_returnpressed) 

4359 self.inputline.editingFinished.connect( 

4360 self.inputline_finished) 

4361 self.inputline.aborted.connect( 

4362 self.inputline_aborted) 

4363 

4364 self.inputline.history_down.connect( 

4365 lambda: self.step_through_history(1)) 

4366 self.inputline.history_up.connect( 

4367 lambda: self.step_through_history(-1)) 

4368 

4369 self.inputline.textEdited.connect( 

4370 self.inputline_changed) 

4371 

4372 self.inputline.setPlaceholderText( 

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

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

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

4376 self.input_area.hide() 

4377 self.history = None 

4378 

4379 self.inputline_error_str = None 

4380 

4381 self.inputline_error = qw.QLabel() 

4382 self.inputline_error.hide() 

4383 

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

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

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

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

4388 

4389 pb = Progressbars(self) 

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

4391 self.progressbars = pb 

4392 

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

4394 self.scrollbar = scrollbar 

4395 layout.addWidget(scrollbar, 1, 1) 

4396 self.scrollbar.valueChanged.connect( 

4397 self.scrollbar_changed) 

4398 

4399 self.block_scrollbar_changes = False 

4400 

4401 self.viewer.want_input.connect( 

4402 self.inputline_show) 

4403 self.viewer.tracks_range_changed.connect( 

4404 self.tracks_range_changed) 

4405 self.viewer.pile_has_changed_signal.connect( 

4406 self.adjust_controls) 

4407 self.viewer.about_to_close.connect( 

4408 self.save_inputline_history) 

4409 

4410 self.setLayout(layout) 

4411 

4412 def cleanup(self): 

4413 self.viewer.cleanup() 

4414 

4415 def get_progressbars(self): 

4416 return self.progressbars 

4417 

4418 def inputline_show(self): 

4419 if not self.history: 

4420 self.load_inputline_history() 

4421 

4422 self.input_area.show() 

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

4424 self.inputline.selectAll() 

4425 

4426 def inputline_set_error(self, string): 

4427 self.inputline_error_str = string 

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

4429 self.inputline.selectAll() 

4430 self.inputline_error.setText(string) 

4431 self.input_area.show() 

4432 self.inputline_error.show() 

4433 

4434 def inputline_clear_error(self): 

4435 if self.inputline_error_str: 

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

4437 self.inputline_error_str = None 

4438 self.inputline_error.clear() 

4439 self.inputline_error.hide() 

4440 

4441 def inputline_changed(self, line): 

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

4443 self.inputline_clear_error() 

4444 

4445 def inputline_returnpressed(self): 

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

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

4448 

4449 if error: 

4450 self.inputline_set_error(error) 

4451 

4452 line = line.strip() 

4453 

4454 if line != '' and not error: 

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

4456 self.history.append(line) 

4457 

4458 if clearit: 

4459 

4460 self.inputline.blockSignals(True) 

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

4462 if qpat is None: 

4463 self.inputline.clear() 

4464 else: 

4465 self.inputline.setText(qinp) 

4466 self.inputline.blockSignals(False) 

4467 

4468 if hideit and not error: 

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

4470 self.input_area.hide() 

4471 

4472 self.hist_ind = len(self.history) 

4473 

4474 def inputline_aborted(self): 

4475 ''' 

4476 Hide the input line. 

4477 ''' 

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

4479 self.hist_ind = len(self.history) 

4480 self.input_area.hide() 

4481 

4482 def save_inputline_history(self): 

4483 ''' 

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

4485 ''' 

4486 if not self.history: 

4487 return 

4488 

4489 conf = pyrocko.config 

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

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

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

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

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

4495 

4496 def load_inputline_history(self): 

4497 ''' 

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

4499 ''' 

4500 conf = pyrocko.config 

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

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

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

4504 f.write('\n') 

4505 

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

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

4508 

4509 self.hist_ind = len(self.history) 

4510 

4511 def step_through_history(self, ud=1): 

4512 ''' 

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

4514 ''' 

4515 n = len(self.history) 

4516 self.hist_ind += ud 

4517 self.hist_ind %= (n + 1) 

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

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

4520 else: 

4521 self.inputline.setText('') 

4522 

4523 def inputline_finished(self): 

4524 pass 

4525 

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

4527 if self.block_scrollbar_changes: 

4528 return 

4529 

4530 self.scrollbar.blockSignals(True) 

4531 self.scrollbar.setPageStep(ihi-ilo) 

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

4533 self.scrollbar.setRange(0, vmax) 

4534 self.scrollbar.setValue(ilo) 

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

4536 self.scrollbar.blockSignals(False) 

4537 

4538 def scrollbar_changed(self, value): 

4539 self.block_scrollbar_changes = True 

4540 ilo = value 

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

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

4543 self.block_scrollbar_changes = False 

4544 self.update_contents() 

4545 

4546 def controls(self): 

4547 frame = qw.QFrame(self) 

4548 layout = qw.QGridLayout() 

4549 frame.setLayout(layout) 

4550 

4551 minfreq = 0.001 

4552 maxfreq = 1000.0 

4553 self.lowpass_control = ValControl(high_is_none=True) 

4554 self.lowpass_control.setup( 

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

4556 self.highpass_control = ValControl(low_is_none=True) 

4557 self.highpass_control.setup( 

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

4559 self.gain_control = ValControl() 

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

4561 self.rot_control = LinValControl() 

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

4563 self.colorbar_control = ColorbarControl(self) 

4564 

4565 self.lowpass_control.valchange.connect( 

4566 self.viewer.lowpass_change) 

4567 self.highpass_control.valchange.connect( 

4568 self.viewer.highpass_change) 

4569 self.gain_control.valchange.connect( 

4570 self.viewer.gain_change) 

4571 self.rot_control.valchange.connect( 

4572 self.viewer.rot_change) 

4573 self.colorbar_control.cmap_changed.connect( 

4574 self.viewer.waterfall_cmap_change 

4575 ) 

4576 self.colorbar_control.clip_changed.connect( 

4577 self.viewer.waterfall_clip_change 

4578 ) 

4579 self.colorbar_control.show_absolute_toggled.connect( 

4580 self.viewer.waterfall_show_absolute_change 

4581 ) 

4582 self.colorbar_control.show_integrate_toggled.connect( 

4583 self.viewer.waterfall_set_integrate 

4584 ) 

4585 

4586 for icontrol, control in enumerate(( 

4587 self.highpass_control, 

4588 self.lowpass_control, 

4589 self.gain_control, 

4590 self.rot_control, 

4591 self.colorbar_control)): 

4592 

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

4594 layout.addWidget(widget, icontrol, iwidget) 

4595 

4596 spacer = qw.QSpacerItem( 

4597 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4599 

4600 self.adjust_controls() 

4601 self.viewer.viewmode_change(ViewMode.Wiggle) 

4602 return frame 

4603 

4604 def marker_editor(self): 

4605 editor = pyrocko.gui.marker_editor.MarkerEditor( 

4606 self, sortable=self.marker_editor_sortable) 

4607 

4608 editor.set_viewer(self.get_view()) 

4609 editor.get_marker_model().dataChanged.connect( 

4610 self.update_contents) 

4611 return editor 

4612 

4613 def adjust_controls(self): 

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

4615 maxfreq = 0.5/dtmin 

4616 minfreq = (0.5/dtmax)*0.001 

4617 self.lowpass_control.set_range(minfreq, maxfreq) 

4618 self.highpass_control.set_range(minfreq, maxfreq) 

4619 

4620 def setup_snufflings(self): 

4621 self.viewer.setup_snufflings() 

4622 

4623 def get_view(self): 

4624 return self.viewer 

4625 

4626 def update_contents(self): 

4627 self.viewer.update() 

4628 

4629 def get_pile(self): 

4630 return self.viewer.get_pile() 

4631 

4632 def show_colorbar_ctrl(self, show): 

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

4634 w.setVisible(show) 

4635 

4636 def show_gain_ctrl(self, show): 

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

4638 w.setVisible(show)