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(pattern) 

3029 

3030 vmin, vmax = self.get_time_range() 

3031 

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

3033 for entry in sq.get_coverage( 

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

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

3036 itrack = pattern_to_itrack[tuple(pattern)] 

3037 

3038 if cover_data is None: 

3039 drawbox( 

3040 itrack, tmin, tmax, 

3041 box_styles_coverage[kind][0]) 

3042 else: 

3043 t = None 

3044 pcount = 0 

3045 for tb, count in cover_data: 

3046 if t is not None and tb > t: 

3047 if pcount > 0: 

3048 drawbox( 

3049 itrack, t, tb, 

3050 box_styles_coverage[kind][ 

3051 min(len(box_styles_coverage)-1, 

3052 pcount)]) 

3053 

3054 t = tb 

3055 pcount = count 

3056 

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

3058 ''' 

3059 This performs the actual drawing. 

3060 ''' 

3061 

3062 self.timer_draw.start() 

3063 show_boxes = self.menuitem_showboxes.isChecked() 

3064 sq = self.get_squirrel() 

3065 

3066 if self.gather is None: 

3067 self.set_gathering() 

3068 

3069 if self.pile_has_changed: 

3070 

3071 if not self.sortingmode_change_delayed(): 

3072 self.sortingmode_change() 

3073 

3074 if show_boxes and sq is None: 

3075 self.determine_box_styles() 

3076 

3077 self.pile_has_changed = False 

3078 

3079 if h is None: 

3080 h = float(self.height()) 

3081 if w is None: 

3082 w = float(self.width()) 

3083 

3084 if printmode: 

3085 primary_color = (0, 0, 0) 

3086 else: 

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

3088 

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

3090 

3091 ax_h = float(self.ax_height) 

3092 

3093 vbottom_ax_projection = Projection() 

3094 vtop_ax_projection = Projection() 

3095 vcenter_projection = Projection() 

3096 

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

3098 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3099 vtop_ax_projection.set_out_range(0., ax_h) 

3100 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3101 vcenter_projection.set_in_range(0., 1.) 

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

3103 

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

3105 track_projections = {} 

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

3107 proj = Projection() 

3108 proj.set_out_range( 

3109 self.track_to_screen(i+0.05), 

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

3111 

3112 track_projections[i] = proj 

3113 

3114 if self.tmin > self.tmax: 

3115 return 

3116 

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

3118 vbottom_ax_projection.set_in_range(0, ax_h) 

3119 

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

3121 

3122 yscaler = pyrocko.plot.AutoScaler() 

3123 

3124 p.setPen(primary_pen) 

3125 

3126 font = qg.QFont() 

3127 font.setBold(True) 

3128 

3129 axannotfont = qg.QFont() 

3130 axannotfont.setBold(True) 

3131 axannotfont.setPointSize(8) 

3132 

3133 processed_traces = self.prepare_cutout2( 

3134 self.tmin, self.tmax, 

3135 trace_selector=self.trace_selector, 

3136 degap=self.menuitem_degap.isChecked(), 

3137 demean=self.menuitem_demean.isChecked()) 

3138 

3139 if not printmode and show_boxes: 

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

3141 or (self.view_mode is ViewMode.Waterfall 

3142 and not processed_traces): 

3143 

3144 if sq is None: 

3145 self.draw_trace_boxes( 

3146 p, self.time_projection, track_projections) 

3147 

3148 else: 

3149 self.draw_coverage( 

3150 p, self.time_projection, track_projections) 

3151 

3152 p.setFont(font) 

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

3154 

3155 color_lookup = dict( 

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

3157 

3158 self.track_to_nslc_ids = {} 

3159 nticks = 0 

3160 annot_labels = [] 

3161 

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

3163 waterfall = self.waterfall 

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

3165 waterfall.set_traces(processed_traces) 

3166 waterfall.set_cmap(self.waterfall_cmap) 

3167 waterfall.set_integrate(self.waterfall_integrate) 

3168 waterfall.set_clip( 

3169 self.waterfall_clip_min, self.waterfall_clip_max) 

3170 waterfall.show_absolute_values( 

3171 self.waterfall_show_absolute) 

3172 

3173 rect = qc.QRectF( 

3174 0, self.ax_height, 

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

3176 ) 

3177 waterfall.draw_waterfall(p, rect=rect) 

3178 

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

3180 show_scales = self.menuitem_showscalerange.isChecked() \ 

3181 or self.menuitem_showscaleaxis.isChecked() 

3182 

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

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

3185 - self.track_to_screen(0.05) 

3186 

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

3188 

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

3190 if self.menuitem_showscaleaxis.isChecked() \ 

3191 else 15 

3192 

3193 yscaler = pyrocko.plot.AutoScaler( 

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

3195 snap=show_scales 

3196 and not self.menuitem_showscaleaxis.isChecked()) 

3197 

3198 data_ranges = pyrocko.trace.minmax( 

3199 processed_traces, 

3200 key=self.scaling_key, 

3201 mode=self.scaling_base) 

3202 

3203 if not self.menuitem_fixscalerange.isChecked(): 

3204 self.old_data_ranges = data_ranges 

3205 else: 

3206 data_ranges.update(self.old_data_ranges) 

3207 

3208 self.apply_scaling_hooks(data_ranges) 

3209 

3210 trace_to_itrack = {} 

3211 track_scaling_keys = {} 

3212 track_scaling_colors = {} 

3213 for trace in processed_traces: 

3214 gt = self.gather(trace) 

3215 if gt not in self.key_to_row: 

3216 continue 

3217 

3218 itrack = self.key_to_row[gt] 

3219 if itrack not in track_projections: 

3220 continue 

3221 

3222 trace_to_itrack[trace] = itrack 

3223 

3224 if itrack not in self.track_to_nslc_ids: 

3225 self.track_to_nslc_ids[itrack] = set() 

3226 

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

3228 

3229 if itrack not in track_scaling_keys: 

3230 track_scaling_keys[itrack] = set() 

3231 

3232 scaling_key = self.scaling_key(trace) 

3233 track_scaling_keys[itrack].add(scaling_key) 

3234 

3235 color = pyrocko.plot.color( 

3236 color_lookup[self.color_gather(trace)]) 

3237 

3238 k = itrack, scaling_key 

3239 if k not in track_scaling_colors \ 

3240 and self.menuitem_colortraces.isChecked(): 

3241 track_scaling_colors[k] = color 

3242 else: 

3243 track_scaling_colors[k] = primary_color 

3244 

3245 # y axes, zero lines 

3246 trace_projections = {} 

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

3248 if itrack not in track_scaling_keys: 

3249 continue 

3250 uoff = 0 

3251 for scaling_key in track_scaling_keys[itrack]: 

3252 data_range = data_ranges[scaling_key] 

3253 dymin, dymax = data_range 

3254 ymin, ymax, yinc = yscaler.make_scale( 

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

3256 iexp = yscaler.make_exp(yinc) 

3257 factor = 10**iexp 

3258 trace_projection = track_projections[itrack].copy() 

3259 trace_projection.set_in_range(ymax, ymin) 

3260 trace_projections[itrack, scaling_key] = \ 

3261 trace_projection 

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

3263 vmin, vmax = trace_projection.get_out_range() 

3264 umax_zeroline = umax 

3265 uoffnext = uoff 

3266 

3267 if show_scales: 

3268 pen = qg.QPen(primary_pen) 

3269 k = itrack, scaling_key 

3270 if k in track_scaling_colors: 

3271 c = qg.QColor(*track_scaling_colors[ 

3272 itrack, scaling_key]) 

3273 

3274 pen.setColor(c) 

3275 

3276 p.setPen(pen) 

3277 if nlinesavail > 3: 

3278 if self.menuitem_showscaleaxis.isChecked(): 

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

3280 ny_annot = int( 

3281 math.floor(ymax/yinc) 

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

3283 

3284 for iy_annot in range(ny_annot): 

3285 y = ymin_annot + iy_annot*yinc 

3286 v = trace_projection(y) 

3287 line = qc.QLineF( 

3288 umax-10-uoff, v, umax-uoff, v) 

3289 

3290 p.drawLine(line) 

3291 if iy_annot == ny_annot - 1 \ 

3292 and iexp != 0: 

3293 sexp = ' &times; ' \ 

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

3295 else: 

3296 sexp = '' 

3297 

3298 snum = num_to_html(y/factor) 

3299 lab = Label( 

3300 p, 

3301 umax-20-uoff, 

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

3303 label_bg=None, 

3304 anchor='MR', 

3305 font=axannotfont, 

3306 color=c) 

3307 

3308 uoffnext = max( 

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

3310 

3311 annot_labels.append(lab) 

3312 if y == 0.: 

3313 umax_zeroline = \ 

3314 umax - 20 \ 

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

3316 - uoff 

3317 else: 

3318 if not show_boxes: 

3319 qpoints = make_QPolygonF( 

3320 [umax-20-uoff, 

3321 umax-10-uoff, 

3322 umax-10-uoff, 

3323 umax-20-uoff], 

3324 [vmax, vmax, vmin, vmin]) 

3325 p.drawPolyline(qpoints) 

3326 

3327 snum = num_to_html(ymin) 

3328 labmin = Label( 

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

3330 label_bg=None, 

3331 anchor='BR', 

3332 font=axannotfont, 

3333 color=c) 

3334 

3335 annot_labels.append(labmin) 

3336 snum = num_to_html(ymax) 

3337 labmax = Label( 

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

3339 label_bg=None, 

3340 anchor='TR', 

3341 font=axannotfont, 

3342 color=c) 

3343 

3344 annot_labels.append(labmax) 

3345 

3346 for lab in (labmin, labmax): 

3347 uoffnext = max( 

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

3349 

3350 if self.menuitem_showzeroline.isChecked(): 

3351 v = trace_projection(0.) 

3352 if vmin <= v <= vmax: 

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

3354 p.drawLine(line) 

3355 

3356 uoff = uoffnext 

3357 

3358 p.setFont(font) 

3359 p.setPen(primary_pen) 

3360 for trace in processed_traces: 

3361 if self.view_mode is not ViewMode.Wiggle: 

3362 break 

3363 

3364 if trace not in trace_to_itrack: 

3365 continue 

3366 

3367 itrack = trace_to_itrack[trace] 

3368 scaling_key = self.scaling_key(trace) 

3369 trace_projection = trace_projections[ 

3370 itrack, scaling_key] 

3371 

3372 vdata = trace_projection(trace.get_ydata()) 

3373 

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

3375 udata_max = float(self.time_projection( 

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

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

3378 

3379 qpoints = make_QPolygonF(udata, vdata) 

3380 

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

3382 vmin, vmax = trace_projection.get_out_range() 

3383 

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

3385 

3386 if self.menuitem_cliptraces.isChecked(): 

3387 p.setClipRect(trackrect) 

3388 

3389 if self.menuitem_colortraces.isChecked(): 

3390 color = pyrocko.plot.color( 

3391 color_lookup[self.color_gather(trace)]) 

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

3393 p.setPen(pen) 

3394 

3395 p.drawPolyline(qpoints) 

3396 

3397 if self.floating_marker: 

3398 self.floating_marker.draw_trace( 

3399 self, p, trace, 

3400 self.time_projection, trace_projection, 1.0) 

3401 

3402 for marker in self.markers.with_key_in( 

3403 self.tmin - self.markers_deltat_max, 

3404 self.tmax): 

3405 

3406 if marker.tmin < self.tmax \ 

3407 and self.tmin < marker.tmax \ 

3408 and marker.kind \ 

3409 in self.visible_marker_kinds: 

3410 marker.draw_trace( 

3411 self, p, trace, self.time_projection, 

3412 trace_projection, 1.0) 

3413 

3414 p.setPen(primary_pen) 

3415 

3416 if self.menuitem_cliptraces.isChecked(): 

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

3418 

3419 if self.floating_marker: 

3420 self.floating_marker.draw( 

3421 p, self.time_projection, vcenter_projection) 

3422 

3423 self.draw_visible_markers( 

3424 p, vcenter_projection, primary_pen) 

3425 

3426 p.setPen(primary_pen) 

3427 while font.pointSize() > 2: 

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

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

3430 - self.track_to_screen(0.05) 

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

3432 if nlinesavail > 1: 

3433 break 

3434 

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

3436 

3437 p.setFont(font) 

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

3439 

3440 for key in self.track_keys: 

3441 itrack = self.key_to_row[key] 

3442 if itrack in track_projections: 

3443 plabel = ' '.join( 

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

3445 lx = 10 

3446 ly = self.track_to_screen(itrack+0.5) 

3447 

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

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

3450 continue 

3451 

3452 contains_cursor = \ 

3453 self.track_to_screen(itrack) \ 

3454 < mouse_pos.y() \ 

3455 < self.track_to_screen(itrack+1) 

3456 

3457 if not contains_cursor: 

3458 continue 

3459 

3460 font_large = p.font() 

3461 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3462 p.setFont(font_large) 

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

3464 p.setFont(font) 

3465 

3466 for lab in annot_labels: 

3467 lab.draw() 

3468 

3469 self.timer_draw.stop() 

3470 

3471 def see_data_params(self): 

3472 

3473 min_deltat = self.content_deltat_range()[0] 

3474 

3475 # determine padding and downampling requirements 

3476 if self.lowpass is not None: 

3477 deltat_target = 1./self.lowpass * 0.25 

3478 ndecimate = min( 

3479 50, 

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

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

3482 else: 

3483 ndecimate = 1 

3484 tpad = min_deltat*5. 

3485 

3486 if self.highpass is not None: 

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

3488 

3489 nsee_points_per_trace = 5000*10 

3490 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3491 

3492 return ndecimate, tpad, tsee 

3493 

3494 def clean_update(self): 

3495 self.cached_processed_traces = None 

3496 self.update() 

3497 

3498 def get_adequate_tpad(self): 

3499 tpad = 0. 

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

3501 if f is not None: 

3502 tpad = max(tpad, 1.0/f) 

3503 

3504 for snuffling in self.snufflings: 

3505 if snuffling._post_process_hook_enabled \ 

3506 or snuffling._pre_process_hook_enabled: 

3507 

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

3509 

3510 return tpad 

3511 

3512 def prepare_cutout2( 

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

3514 demean=True, nmax=6000): 

3515 

3516 if self.pile.is_empty(): 

3517 return [] 

3518 

3519 nmax = self.visible_length 

3520 

3521 self.timer_cutout.start() 

3522 

3523 tsee = tmax-tmin 

3524 min_deltat_wo_decimate = tsee/nmax 

3525 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3526 

3527 min_deltat_allow = min_deltat_wo_decimate 

3528 if self.lowpass is not None: 

3529 target_deltat_lp = 0.25/self.lowpass 

3530 if target_deltat_lp > min_deltat_wo_decimate: 

3531 min_deltat_allow = min_deltat_w_decimate 

3532 

3533 min_deltat_allow = math.exp( 

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

3535 

3536 tmin_ = tmin 

3537 tmax_ = tmax 

3538 

3539 # fetch more than needed? 

3540 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3544 

3545 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3546 lphp = self.menuitem_lphp.isChecked() 

3547 ads = self.menuitem_allowdownsampling.isChecked() 

3548 

3549 tpad = self.get_adequate_tpad() 

3550 tpad = max(tpad, tsee) 

3551 

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

3553 vec = ( 

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

3555 self.highpass, fft_filtering, lphp, 

3556 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3557 ads, self.pile.get_update_count()) 

3558 

3559 if (self.cached_vec 

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

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

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

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

3564 and self.cached_processed_traces is not None): 

3565 

3566 logger.debug('Using cached traces') 

3567 processed_traces = self.cached_processed_traces 

3568 

3569 else: 

3570 processed_traces = [] 

3571 if self.pile.deltatmax >= min_deltat_allow: 

3572 

3573 def group_selector(gr): 

3574 return gr.deltatmax >= min_deltat_allow 

3575 

3576 if trace_selector is not None: 

3577 def trace_selectorx(tr): 

3578 return tr.deltat >= min_deltat_allow \ 

3579 and trace_selector(tr) 

3580 else: 

3581 def trace_selectorx(tr): 

3582 return tr.deltat >= min_deltat_allow 

3583 

3584 for traces in self.pile.chopper( 

3585 tmin=tmin, tmax=tmax, tpad=tpad, 

3586 want_incomplete=True, 

3587 degap=degap, 

3588 maxgap=gap_lap_tolerance, 

3589 maxlap=gap_lap_tolerance, 

3590 keep_current_files_open=True, 

3591 group_selector=group_selector, 

3592 trace_selector=trace_selectorx, 

3593 accessor_id=id(self), 

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

3595 include_last=True): 

3596 

3597 if demean: 

3598 for tr in traces: 

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

3600 continue 

3601 y = tr.get_ydata() 

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

3603 

3604 traces = self.pre_process_hooks(traces) 

3605 

3606 for trace in traces: 

3607 

3608 if not (trace.meta 

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

3610 

3611 if fft_filtering: 

3612 but = pyrocko.response.ButterworthResponse 

3613 multres = pyrocko.response.MultiplyResponse 

3614 if self.lowpass is not None \ 

3615 or self.highpass is not None: 

3616 

3617 it = num.arange( 

3618 trace.data_len(), dtype=float) 

3619 detr_data, m, b = detrend( 

3620 it, trace.get_ydata()) 

3621 

3622 trace.set_ydata(detr_data) 

3623 

3624 freqs, fdata = trace.spectrum( 

3625 pad_to_pow2=True, tfade=None) 

3626 

3627 nfreqs = fdata.size 

3628 

3629 key = (trace.deltat, nfreqs) 

3630 

3631 if key not in self.tf_cache: 

3632 resps = [] 

3633 if self.lowpass is not None: 

3634 resps.append(but( 

3635 order=4, 

3636 corner=self.lowpass, 

3637 type='low')) 

3638 

3639 if self.highpass is not None: 

3640 resps.append(but( 

3641 order=4, 

3642 corner=self.highpass, 

3643 type='high')) 

3644 

3645 resp = multres(resps) 

3646 self.tf_cache[key] = \ 

3647 resp.evaluate(freqs) 

3648 

3649 filtered_data = num.fft.irfft( 

3650 fdata*self.tf_cache[key] 

3651 )[:trace.data_len()] 

3652 

3653 retrended_data = retrend( 

3654 it, filtered_data, m, b) 

3655 

3656 trace.set_ydata(retrended_data) 

3657 

3658 else: 

3659 

3660 if ads and self.lowpass is not None: 

3661 while trace.deltat \ 

3662 < min_deltat_wo_decimate: 

3663 

3664 trace.downsample(2, demean=False) 

3665 

3666 fmax = 0.5/trace.deltat 

3667 if not lphp and ( 

3668 self.lowpass is not None 

3669 and self.highpass is not None 

3670 and self.lowpass < fmax 

3671 and self.highpass < fmax 

3672 and self.highpass < self.lowpass): 

3673 

3674 trace.bandpass( 

3675 2, self.highpass, self.lowpass) 

3676 else: 

3677 if self.lowpass is not None: 

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

3679 trace.lowpass( 

3680 4, self.lowpass, 

3681 demean=False) 

3682 

3683 if self.highpass is not None: 

3684 if self.lowpass is None \ 

3685 or self.highpass \ 

3686 < self.lowpass: 

3687 

3688 if self.highpass < \ 

3689 0.5/trace.deltat: 

3690 trace.highpass( 

3691 4, self.highpass, 

3692 demean=False) 

3693 

3694 processed_traces.append(trace) 

3695 

3696 if self.rotate != 0.0: 

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

3698 cphi = math.cos(phi) 

3699 sphi = math.sin(phi) 

3700 for a in processed_traces: 

3701 for b in processed_traces: 

3702 if (a.network == b.network 

3703 and a.station == b.station 

3704 and a.location == b.location 

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

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

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

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

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

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

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

3712 

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

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

3715 a.set_ydata(aydata) 

3716 b.set_ydata(bydata) 

3717 

3718 processed_traces = self.post_process_hooks(processed_traces) 

3719 

3720 self.cached_processed_traces = processed_traces 

3721 self.cached_vec = vec 

3722 

3723 chopped_traces = [] 

3724 for trace in processed_traces: 

3725 chop_tmin = tmin_ - trace.deltat*4 

3726 chop_tmax = tmax_ + trace.deltat*4 

3727 

3728 try: 

3729 ctrace = trace.chop( 

3730 chop_tmin, chop_tmax, 

3731 inplace=False) 

3732 

3733 except pyrocko.trace.NoData: 

3734 continue 

3735 

3736 if ctrace.data_len() < 2: 

3737 continue 

3738 

3739 chopped_traces.append(ctrace) 

3740 

3741 self.timer_cutout.stop() 

3742 return chopped_traces 

3743 

3744 def pre_process_hooks(self, traces): 

3745 for snuffling in self.snufflings: 

3746 if snuffling._pre_process_hook_enabled: 

3747 traces = snuffling.pre_process_hook(traces) 

3748 

3749 return traces 

3750 

3751 def post_process_hooks(self, traces): 

3752 for snuffling in self.snufflings: 

3753 if snuffling._post_process_hook_enabled: 

3754 traces = snuffling.post_process_hook(traces) 

3755 

3756 return traces 

3757 

3758 def visible_length_change(self, ignore=None): 

3759 for menuitem, vlen in self.menuitems_visible_length: 

3760 if menuitem.isChecked(): 

3761 self.visible_length = vlen 

3762 

3763 def scaling_base_change(self, ignore=None): 

3764 for menuitem, scaling_base in self.menuitems_scaling_base: 

3765 if menuitem.isChecked(): 

3766 self.scaling_base = scaling_base 

3767 

3768 def scalingmode_change(self, ignore=None): 

3769 for menuitem, scaling_key in self.menuitems_scaling: 

3770 if menuitem.isChecked(): 

3771 self.scaling_key = scaling_key 

3772 self.update() 

3773 

3774 def apply_scaling_hooks(self, data_ranges): 

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

3776 hook = self.scaling_hooks[k] 

3777 hook(data_ranges) 

3778 

3779 def viewmode_change(self, ignore=True): 

3780 for item, mode in self.menuitems_viewmode: 

3781 if item.isChecked(): 

3782 self.view_mode = mode 

3783 break 

3784 else: 

3785 raise AttributeError('unknown view mode') 

3786 

3787 items_waterfall_disabled = ( 

3788 self.menuitem_showscaleaxis, 

3789 self.menuitem_showscalerange, 

3790 self.menuitem_showzeroline, 

3791 self.menuitem_colortraces, 

3792 self.menuitem_cliptraces, 

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

3794 ) 

3795 

3796 if self.view_mode is ViewMode.Waterfall: 

3797 self.parent().show_colorbar_ctrl(True) 

3798 self.parent().show_gain_ctrl(False) 

3799 

3800 for item in items_waterfall_disabled: 

3801 item.setDisabled(True) 

3802 

3803 self.visible_length = 180. 

3804 else: 

3805 self.parent().show_colorbar_ctrl(False) 

3806 self.parent().show_gain_ctrl(True) 

3807 

3808 for item in items_waterfall_disabled: 

3809 item.setDisabled(False) 

3810 

3811 self.visible_length_change() 

3812 self.update() 

3813 

3814 def set_scaling_hook(self, k, hook): 

3815 self.scaling_hooks[k] = hook 

3816 

3817 def remove_scaling_hook(self, k): 

3818 del self.scaling_hooks[k] 

3819 

3820 def remove_scaling_hooks(self): 

3821 self.scaling_hooks = {} 

3822 

3823 def s_sortingmode_change(self, ignore=None): 

3824 for menuitem, valfunc in self.menuitems_ssorting: 

3825 if menuitem.isChecked(): 

3826 self._ssort = valfunc 

3827 

3828 self.sortingmode_change() 

3829 

3830 def sortingmode_change(self, ignore=None): 

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

3832 if menuitem.isChecked(): 

3833 self.set_gathering(gather, color) 

3834 

3835 self.sortingmode_change_time = time.time() 

3836 

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

3838 self.lowpass = value 

3839 self.passband_check() 

3840 self.tf_cache = {} 

3841 self.update() 

3842 

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

3844 self.highpass = value 

3845 self.passband_check() 

3846 self.tf_cache = {} 

3847 self.update() 

3848 

3849 def passband_check(self): 

3850 if self.highpass and self.lowpass \ 

3851 and self.highpass >= self.lowpass: 

3852 

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

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

3855 'deactivate the highpass.' 

3856 

3857 self.update_status() 

3858 else: 

3859 oldmess = self.message 

3860 self.message = None 

3861 if oldmess is not None: 

3862 self.update_status() 

3863 

3864 def gain_change(self, value, ignore): 

3865 self.gain = value 

3866 self.update() 

3867 

3868 def rot_change(self, value, ignore): 

3869 self.rotate = value 

3870 self.update() 

3871 

3872 def waterfall_cmap_change(self, cmap): 

3873 self.waterfall_cmap = cmap 

3874 self.update() 

3875 

3876 def waterfall_clip_change(self, clip_min, clip_max): 

3877 self.waterfall_clip_min = clip_min 

3878 self.waterfall_clip_max = clip_max 

3879 self.update() 

3880 

3881 def waterfall_show_absolute_change(self, toggle): 

3882 self.waterfall_show_absolute = toggle 

3883 self.update() 

3884 

3885 def waterfall_set_integrate(self, toggle): 

3886 self.waterfall_integrate = toggle 

3887 self.update() 

3888 

3889 def set_selected_markers(self, markers): 

3890 ''' 

3891 Set a list of markers selected 

3892 

3893 :param markers: list of markers 

3894 ''' 

3895 self.deselect_all() 

3896 for m in markers: 

3897 m.selected = True 

3898 

3899 self.update() 

3900 

3901 def deselect_all(self): 

3902 for marker in self.markers: 

3903 marker.selected = False 

3904 

3905 def animate_picking(self): 

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

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

3908 

3909 def get_nslc_ids_for_track(self, ftrack): 

3910 itrack = int(ftrack) 

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

3912 

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

3914 if self.picking: 

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

3916 self.picking = None 

3917 self.picking_down = None 

3918 self.picking_timer.stop() 

3919 self.picking_timer = None 

3920 if not abort: 

3921 self.add_marker(self.floating_marker) 

3922 self.floating_marker.selected = True 

3923 self.emit_selected_markers() 

3924 

3925 self.floating_marker = None 

3926 

3927 def start_picking(self, ignore): 

3928 

3929 if not self.picking: 

3930 self.deselect_all() 

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

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

3933 

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

3935 self.picking.setGeometry( 

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

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

3938 

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

3940 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3942 self.floating_marker.selected = True 

3943 

3944 self.picking_timer = qc.QTimer() 

3945 self.picking_timer.timeout.connect( 

3946 self.animate_picking) 

3947 

3948 self.picking_timer.setInterval(50) 

3949 self.picking_timer.start() 

3950 

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

3952 if self.picking: 

3953 mouset = self.time_projection.rev(x) 

3954 dt = 0.0 

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

3956 if mouset < self.tmin: 

3957 dt = -(self.tmin - mouset) 

3958 else: 

3959 dt = mouset - self.tmax 

3960 ddt = self.tmax-self.tmin 

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

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

3963 

3964 x0 = x 

3965 if self.picking_down is not None: 

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

3967 

3968 w = abs(x-x0) 

3969 x0 = min(x0, x) 

3970 

3971 tmin, tmax = ( 

3972 self.time_projection.rev(x0), 

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

3974 

3975 tmin, tmax = ( 

3976 max(working_system_time_range[0], tmin), 

3977 min(working_system_time_range[1], tmax)) 

3978 

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

3980 

3981 self.picking.setGeometry( 

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

3983 

3984 ftrack = self.track_to_screen.rev(y) 

3985 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3987 

3988 if dt != 0.0 and doshift: 

3989 self.interrupt_following() 

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

3991 

3992 self.update() 

3993 

3994 def update_status(self): 

3995 

3996 if self.message is None: 

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

3998 

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

4000 if not is_working_time(mouse_t): 

4001 return 

4002 

4003 if self.floating_marker: 

4004 tmi, tma = ( 

4005 self.floating_marker.tmin, 

4006 self.floating_marker.tmax) 

4007 

4008 tt, ms = gmtime_x(tmi) 

4009 

4010 if tmi == tma: 

4011 message = mystrftime( 

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

4013 tt=tt, milliseconds=ms) 

4014 else: 

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

4016 message = mystrftime( 

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

4018 tt=tt, milliseconds=ms) 

4019 else: 

4020 tt, ms = gmtime_x(mouse_t) 

4021 

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

4023 else: 

4024 message = self.message 

4025 

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

4027 sb.clearMessage() 

4028 sb.showMessage(message) 

4029 

4030 def set_sortingmode_change_delay_time(self, dt): 

4031 self.sortingmode_change_delay_time = dt 

4032 

4033 def sortingmode_change_delayed(self): 

4034 now = time.time() 

4035 return ( 

4036 self.sortingmode_change_delay_time is not None 

4037 and now - self.sortingmode_change_time 

4038 < self.sortingmode_change_delay_time) 

4039 

4040 def set_visible_marker_kinds(self, kinds): 

4041 self.deselect_all() 

4042 self.visible_marker_kinds = tuple(kinds) 

4043 self.emit_selected_markers() 

4044 

4045 def following(self): 

4046 return self.follow_timer is not None \ 

4047 and not self.following_interrupted() 

4048 

4049 def interrupt_following(self): 

4050 self.interactive_range_change_time = time.time() 

4051 

4052 def following_interrupted(self, now=None): 

4053 if now is None: 

4054 now = time.time() 

4055 return now - self.interactive_range_change_time \ 

4056 < self.interactive_range_change_delay_time 

4057 

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

4059 if tmax_start is None: 

4060 tmax_start = time.time() 

4061 self.show_all = False 

4062 self.follow_time = tlen 

4063 self.follow_timer = qc.QTimer(self) 

4064 self.follow_timer.timeout.connect( 

4065 self.follow_update) 

4066 self.follow_timer.setInterval(interval) 

4067 self.follow_timer.start() 

4068 self.follow_started = time.time() 

4069 self.follow_lapse = lapse 

4070 self.follow_tshift = self.follow_started - tmax_start 

4071 self.interactive_range_change_time = 0.0 

4072 

4073 def unfollow(self): 

4074 if self.follow_timer is not None: 

4075 self.follow_timer.stop() 

4076 self.follow_timer = None 

4077 self.interactive_range_change_time = 0.0 

4078 

4079 def follow_update(self): 

4080 rnow = time.time() 

4081 if self.follow_lapse is None: 

4082 now = rnow 

4083 else: 

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

4085 * self.follow_lapse 

4086 

4087 if self.following_interrupted(rnow): 

4088 return 

4089 self.set_time_range( 

4090 now-self.follow_time-self.follow_tshift, 

4091 now-self.follow_tshift) 

4092 

4093 self.update() 

4094 

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

4096 self.return_tag = return_tag 

4097 self.window().close() 

4098 

4099 def cleanup(self): 

4100 self.about_to_close.emit() 

4101 self.timer.stop() 

4102 if self.follow_timer is not None: 

4103 self.follow_timer.stop() 

4104 

4105 for snuffling in list(self.snufflings): 

4106 self.remove_snuffling(snuffling) 

4107 

4108 def set_error_message(self, key, value): 

4109 if value is None: 

4110 if key in self.error_messages: 

4111 del self.error_messages[key] 

4112 else: 

4113 self.error_messages[key] = value 

4114 

4115 def inputline_changed(self, text): 

4116 pass 

4117 

4118 def inputline_finished(self, text): 

4119 line = str(text) 

4120 

4121 toks = line.split() 

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

4123 if len(toks) >= 1: 

4124 command = toks[0].lower() 

4125 

4126 try: 

4127 quick_filter_commands = { 

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

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

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

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

4132 

4133 if command in quick_filter_commands: 

4134 if len(toks) >= 2: 

4135 patterns = [ 

4136 quick_filter_commands[toks[0]] % pat 

4137 for pat in toks[1:]] 

4138 self.set_quick_filter_patterns(patterns, line) 

4139 else: 

4140 self.set_quick_filter_patterns(None) 

4141 

4142 self.update() 

4143 

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

4145 if len(toks) >= 2: 

4146 patterns = [] 

4147 if len(toks) == 2: 

4148 patterns = [toks[1]] 

4149 elif len(toks) >= 3: 

4150 x = { 

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

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

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

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

4155 

4156 if toks[1] in x: 

4157 patterns.extend( 

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

4159 

4160 for pattern in patterns: 

4161 if command == 'hide': 

4162 self.add_blacklist_pattern(pattern) 

4163 else: 

4164 self.remove_blacklist_pattern(pattern) 

4165 

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

4167 self.clear_blacklist() 

4168 

4169 clearit = True 

4170 

4171 self.update() 

4172 

4173 elif command == 'markers': 

4174 if len(toks) == 2: 

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

4176 kinds = self.all_marker_kinds 

4177 else: 

4178 kinds = [] 

4179 for x in toks[1]: 

4180 try: 

4181 kinds.append(int(x)) 

4182 except Exception: 

4183 pass 

4184 

4185 self.set_visible_marker_kinds(kinds) 

4186 

4187 elif len(toks) == 1: 

4188 self.set_visible_marker_kinds(()) 

4189 

4190 self.update() 

4191 

4192 elif command == 'scaling': 

4193 if len(toks) == 2: 

4194 hideit = False 

4195 error = 'wrong number of arguments' 

4196 

4197 if len(toks) >= 3: 

4198 vmin, vmax = [ 

4199 pyrocko.model.float_or_none(x) 

4200 for x in toks[-2:]] 

4201 

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

4203 if k in d: 

4204 if vmin is not None: 

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

4206 if vmax is not None: 

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

4208 

4209 if len(toks) == 1: 

4210 self.remove_scaling_hooks() 

4211 

4212 elif len(toks) == 3: 

4213 def hook(data_ranges): 

4214 for k in data_ranges: 

4215 upd(data_ranges, k, vmin, vmax) 

4216 

4217 self.set_scaling_hook('_', hook) 

4218 

4219 elif len(toks) == 4: 

4220 pattern = toks[1] 

4221 

4222 def hook(data_ranges): 

4223 for k in pyrocko.util.match_nslcs( 

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

4225 

4226 upd(data_ranges, k, vmin, vmax) 

4227 

4228 self.set_scaling_hook(pattern, hook) 

4229 

4230 elif command == 'goto': 

4231 toks2 = line.split(None, 1) 

4232 if len(toks2) == 2: 

4233 arg = toks2[1] 

4234 m = re.match( 

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

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

4237 if m: 

4238 tlen = None 

4239 if not m.group(1): 

4240 tlen = 12*32*24*60*60 

4241 elif not m.group(2): 

4242 tlen = 32*24*60*60 

4243 elif not m.group(3): 

4244 tlen = 24*60*60 

4245 elif not m.group(4): 

4246 tlen = 60*60 

4247 elif not m.group(5): 

4248 tlen = 60 

4249 

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

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

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

4253 t = pyrocko.util.str_to_time(arg) 

4254 self.go_to_time(t, tlen=tlen) 

4255 

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

4257 supl = '00:00:00' 

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

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

4260 tmin, tmax = self.get_time_range() 

4261 sdate = pyrocko.util.time_to_str( 

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

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

4264 self.go_to_time(t) 

4265 

4266 elif arg == 'today': 

4267 self.go_to_time( 

4268 day_start( 

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

4270 

4271 elif arg == 'yesterday': 

4272 self.go_to_time( 

4273 day_start( 

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

4275 

4276 else: 

4277 self.go_to_event_by_name(arg) 

4278 

4279 else: 

4280 raise PileViewerMainException( 

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

4282 

4283 except PileViewerMainException as e: 

4284 error = str(e) 

4285 hideit = False 

4286 

4287 return clearit, hideit, error 

4288 

4289 return PileViewerMain 

4290 

4291 

4292PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4293GLPileViewerMain = MakePileViewerMainClass(qgl.QGLWidget) 

4294 

4295 

4296class LineEditWithAbort(qw.QLineEdit): 

4297 

4298 aborted = qc.pyqtSignal() 

4299 history_down = qc.pyqtSignal() 

4300 history_up = qc.pyqtSignal() 

4301 

4302 def keyPressEvent(self, key_event): 

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

4304 self.aborted.emit() 

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

4306 self.history_down.emit() 

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

4308 self.history_up.emit() 

4309 else: 

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

4311 

4312 

4313class PileViewer(qw.QFrame): 

4314 ''' 

4315 PileViewerMain + Controls + Inputline 

4316 ''' 

4317 

4318 def __init__( 

4319 self, pile, 

4320 ntracks_shown_max=20, 

4321 marker_editor_sortable=True, 

4322 use_opengl=False, 

4323 panel_parent=None, 

4324 *args): 

4325 

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

4327 

4328 layout = qw.QGridLayout() 

4329 layout.setContentsMargins(0, 0, 0, 0) 

4330 layout.setSpacing(0) 

4331 

4332 self.menu = PileViewerMenuBar(self) 

4333 

4334 if use_opengl: 

4335 self.viewer = GLPileViewerMain( 

4336 pile, 

4337 ntracks_shown_max=ntracks_shown_max, 

4338 panel_parent=panel_parent, 

4339 menu=self.menu) 

4340 else: 

4341 self.viewer = PileViewerMain( 

4342 pile, 

4343 ntracks_shown_max=ntracks_shown_max, 

4344 panel_parent=panel_parent, 

4345 menu=self.menu) 

4346 

4347 self.marker_editor_sortable = marker_editor_sortable 

4348 

4349 self.setFrameShape(qw.QFrame.StyledPanel) 

4350 self.setFrameShadow(qw.QFrame.Sunken) 

4351 

4352 self.input_area = qw.QFrame(self) 

4353 ia_layout = qw.QGridLayout() 

4354 ia_layout.setContentsMargins(11, 11, 11, 11) 

4355 self.input_area.setLayout(ia_layout) 

4356 

4357 self.inputline = LineEditWithAbort(self.input_area) 

4358 self.inputline.returnPressed.connect( 

4359 self.inputline_returnpressed) 

4360 self.inputline.editingFinished.connect( 

4361 self.inputline_finished) 

4362 self.inputline.aborted.connect( 

4363 self.inputline_aborted) 

4364 

4365 self.inputline.history_down.connect( 

4366 lambda: self.step_through_history(1)) 

4367 self.inputline.history_up.connect( 

4368 lambda: self.step_through_history(-1)) 

4369 

4370 self.inputline.textEdited.connect( 

4371 self.inputline_changed) 

4372 

4373 self.inputline.setPlaceholderText( 

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

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

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

4377 self.input_area.hide() 

4378 self.history = None 

4379 

4380 self.inputline_error_str = None 

4381 

4382 self.inputline_error = qw.QLabel() 

4383 self.inputline_error.hide() 

4384 

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

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

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

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

4389 

4390 pb = Progressbars(self) 

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

4392 self.progressbars = pb 

4393 

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

4395 self.scrollbar = scrollbar 

4396 layout.addWidget(scrollbar, 1, 1) 

4397 self.scrollbar.valueChanged.connect( 

4398 self.scrollbar_changed) 

4399 

4400 self.block_scrollbar_changes = False 

4401 

4402 self.viewer.want_input.connect( 

4403 self.inputline_show) 

4404 self.viewer.tracks_range_changed.connect( 

4405 self.tracks_range_changed) 

4406 self.viewer.pile_has_changed_signal.connect( 

4407 self.adjust_controls) 

4408 self.viewer.about_to_close.connect( 

4409 self.save_inputline_history) 

4410 

4411 self.setLayout(layout) 

4412 

4413 def cleanup(self): 

4414 self.viewer.cleanup() 

4415 

4416 def get_progressbars(self): 

4417 return self.progressbars 

4418 

4419 def inputline_show(self): 

4420 if not self.history: 

4421 self.load_inputline_history() 

4422 

4423 self.input_area.show() 

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

4425 self.inputline.selectAll() 

4426 

4427 def inputline_set_error(self, string): 

4428 self.inputline_error_str = string 

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

4430 self.inputline.selectAll() 

4431 self.inputline_error.setText(string) 

4432 self.input_area.show() 

4433 self.inputline_error.show() 

4434 

4435 def inputline_clear_error(self): 

4436 if self.inputline_error_str: 

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

4438 self.inputline_error_str = None 

4439 self.inputline_error.clear() 

4440 self.inputline_error.hide() 

4441 

4442 def inputline_changed(self, line): 

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

4444 self.inputline_clear_error() 

4445 

4446 def inputline_returnpressed(self): 

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

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

4449 

4450 if error: 

4451 self.inputline_set_error(error) 

4452 

4453 line = line.strip() 

4454 

4455 if line != '' and not error: 

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

4457 self.history.append(line) 

4458 

4459 if clearit: 

4460 

4461 self.inputline.blockSignals(True) 

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

4463 if qpat is None: 

4464 self.inputline.clear() 

4465 else: 

4466 self.inputline.setText(qinp) 

4467 self.inputline.blockSignals(False) 

4468 

4469 if hideit and not error: 

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

4471 self.input_area.hide() 

4472 

4473 self.hist_ind = len(self.history) 

4474 

4475 def inputline_aborted(self): 

4476 ''' 

4477 Hide the input line. 

4478 ''' 

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

4480 self.hist_ind = len(self.history) 

4481 self.input_area.hide() 

4482 

4483 def save_inputline_history(self): 

4484 ''' 

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

4486 ''' 

4487 if not self.history: 

4488 return 

4489 

4490 conf = pyrocko.config 

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

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

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

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

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

4496 

4497 def load_inputline_history(self): 

4498 ''' 

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

4500 ''' 

4501 conf = pyrocko.config 

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

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

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

4505 f.write('\n') 

4506 

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

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

4509 

4510 self.hist_ind = len(self.history) 

4511 

4512 def step_through_history(self, ud=1): 

4513 ''' 

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

4515 ''' 

4516 n = len(self.history) 

4517 self.hist_ind += ud 

4518 self.hist_ind %= (n + 1) 

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

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

4521 else: 

4522 self.inputline.setText('') 

4523 

4524 def inputline_finished(self): 

4525 pass 

4526 

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

4528 if self.block_scrollbar_changes: 

4529 return 

4530 

4531 self.scrollbar.blockSignals(True) 

4532 self.scrollbar.setPageStep(ihi-ilo) 

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

4534 self.scrollbar.setRange(0, vmax) 

4535 self.scrollbar.setValue(ilo) 

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

4537 self.scrollbar.blockSignals(False) 

4538 

4539 def scrollbar_changed(self, value): 

4540 self.block_scrollbar_changes = True 

4541 ilo = value 

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

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

4544 self.block_scrollbar_changes = False 

4545 self.update_contents() 

4546 

4547 def controls(self): 

4548 frame = qw.QFrame(self) 

4549 layout = qw.QGridLayout() 

4550 frame.setLayout(layout) 

4551 

4552 minfreq = 0.001 

4553 maxfreq = 1000.0 

4554 self.lowpass_control = ValControl(high_is_none=True) 

4555 self.lowpass_control.setup( 

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

4557 self.highpass_control = ValControl(low_is_none=True) 

4558 self.highpass_control.setup( 

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

4560 self.gain_control = ValControl() 

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

4562 self.rot_control = LinValControl() 

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

4564 self.colorbar_control = ColorbarControl(self) 

4565 

4566 self.lowpass_control.valchange.connect( 

4567 self.viewer.lowpass_change) 

4568 self.highpass_control.valchange.connect( 

4569 self.viewer.highpass_change) 

4570 self.gain_control.valchange.connect( 

4571 self.viewer.gain_change) 

4572 self.rot_control.valchange.connect( 

4573 self.viewer.rot_change) 

4574 self.colorbar_control.cmap_changed.connect( 

4575 self.viewer.waterfall_cmap_change 

4576 ) 

4577 self.colorbar_control.clip_changed.connect( 

4578 self.viewer.waterfall_clip_change 

4579 ) 

4580 self.colorbar_control.show_absolute_toggled.connect( 

4581 self.viewer.waterfall_show_absolute_change 

4582 ) 

4583 self.colorbar_control.show_integrate_toggled.connect( 

4584 self.viewer.waterfall_set_integrate 

4585 ) 

4586 

4587 for icontrol, control in enumerate(( 

4588 self.highpass_control, 

4589 self.lowpass_control, 

4590 self.gain_control, 

4591 self.rot_control, 

4592 self.colorbar_control)): 

4593 

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

4595 layout.addWidget(widget, icontrol, iwidget) 

4596 

4597 spacer = qw.QSpacerItem( 

4598 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4600 

4601 self.adjust_controls() 

4602 self.viewer.viewmode_change(ViewMode.Wiggle) 

4603 return frame 

4604 

4605 def marker_editor(self): 

4606 editor = pyrocko.gui.marker_editor.MarkerEditor( 

4607 self, sortable=self.marker_editor_sortable) 

4608 

4609 editor.set_viewer(self.get_view()) 

4610 editor.get_marker_model().dataChanged.connect( 

4611 self.update_contents) 

4612 return editor 

4613 

4614 def adjust_controls(self): 

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

4616 maxfreq = 0.5/dtmin 

4617 minfreq = (0.5/dtmax)*0.001 

4618 self.lowpass_control.set_range(minfreq, maxfreq) 

4619 self.highpass_control.set_range(minfreq, maxfreq) 

4620 

4621 def setup_snufflings(self): 

4622 self.viewer.setup_snufflings() 

4623 

4624 def get_view(self): 

4625 return self.viewer 

4626 

4627 def update_contents(self): 

4628 self.viewer.update() 

4629 

4630 def get_pile(self): 

4631 return self.viewer.get_pile() 

4632 

4633 def show_colorbar_ctrl(self, show): 

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

4635 w.setVisible(show) 

4636 

4637 def show_gain_ctrl(self, show): 

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

4639 w.setVisible(show)