1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5 

6import os 

7import time 

8import calendar 

9import datetime 

10import re 

11import math 

12import logging 

13import operator 

14import copy 

15import enum 

16from itertools import groupby 

17 

18import numpy as num 

19import pyrocko.model 

20import pyrocko.pile 

21import pyrocko.trace 

22import pyrocko.response 

23import pyrocko.util 

24import pyrocko.plot 

25import pyrocko.gui.snuffling 

26import pyrocko.gui.snufflings 

27import pyrocko.gui.marker_editor 

28 

29from pyrocko.util import hpfloat, gmtime_x, mystrftime 

30 

31from .marker import associate_phases_to_events, MarkerOneNSLCRequired 

32 

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

34 PhaseMarker, make_QPolygonF, draw_label, Label, 

35 Progressbars, ColorbarControl) 

36 

37from .qt_compat import qc, qg, qw, qsvg 

38 

39from .pile_viewer_waterfall import TraceWaterfall 

40 

41import scipy.stats as sstats 

42import platform 

43 

44MIN_LABEL_SIZE_PT = 6 

45 

46 

47qc.QString = str 

48 

49qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \ 

50 qw.QFileDialog.DontUseSheet 

51 

52is_macos = platform.uname()[0] == 'Darwin' 

53 

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

55 

56 

57def detrend(x, y): 

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

59 y_detrended = y - slope * x - offset 

60 return y_detrended, slope, offset 

61 

62 

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

64 return x * slope + y_detrended + offset 

65 

66 

67class Global(object): 

68 appOnDemand = None 

69 

70 

71class NSLC(object): 

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

73 self.network = n 

74 self.station = s 

75 self.location = l 

76 self.channel = c 

77 

78 

79class m_float(float): 

80 

81 def __str__(self): 

82 if abs(self) >= 10000.: 

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

84 elif abs(self) >= 1000.: 

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

86 else: 

87 return '%.5g m' % self 

88 

89 def __lt__(self, other): 

90 if other is None: 

91 return True 

92 return float(self) < float(other) 

93 

94 def __gt__(self, other): 

95 if other is None: 

96 return False 

97 return float(self) > float(other) 

98 

99 

100def m_float_or_none(x): 

101 if x is None: 

102 return None 

103 else: 

104 return m_float(x) 

105 

106 

107def make_chunks(items): 

108 ''' 

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

110 ''' 

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

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

113 

114 

115class deg_float(float): 

116 

117 def __str__(self): 

118 return '%4.0f' % self 

119 

120 def __lt__(self, other): 

121 if other is None: 

122 return True 

123 return float(self) < float(other) 

124 

125 def __gt__(self, other): 

126 if other is None: 

127 return False 

128 return float(self) > float(other) 

129 

130 

131def deg_float_or_none(x): 

132 if x is None: 

133 return None 

134 else: 

135 return deg_float(x) 

136 

137 

138class sector_int(int): 

139 

140 def __str__(self): 

141 return '[%i]' % self 

142 

143 def __lt__(self, other): 

144 if other is None: 

145 return True 

146 return int(self) < int(other) 

147 

148 def __gt__(self, other): 

149 if other is None: 

150 return False 

151 return int(self) > int(other) 

152 

153 

154def num_to_html(num): 

155 snum = '%g' % num 

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

157 if m: 

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

159 

160 return snum 

161 

162 

163gap_lap_tolerance = 5. 

164 

165 

166class ViewMode(enum.Enum): 

167 Wiggle = 1 

168 Waterfall = 2 

169 

170 

171class Timer(object): 

172 def __init__(self): 

173 self._start = None 

174 self._stop = None 

175 

176 def start(self): 

177 self._start = os.times() 

178 

179 def stop(self): 

180 self._stop = os.times() 

181 

182 def get(self): 

183 a = self._start 

184 b = self._stop 

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

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

187 else: 

188 return tuple([0.] * 5) 

189 

190 def __sub__(self, other): 

191 a = self.get() 

192 b = other.get() 

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

194 

195 

196class ObjectStyle(object): 

197 def __init__(self, frame_pen, fill_brush): 

198 self.frame_pen = frame_pen 

199 self.fill_brush = fill_brush 

200 

201 

202box_styles = [] 

203box_alpha = 100 

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

205 'scarletred'.split(): 

206 

207 box_styles.append(ObjectStyle( 

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

209 qg.QBrush(qg.QColor( 

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

211 )) 

212 

213box_styles_coverage = {} 

214 

215box_styles_coverage['waveform'] = [ 

216 ObjectStyle( 

217 qg.QPen( 

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

219 1, qc.Qt.DashLine), 

220 qg.QBrush(qg.QColor( 

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

222 ), 

223 ObjectStyle( 

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

225 qg.QBrush(qg.QColor( 

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

227 ), 

228 ObjectStyle( 

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

230 qg.QBrush(qg.QColor( 

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

232 )] 

233 

234box_styles_coverage['waveform_promise'] = [ 

235 ObjectStyle( 

236 qg.QPen( 

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

238 1, qc.Qt.DashLine), 

239 qg.QBrush(qg.QColor( 

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

241 ), 

242 ObjectStyle( 

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

244 qg.QBrush(qg.QColor( 

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

246 ), 

247 ObjectStyle( 

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

249 qg.QBrush(qg.QColor( 

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

251 )] 

252 

253sday = 60*60*24. # \ 

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

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

256 

257acceptable_tincs = num.array([ 

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

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

260 

261 

262working_system_time_range = \ 

263 pyrocko.util.working_system_time_range() 

264 

265initial_time_range = [] 

266 

267try: 

268 initial_time_range.append( 

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

270except Exception: 

271 initial_time_range.append(working_system_time_range[0]) 

272 

273try: 

274 initial_time_range.append( 

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

276except Exception: 

277 initial_time_range.append(working_system_time_range[1]) 

278 

279 

280def is_working_time(t): 

281 return working_system_time_range[0] <= t and \ 

282 t <= working_system_time_range[1] 

283 

284 

285def fancy_time_ax_format(inc): 

286 l0_fmt_brief = '' 

287 l2_fmt = '' 

288 l2_trig = 0 

289 if inc < 0.000001: 

290 l0_fmt = '.%n' 

291 l0_center = False 

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

293 l1_trig = 6 

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

295 l2_trig = 3 

296 elif inc < 0.001: 

297 l0_fmt = '.%u' 

298 l0_center = False 

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

300 l1_trig = 6 

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

302 l2_trig = 3 

303 elif inc < 1: 

304 l0_fmt = '.%r' 

305 l0_center = False 

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

307 l1_trig = 6 

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

309 l2_trig = 3 

310 elif inc < 60: 

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

312 l0_center = False 

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

314 l1_trig = 3 

315 elif inc < 3600: 

316 l0_fmt = '%H:%M' 

317 l0_center = False 

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

319 l1_trig = 3 

320 elif inc < sday: 

321 l0_fmt = '%H:%M' 

322 l0_center = False 

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

324 l1_trig = 3 

325 elif inc < smonth: 

326 l0_fmt = '%a %d' 

327 l0_fmt_brief = '%d' 

328 l0_center = True 

329 l1_fmt = '%b, %Y' 

330 l1_trig = 2 

331 elif inc < syear: 

332 l0_fmt = '%b' 

333 l0_center = True 

334 l1_fmt = '%Y' 

335 l1_trig = 1 

336 else: 

337 l0_fmt = '%Y' 

338 l0_center = False 

339 l1_fmt = '' 

340 l1_trig = 0 

341 

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

343 

344 

345def day_start(timestamp): 

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

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

348 return calendar.timegm(tts) 

349 

350 

351def month_start(timestamp): 

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

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

354 return calendar.timegm(tts) 

355 

356 

357def year_start(timestamp): 

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

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

360 return calendar.timegm(tts) 

361 

362 

363def time_nice_value(inc0): 

364 if inc0 < acceptable_tincs[0]: 

365 return pyrocko.plot.nice_value(inc0) 

366 elif inc0 > acceptable_tincs[-1]: 

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

368 else: 

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

370 return acceptable_tincs[i] 

371 

372 

373class TimeScaler(pyrocko.plot.AutoScaler): 

374 def __init__(self): 

375 pyrocko.plot.AutoScaler.__init__(self) 

376 self.mode = 'min-max' 

377 

378 def make_scale(self, data_range): 

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

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

381 

382 data_min = min(data_range) 

383 data_max = max(data_range) 

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

385 

386 mi, ma = data_min, data_max 

387 nmi = mi 

388 if self.mode != 'off': 

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

390 

391 nma = ma 

392 if self.mode != 'off': 

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

394 

395 mi, ma = nmi, nma 

396 

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

398 mi -= 1.0 

399 ma += 1.0 

400 

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

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

403 

404 # make nice tick increment 

405 if self.inc is not None: 

406 inc = self.inc 

407 else: 

408 if self.approx_ticks > 0.: 

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

410 else: 

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

412 

413 if inc == 0.0: 

414 inc = 1.0 

415 

416 if is_reverse: 

417 return ma, mi, -inc 

418 else: 

419 return mi, ma, inc 

420 

421 def make_ticks(self, data_range): 

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

423 

424 is_reverse = False 

425 if inc < 0: 

426 mi, ma, inc = ma, mi, -inc 

427 is_reverse = True 

428 

429 ticks = [] 

430 

431 if inc < sday: 

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

433 if inc < 0.001: 

434 mi_day = hpfloat(mi_day) 

435 

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

437 if inc < 0.001: 

438 base = hpfloat(base) 

439 

440 base_day = mi_day 

441 i = 0 

442 while True: 

443 tick = base+i*inc 

444 if tick > ma: 

445 break 

446 

447 tick_day = day_start(tick) 

448 if tick_day > base_day: 

449 base_day = tick_day 

450 base = base_day 

451 i = 0 

452 else: 

453 ticks.append(tick) 

454 i += 1 

455 

456 elif inc < smonth: 

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

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

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

460 if mi_day == mi: 

461 dt_base += delta 

462 i = 0 

463 while True: 

464 current = dt_base + i*delta 

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

466 if tick > ma: 

467 break 

468 ticks.append(tick) 

469 i += 1 

470 

471 elif inc < syear: 

472 mi_month = month_start(max( 

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

474 

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

476 while True: 

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

478 m += 1 

479 if m > 12: 

480 y, m = y+1, 1 

481 

482 if tick > ma: 

483 break 

484 

485 if tick >= mi: 

486 ticks.append(tick) 

487 

488 else: 

489 mi_year = year_start(max( 

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

491 

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

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

494 

495 while True: 

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

497 y += incy 

498 if tick > ma: 

499 break 

500 if tick >= mi: 

501 ticks.append(tick) 

502 

503 if is_reverse: 

504 ticks.reverse() 

505 

506 return ticks, inc 

507 

508 

509def need_l1_tick(tt, ms, l1_trig): 

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

511 

512 

513def tick_to_labels(tick, inc): 

514 tt, ms = gmtime_x(tick) 

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

516 fancy_time_ax_format(inc) 

517 

518 l0 = mystrftime(l0_fmt, tt, ms) 

519 l0_brief = mystrftime(l0_fmt_brief, tt, ms) 

520 l1, l2 = None, None 

521 if need_l1_tick(tt, ms, l1_trig): 

522 l1 = mystrftime(l1_fmt, tt, ms) 

523 if need_l1_tick(tt, ms, l2_trig): 

524 l2 = mystrftime(l2_fmt, tt, ms) 

525 

526 return l0, l0_brief, l0_center, l1, l2 

527 

528 

529def l1_l2_tick(tick, inc): 

530 tt, ms = gmtime_x(tick) 

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

532 fancy_time_ax_format(inc) 

533 

534 l1 = mystrftime(l1_fmt, tt, ms) 

535 l2 = mystrftime(l2_fmt, tt, ms) 

536 return l1, l2 

537 

538 

539class TimeAx(TimeScaler): 

540 def __init__(self, *args): 

541 TimeScaler.__init__(self, *args) 

542 

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

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

545 p.setPen(pen) 

546 font = qg.QFont() 

547 font.setBold(True) 

548 p.setFont(font) 

549 fm = p.fontMetrics() 

550 ticklen = 10 

551 pad = 10 

552 tmin, tmax = xprojection.get_in_range() 

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

554 l1_hits = 0 

555 l2_hits = 0 

556 

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

558 uumin, uumax = xprojection.get_out_range() 

559 first_tick_with_label = None 

560 

561 data = [] 

562 for tick in ticks: 

563 umin = xprojection(tick) 

564 

565 umin_approx_next = xprojection(tick+inc) 

566 umax = xprojection(tick) 

567 

568 pinc_approx = umin_approx_next - umin 

569 

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

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

572 

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

574 # hide year at epoch 

575 # (we assume that synthetic data is shown) 

576 if l2: 

577 l2 = None 

578 elif l1: 

579 l1 = None 

580 

581 if l0_center: 

582 ushift = (umin_approx_next-umin)/2. 

583 else: 

584 ushift = 0. 

585 

586 abbr_level = 0 

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

588 label0 = l0x 

589 rect0 = fm.boundingRect(label0) 

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

591 break 

592 

593 abbr_level += 1 

594 

595 data.append(( 

596 l0, l0_brief, l0_center, l1, l2, tick, ushift, umin, 

597 pinc_approx)) 

598 

599 for (l0, l0_brief, l0_center, l1, l2, tick, ushift, umin, 

600 pinc_approx) in data: 

601 

602 label0 = (l0, l0_brief, '')[abbr_level] 

603 rect0 = fm.boundingRect(label0) 

604 

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

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

607 

608 if first_tick_with_label is None: 

609 first_tick_with_label = tick 

610 p.drawText(qc.QPointF( 

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

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

613 

614 if l1: 

615 label1 = l1 

616 rect1 = fm.boundingRect(label1) 

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

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

619 

620 p.drawText(qc.QPointF( 

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

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

623 label1) 

624 

625 l1_hits += 1 

626 

627 if l2: 

628 label2 = l2 

629 rect2 = fm.boundingRect(label2) 

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

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

632 

633 p.drawText(qc.QPointF( 

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

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

636 ticklen), label2) 

637 

638 l2_hits += 1 

639 

640 if first_tick_with_label is None: 

641 first_tick_with_label = tmin 

642 

643 l1, l2 = l1_l2_tick(first_tick_with_label, inc) 

644 

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

646 tmax - tmin < 3600*24: 

647 

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

649 if l2: 

650 l2 = None 

651 elif l1: 

652 l1 = None 

653 

654 if l1_hits == 0 and l1: 

655 label1 = l1 

656 rect1 = fm.boundingRect(label1) 

657 p.drawText(qc.QPointF( 

658 uumin+pad, 

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

660 label1) 

661 

662 l1_hits += 1 

663 

664 if l2_hits == 0 and l2: 

665 label2 = l2 

666 rect2 = fm.boundingRect(label2) 

667 p.drawText(qc.QPointF( 

668 uumin+pad, 

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

670 label2) 

671 

672 v = yprojection(0) 

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

674 

675 

676class Projection(object): 

677 def __init__(self): 

678 self.xr = 0., 1. 

679 self.ur = 0., 1. 

680 

681 def set_in_range(self, xmin, xmax): 

682 if xmax == xmin: 

683 xmax = xmin + 1. 

684 

685 self.xr = xmin, xmax 

686 

687 def get_in_range(self): 

688 return self.xr 

689 

690 def set_out_range(self, umin, umax): 

691 if umax == umin: 

692 umax = umin + 1. 

693 

694 self.ur = umin, umax 

695 

696 def get_out_range(self): 

697 return self.ur 

698 

699 def __call__(self, x): 

700 umin, umax = self.ur 

701 xmin, xmax = self.xr 

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

703 

704 def clipped(self, x, umax_pad): 

705 umin, umax = self.ur 

706 xmin, xmax = self.xr 

707 return min( 

708 umax-umax_pad, 

709 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: str(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 base.__init__(self, *args) 

797 

798 self.pile = pile 

799 self.ax_height = 80 

800 self.panel_parent = panel_parent 

801 

802 self.click_tolerance = 5 

803 

804 self.ntracks_shown_max = ntracks_shown_max 

805 self.initial_ntracks_shown_max = ntracks_shown_max 

806 self.ntracks = 0 

807 self.show_all = True 

808 self.shown_tracks_range = None 

809 self.track_start = None 

810 self.track_trange = None 

811 

812 self.lowpass = None 

813 self.highpass = None 

814 self.gain = 1.0 

815 self.rotate = 0.0 

816 self.picking_down = None 

817 self.picking = None 

818 self.floating_marker = None 

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

820 self.markers_deltat_max = 0. 

821 self.n_selected_markers = 0 

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

823 self.visible_marker_kinds = self.all_marker_kinds 

824 self.active_event_marker = None 

825 self.ignore_releases = 0 

826 self.message = None 

827 self.reloaded = False 

828 self.pile_has_changed = False 

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

830 

831 self.tax = TimeAx() 

832 self.setBackgroundRole(qg.QPalette.Base) 

833 self.setAutoFillBackground(True) 

834 poli = qw.QSizePolicy( 

835 qw.QSizePolicy.Expanding, 

836 qw.QSizePolicy.Expanding) 

837 

838 self.setSizePolicy(poli) 

839 self.setMinimumSize(300, 200) 

840 self.setFocusPolicy(qc.Qt.ClickFocus) 

841 

842 self.menu = menu or PileViewerMenu(self) 

843 

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

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

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

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

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

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

850 

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

852 

853 self.snufflings_menu = self.toggle_panel_menu.addMenu( 

854 'Run Snuffling') 

855 self.toggle_panel_menu.addSeparator() 

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

857 help_menu.addSeparator() 

858 

859 file_menu.addAction( 

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

861 'Open waveform files...', 

862 self.open_waveforms, 

863 qg.QKeySequence.Open) 

864 

865 file_menu.addAction( 

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

867 'Open waveform directory...', 

868 self.open_waveform_directory) 

869 

870 file_menu.addAction( 

871 'Open station files...', 

872 self.open_stations) 

873 

874 file_menu.addAction( 

875 'Open StationXML files...', 

876 self.open_stations_xml) 

877 

878 file_menu.addAction( 

879 'Open event file...', 

880 self.read_events) 

881 

882 file_menu.addSeparator() 

883 file_menu.addAction( 

884 'Open marker file...', 

885 self.read_markers) 

886 

887 file_menu.addAction( 

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

889 'Save markers...', 

890 self.write_markers, 

891 qg.QKeySequence.Save) 

892 

893 file_menu.addAction( 

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

895 'Save selected markers...', 

896 self.write_selected_markers, 

897 qg.QKeySequence.SaveAs) 

898 

899 file_menu.addSeparator() 

900 file_menu.addAction( 

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

902 'Print', 

903 self.printit, 

904 qg.QKeySequence.Print) 

905 

906 file_menu.addAction( 

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

908 'Save as SVG or PNG', 

909 self.savesvg, 

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

911 

912 file_menu.addSeparator() 

913 close = file_menu.addAction( 

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

915 'Close', 

916 self.myclose) 

917 close.setShortcuts( 

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

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

920 

921 # Scale Menu 

922 menudef = [ 

923 ('Individual Scale', 

924 lambda tr: tr.nslc_id, 

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

926 ('Common Scale', 

927 lambda tr: None, 

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

929 ('Common Scale per Station', 

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

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

932 ('Common Scale per Station Location', 

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

934 ('Common Scale per Component', 

935 lambda tr: (tr.channel)), 

936 ] 

937 

938 self.menuitems_scaling = add_radiobuttongroup( 

939 scale_menu, menudef, self.scalingmode_change, 

940 default=self.config.trace_scale) 

941 scale_menu.addSeparator() 

942 

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

944 self.scaling_hooks = {} 

945 self.scalingmode_change() 

946 

947 menudef = [ 

948 ('Scaling based on Minimum and Maximum', 

949 ('minmax', 'minmax')), 

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

951 ('minmax', 'robust')), 

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

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

954 ] 

955 

956 self.menuitems_scaling_base = add_radiobuttongroup( 

957 scale_menu, menudef, self.scaling_base_change) 

958 

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

960 scale_menu.addSeparator() 

961 

962 self.menuitem_fixscalerange = scale_menu.addAction( 

963 'Fix Scale Ranges') 

964 self.menuitem_fixscalerange.setCheckable(True) 

965 

966 # Sort Menu 

967 def sector_dist(sta): 

968 if sta.dist_m is None: 

969 return None, None 

970 else: 

971 return ( 

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

973 m_float(sta.dist_m)) 

974 

975 menudef = [ 

976 ('Sort by Names', 

977 lambda tr: (), 

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

979 ('Sort by Distance', 

980 lambda tr: self.station_attrib( 

981 tr, 

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

983 lambda tr: (None,)), 

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

985 ('Sort by Azimuth', 

986 lambda tr: self.station_attrib( 

987 tr, 

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

989 lambda tr: (None,))), 

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

991 lambda tr: self.station_attrib( 

992 tr, 

993 sector_dist, 

994 lambda tr: (None, None))), 

995 ('Sort by Backazimuth', 

996 lambda tr: self.station_attrib( 

997 tr, 

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

999 lambda tr: (None,))), 

1000 ] 

1001 self.menuitems_ssorting = add_radiobuttongroup( 

1002 sort_menu, menudef, self.s_sortingmode_change) 

1003 sort_menu.addSeparator() 

1004 

1005 self._ssort = lambda tr: () 

1006 

1007 self.menu.addSeparator() 

1008 

1009 menudef = [ 

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

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

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

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

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

1015 lambda tr: tr.channel)), 

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

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

1018 lambda tr: tr.channel)), 

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

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

1021 lambda tr: tr.channel)), 

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

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

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

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

1026 ((0, 1, 3), 

1027 lambda tr: tr.location)), 

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

1029 ((1, 0, 3), 

1030 lambda tr: tr.location)), 

1031 ] 

1032 

1033 self.menuitems_sorting = add_radiobuttongroup( 

1034 sort_menu, menudef, self.sortingmode_change) 

1035 

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

1037 self.config.visible_length_setting] 

1038 

1039 # View menu 

1040 self.menuitems_visible_length = add_radiobuttongroup( 

1041 view_menu, menudef, 

1042 self.visible_length_change) 

1043 view_menu.addSeparator() 

1044 

1045 view_modes = [ 

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

1047 ('Waterfall', ViewMode.Waterfall) 

1048 ] 

1049 

1050 self.menuitems_viewmode = add_radiobuttongroup( 

1051 view_menu, view_modes, 

1052 self.viewmode_change, default=ViewMode.Wiggle) 

1053 view_menu.addSeparator() 

1054 

1055 self.menuitem_cliptraces = view_menu.addAction( 

1056 'Clip Traces') 

1057 self.menuitem_cliptraces.setCheckable(True) 

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

1059 

1060 self.menuitem_showboxes = view_menu.addAction( 

1061 'Show Boxes') 

1062 self.menuitem_showboxes.setCheckable(True) 

1063 self.menuitem_showboxes.setChecked( 

1064 self.config.show_boxes) 

1065 

1066 self.menuitem_colortraces = view_menu.addAction( 

1067 'Color Traces') 

1068 self.menuitem_colortraces.setCheckable(True) 

1069 self.menuitem_antialias = view_menu.addAction( 

1070 'Antialiasing') 

1071 self.menuitem_antialias.setCheckable(True) 

1072 

1073 view_menu.addSeparator() 

1074 self.menuitem_showscalerange = view_menu.addAction( 

1075 'Show Scale Ranges') 

1076 self.menuitem_showscalerange.setCheckable(True) 

1077 self.menuitem_showscalerange.setChecked( 

1078 self.config.show_scale_ranges) 

1079 

1080 self.menuitem_showscaleaxis = view_menu.addAction( 

1081 'Show Scale Axes') 

1082 self.menuitem_showscaleaxis.setCheckable(True) 

1083 self.menuitem_showscaleaxis.setChecked( 

1084 self.config.show_scale_axes) 

1085 

1086 self.menuitem_showzeroline = view_menu.addAction( 

1087 'Show Zero Lines') 

1088 self.menuitem_showzeroline.setCheckable(True) 

1089 

1090 view_menu.addSeparator() 

1091 view_menu.addAction( 

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

1093 'Fullscreen', 

1094 self.toggle_fullscreen, 

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

1096 

1097 # Options Menu 

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

1099 self.menuitem_demean.setCheckable(True) 

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

1101 self.menuitem_demean.setShortcut( 

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

1103 

1104 self.menuitem_distances_3d = options_menu.addAction( 

1105 '3D distances', 

1106 self.distances_3d_changed) 

1107 self.menuitem_distances_3d.setCheckable(True) 

1108 

1109 self.menuitem_allowdownsampling = options_menu.addAction( 

1110 'Allow Downsampling') 

1111 self.menuitem_allowdownsampling.setCheckable(True) 

1112 self.menuitem_allowdownsampling.setChecked(True) 

1113 

1114 self.menuitem_degap = options_menu.addAction( 

1115 'Allow Degapping') 

1116 self.menuitem_degap.setCheckable(True) 

1117 self.menuitem_degap.setChecked(True) 

1118 

1119 options_menu.addSeparator() 

1120 

1121 self.menuitem_fft_filtering = options_menu.addAction( 

1122 'FFT Filtering') 

1123 self.menuitem_fft_filtering.setCheckable(True) 

1124 

1125 self.menuitem_lphp = options_menu.addAction( 

1126 'Bandpass is Low- + Highpass') 

1127 self.menuitem_lphp.setCheckable(True) 

1128 self.menuitem_lphp.setChecked(True) 

1129 

1130 options_menu.addSeparator() 

1131 self.menuitem_watch = options_menu.addAction( 

1132 'Watch Files') 

1133 self.menuitem_watch.setCheckable(True) 

1134 

1135 self.menuitem_liberal_fetch = options_menu.addAction( 

1136 'Liberal Fetch Optimization') 

1137 self.menuitem_liberal_fetch.setCheckable(True) 

1138 

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

1140 

1141 self.snufflings_menu.addAction( 

1142 'Reload Snufflings', 

1143 self.setup_snufflings) 

1144 

1145 # Disable ShadowPileTest 

1146 if False: 

1147 test_action = self.menu.addAction( 

1148 'Test', 

1149 self.toggletest) 

1150 test_action.setCheckable(True) 

1151 

1152 help_menu.addAction( 

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

1154 'Snuffler Controls', 

1155 self.help, 

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

1157 

1158 help_menu.addAction( 

1159 'About', 

1160 self.about) 

1161 

1162 self.time_projection = Projection() 

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

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

1165 

1166 self.gather = None 

1167 

1168 self.trace_filter = None 

1169 self.quick_filter = None 

1170 self.quick_filter_patterns = None, None 

1171 self.blacklist = [] 

1172 

1173 self.track_to_screen = Projection() 

1174 self.track_to_nslc_ids = {} 

1175 

1176 self.cached_vec = None 

1177 self.cached_processed_traces = None 

1178 

1179 self.timer = qc.QTimer(self) 

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

1181 self.timer.setInterval(1000) 

1182 self.timer.start() 

1183 self.pile.add_listener(self) 

1184 self.trace_styles = {} 

1185 if self.get_squirrel() is None: 

1186 self.determine_box_styles() 

1187 

1188 self.setMouseTracking(True) 

1189 

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

1191 self.snuffling_modules = {} 

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

1193 self.default_snufflings = None 

1194 self.snufflings = [] 

1195 

1196 self.stations = {} 

1197 

1198 self.timer_draw = Timer() 

1199 self.timer_cutout = Timer() 

1200 self.time_spent_painting = 0.0 

1201 self.time_last_painted = time.time() 

1202 

1203 self.interactive_range_change_time = 0.0 

1204 self.interactive_range_change_delay_time = 10.0 

1205 self.follow_timer = None 

1206 

1207 self.sortingmode_change_time = 0.0 

1208 self.sortingmode_change_delay_time = None 

1209 

1210 self.old_data_ranges = {} 

1211 

1212 self.error_messages = {} 

1213 self.return_tag = None 

1214 self.wheel_pos = 60 

1215 

1216 self.setAcceptDrops(True) 

1217 self._paths_to_load = [] 

1218 

1219 self.tf_cache = {} 

1220 

1221 self.waterfall = TraceWaterfall() 

1222 self.waterfall_cmap = 'viridis' 

1223 self.waterfall_clip_min = 0. 

1224 self.waterfall_clip_max = 1. 

1225 self.waterfall_show_absolute = False 

1226 self.waterfall_integrate = False 

1227 self.view_mode = ViewMode.Wiggle 

1228 

1229 self.automatic_updates = True 

1230 

1231 self.closing = False 

1232 self.in_paint_event = False 

1233 

1234 def fail(self, reason): 

1235 box = qw.QMessageBox(self) 

1236 box.setText(reason) 

1237 box.exec_() 

1238 

1239 def set_trace_filter(self, filter_func): 

1240 self.trace_filter = filter_func 

1241 self.sortingmode_change() 

1242 

1243 def update_trace_filter(self): 

1244 if self.blacklist: 

1245 

1246 def blacklist_func(tr): 

1247 return not pyrocko.util.match_nslc( 

1248 self.blacklist, tr.nslc_id) 

1249 

1250 else: 

1251 blacklist_func = None 

1252 

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

1254 self.set_trace_filter(None) 

1255 elif self.quick_filter is None: 

1256 self.set_trace_filter(blacklist_func) 

1257 elif blacklist_func is None: 

1258 self.set_trace_filter(self.quick_filter) 

1259 else: 

1260 self.set_trace_filter( 

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

1262 

1263 def set_quick_filter(self, filter_func): 

1264 self.quick_filter = filter_func 

1265 self.update_trace_filter() 

1266 

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

1268 if patterns is not None: 

1269 self.set_quick_filter( 

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

1271 else: 

1272 self.set_quick_filter(None) 

1273 

1274 self.quick_filter_patterns = patterns, inputline 

1275 

1276 def get_quick_filter_patterns(self): 

1277 return self.quick_filter_patterns 

1278 

1279 def add_blacklist_pattern(self, pattern): 

1280 if pattern == 'empty': 

1281 keys = set(self.pile.nslc_ids) 

1282 trs = self.pile.all( 

1283 tmin=self.tmin, 

1284 tmax=self.tmax, 

1285 load_data=False, 

1286 degap=False) 

1287 

1288 for tr in trs: 

1289 if tr.nslc_id in keys: 

1290 keys.remove(tr.nslc_id) 

1291 

1292 for key in keys: 

1293 xpattern = '.'.join(key) 

1294 if xpattern not in self.blacklist: 

1295 self.blacklist.append(xpattern) 

1296 

1297 else: 

1298 if pattern in self.blacklist: 

1299 self.blacklist.remove(pattern) 

1300 

1301 self.blacklist.append(pattern) 

1302 

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

1304 self.update_trace_filter() 

1305 

1306 def remove_blacklist_pattern(self, pattern): 

1307 if pattern in self.blacklist: 

1308 self.blacklist.remove(pattern) 

1309 else: 

1310 raise PileViewerMainException( 

1311 'Pattern not found in blacklist.') 

1312 

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

1314 self.update_trace_filter() 

1315 

1316 def clear_blacklist(self): 

1317 self.blacklist = [] 

1318 self.update_trace_filter() 

1319 

1320 def ssort(self, tr): 

1321 return self._ssort(tr) 

1322 

1323 def station_key(self, x): 

1324 return x.network, x.station 

1325 

1326 def station_keys(self, x): 

1327 return [ 

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

1329 (x.network, x.station)] 

1330 

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

1332 for sk in self.station_keys(tr): 

1333 if sk in self.stations: 

1334 station = self.stations[sk] 

1335 return getter(station) 

1336 

1337 return default_getter(tr) 

1338 

1339 def get_station(self, sk): 

1340 return self.stations[sk] 

1341 

1342 def has_station(self, station): 

1343 for sk in self.station_keys(station): 

1344 if sk in self.stations: 

1345 return True 

1346 

1347 return False 

1348 

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

1350 return self.station_attrib( 

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

1352 

1353 def set_stations(self, stations): 

1354 self.stations = {} 

1355 self.add_stations(stations) 

1356 

1357 def add_stations(self, stations): 

1358 for station in stations: 

1359 for sk in self.station_keys(station): 

1360 self.stations[sk] = station 

1361 

1362 ev = self.get_active_event() 

1363 if ev: 

1364 self.set_origin(ev) 

1365 

1366 def add_event(self, event): 

1367 marker = EventMarker(event) 

1368 self.add_marker(marker) 

1369 

1370 def add_events(self, events): 

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

1372 self.add_markers(markers) 

1373 

1374 def set_event_marker_as_origin(self, ignore=None): 

1375 selected = self.selected_markers() 

1376 if not selected: 

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

1378 return 

1379 

1380 m = selected[0] 

1381 if not isinstance(m, EventMarker): 

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

1383 return 

1384 

1385 self.set_active_event_marker(m) 

1386 

1387 def deactivate_event_marker(self): 

1388 if self.active_event_marker: 

1389 self.active_event_marker.active = False 

1390 

1391 self.active_event_marker_changed.emit() 

1392 self.active_event_marker = None 

1393 

1394 def set_active_event_marker(self, event_marker): 

1395 if self.active_event_marker: 

1396 self.active_event_marker.active = False 

1397 

1398 self.active_event_marker = event_marker 

1399 event_marker.active = True 

1400 event = event_marker.get_event() 

1401 self.set_origin(event) 

1402 self.active_event_marker_changed.emit() 

1403 

1404 def set_active_event(self, event): 

1405 for marker in self.markers: 

1406 if isinstance(marker, EventMarker): 

1407 if marker.get_event() is event: 

1408 self.set_active_event_marker(marker) 

1409 

1410 def get_active_event_marker(self): 

1411 return self.active_event_marker 

1412 

1413 def get_active_event(self): 

1414 m = self.get_active_event_marker() 

1415 if m is not None: 

1416 return m.get_event() 

1417 else: 

1418 return None 

1419 

1420 def get_active_markers(self): 

1421 emarker = self.get_active_event_marker() 

1422 if emarker is None: 

1423 return None, [] 

1424 

1425 else: 

1426 ev = emarker.get_event() 

1427 pmarkers = [ 

1428 m for m in self.markers 

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

1430 

1431 return emarker, pmarkers 

1432 

1433 def set_origin(self, location): 

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

1435 station.set_event_relative_data( 

1436 location, 

1437 distance_3d=self.menuitem_distances_3d.isChecked()) 

1438 

1439 self.sortingmode_change() 

1440 

1441 def distances_3d_changed(self): 

1442 ignore = self.menuitem_distances_3d.isChecked() 

1443 self.set_event_marker_as_origin(ignore) 

1444 

1445 def iter_snuffling_modules(self): 

1446 pjoin = os.path.join 

1447 for path in self.snuffling_paths: 

1448 

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

1450 os.mkdir(path) 

1451 

1452 for entry in os.listdir(path): 

1453 directory = path 

1454 fn = entry 

1455 d = pjoin(path, entry) 

1456 if os.path.isdir(d): 

1457 directory = d 

1458 if os.path.isfile( 

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

1460 fn = 'snuffling.py' 

1461 

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

1463 continue 

1464 

1465 name = fn[:-3] 

1466 

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

1468 self.snuffling_modules[directory, name] = \ 

1469 pyrocko.gui.snuffling.SnufflingModule( 

1470 directory, name, self) 

1471 

1472 yield self.snuffling_modules[directory, name] 

1473 

1474 def setup_snufflings(self): 

1475 # user snufflings 

1476 for mod in self.iter_snuffling_modules(): 

1477 try: 

1478 mod.load_if_needed() 

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

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

1481 

1482 # load the default snufflings on first run 

1483 if self.default_snufflings is None: 

1484 self.default_snufflings = pyrocko.gui\ 

1485 .snufflings.__snufflings__() 

1486 for snuffling in self.default_snufflings: 

1487 self.add_snuffling(snuffling) 

1488 

1489 def set_panel_parent(self, panel_parent): 

1490 self.panel_parent = panel_parent 

1491 

1492 def get_panel_parent(self): 

1493 return self.panel_parent 

1494 

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

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

1497 snuffling.init_gui( 

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

1499 self.snufflings.append(snuffling) 

1500 self.update() 

1501 

1502 def remove_snuffling(self, snuffling): 

1503 snuffling.delete_gui() 

1504 self.update() 

1505 self.snufflings.remove(snuffling) 

1506 snuffling.pre_destroy() 

1507 

1508 def add_snuffling_menuitem(self, item): 

1509 self.snufflings_menu.addAction(item) 

1510 item.setParent(self.snufflings_menu) 

1511 sort_actions(self.snufflings_menu) 

1512 

1513 def remove_snuffling_menuitem(self, item): 

1514 self.snufflings_menu.removeAction(item) 

1515 

1516 def add_snuffling_help_menuitem(self, item): 

1517 self.snuffling_help.addAction(item) 

1518 item.setParent(self.snuffling_help) 

1519 sort_actions(self.snuffling_help) 

1520 

1521 def remove_snuffling_help_menuitem(self, item): 

1522 self.snuffling_help.removeAction(item) 

1523 

1524 def add_panel_toggler(self, item): 

1525 self.toggle_panel_menu.addAction(item) 

1526 item.setParent(self.toggle_panel_menu) 

1527 sort_actions(self.toggle_panel_menu) 

1528 

1529 def remove_panel_toggler(self, item): 

1530 self.toggle_panel_menu.removeAction(item) 

1531 

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

1533 cache_dir=None, force_cache=False): 

1534 

1535 if cache_dir is None: 

1536 cache_dir = pyrocko.config.config().cache_dir 

1537 if isinstance(paths, str): 

1538 paths = [paths] 

1539 

1540 fns = pyrocko.util.select_files( 

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

1542 

1543 if not fns: 

1544 return 

1545 

1546 cache = pyrocko.pile.get_cache(cache_dir) 

1547 

1548 t = [time.time()] 

1549 

1550 def update_bar(label, value): 

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

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

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

1554 else: 

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

1556 

1557 return pbs.set_status(label, value) 

1558 

1559 def update_progress(label, i, n): 

1560 abort = False 

1561 

1562 qw.qApp.processEvents() 

1563 if n != 0: 

1564 perc = i*100/n 

1565 else: 

1566 perc = 100 

1567 abort |= update_bar(label, perc) 

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

1569 

1570 tnow = time.time() 

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

1572 self.update() 

1573 t[0] = tnow 

1574 

1575 return abort 

1576 

1577 self.automatic_updates = False 

1578 

1579 self.pile.load_files( 

1580 sorted(fns), 

1581 filename_attributes=regex, 

1582 cache=cache, 

1583 fileformat=format, 

1584 show_progress=False, 

1585 update_progress=update_progress) 

1586 

1587 self.automatic_updates = True 

1588 self.update() 

1589 

1590 def load_queued(self): 

1591 if not self._paths_to_load: 

1592 return 

1593 paths = self._paths_to_load 

1594 self._paths_to_load = [] 

1595 self.load(paths) 

1596 

1597 def load_soon(self, paths): 

1598 self._paths_to_load.extend(paths) 

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

1600 

1601 def open_waveforms(self): 

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

1603 

1604 fns, _ = qw.QFileDialog.getOpenFileNames( 

1605 self, caption, options=qfiledialog_options) 

1606 

1607 if fns: 

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

1609 

1610 def open_waveform_directory(self): 

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

1612 

1613 dn = qw.QFileDialog.getExistingDirectory( 

1614 self, caption, options=qfiledialog_options) 

1615 

1616 if dn: 

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

1618 

1619 def open_stations(self, fns=None): 

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

1621 

1622 if not fns: 

1623 fns, _ = qw.QFileDialog.getOpenFileNames( 

1624 self, caption, options=qfiledialog_options) 

1625 

1626 try: 

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

1628 for stat in stations: 

1629 self.add_stations(stat) 

1630 

1631 except Exception as e: 

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

1633 

1634 def open_stations_xml(self, fns=None): 

1635 from pyrocko.io import stationxml 

1636 

1637 caption = 'Select one or more StationXML files' 

1638 if not fns: 

1639 fns, _ = qw.QFileDialog.getOpenFileNames( 

1640 self, caption, options=qfiledialog_options, 

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

1642 ';;All files (*)') 

1643 

1644 try: 

1645 stations = [ 

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

1647 for x in fns] 

1648 

1649 for stat in stations: 

1650 self.add_stations(stat) 

1651 

1652 except Exception as e: 

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

1654 

1655 def add_traces(self, traces): 

1656 if traces: 

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

1658 self.pile.add_file(mtf) 

1659 ticket = (self.pile, mtf) 

1660 return ticket 

1661 else: 

1662 return (None, None) 

1663 

1664 def release_data(self, tickets): 

1665 for ticket in tickets: 

1666 pile, mtf = ticket 

1667 if pile is not None: 

1668 pile.remove_file(mtf) 

1669 

1670 def periodical(self): 

1671 if self.menuitem_watch.isChecked(): 

1672 if self.pile.reload_modified(): 

1673 self.update() 

1674 

1675 def get_pile(self): 

1676 return self.pile 

1677 

1678 def pile_changed(self, what): 

1679 self.pile_has_changed = True 

1680 self.pile_has_changed_signal.emit() 

1681 if self.automatic_updates: 

1682 self.update() 

1683 

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

1685 

1686 if gather is None: 

1687 def gather_func(tr): 

1688 return tr.nslc_id 

1689 

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

1691 

1692 else: 

1693 def gather_func(tr): 

1694 return ( 

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

1696 

1697 if color is None: 

1698 def color(tr): 

1699 return tr.location 

1700 

1701 self.gather = gather_func 

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

1703 

1704 self.color_gather = color 

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

1706 previous_ntracks = self.ntracks 

1707 self.set_ntracks(len(keys)) 

1708 

1709 if self.shown_tracks_range is None or \ 

1710 previous_ntracks == 0 or \ 

1711 self.show_all: 

1712 

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

1714 key_at_top = None 

1715 n = high-low 

1716 

1717 else: 

1718 low, high = self.shown_tracks_range 

1719 key_at_top = self.track_keys[low] 

1720 n = high-low 

1721 

1722 self.track_keys = sorted(keys) 

1723 

1724 track_patterns = [] 

1725 for k in self.track_keys: 

1726 pat = ['*', '*', '*', '*'] 

1727 for i, j in enumerate(gather): 

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

1729 

1730 track_patterns.append(pat) 

1731 

1732 self.track_patterns = track_patterns 

1733 

1734 if key_at_top is not None: 

1735 try: 

1736 ind = self.track_keys.index(key_at_top) 

1737 low = ind 

1738 high = low+n 

1739 except Exception: 

1740 pass 

1741 

1742 self.set_tracks_range((low, high)) 

1743 

1744 self.key_to_row = dict( 

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

1746 

1747 def inrange(x, r): 

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

1749 

1750 def trace_selector(trace): 

1751 gt = self.gather(trace) 

1752 return ( 

1753 gt in self.key_to_row and 

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

1755 

1756 self.trace_selector = lambda x: \ 

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

1758 and trace_selector(x) 

1759 

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

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

1762 self.show_all: 

1763 

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

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

1766 tlen = (tmax - tmin) 

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

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

1769 

1770 def set_time_range(self, tmin, tmax): 

1771 if tmin is None: 

1772 tmin = initial_time_range[0] 

1773 

1774 if tmax is None: 

1775 tmax = initial_time_range[1] 

1776 

1777 if tmin > tmax: 

1778 tmin, tmax = tmax, tmin 

1779 

1780 if tmin == tmax: 

1781 tmin -= 1. 

1782 tmax += 1. 

1783 

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

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

1786 

1787 min_deltat = self.content_deltat_range()[0] 

1788 if (tmax - tmin < min_deltat): 

1789 m = (tmin + tmax) / 2. 

1790 tmin = m - min_deltat/2. 

1791 tmax = m + min_deltat/2. 

1792 

1793 self.time_projection.set_in_range(tmin, tmax) 

1794 self.tmin, self.tmax = tmin, tmax 

1795 

1796 def get_time_range(self): 

1797 return self.tmin, self.tmax 

1798 

1799 def ypart(self, y): 

1800 if y < self.ax_height: 

1801 return -1 

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

1803 return 1 

1804 else: 

1805 return 0 

1806 

1807 def time_fractional_digits(self): 

1808 min_deltat = self.content_deltat_range()[0] 

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

1810 

1811 def write_markers(self, fn=None): 

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

1813 if not fn: 

1814 fn, _ = qw.QFileDialog.getSaveFileName( 

1815 self, caption, options=qfiledialog_options) 

1816 if fn: 

1817 try: 

1818 Marker.save_markers( 

1819 self.markers, fn, 

1820 fdigits=self.time_fractional_digits()) 

1821 

1822 except Exception as e: 

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

1824 

1825 def write_selected_markers(self, fn=None): 

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

1827 if not fn: 

1828 fn, _ = qw.QFileDialog.getSaveFileName( 

1829 self, caption, options=qfiledialog_options) 

1830 if fn: 

1831 try: 

1832 Marker.save_markers( 

1833 self.iter_selected_markers(), 

1834 fn, 

1835 fdigits=self.time_fractional_digits()) 

1836 

1837 except Exception as e: 

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

1839 

1840 def read_events(self, fn=None): 

1841 ''' 

1842 Open QFileDialog to open, read and add 

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

1844 representation to the pile viewer. 

1845 ''' 

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

1847 if not fn: 

1848 fn, _ = qw.QFileDialog.getOpenFileName( 

1849 self, caption, options=qfiledialog_options) 

1850 if fn: 

1851 try: 

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

1853 self.associate_phases_to_events() 

1854 

1855 except Exception as e: 

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

1857 

1858 def read_markers(self, fn=None): 

1859 ''' 

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

1861 ''' 

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

1863 if not fn: 

1864 fn, _ = qw.QFileDialog.getOpenFileName( 

1865 self, caption, options=qfiledialog_options) 

1866 if fn: 

1867 try: 

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

1869 self.associate_phases_to_events() 

1870 

1871 except Exception as e: 

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

1873 

1874 def associate_phases_to_events(self): 

1875 associate_phases_to_events(self.markers) 

1876 

1877 def add_marker(self, marker): 

1878 # need index to inform QAbstactTableModel about upcoming change, 

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

1880 self.markers.insert(marker) 

1881 i = self.markers.remove(marker) 

1882 

1883 self.begin_markers_add.emit(i, i) 

1884 self.markers.insert(marker) 

1885 self.end_markers_add.emit() 

1886 self.markers_deltat_max = max( 

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

1888 

1889 def add_markers(self, markers): 

1890 if not self.markers: 

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

1892 self.markers.insert_many(markers) 

1893 self.end_markers_add.emit() 

1894 self.update_markers_deltat_max() 

1895 else: 

1896 for marker in markers: 

1897 self.add_marker(marker) 

1898 

1899 def update_markers_deltat_max(self): 

1900 if self.markers: 

1901 self.markers_deltat_max = max( 

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

1903 

1904 def remove_marker(self, marker): 

1905 ''' 

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

1907 

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

1909 ''' 

1910 

1911 if marker is self.active_event_marker: 

1912 self.deactivate_event_marker() 

1913 

1914 try: 

1915 i = self.markers.index(marker) 

1916 self.begin_markers_remove.emit(i, i) 

1917 self.markers.remove_at(i) 

1918 self.end_markers_remove.emit() 

1919 except ValueError: 

1920 pass 

1921 

1922 def remove_markers(self, markers): 

1923 ''' 

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

1925 

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

1927 instances 

1928 ''' 

1929 

1930 if markers is self.markers: 

1931 markers = list(markers) 

1932 

1933 for marker in markers: 

1934 self.remove_marker(marker) 

1935 

1936 self.update_markers_deltat_max() 

1937 

1938 def remove_selected_markers(self): 

1939 def delete_segment(istart, iend): 

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

1941 for _ in range(iend - istart): 

1942 self.markers.remove_at(istart) 

1943 

1944 self.end_markers_remove.emit() 

1945 

1946 istart = None 

1947 ipos = 0 

1948 markers = self.markers 

1949 nmarkers = len(self.markers) 

1950 while ipos < nmarkers: 

1951 marker = markers[ipos] 

1952 if marker.is_selected(): 

1953 if marker is self.active_event_marker: 

1954 self.deactivate_event_marker() 

1955 

1956 if istart is None: 

1957 istart = ipos 

1958 else: 

1959 if istart is not None: 

1960 delete_segment(istart, ipos) 

1961 nmarkers -= ipos - istart 

1962 ipos = istart - 1 

1963 istart = None 

1964 

1965 ipos += 1 

1966 

1967 if istart is not None: 

1968 delete_segment(istart, ipos) 

1969 

1970 self.update_markers_deltat_max() 

1971 

1972 def selected_markers(self): 

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

1974 

1975 def iter_selected_markers(self): 

1976 for marker in self.markers: 

1977 if marker.is_selected(): 

1978 yield marker 

1979 

1980 def get_markers(self): 

1981 return self.markers 

1982 

1983 def mousePressEvent(self, mouse_ev): 

1984 self.show_all = False 

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

1986 

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

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

1989 if self.picking: 

1990 if self.picking_down is None: 

1991 self.picking_down = ( 

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

1993 mouse_ev.y()) 

1994 

1995 elif marker is not None: 

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

1997 self.deselect_all() 

1998 marker.selected = True 

1999 self.emit_selected_markers() 

2000 self.update() 

2001 else: 

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

2003 self.track_trange = self.tmin, self.tmax 

2004 

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

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

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

2008 self.update_status() 

2009 

2010 def mouseReleaseEvent(self, mouse_ev): 

2011 if self.ignore_releases: 

2012 self.ignore_releases -= 1 

2013 return 

2014 

2015 if self.picking: 

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

2017 self.emit_selected_markers() 

2018 

2019 if self.track_start: 

2020 self.update() 

2021 

2022 self.track_start = None 

2023 self.track_trange = None 

2024 self.update_status() 

2025 

2026 def mouseDoubleClickEvent(self, mouse_ev): 

2027 self.show_all = False 

2028 self.start_picking(None) 

2029 self.ignore_releases = 1 

2030 

2031 def mouseMoveEvent(self, mouse_ev): 

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

2033 

2034 if self.picking: 

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

2036 

2037 elif self.track_start is not None: 

2038 x0, y0 = self.track_start 

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

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

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

2042 dy = 0 

2043 

2044 tmin0, tmax0 = self.track_trange 

2045 

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

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

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

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

2050 

2051 self.interrupt_following() 

2052 self.set_time_range( 

2053 tmin0 - dt - dtr*frac, 

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

2055 

2056 self.update() 

2057 else: 

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

2059 

2060 self.update_status() 

2061 

2062 def nslc_ids_under_cursor(self, x, y): 

2063 ftrack = self.track_to_screen.rev(y) 

2064 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2065 return nslc_ids 

2066 

2067 def marker_under_cursor(self, x, y): 

2068 mouset = self.time_projection.rev(x) 

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

2070 relevant_nslc_ids = None 

2071 for marker in self.markers: 

2072 if marker.kind not in self.visible_marker_kinds: 

2073 continue 

2074 

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

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

2077 

2078 if relevant_nslc_ids is None: 

2079 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2080 

2081 marker_nslc_ids = marker.get_nslc_ids() 

2082 if not marker_nslc_ids: 

2083 return marker 

2084 

2085 for nslc_id in marker_nslc_ids: 

2086 if nslc_id in relevant_nslc_ids: 

2087 return marker 

2088 

2089 def hoovering(self, x, y): 

2090 mouset = self.time_projection.rev(x) 

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

2092 needupdate = False 

2093 haveone = False 

2094 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2095 for marker in self.markers: 

2096 if marker.kind not in self.visible_marker_kinds: 

2097 continue 

2098 

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

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

2101 

2102 if state: 

2103 xstate = False 

2104 

2105 marker_nslc_ids = marker.get_nslc_ids() 

2106 if not marker_nslc_ids: 

2107 xstate = True 

2108 

2109 for nslc in relevant_nslc_ids: 

2110 if marker.match_nslc(nslc): 

2111 xstate = True 

2112 

2113 state = xstate 

2114 

2115 if state: 

2116 haveone = True 

2117 oldstate = marker.is_alerted() 

2118 if oldstate != state: 

2119 needupdate = True 

2120 marker.set_alerted(state) 

2121 if state: 

2122 self.message = marker.hoover_message() 

2123 

2124 if not haveone: 

2125 self.message = None 

2126 

2127 if needupdate: 

2128 self.update() 

2129 

2130 def event(self, event): 

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

2132 self.keyPressEvent(event) 

2133 return True 

2134 else: 

2135 return base.event(self, event) 

2136 

2137 def keyPressEvent(self, key_event): 

2138 self.show_all = False 

2139 dt = self.tmax - self.tmin 

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

2141 

2142 key = key_event.key() 

2143 try: 

2144 keytext = str(key_event.text()) 

2145 except UnicodeEncodeError: 

2146 return 

2147 

2148 if key == qc.Qt.Key_Space: 

2149 self.interrupt_following() 

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

2151 

2152 elif key == qc.Qt.Key_Up: 

2153 for m in self.selected_markers(): 

2154 if isinstance(m, PhaseMarker): 

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

2156 p = 0 

2157 else: 

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

2159 m.set_polarity(p) 

2160 

2161 elif key == qc.Qt.Key_Down: 

2162 for m in self.selected_markers(): 

2163 if isinstance(m, PhaseMarker): 

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

2165 p = 0 

2166 else: 

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

2168 m.set_polarity(p) 

2169 

2170 elif key == qc.Qt.Key_B: 

2171 dt = self.tmax - self.tmin 

2172 self.interrupt_following() 

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

2174 

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

2176 self.interrupt_following() 

2177 

2178 tgo = None 

2179 

2180 class TraceDummy(object): 

2181 def __init__(self, marker): 

2182 self._marker = marker 

2183 

2184 @property 

2185 def nslc_id(self): 

2186 return self._marker.one_nslc() 

2187 

2188 def marker_to_itrack(marker): 

2189 try: 

2190 return self.key_to_row.get( 

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

2192 

2193 except MarkerOneNSLCRequired: 

2194 return -1 

2195 

2196 emarker, pmarkers = self.get_active_markers() 

2197 pmarkers = [ 

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

2199 pmarkers.sort(key=lambda m: ( 

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

2201 

2202 if key == qc.Qt.Key_Backtab: 

2203 pmarkers.reverse() 

2204 

2205 smarkers = self.selected_markers() 

2206 iselected = [] 

2207 for sm in smarkers: 

2208 try: 

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

2210 except ValueError: 

2211 pass 

2212 

2213 if iselected: 

2214 icurrent = max(iselected) + 1 

2215 else: 

2216 icurrent = 0 

2217 

2218 if icurrent < len(pmarkers): 

2219 self.deselect_all() 

2220 cmarker = pmarkers[icurrent] 

2221 cmarker.selected = True 

2222 tgo = cmarker.tmin 

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

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

2225 

2226 itrack = marker_to_itrack(cmarker) 

2227 if itrack != -1: 

2228 if itrack < self.shown_tracks_range[0]: 

2229 self.scroll_tracks( 

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

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

2232 self.scroll_tracks( 

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

2234 

2235 if itrack not in self.track_to_nslc_ids: 

2236 self.go_to_selection() 

2237 

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

2239 smarkers = self.selected_markers() 

2240 tgo = None 

2241 dir = str(keytext) 

2242 if smarkers: 

2243 tmid = smarkers[0].tmin 

2244 for smarker in smarkers: 

2245 if dir == 'n': 

2246 tmid = max(smarker.tmin, tmid) 

2247 else: 

2248 tmid = min(smarker.tmin, tmid) 

2249 

2250 tgo = tmid 

2251 

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

2253 for marker in sorted( 

2254 self.markers, 

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

2256 

2257 t = marker.tmin 

2258 if t > tmid and \ 

2259 marker.kind in self.visible_marker_kinds and \ 

2260 (dir == 'n' or 

2261 isinstance(marker, EventMarker)): 

2262 

2263 self.deselect_all() 

2264 marker.selected = True 

2265 tgo = t 

2266 break 

2267 else: 

2268 for marker in sorted( 

2269 self.markers, 

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

2271 reverse=True): 

2272 

2273 t = marker.tmin 

2274 if t < tmid and \ 

2275 marker.kind in self.visible_marker_kinds and \ 

2276 (dir == 'p' or 

2277 isinstance(marker, EventMarker)): 

2278 self.deselect_all() 

2279 marker.selected = True 

2280 tgo = t 

2281 break 

2282 

2283 if tgo is not None: 

2284 self.interrupt_following() 

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

2286 

2287 elif keytext == 'r': 

2288 if self.pile.reload_modified(): 

2289 self.reloaded = True 

2290 

2291 elif keytext == 'R': 

2292 self.setup_snufflings() 

2293 

2294 elif key == qc.Qt.Key_Backspace: 

2295 self.remove_selected_markers() 

2296 

2297 elif keytext == 'a': 

2298 for marker in self.markers: 

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

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

2301 marker.kind in self.visible_marker_kinds): 

2302 marker.selected = True 

2303 else: 

2304 marker.selected = False 

2305 

2306 elif keytext == 'A': 

2307 for marker in self.markers: 

2308 if marker.kind in self.visible_marker_kinds: 

2309 marker.selected = True 

2310 

2311 elif keytext == 'd': 

2312 self.deselect_all() 

2313 

2314 elif keytext == 'E': 

2315 self.deactivate_event_marker() 

2316 

2317 elif keytext == 'e': 

2318 markers = self.selected_markers() 

2319 event_markers_in_spe = [ 

2320 marker for marker in markers 

2321 if not isinstance(marker, PhaseMarker)] 

2322 

2323 phase_markers = [ 

2324 marker for marker in markers 

2325 if isinstance(marker, PhaseMarker)] 

2326 

2327 if len(event_markers_in_spe) == 1: 

2328 event_marker = event_markers_in_spe[0] 

2329 if not isinstance(event_marker, EventMarker): 

2330 nslcs = list(event_marker.nslc_ids) 

2331 lat, lon = 0.0, 0.0 

2332 old = self.get_active_event() 

2333 if len(nslcs) == 1: 

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

2335 elif old is not None: 

2336 lat, lon = old.lat, old.lon 

2337 

2338 event_marker.convert_to_event_marker(lat, lon) 

2339 

2340 self.set_active_event_marker(event_marker) 

2341 event = event_marker.get_event() 

2342 for marker in phase_markers: 

2343 marker.set_event(event) 

2344 

2345 else: 

2346 for marker in event_markers_in_spe: 

2347 marker.convert_to_event_marker() 

2348 

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

2350 for marker in self.selected_markers(): 

2351 marker.set_kind(int(keytext)) 

2352 self.emit_selected_markers() 

2353 

2354 elif key in fkey_map: 

2355 self.handle_fkeys(key) 

2356 

2357 elif key == qc.Qt.Key_Escape: 

2358 if self.picking: 

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

2360 

2361 elif key == qc.Qt.Key_PageDown: 

2362 self.scroll_tracks( 

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

2364 

2365 elif key == qc.Qt.Key_PageUp: 

2366 self.scroll_tracks( 

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

2368 

2369 elif key == qc.Qt.Key_Plus: 

2370 self.zoom_tracks(0., 1.) 

2371 

2372 elif key == qc.Qt.Key_Minus: 

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

2374 

2375 elif key == qc.Qt.Key_Equal: 

2376 ntracks_shown = self.shown_tracks_range[1] - \ 

2377 self.shown_tracks_range[0] 

2378 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2379 self.zoom_tracks(0., dtracks) 

2380 

2381 elif key == qc.Qt.Key_Colon: 

2382 self.want_input.emit() 

2383 

2384 elif keytext == 'f': 

2385 self.toggle_fullscreen() 

2386 

2387 elif keytext == 'g': 

2388 self.go_to_selection() 

2389 

2390 elif keytext == 'G': 

2391 self.go_to_selection(tight=True) 

2392 

2393 elif keytext == 'm': 

2394 self.toggle_marker_editor() 

2395 

2396 elif keytext == 'c': 

2397 self.toggle_main_controls() 

2398 

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

2400 dir = 1 

2401 amount = 1 

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

2403 dir = -1 

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

2405 amount = 10 

2406 self.nudge_selected_markers(dir*amount) 

2407 else: 

2408 super().keyPressEvent(key_event) 

2409 

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

2411 self.emit_selected_markers() 

2412 

2413 self.update() 

2414 self.update_status() 

2415 

2416 def handle_fkeys(self, key): 

2417 self.set_phase_kind( 

2418 self.selected_markers(), 

2419 fkey_map[key] + 1) 

2420 self.emit_selected_markers() 

2421 

2422 def emit_selected_markers(self): 

2423 ibounds = [] 

2424 last_selected = False 

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

2426 this_selected = marker.is_selected() 

2427 if this_selected != last_selected: 

2428 ibounds.append(imarker) 

2429 

2430 last_selected = this_selected 

2431 

2432 if last_selected: 

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

2434 

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

2436 self.n_selected_markers = sum( 

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

2438 self.marker_selection_changed.emit(chunks) 

2439 

2440 def toggle_marker_editor(self): 

2441 self.panel_parent.toggle_marker_editor() 

2442 

2443 def toggle_main_controls(self): 

2444 self.panel_parent.toggle_main_controls() 

2445 

2446 def nudge_selected_markers(self, npixels): 

2447 a, b = self.time_projection.ur 

2448 c, d = self.time_projection.xr 

2449 for marker in self.selected_markers(): 

2450 if not isinstance(marker, EventMarker): 

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

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

2453 

2454 def toggle_fullscreen(self): 

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

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

2457 self.window().showNormal() 

2458 else: 

2459 if is_macos: 

2460 self.window().showMaximized() 

2461 else: 

2462 self.window().showFullScreen() 

2463 

2464 def about(self): 

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

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

2467 txt = f.read() 

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

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

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

2471 

2472 def help(self): 

2473 class MyScrollArea(qw.QScrollArea): 

2474 

2475 def sizeHint(self): 

2476 s = qc.QSize() 

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

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

2479 return s 

2480 

2481 with open(pyrocko.util.data_file( 

2482 'snuffler_help.html')) as f: 

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

2484 

2485 with open(pyrocko.util.data_file( 

2486 'snuffler_help_epilog.html')) as f: 

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

2488 

2489 for h in [hcheat, hepilog]: 

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

2491 h.setWordWrap(True) 

2492 

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

2494 

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

2496 scroller = qw.QScrollArea() 

2497 frame = qw.QFrame(scroller) 

2498 frame.setLineWidth(0) 

2499 layout = qw.QVBoxLayout() 

2500 layout.setContentsMargins(0, 0, 0, 0) 

2501 layout.setSpacing(0) 

2502 frame.setLayout(layout) 

2503 scroller.setWidget(frame) 

2504 scroller.setWidgetResizable(True) 

2505 frame.setBackgroundRole(qg.QPalette.Base) 

2506 for h in labels: 

2507 h.setParent(frame) 

2508 h.setMargin(3) 

2509 h.setTextInteractionFlags( 

2510 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2511 h.setBackgroundRole(qg.QPalette.Base) 

2512 layout.addWidget(h) 

2513 h.linkActivated.connect( 

2514 self.open_link) 

2515 

2516 if self.panel_parent is not None: 

2517 if target == 'panel': 

2518 self.panel_parent.add_panel( 

2519 name, scroller, True, volatile=False) 

2520 else: 

2521 self.panel_parent.add_tab(name, scroller) 

2522 

2523 def open_link(self, link): 

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

2525 

2526 def wheelEvent(self, wheel_event): 

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

2528 

2529 n = self.wheel_pos // 120 

2530 self.wheel_pos = self.wheel_pos % 120 

2531 if n == 0: 

2532 return 

2533 

2534 amount = max( 

2535 1., 

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

2537 wdelta = amount * n 

2538 

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

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

2541 / (trmax-trmin) 

2542 

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

2544 self.zoom_tracks(anchor, wdelta) 

2545 else: 

2546 self.scroll_tracks(-wdelta) 

2547 

2548 def dragEnterEvent(self, event): 

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

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

2551 event.setDropAction(qc.Qt.LinkAction) 

2552 event.accept() 

2553 

2554 def dropEvent(self, event): 

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

2556 paths = list( 

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

2558 event.acceptProposedAction() 

2559 self.load(paths) 

2560 

2561 def get_phase_name(self, kind): 

2562 return self.config.get_phase_name(kind) 

2563 

2564 def set_phase_kind(self, markers, kind): 

2565 phasename = self.get_phase_name(kind) 

2566 

2567 for marker in markers: 

2568 if isinstance(marker, PhaseMarker): 

2569 if kind == 10: 

2570 marker.convert_to_marker() 

2571 else: 

2572 marker.set_phasename(phasename) 

2573 marker.set_event(self.get_active_event()) 

2574 

2575 elif isinstance(marker, EventMarker): 

2576 pass 

2577 

2578 else: 

2579 if kind != 10: 

2580 event = self.get_active_event() 

2581 marker.convert_to_phase_marker( 

2582 event, phasename, None, False) 

2583 

2584 def set_ntracks(self, ntracks): 

2585 if self.ntracks != ntracks: 

2586 self.ntracks = ntracks 

2587 if self.shown_tracks_range is not None: 

2588 l, h = self.shown_tracks_range 

2589 else: 

2590 l, h = 0, self.ntracks 

2591 

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

2593 

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

2595 

2596 low, high = range 

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

2598 high = min(self.ntracks, high) 

2599 low = max(0, low) 

2600 high = max(1, high) 

2601 

2602 if start is None: 

2603 start = float(low) 

2604 

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

2606 self.shown_tracks_range = low, high 

2607 self.shown_tracks_start = start 

2608 

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

2610 

2611 def scroll_tracks(self, shift): 

2612 shown = self.shown_tracks_range 

2613 shiftmin = -shown[0] 

2614 shiftmax = self.ntracks-shown[1] 

2615 shift = max(shiftmin, shift) 

2616 shift = min(shiftmax, shift) 

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

2618 

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

2620 

2621 self.update() 

2622 

2623 def zoom_tracks(self, anchor, delta): 

2624 ntracks_shown = self.shown_tracks_range[1] \ 

2625 - self.shown_tracks_range[0] 

2626 

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

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

2629 return 

2630 

2631 ntracks_shown += int(round(delta)) 

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

2633 

2634 u = self.shown_tracks_start 

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

2636 nv = nu + ntracks_shown 

2637 if nv > self.ntracks: 

2638 nu -= nv - self.ntracks 

2639 nv -= nv - self.ntracks 

2640 

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

2642 

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

2644 - self.shown_tracks_range[0] 

2645 

2646 self.update() 

2647 

2648 def content_time_range(self): 

2649 pile = self.get_pile() 

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

2651 if tmin is None: 

2652 tmin = initial_time_range[0] 

2653 if tmax is None: 

2654 tmax = initial_time_range[1] 

2655 

2656 return tmin, tmax 

2657 

2658 def content_deltat_range(self): 

2659 pile = self.get_pile() 

2660 

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

2662 

2663 if deltatmin is None: 

2664 deltatmin = 0.001 

2665 

2666 if deltatmax is None: 

2667 deltatmax = 1000.0 

2668 

2669 return deltatmin, deltatmax 

2670 

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

2672 if tmax < tmin: 

2673 tmin, tmax = tmax, tmin 

2674 

2675 deltatmin = self.content_deltat_range()[0] 

2676 dt = deltatmin * self.visible_length * 0.95 

2677 

2678 if dt == 0.0: 

2679 dt = 1.0 

2680 

2681 if tight: 

2682 if tmax != tmin: 

2683 dtm = tmax - tmin 

2684 tmin -= dtm*0.1 

2685 tmax += dtm*0.1 

2686 return tmin, tmax 

2687 else: 

2688 tcenter = (tmin + tmax) / 2. 

2689 tmin = tcenter - 0.5*dt 

2690 tmax = tcenter + 0.5*dt 

2691 return tmin, tmax 

2692 

2693 if tmax-tmin < dt: 

2694 vmin, vmax = self.get_time_range() 

2695 dt = min(vmax - vmin, dt) 

2696 

2697 tcenter = (tmin+tmax)/2. 

2698 etmin, etmax = tmin, tmax 

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

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

2701 dtm = tmax-tmin 

2702 if etmin == tmin: 

2703 tmin -= dtm*0.1 

2704 if etmax == tmax: 

2705 tmax += dtm*0.1 

2706 

2707 else: 

2708 dtm = tmax-tmin 

2709 tmin -= dtm*0.1 

2710 tmax += dtm*0.1 

2711 

2712 return tmin, tmax 

2713 

2714 def go_to_selection(self, tight=False): 

2715 markers = self.selected_markers() 

2716 if markers: 

2717 tmax, tmin = self.content_time_range() 

2718 for marker in markers: 

2719 tmin = min(tmin, marker.tmin) 

2720 tmax = max(tmax, marker.tmax) 

2721 

2722 else: 

2723 if tight: 

2724 vmin, vmax = self.get_time_range() 

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

2726 else: 

2727 tmin, tmax = self.content_time_range() 

2728 

2729 tmin, tmax = self.make_good_looking_time_range( 

2730 tmin, tmax, tight=tight) 

2731 

2732 self.interrupt_following() 

2733 self.set_time_range(tmin, tmax) 

2734 self.update() 

2735 

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

2737 tmax = t 

2738 if tlen is not None: 

2739 tmax = t+tlen 

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

2741 self.interrupt_following() 

2742 self.set_time_range(tmin, tmax) 

2743 self.update() 

2744 

2745 def go_to_event_by_name(self, name): 

2746 for marker in self.markers: 

2747 if isinstance(marker, EventMarker): 

2748 event = marker.get_event() 

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

2750 tmin, tmax = self.make_good_looking_time_range( 

2751 event.time, event.time) 

2752 

2753 self.interrupt_following() 

2754 self.set_time_range(tmin, tmax) 

2755 

2756 def printit(self): 

2757 from .qt_compat import qprint 

2758 printer = qprint.QPrinter() 

2759 printer.setOrientation(qprint.QPrinter.Landscape) 

2760 

2761 dialog = qprint.QPrintDialog(printer, self) 

2762 dialog.setWindowTitle('Print') 

2763 

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

2765 return 

2766 

2767 painter = qg.QPainter() 

2768 painter.begin(printer) 

2769 page = printer.pageRect() 

2770 self.drawit( 

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

2772 

2773 painter.end() 

2774 

2775 def savesvg(self, fn=None): 

2776 

2777 if not fn: 

2778 fn, _ = qw.QFileDialog.getSaveFileName( 

2779 self, 

2780 'Save as SVG|PNG', 

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

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

2783 options=qfiledialog_options) 

2784 

2785 if fn == '': 

2786 return 

2787 

2788 fn = str(fn) 

2789 

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

2791 try: 

2792 w, h = 842, 595 

2793 margin = 0.025 

2794 m = max(w, h)*margin 

2795 

2796 generator = qsvg.QSvgGenerator() 

2797 generator.setFileName(fn) 

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

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

2800 

2801 painter = qg.QPainter() 

2802 painter.begin(generator) 

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

2804 painter.end() 

2805 

2806 except Exception as e: 

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

2808 

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

2810 pixmap = self.grab() 

2811 

2812 try: 

2813 pixmap.save(fn) 

2814 

2815 except Exception as e: 

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

2817 

2818 else: 

2819 self.fail( 

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

2821 '".png".') 

2822 

2823 def paintEvent(self, paint_ev): 

2824 ''' 

2825 Called by QT whenever widget needs to be painted. 

2826 ''' 

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

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

2829 if self.in_paint_event: 

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

2831 return 

2832 

2833 self.in_paint_event = True 

2834 

2835 painter = qg.QPainter(self) 

2836 

2837 if self.menuitem_antialias.isChecked(): 

2838 painter.setRenderHint(qg.QPainter.Antialiasing) 

2839 

2840 self.drawit(painter) 

2841 

2842 logger.debug( 

2843 'Time spent drawing: ' 

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

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

2846 (self.timer_draw - self.timer_cutout)) 

2847 

2848 logger.debug( 

2849 'Time spent processing:' 

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

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

2852 self.timer_cutout.get()) 

2853 

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

2855 self.time_last_painted = time.time() 

2856 self.in_paint_event = False 

2857 

2858 def determine_box_styles(self): 

2859 

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

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

2862 istyle = 0 

2863 trace_styles = {} 

2864 for itr, tr in enumerate(traces): 

2865 if itr > 0: 

2866 other = traces[itr-1] 

2867 if not ( 

2868 other.nslc_id == tr.nslc_id 

2869 and other.deltat == tr.deltat 

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

2871 < gap_lap_tolerance*tr.deltat): 

2872 

2873 istyle += 1 

2874 

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

2876 

2877 self.trace_styles = trace_styles 

2878 

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

2880 

2881 for v_projection in track_projections.values(): 

2882 v_projection.set_in_range(0., 1.) 

2883 

2884 def selector(x): 

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

2886 

2887 if self.trace_filter is not None: 

2888 def tselector(x): 

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

2890 

2891 else: 

2892 tselector = selector 

2893 

2894 traces = list(self.pile.iter_traces( 

2895 group_selector=selector, trace_selector=tselector)) 

2896 

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

2898 

2899 def drawbox(itrack, istyle, traces): 

2900 v_projection = track_projections[itrack] 

2901 dvmin = v_projection(0.) 

2902 dvmax = v_projection(1.) 

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

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

2905 

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

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

2908 p.fillRect(rect, style.fill_brush) 

2909 p.setPen(style.frame_pen) 

2910 p.drawRect(rect) 

2911 

2912 traces_by_style = {} 

2913 for itr, tr in enumerate(traces): 

2914 gt = self.gather(tr) 

2915 if gt not in self.key_to_row: 

2916 continue 

2917 

2918 itrack = self.key_to_row[gt] 

2919 if itrack not in track_projections: 

2920 continue 

2921 

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

2923 

2924 if len(traces) < 500: 

2925 drawbox(itrack, istyle, [tr]) 

2926 else: 

2927 if (itrack, istyle) not in traces_by_style: 

2928 traces_by_style[itrack, istyle] = [] 

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

2930 

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

2932 drawbox(itrack, istyle, traces) 

2933 

2934 def draw_visible_markers( 

2935 self, p, vcenter_projection, primary_pen): 

2936 

2937 try: 

2938 markers = self.markers.with_key_in_limited( 

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

2940 

2941 except pyrocko.pile.TooMany: 

2942 tmin = self.markers[0].tmin 

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

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

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

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

2947 v0, _ = vcenter_projection.get_out_range() 

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

2949 

2950 p.save() 

2951 

2952 pen = qg.QPen(primary_pen) 

2953 pen.setWidth(2) 

2954 pen.setStyle(qc.Qt.DotLine) 

2955 # pat = [5., 3.] 

2956 # pen.setDashPattern(pat) 

2957 p.setPen(pen) 

2958 

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

2960 s_selected = ' (all selected)' 

2961 elif self.n_selected_markers > 0: 

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

2963 else: 

2964 s_selected = '' 

2965 

2966 draw_label( 

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

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

2969 label_bg, 'LB') 

2970 

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

2972 p.drawLine(line) 

2973 p.restore() 

2974 

2975 return 

2976 

2977 for marker in markers: 

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

2979 and marker.kind in self.visible_marker_kinds: 

2980 

2981 marker.draw( 

2982 p, self.time_projection, vcenter_projection, 

2983 with_label=True) 

2984 

2985 def get_squirrel(self): 

2986 try: 

2987 return self.pile._squirrel 

2988 except AttributeError: 

2989 return None 

2990 

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

2992 sq = self.get_squirrel() 

2993 if sq is None: 

2994 return 

2995 

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

2997 v_projection = track_projections[itrack] 

2998 dvmin = v_projection(0.) 

2999 dvmax = v_projection(1.) 

3000 dtmin = time_projection.clipped(tmin, 0) 

3001 dtmax = time_projection.clipped(tmax, 1) 

3002 

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

3004 p.fillRect(rect, style.fill_brush) 

3005 p.setPen(style.frame_pen) 

3006 p.drawRect(rect) 

3007 

3008 pattern_list = [] 

3009 pattern_to_itrack = {} 

3010 for key in self.track_keys: 

3011 itrack = self.key_to_row[key] 

3012 if itrack not in track_projections: 

3013 continue 

3014 

3015 pattern = self.track_patterns[itrack] 

3016 pattern_to_itrack[tuple(pattern)] = itrack 

3017 pattern_list.append(tuple(pattern)) 

3018 

3019 vmin, vmax = self.get_time_range() 

3020 

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

3022 for coverage in sq.get_coverage( 

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

3024 itrack = pattern_to_itrack[coverage.pattern.nslc] 

3025 

3026 if coverage.changes is None: 

3027 drawbox( 

3028 itrack, coverage.tmin, coverage.tmax, 

3029 box_styles_coverage[kind][0]) 

3030 else: 

3031 t = None 

3032 pcount = 0 

3033 for tb, count in coverage.changes: 

3034 if t is not None and tb > t: 

3035 if pcount > 0: 

3036 drawbox( 

3037 itrack, t, tb, 

3038 box_styles_coverage[kind][ 

3039 min(len(box_styles_coverage)-1, 

3040 pcount)]) 

3041 

3042 t = tb 

3043 pcount = count 

3044 

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

3046 ''' 

3047 This performs the actual drawing. 

3048 ''' 

3049 

3050 self.timer_draw.start() 

3051 show_boxes = self.menuitem_showboxes.isChecked() 

3052 sq = self.get_squirrel() 

3053 

3054 if self.gather is None: 

3055 self.set_gathering() 

3056 

3057 if self.pile_has_changed: 

3058 

3059 if not self.sortingmode_change_delayed(): 

3060 self.sortingmode_change() 

3061 

3062 if show_boxes and sq is None: 

3063 self.determine_box_styles() 

3064 

3065 self.pile_has_changed = False 

3066 

3067 if h is None: 

3068 h = float(self.height()) 

3069 if w is None: 

3070 w = float(self.width()) 

3071 

3072 if printmode: 

3073 primary_color = (0, 0, 0) 

3074 else: 

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

3076 

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

3078 

3079 ax_h = float(self.ax_height) 

3080 

3081 vbottom_ax_projection = Projection() 

3082 vtop_ax_projection = Projection() 

3083 vcenter_projection = Projection() 

3084 

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

3086 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3087 vtop_ax_projection.set_out_range(0., ax_h) 

3088 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3089 vcenter_projection.set_in_range(0., 1.) 

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

3091 

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

3093 track_projections = {} 

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

3095 proj = Projection() 

3096 proj.set_out_range( 

3097 self.track_to_screen(i+0.05), 

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

3099 

3100 track_projections[i] = proj 

3101 

3102 if self.tmin > self.tmax: 

3103 return 

3104 

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

3106 vbottom_ax_projection.set_in_range(0, ax_h) 

3107 

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

3109 

3110 yscaler = pyrocko.plot.AutoScaler() 

3111 

3112 p.setPen(primary_pen) 

3113 

3114 font = qg.QFont() 

3115 font.setBold(True) 

3116 

3117 axannotfont = qg.QFont() 

3118 axannotfont.setBold(True) 

3119 axannotfont.setPointSize(8) 

3120 

3121 processed_traces = self.prepare_cutout2( 

3122 self.tmin, self.tmax, 

3123 trace_selector=self.trace_selector, 

3124 degap=self.menuitem_degap.isChecked(), 

3125 demean=self.menuitem_demean.isChecked()) 

3126 

3127 if not printmode and show_boxes: 

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

3129 or (self.view_mode is ViewMode.Waterfall 

3130 and not processed_traces): 

3131 

3132 if sq is None: 

3133 self.draw_trace_boxes( 

3134 p, self.time_projection, track_projections) 

3135 

3136 else: 

3137 self.draw_coverage( 

3138 p, self.time_projection, track_projections) 

3139 

3140 p.setFont(font) 

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

3142 

3143 color_lookup = dict( 

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

3145 

3146 self.track_to_nslc_ids = {} 

3147 nticks = 0 

3148 annot_labels = [] 

3149 

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

3151 waterfall = self.waterfall 

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

3153 waterfall.set_traces(processed_traces) 

3154 waterfall.set_cmap(self.waterfall_cmap) 

3155 waterfall.set_integrate(self.waterfall_integrate) 

3156 waterfall.set_clip( 

3157 self.waterfall_clip_min, self.waterfall_clip_max) 

3158 waterfall.show_absolute_values( 

3159 self.waterfall_show_absolute) 

3160 

3161 rect = qc.QRectF( 

3162 0, self.ax_height, 

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

3164 ) 

3165 waterfall.draw_waterfall(p, rect=rect) 

3166 

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

3168 show_scales = self.menuitem_showscalerange.isChecked() \ 

3169 or self.menuitem_showscaleaxis.isChecked() 

3170 

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

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

3173 - self.track_to_screen(0.05) 

3174 

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

3176 

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

3178 if self.menuitem_showscaleaxis.isChecked() \ 

3179 else 15 

3180 

3181 yscaler = pyrocko.plot.AutoScaler( 

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

3183 snap=show_scales 

3184 and not self.menuitem_showscaleaxis.isChecked()) 

3185 

3186 data_ranges = pyrocko.trace.minmax( 

3187 processed_traces, 

3188 key=self.scaling_key, 

3189 mode=self.scaling_base[0], 

3190 outer_mode=self.scaling_base[1]) 

3191 

3192 if not self.menuitem_fixscalerange.isChecked(): 

3193 self.old_data_ranges = data_ranges 

3194 else: 

3195 data_ranges.update(self.old_data_ranges) 

3196 

3197 self.apply_scaling_hooks(data_ranges) 

3198 

3199 trace_to_itrack = {} 

3200 track_scaling_keys = {} 

3201 track_scaling_colors = {} 

3202 for trace in processed_traces: 

3203 gt = self.gather(trace) 

3204 if gt not in self.key_to_row: 

3205 continue 

3206 

3207 itrack = self.key_to_row[gt] 

3208 if itrack not in track_projections: 

3209 continue 

3210 

3211 trace_to_itrack[trace] = itrack 

3212 

3213 if itrack not in self.track_to_nslc_ids: 

3214 self.track_to_nslc_ids[itrack] = set() 

3215 

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

3217 

3218 if itrack not in track_scaling_keys: 

3219 track_scaling_keys[itrack] = set() 

3220 

3221 scaling_key = self.scaling_key(trace) 

3222 track_scaling_keys[itrack].add(scaling_key) 

3223 

3224 color = pyrocko.plot.color( 

3225 color_lookup[self.color_gather(trace)]) 

3226 

3227 k = itrack, scaling_key 

3228 if k not in track_scaling_colors \ 

3229 and self.menuitem_colortraces.isChecked(): 

3230 track_scaling_colors[k] = color 

3231 else: 

3232 track_scaling_colors[k] = primary_color 

3233 

3234 # y axes, zero lines 

3235 trace_projections = {} 

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

3237 if itrack not in track_scaling_keys: 

3238 continue 

3239 uoff = 0 

3240 for scaling_key in track_scaling_keys[itrack]: 

3241 data_range = data_ranges[scaling_key] 

3242 dymin, dymax = data_range 

3243 ymin, ymax, yinc = yscaler.make_scale( 

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

3245 iexp = yscaler.make_exp(yinc) 

3246 factor = 10**iexp 

3247 trace_projection = track_projections[itrack].copy() 

3248 trace_projection.set_in_range(ymax, ymin) 

3249 trace_projections[itrack, scaling_key] = \ 

3250 trace_projection 

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

3252 vmin, vmax = trace_projection.get_out_range() 

3253 umax_zeroline = umax 

3254 uoffnext = uoff 

3255 

3256 if show_scales: 

3257 pen = qg.QPen(primary_pen) 

3258 k = itrack, scaling_key 

3259 if k in track_scaling_colors: 

3260 c = qg.QColor(*track_scaling_colors[ 

3261 itrack, scaling_key]) 

3262 

3263 pen.setColor(c) 

3264 

3265 p.setPen(pen) 

3266 if nlinesavail > 3: 

3267 if self.menuitem_showscaleaxis.isChecked(): 

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

3269 ny_annot = int( 

3270 math.floor(ymax/yinc) 

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

3272 

3273 for iy_annot in range(ny_annot): 

3274 y = ymin_annot + iy_annot*yinc 

3275 v = trace_projection(y) 

3276 line = qc.QLineF( 

3277 umax-10-uoff, v, umax-uoff, v) 

3278 

3279 p.drawLine(line) 

3280 if iy_annot == ny_annot - 1 \ 

3281 and iexp != 0: 

3282 sexp = ' &times; ' \ 

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

3284 else: 

3285 sexp = '' 

3286 

3287 snum = num_to_html(y/factor) 

3288 lab = Label( 

3289 p, 

3290 umax-20-uoff, 

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

3292 label_bg=None, 

3293 anchor='MR', 

3294 font=axannotfont, 

3295 color=c) 

3296 

3297 uoffnext = max( 

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

3299 

3300 annot_labels.append(lab) 

3301 if y == 0.: 

3302 umax_zeroline = \ 

3303 umax - 20 \ 

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

3305 - uoff 

3306 else: 

3307 if not show_boxes: 

3308 qpoints = make_QPolygonF( 

3309 [umax-20-uoff, 

3310 umax-10-uoff, 

3311 umax-10-uoff, 

3312 umax-20-uoff], 

3313 [vmax, vmax, vmin, vmin]) 

3314 p.drawPolyline(qpoints) 

3315 

3316 snum = num_to_html(ymin) 

3317 labmin = Label( 

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

3319 label_bg=None, 

3320 anchor='BR', 

3321 font=axannotfont, 

3322 color=c) 

3323 

3324 annot_labels.append(labmin) 

3325 snum = num_to_html(ymax) 

3326 labmax = Label( 

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

3328 label_bg=None, 

3329 anchor='TR', 

3330 font=axannotfont, 

3331 color=c) 

3332 

3333 annot_labels.append(labmax) 

3334 

3335 for lab in (labmin, labmax): 

3336 uoffnext = max( 

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

3338 

3339 if self.menuitem_showzeroline.isChecked(): 

3340 v = trace_projection(0.) 

3341 if vmin <= v <= vmax: 

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

3343 p.drawLine(line) 

3344 

3345 uoff = uoffnext 

3346 

3347 p.setFont(font) 

3348 p.setPen(primary_pen) 

3349 for trace in processed_traces: 

3350 if self.view_mode is not ViewMode.Wiggle: 

3351 break 

3352 

3353 if trace not in trace_to_itrack: 

3354 continue 

3355 

3356 itrack = trace_to_itrack[trace] 

3357 scaling_key = self.scaling_key(trace) 

3358 trace_projection = trace_projections[ 

3359 itrack, scaling_key] 

3360 

3361 vdata = trace_projection(trace.get_ydata()) 

3362 

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

3364 udata_max = float(self.time_projection( 

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

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

3367 

3368 qpoints = make_QPolygonF(udata, vdata) 

3369 

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

3371 vmin, vmax = trace_projection.get_out_range() 

3372 

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

3374 

3375 if self.menuitem_cliptraces.isChecked(): 

3376 p.setClipRect(trackrect) 

3377 

3378 if self.menuitem_colortraces.isChecked(): 

3379 color = pyrocko.plot.color( 

3380 color_lookup[self.color_gather(trace)]) 

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

3382 p.setPen(pen) 

3383 

3384 p.drawPolyline(qpoints) 

3385 

3386 if self.floating_marker: 

3387 self.floating_marker.draw_trace( 

3388 self, p, trace, 

3389 self.time_projection, trace_projection, 1.0) 

3390 

3391 for marker in self.markers.with_key_in( 

3392 self.tmin - self.markers_deltat_max, 

3393 self.tmax): 

3394 

3395 if marker.tmin < self.tmax \ 

3396 and self.tmin < marker.tmax \ 

3397 and marker.kind \ 

3398 in self.visible_marker_kinds: 

3399 marker.draw_trace( 

3400 self, p, trace, self.time_projection, 

3401 trace_projection, 1.0) 

3402 

3403 p.setPen(primary_pen) 

3404 

3405 if self.menuitem_cliptraces.isChecked(): 

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

3407 

3408 if self.floating_marker: 

3409 self.floating_marker.draw( 

3410 p, self.time_projection, vcenter_projection) 

3411 

3412 self.draw_visible_markers( 

3413 p, vcenter_projection, primary_pen) 

3414 

3415 p.setPen(primary_pen) 

3416 while font.pointSize() > 2: 

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

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

3419 - self.track_to_screen(0.05) 

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

3421 if nlinesavail > 1: 

3422 break 

3423 

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

3425 

3426 p.setFont(font) 

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

3428 

3429 for key in self.track_keys: 

3430 itrack = self.key_to_row[key] 

3431 if itrack in track_projections: 

3432 plabel = ' '.join( 

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

3434 lx = 10 

3435 ly = self.track_to_screen(itrack+0.5) 

3436 

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

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

3439 continue 

3440 

3441 contains_cursor = \ 

3442 self.track_to_screen(itrack) \ 

3443 < mouse_pos.y() \ 

3444 < self.track_to_screen(itrack+1) 

3445 

3446 if not contains_cursor: 

3447 continue 

3448 

3449 font_large = p.font() 

3450 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3451 p.setFont(font_large) 

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

3453 p.setFont(font) 

3454 

3455 for lab in annot_labels: 

3456 lab.draw() 

3457 

3458 self.timer_draw.stop() 

3459 

3460 def see_data_params(self): 

3461 

3462 min_deltat = self.content_deltat_range()[0] 

3463 

3464 # determine padding and downampling requirements 

3465 if self.lowpass is not None: 

3466 deltat_target = 1./self.lowpass * 0.25 

3467 ndecimate = min( 

3468 50, 

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

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

3471 else: 

3472 ndecimate = 1 

3473 tpad = min_deltat*5. 

3474 

3475 if self.highpass is not None: 

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

3477 

3478 nsee_points_per_trace = 5000*10 

3479 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3480 

3481 return ndecimate, tpad, tsee 

3482 

3483 def clean_update(self): 

3484 self.cached_processed_traces = None 

3485 self.update() 

3486 

3487 def get_adequate_tpad(self): 

3488 tpad = 0. 

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

3490 if f is not None: 

3491 tpad = max(tpad, 1.0/f) 

3492 

3493 for snuffling in self.snufflings: 

3494 if snuffling._post_process_hook_enabled \ 

3495 or snuffling._pre_process_hook_enabled: 

3496 

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

3498 

3499 return tpad 

3500 

3501 def prepare_cutout2( 

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

3503 demean=True, nmax=6000): 

3504 

3505 if self.pile.is_empty(): 

3506 return [] 

3507 

3508 nmax = self.visible_length 

3509 

3510 self.timer_cutout.start() 

3511 

3512 tsee = tmax-tmin 

3513 min_deltat_wo_decimate = tsee/nmax 

3514 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3515 

3516 min_deltat_allow = min_deltat_wo_decimate 

3517 if self.lowpass is not None: 

3518 target_deltat_lp = 0.25/self.lowpass 

3519 if target_deltat_lp > min_deltat_wo_decimate: 

3520 min_deltat_allow = min_deltat_w_decimate 

3521 

3522 min_deltat_allow = math.exp( 

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

3524 

3525 tmin_ = tmin 

3526 tmax_ = tmax 

3527 

3528 # fetch more than needed? 

3529 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3533 

3534 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3535 lphp = self.menuitem_lphp.isChecked() 

3536 ads = self.menuitem_allowdownsampling.isChecked() 

3537 

3538 tpad = self.get_adequate_tpad() 

3539 tpad = max(tpad, tsee) 

3540 

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

3542 vec = ( 

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

3544 self.highpass, fft_filtering, lphp, 

3545 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3546 ads, self.pile.get_update_count()) 

3547 

3548 if (self.cached_vec 

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

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

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

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

3553 and self.cached_processed_traces is not None): 

3554 

3555 logger.debug('Using cached traces') 

3556 processed_traces = self.cached_processed_traces 

3557 

3558 else: 

3559 processed_traces = [] 

3560 if self.pile.deltatmax >= min_deltat_allow: 

3561 

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

3563 def group_selector(gr): 

3564 return gr.deltatmax >= min_deltat_allow 

3565 

3566 kwargs = dict(group_selector=group_selector) 

3567 else: 

3568 kwargs = {} 

3569 

3570 if trace_selector is not None: 

3571 def trace_selectorx(tr): 

3572 return tr.deltat >= min_deltat_allow \ 

3573 and trace_selector(tr) 

3574 else: 

3575 def trace_selectorx(tr): 

3576 return tr.deltat >= min_deltat_allow 

3577 

3578 for traces in self.pile.chopper( 

3579 tmin=tmin, tmax=tmax, tpad=tpad, 

3580 want_incomplete=True, 

3581 degap=degap, 

3582 maxgap=gap_lap_tolerance, 

3583 maxlap=gap_lap_tolerance, 

3584 keep_current_files_open=True, 

3585 trace_selector=trace_selectorx, 

3586 accessor_id=id(self), 

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

3588 include_last=True, **kwargs): 

3589 

3590 if demean: 

3591 for tr in traces: 

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

3593 continue 

3594 y = tr.get_ydata() 

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

3596 

3597 traces = self.pre_process_hooks(traces) 

3598 

3599 for trace in traces: 

3600 

3601 if not (trace.meta 

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

3603 

3604 if fft_filtering: 

3605 but = pyrocko.response.ButterworthResponse 

3606 multres = pyrocko.response.MultiplyResponse 

3607 if self.lowpass is not None \ 

3608 or self.highpass is not None: 

3609 

3610 it = num.arange( 

3611 trace.data_len(), dtype=float) 

3612 detr_data, m, b = detrend( 

3613 it, trace.get_ydata()) 

3614 

3615 trace.set_ydata(detr_data) 

3616 

3617 freqs, fdata = trace.spectrum( 

3618 pad_to_pow2=True, tfade=None) 

3619 

3620 nfreqs = fdata.size 

3621 

3622 key = (trace.deltat, nfreqs) 

3623 

3624 if key not in self.tf_cache: 

3625 resps = [] 

3626 if self.lowpass is not None: 

3627 resps.append(but( 

3628 order=4, 

3629 corner=self.lowpass, 

3630 type='low')) 

3631 

3632 if self.highpass is not None: 

3633 resps.append(but( 

3634 order=4, 

3635 corner=self.highpass, 

3636 type='high')) 

3637 

3638 resp = multres(resps) 

3639 self.tf_cache[key] = \ 

3640 resp.evaluate(freqs) 

3641 

3642 filtered_data = num.fft.irfft( 

3643 fdata*self.tf_cache[key] 

3644 )[:trace.data_len()] 

3645 

3646 retrended_data = retrend( 

3647 it, filtered_data, m, b) 

3648 

3649 trace.set_ydata(retrended_data) 

3650 

3651 else: 

3652 

3653 if ads and self.lowpass is not None: 

3654 while trace.deltat \ 

3655 < min_deltat_wo_decimate: 

3656 

3657 trace.downsample(2, demean=False) 

3658 

3659 fmax = 0.5/trace.deltat 

3660 if not lphp and ( 

3661 self.lowpass is not None 

3662 and self.highpass is not None 

3663 and self.lowpass < fmax 

3664 and self.highpass < fmax 

3665 and self.highpass < self.lowpass): 

3666 

3667 trace.bandpass( 

3668 2, self.highpass, self.lowpass) 

3669 else: 

3670 if self.lowpass is not None: 

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

3672 trace.lowpass( 

3673 4, self.lowpass, 

3674 demean=False) 

3675 

3676 if self.highpass is not None: 

3677 if self.lowpass is None \ 

3678 or self.highpass \ 

3679 < self.lowpass: 

3680 

3681 if self.highpass < \ 

3682 0.5/trace.deltat: 

3683 trace.highpass( 

3684 4, self.highpass, 

3685 demean=False) 

3686 

3687 processed_traces.append(trace) 

3688 

3689 if self.rotate != 0.0: 

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

3691 cphi = math.cos(phi) 

3692 sphi = math.sin(phi) 

3693 for a in processed_traces: 

3694 for b in processed_traces: 

3695 if (a.network == b.network 

3696 and a.station == b.station 

3697 and a.location == b.location 

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

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

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

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

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

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

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

3705 

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

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

3708 a.set_ydata(aydata) 

3709 b.set_ydata(bydata) 

3710 

3711 processed_traces = self.post_process_hooks(processed_traces) 

3712 

3713 self.cached_processed_traces = processed_traces 

3714 self.cached_vec = vec 

3715 

3716 chopped_traces = [] 

3717 for trace in processed_traces: 

3718 chop_tmin = tmin_ - trace.deltat*4 

3719 chop_tmax = tmax_ + trace.deltat*4 

3720 

3721 try: 

3722 ctrace = trace.chop( 

3723 chop_tmin, chop_tmax, 

3724 inplace=False) 

3725 

3726 except pyrocko.trace.NoData: 

3727 continue 

3728 

3729 if ctrace.data_len() < 2: 

3730 continue 

3731 

3732 chopped_traces.append(ctrace) 

3733 

3734 self.timer_cutout.stop() 

3735 return chopped_traces 

3736 

3737 def pre_process_hooks(self, traces): 

3738 for snuffling in self.snufflings: 

3739 if snuffling._pre_process_hook_enabled: 

3740 traces = snuffling.pre_process_hook(traces) 

3741 

3742 return traces 

3743 

3744 def post_process_hooks(self, traces): 

3745 for snuffling in self.snufflings: 

3746 if snuffling._post_process_hook_enabled: 

3747 traces = snuffling.post_process_hook(traces) 

3748 

3749 return traces 

3750 

3751 def visible_length_change(self, ignore=None): 

3752 for menuitem, vlen in self.menuitems_visible_length: 

3753 if menuitem.isChecked(): 

3754 self.visible_length = vlen 

3755 

3756 def scaling_base_change(self, ignore=None): 

3757 for menuitem, scaling_base in self.menuitems_scaling_base: 

3758 if menuitem.isChecked(): 

3759 self.scaling_base = scaling_base 

3760 

3761 def scalingmode_change(self, ignore=None): 

3762 for menuitem, scaling_key in self.menuitems_scaling: 

3763 if menuitem.isChecked(): 

3764 self.scaling_key = scaling_key 

3765 self.update() 

3766 

3767 def apply_scaling_hooks(self, data_ranges): 

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

3769 hook = self.scaling_hooks[k] 

3770 hook(data_ranges) 

3771 

3772 def viewmode_change(self, ignore=True): 

3773 for item, mode in self.menuitems_viewmode: 

3774 if item.isChecked(): 

3775 self.view_mode = mode 

3776 break 

3777 else: 

3778 raise AttributeError('unknown view mode') 

3779 

3780 items_waterfall_disabled = ( 

3781 self.menuitem_showscaleaxis, 

3782 self.menuitem_showscalerange, 

3783 self.menuitem_showzeroline, 

3784 self.menuitem_colortraces, 

3785 self.menuitem_cliptraces, 

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

3787 ) 

3788 

3789 if self.view_mode is ViewMode.Waterfall: 

3790 self.parent().show_colorbar_ctrl(True) 

3791 self.parent().show_gain_ctrl(False) 

3792 

3793 for item in items_waterfall_disabled: 

3794 item.setDisabled(True) 

3795 

3796 self.visible_length = 180. 

3797 else: 

3798 self.parent().show_colorbar_ctrl(False) 

3799 self.parent().show_gain_ctrl(True) 

3800 

3801 for item in items_waterfall_disabled: 

3802 item.setDisabled(False) 

3803 

3804 self.visible_length_change() 

3805 self.update() 

3806 

3807 def set_scaling_hook(self, k, hook): 

3808 self.scaling_hooks[k] = hook 

3809 

3810 def remove_scaling_hook(self, k): 

3811 del self.scaling_hooks[k] 

3812 

3813 def remove_scaling_hooks(self): 

3814 self.scaling_hooks = {} 

3815 

3816 def s_sortingmode_change(self, ignore=None): 

3817 for menuitem, valfunc in self.menuitems_ssorting: 

3818 if menuitem.isChecked(): 

3819 self._ssort = valfunc 

3820 

3821 self.sortingmode_change() 

3822 

3823 def sortingmode_change(self, ignore=None): 

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

3825 if menuitem.isChecked(): 

3826 self.set_gathering(gather, color) 

3827 

3828 self.sortingmode_change_time = time.time() 

3829 

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

3831 self.lowpass = value 

3832 self.passband_check() 

3833 self.tf_cache = {} 

3834 self.update() 

3835 

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

3837 self.highpass = value 

3838 self.passband_check() 

3839 self.tf_cache = {} 

3840 self.update() 

3841 

3842 def passband_check(self): 

3843 if self.highpass and self.lowpass \ 

3844 and self.highpass >= self.lowpass: 

3845 

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

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

3848 'deactivate the highpass.' 

3849 

3850 self.update_status() 

3851 else: 

3852 oldmess = self.message 

3853 self.message = None 

3854 if oldmess is not None: 

3855 self.update_status() 

3856 

3857 def gain_change(self, value, ignore): 

3858 self.gain = value 

3859 self.update() 

3860 

3861 def rot_change(self, value, ignore): 

3862 self.rotate = value 

3863 self.update() 

3864 

3865 def waterfall_cmap_change(self, cmap): 

3866 self.waterfall_cmap = cmap 

3867 self.update() 

3868 

3869 def waterfall_clip_change(self, clip_min, clip_max): 

3870 self.waterfall_clip_min = clip_min 

3871 self.waterfall_clip_max = clip_max 

3872 self.update() 

3873 

3874 def waterfall_show_absolute_change(self, toggle): 

3875 self.waterfall_show_absolute = toggle 

3876 self.update() 

3877 

3878 def waterfall_set_integrate(self, toggle): 

3879 self.waterfall_integrate = toggle 

3880 self.update() 

3881 

3882 def set_selected_markers(self, markers): 

3883 ''' 

3884 Set a list of markers selected 

3885 

3886 :param markers: list of markers 

3887 ''' 

3888 self.deselect_all() 

3889 for m in markers: 

3890 m.selected = True 

3891 

3892 self.update() 

3893 

3894 def deselect_all(self): 

3895 for marker in self.markers: 

3896 marker.selected = False 

3897 

3898 def animate_picking(self): 

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

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

3901 

3902 def get_nslc_ids_for_track(self, ftrack): 

3903 itrack = int(ftrack) 

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

3905 

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

3907 if self.picking: 

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

3909 self.picking = None 

3910 self.picking_down = None 

3911 self.picking_timer.stop() 

3912 self.picking_timer = None 

3913 if not abort: 

3914 self.add_marker(self.floating_marker) 

3915 self.floating_marker.selected = True 

3916 self.emit_selected_markers() 

3917 

3918 self.floating_marker = None 

3919 

3920 def start_picking(self, ignore): 

3921 

3922 if not self.picking: 

3923 self.deselect_all() 

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

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

3926 

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

3928 self.picking.setGeometry( 

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

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

3931 

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

3933 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3935 self.floating_marker.selected = True 

3936 

3937 self.picking_timer = qc.QTimer() 

3938 self.picking_timer.timeout.connect( 

3939 self.animate_picking) 

3940 

3941 self.picking_timer.setInterval(50) 

3942 self.picking_timer.start() 

3943 

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

3945 if self.picking: 

3946 mouset = self.time_projection.rev(x) 

3947 dt = 0.0 

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

3949 if mouset < self.tmin: 

3950 dt = -(self.tmin - mouset) 

3951 else: 

3952 dt = mouset - self.tmax 

3953 ddt = self.tmax-self.tmin 

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

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

3956 

3957 x0 = x 

3958 if self.picking_down is not None: 

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

3960 

3961 w = abs(x-x0) 

3962 x0 = min(x0, x) 

3963 

3964 tmin, tmax = ( 

3965 self.time_projection.rev(x0), 

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

3967 

3968 tmin, tmax = ( 

3969 max(working_system_time_range[0], tmin), 

3970 min(working_system_time_range[1], tmax)) 

3971 

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

3973 

3974 self.picking.setGeometry( 

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

3976 

3977 ftrack = self.track_to_screen.rev(y) 

3978 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3980 

3981 if dt != 0.0 and doshift: 

3982 self.interrupt_following() 

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

3984 

3985 self.update() 

3986 

3987 def update_status(self): 

3988 

3989 if self.message is None: 

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

3991 

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

3993 if not is_working_time(mouse_t): 

3994 return 

3995 

3996 if self.floating_marker: 

3997 tmi, tma = ( 

3998 self.floating_marker.tmin, 

3999 self.floating_marker.tmax) 

4000 

4001 tt, ms = gmtime_x(tmi) 

4002 

4003 if tmi == tma: 

4004 message = mystrftime( 

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

4006 tt=tt, milliseconds=ms) 

4007 else: 

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

4009 message = mystrftime( 

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

4011 tt=tt, milliseconds=ms) 

4012 else: 

4013 tt, ms = gmtime_x(mouse_t) 

4014 

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

4016 else: 

4017 message = self.message 

4018 

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

4020 sb.clearMessage() 

4021 sb.showMessage(message) 

4022 

4023 def set_sortingmode_change_delay_time(self, dt): 

4024 self.sortingmode_change_delay_time = dt 

4025 

4026 def sortingmode_change_delayed(self): 

4027 now = time.time() 

4028 return ( 

4029 self.sortingmode_change_delay_time is not None 

4030 and now - self.sortingmode_change_time 

4031 < self.sortingmode_change_delay_time) 

4032 

4033 def set_visible_marker_kinds(self, kinds): 

4034 self.deselect_all() 

4035 self.visible_marker_kinds = tuple(kinds) 

4036 self.emit_selected_markers() 

4037 

4038 def following(self): 

4039 return self.follow_timer is not None \ 

4040 and not self.following_interrupted() 

4041 

4042 def interrupt_following(self): 

4043 self.interactive_range_change_time = time.time() 

4044 

4045 def following_interrupted(self, now=None): 

4046 if now is None: 

4047 now = time.time() 

4048 return now - self.interactive_range_change_time \ 

4049 < self.interactive_range_change_delay_time 

4050 

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

4052 if tmax_start is None: 

4053 tmax_start = time.time() 

4054 self.show_all = False 

4055 self.follow_time = tlen 

4056 self.follow_timer = qc.QTimer(self) 

4057 self.follow_timer.timeout.connect( 

4058 self.follow_update) 

4059 self.follow_timer.setInterval(interval) 

4060 self.follow_timer.start() 

4061 self.follow_started = time.time() 

4062 self.follow_lapse = lapse 

4063 self.follow_tshift = self.follow_started - tmax_start 

4064 self.interactive_range_change_time = 0.0 

4065 

4066 def unfollow(self): 

4067 if self.follow_timer is not None: 

4068 self.follow_timer.stop() 

4069 self.follow_timer = None 

4070 self.interactive_range_change_time = 0.0 

4071 

4072 def follow_update(self): 

4073 rnow = time.time() 

4074 if self.follow_lapse is None: 

4075 now = rnow 

4076 else: 

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

4078 * self.follow_lapse 

4079 

4080 if self.following_interrupted(rnow): 

4081 return 

4082 self.set_time_range( 

4083 now-self.follow_time-self.follow_tshift, 

4084 now-self.follow_tshift) 

4085 

4086 self.update() 

4087 

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

4089 self.return_tag = return_tag 

4090 self.window().close() 

4091 

4092 def cleanup(self): 

4093 self.about_to_close.emit() 

4094 self.timer.stop() 

4095 if self.follow_timer is not None: 

4096 self.follow_timer.stop() 

4097 

4098 for snuffling in list(self.snufflings): 

4099 self.remove_snuffling(snuffling) 

4100 

4101 def set_error_message(self, key, value): 

4102 if value is None: 

4103 if key in self.error_messages: 

4104 del self.error_messages[key] 

4105 else: 

4106 self.error_messages[key] = value 

4107 

4108 def inputline_changed(self, text): 

4109 pass 

4110 

4111 def inputline_finished(self, text): 

4112 line = str(text) 

4113 

4114 toks = line.split() 

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

4116 if len(toks) >= 1: 

4117 command = toks[0].lower() 

4118 

4119 try: 

4120 quick_filter_commands = { 

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

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

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

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

4125 

4126 if command in quick_filter_commands: 

4127 if len(toks) >= 2: 

4128 patterns = [ 

4129 quick_filter_commands[toks[0]] % pat 

4130 for pat in toks[1:]] 

4131 self.set_quick_filter_patterns(patterns, line) 

4132 else: 

4133 self.set_quick_filter_patterns(None) 

4134 

4135 self.update() 

4136 

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

4138 if len(toks) >= 2: 

4139 patterns = [] 

4140 if len(toks) == 2: 

4141 patterns = [toks[1]] 

4142 elif len(toks) >= 3: 

4143 x = { 

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

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

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

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

4148 

4149 if toks[1] in x: 

4150 patterns.extend( 

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

4152 

4153 for pattern in patterns: 

4154 if command == 'hide': 

4155 self.add_blacklist_pattern(pattern) 

4156 else: 

4157 self.remove_blacklist_pattern(pattern) 

4158 

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

4160 self.clear_blacklist() 

4161 

4162 clearit = True 

4163 

4164 self.update() 

4165 

4166 elif command == 'markers': 

4167 if len(toks) == 2: 

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

4169 kinds = self.all_marker_kinds 

4170 else: 

4171 kinds = [] 

4172 for x in toks[1]: 

4173 try: 

4174 kinds.append(int(x)) 

4175 except Exception: 

4176 pass 

4177 

4178 self.set_visible_marker_kinds(kinds) 

4179 

4180 elif len(toks) == 1: 

4181 self.set_visible_marker_kinds(()) 

4182 

4183 self.update() 

4184 

4185 elif command == 'scaling': 

4186 if len(toks) == 2: 

4187 hideit = False 

4188 error = 'wrong number of arguments' 

4189 

4190 if len(toks) >= 3: 

4191 vmin, vmax = [ 

4192 pyrocko.model.float_or_none(x) 

4193 for x in toks[-2:]] 

4194 

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

4196 if k in d: 

4197 if vmin is not None: 

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

4199 if vmax is not None: 

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

4201 

4202 if len(toks) == 1: 

4203 self.remove_scaling_hooks() 

4204 

4205 elif len(toks) == 3: 

4206 def hook(data_ranges): 

4207 for k in data_ranges: 

4208 upd(data_ranges, k, vmin, vmax) 

4209 

4210 self.set_scaling_hook('_', hook) 

4211 

4212 elif len(toks) == 4: 

4213 pattern = toks[1] 

4214 

4215 def hook(data_ranges): 

4216 for k in pyrocko.util.match_nslcs( 

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

4218 

4219 upd(data_ranges, k, vmin, vmax) 

4220 

4221 self.set_scaling_hook(pattern, hook) 

4222 

4223 elif command == 'goto': 

4224 toks2 = line.split(None, 1) 

4225 if len(toks2) == 2: 

4226 arg = toks2[1] 

4227 m = re.match( 

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

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

4230 if m: 

4231 tlen = None 

4232 if not m.group(1): 

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

4234 elif not m.group(2): 

4235 tlen = 32*24*60*60 

4236 elif not m.group(3): 

4237 tlen = 24*60*60 

4238 elif not m.group(4): 

4239 tlen = 60*60 

4240 elif not m.group(5): 

4241 tlen = 60 

4242 

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

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

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

4246 t = pyrocko.util.str_to_time(arg) 

4247 self.go_to_time(t, tlen=tlen) 

4248 

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

4250 supl = '00:00:00' 

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

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

4253 tmin, tmax = self.get_time_range() 

4254 sdate = pyrocko.util.time_to_str( 

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

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

4257 self.go_to_time(t) 

4258 

4259 elif arg == 'today': 

4260 self.go_to_time( 

4261 day_start( 

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

4263 

4264 elif arg == 'yesterday': 

4265 self.go_to_time( 

4266 day_start( 

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

4268 

4269 else: 

4270 self.go_to_event_by_name(arg) 

4271 

4272 else: 

4273 raise PileViewerMainException( 

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

4275 

4276 except PileViewerMainException as e: 

4277 error = str(e) 

4278 hideit = False 

4279 

4280 return clearit, hideit, error 

4281 

4282 return PileViewerMain 

4283 

4284 

4285PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4286GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget) 

4287 

4288 

4289class LineEditWithAbort(qw.QLineEdit): 

4290 

4291 aborted = qc.pyqtSignal() 

4292 history_down = qc.pyqtSignal() 

4293 history_up = qc.pyqtSignal() 

4294 

4295 def keyPressEvent(self, key_event): 

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

4297 self.aborted.emit() 

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

4299 self.history_down.emit() 

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

4301 self.history_up.emit() 

4302 else: 

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

4304 

4305 

4306class PileViewer(qw.QFrame): 

4307 ''' 

4308 PileViewerMain + Controls + Inputline 

4309 ''' 

4310 

4311 def __init__( 

4312 self, pile, 

4313 ntracks_shown_max=20, 

4314 marker_editor_sortable=True, 

4315 use_opengl=None, 

4316 panel_parent=None, 

4317 *args): 

4318 

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

4320 

4321 layout = qw.QGridLayout() 

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

4323 layout.setSpacing(0) 

4324 

4325 self.menu = PileViewerMenuBar(self) 

4326 

4327 if use_opengl is None: 

4328 use_opengl = is_macos 

4329 

4330 if use_opengl: 

4331 self.viewer = GLPileViewerMain( 

4332 pile, 

4333 ntracks_shown_max=ntracks_shown_max, 

4334 panel_parent=panel_parent, 

4335 menu=self.menu) 

4336 else: 

4337 self.viewer = PileViewerMain( 

4338 pile, 

4339 ntracks_shown_max=ntracks_shown_max, 

4340 panel_parent=panel_parent, 

4341 menu=self.menu) 

4342 

4343 self.marker_editor_sortable = marker_editor_sortable 

4344 

4345 # self.setFrameShape(qw.QFrame.StyledPanel) 

4346 # self.setFrameShadow(qw.QFrame.Sunken) 

4347 

4348 self.input_area = qw.QFrame(self) 

4349 ia_layout = qw.QGridLayout() 

4350 ia_layout.setContentsMargins(11, 11, 11, 11) 

4351 self.input_area.setLayout(ia_layout) 

4352 

4353 self.inputline = LineEditWithAbort(self.input_area) 

4354 self.inputline.returnPressed.connect( 

4355 self.inputline_returnpressed) 

4356 self.inputline.editingFinished.connect( 

4357 self.inputline_finished) 

4358 self.inputline.aborted.connect( 

4359 self.inputline_aborted) 

4360 

4361 self.inputline.history_down.connect( 

4362 lambda: self.step_through_history(1)) 

4363 self.inputline.history_up.connect( 

4364 lambda: self.step_through_history(-1)) 

4365 

4366 self.inputline.textEdited.connect( 

4367 self.inputline_changed) 

4368 

4369 self.inputline.setPlaceholderText( 

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

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

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

4373 self.input_area.hide() 

4374 self.history = None 

4375 

4376 self.inputline_error_str = None 

4377 

4378 self.inputline_error = qw.QLabel() 

4379 self.inputline_error.hide() 

4380 

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

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

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

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

4385 

4386 pb = Progressbars(self) 

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

4388 self.progressbars = pb 

4389 

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

4391 self.scrollbar = scrollbar 

4392 layout.addWidget(scrollbar, 1, 1) 

4393 self.scrollbar.valueChanged.connect( 

4394 self.scrollbar_changed) 

4395 

4396 self.block_scrollbar_changes = False 

4397 

4398 self.viewer.want_input.connect( 

4399 self.inputline_show) 

4400 self.viewer.tracks_range_changed.connect( 

4401 self.tracks_range_changed) 

4402 self.viewer.pile_has_changed_signal.connect( 

4403 self.adjust_controls) 

4404 self.viewer.about_to_close.connect( 

4405 self.save_inputline_history) 

4406 

4407 self.setLayout(layout) 

4408 

4409 def cleanup(self): 

4410 self.viewer.cleanup() 

4411 

4412 def get_progressbars(self): 

4413 return self.progressbars 

4414 

4415 def inputline_show(self): 

4416 if not self.history: 

4417 self.load_inputline_history() 

4418 

4419 self.input_area.show() 

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

4421 self.inputline.selectAll() 

4422 

4423 def inputline_set_error(self, string): 

4424 self.inputline_error_str = string 

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

4426 self.inputline.selectAll() 

4427 self.inputline_error.setText(string) 

4428 self.input_area.show() 

4429 self.inputline_error.show() 

4430 

4431 def inputline_clear_error(self): 

4432 if self.inputline_error_str: 

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

4434 self.inputline_error_str = None 

4435 self.inputline_error.clear() 

4436 self.inputline_error.hide() 

4437 

4438 def inputline_changed(self, line): 

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

4440 self.inputline_clear_error() 

4441 

4442 def inputline_returnpressed(self): 

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

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

4445 

4446 if error: 

4447 self.inputline_set_error(error) 

4448 

4449 line = line.strip() 

4450 

4451 if line != '' and not error: 

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

4453 self.history.append(line) 

4454 

4455 if clearit: 

4456 

4457 self.inputline.blockSignals(True) 

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

4459 if qpat is None: 

4460 self.inputline.clear() 

4461 else: 

4462 self.inputline.setText(qinp) 

4463 self.inputline.blockSignals(False) 

4464 

4465 if hideit and not error: 

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

4467 self.input_area.hide() 

4468 

4469 self.hist_ind = len(self.history) 

4470 

4471 def inputline_aborted(self): 

4472 ''' 

4473 Hide the input line. 

4474 ''' 

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

4476 self.hist_ind = len(self.history) 

4477 self.input_area.hide() 

4478 

4479 def save_inputline_history(self): 

4480 ''' 

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

4482 ''' 

4483 if not self.history: 

4484 return 

4485 

4486 conf = pyrocko.config 

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

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

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

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

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

4492 

4493 def load_inputline_history(self): 

4494 ''' 

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

4496 ''' 

4497 conf = pyrocko.config 

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

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

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

4501 f.write('\n') 

4502 

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

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

4505 

4506 self.hist_ind = len(self.history) 

4507 

4508 def step_through_history(self, ud=1): 

4509 ''' 

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

4511 ''' 

4512 n = len(self.history) 

4513 self.hist_ind += ud 

4514 self.hist_ind %= (n + 1) 

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

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

4517 else: 

4518 self.inputline.setText('') 

4519 

4520 def inputline_finished(self): 

4521 pass 

4522 

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

4524 if self.block_scrollbar_changes: 

4525 return 

4526 

4527 self.scrollbar.blockSignals(True) 

4528 self.scrollbar.setPageStep(ihi-ilo) 

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

4530 self.scrollbar.setRange(0, vmax) 

4531 self.scrollbar.setValue(ilo) 

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

4533 self.scrollbar.blockSignals(False) 

4534 

4535 def scrollbar_changed(self, value): 

4536 self.block_scrollbar_changes = True 

4537 ilo = value 

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

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

4540 self.block_scrollbar_changes = False 

4541 self.update_contents() 

4542 

4543 def controls(self): 

4544 frame = qw.QFrame(self) 

4545 layout = qw.QGridLayout() 

4546 frame.setLayout(layout) 

4547 

4548 minfreq = 0.001 

4549 maxfreq = 1000.0 

4550 self.lowpass_control = ValControl(high_is_none=True) 

4551 self.lowpass_control.setup( 

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

4553 self.highpass_control = ValControl(low_is_none=True) 

4554 self.highpass_control.setup( 

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

4556 self.gain_control = ValControl() 

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

4558 self.rot_control = LinValControl() 

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

4560 self.colorbar_control = ColorbarControl(self) 

4561 

4562 self.lowpass_control.valchange.connect( 

4563 self.viewer.lowpass_change) 

4564 self.highpass_control.valchange.connect( 

4565 self.viewer.highpass_change) 

4566 self.gain_control.valchange.connect( 

4567 self.viewer.gain_change) 

4568 self.rot_control.valchange.connect( 

4569 self.viewer.rot_change) 

4570 self.colorbar_control.cmap_changed.connect( 

4571 self.viewer.waterfall_cmap_change 

4572 ) 

4573 self.colorbar_control.clip_changed.connect( 

4574 self.viewer.waterfall_clip_change 

4575 ) 

4576 self.colorbar_control.show_absolute_toggled.connect( 

4577 self.viewer.waterfall_show_absolute_change 

4578 ) 

4579 self.colorbar_control.show_integrate_toggled.connect( 

4580 self.viewer.waterfall_set_integrate 

4581 ) 

4582 

4583 for icontrol, control in enumerate(( 

4584 self.highpass_control, 

4585 self.lowpass_control, 

4586 self.gain_control, 

4587 self.rot_control, 

4588 self.colorbar_control)): 

4589 

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

4591 layout.addWidget(widget, icontrol, iwidget) 

4592 

4593 spacer = qw.QSpacerItem( 

4594 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4596 

4597 self.adjust_controls() 

4598 self.viewer.viewmode_change(ViewMode.Wiggle) 

4599 return frame 

4600 

4601 def marker_editor(self): 

4602 editor = pyrocko.gui.marker_editor.MarkerEditor( 

4603 self, sortable=self.marker_editor_sortable) 

4604 

4605 editor.set_viewer(self.get_view()) 

4606 editor.get_marker_model().dataChanged.connect( 

4607 self.update_contents) 

4608 return editor 

4609 

4610 def adjust_controls(self): 

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

4612 maxfreq = 0.5/dtmin 

4613 minfreq = (0.5/dtmax)*0.0001 

4614 self.lowpass_control.set_range(minfreq, maxfreq) 

4615 self.highpass_control.set_range(minfreq, maxfreq) 

4616 

4617 def setup_snufflings(self): 

4618 self.viewer.setup_snufflings() 

4619 

4620 def get_view(self): 

4621 return self.viewer 

4622 

4623 def update_contents(self): 

4624 self.viewer.update() 

4625 

4626 def get_pile(self): 

4627 return self.viewer.get_pile() 

4628 

4629 def show_colorbar_ctrl(self, show): 

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

4631 w.setVisible(show) 

4632 

4633 def show_gain_ctrl(self, show): 

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

4635 w.setVisible(show)