Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/gui/snuffler/pile_viewer.py: 70%

2827 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-10-06 15:01 +0000

1# https://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.snuffler.snuffling 

26import pyrocko.gui.snuffler.snufflings 

27import pyrocko.gui.snuffler.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 

46qc.QString = str 

47 

48qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \ 

49 qw.QFileDialog.DontUseSheet 

50 

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

52 

53logger = logging.getLogger('pyrocko.gui.snuffler.pile_viewer') 

54 

55 

56def detrend(x, y): 

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

58 y_detrended = y - slope * x - offset 

59 return y_detrended, slope, offset 

60 

61 

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

63 return x * slope + y_detrended + offset 

64 

65 

66class Global(object): 

67 appOnDemand = None 

68 

69 

70class NSLC(object): 

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

72 self.network = n 

73 self.station = s 

74 self.location = l 

75 self.channel = c 

76 

77 

78class m_float(float): 

79 

80 def __str__(self): 

81 if abs(self) >= 10000.: 

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

83 elif abs(self) >= 1000.: 

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

85 else: 

86 return '%.5g m' % self 

87 

88 def __lt__(self, other): 

89 if other is None: 

90 return True 

91 return float(self) < float(other) 

92 

93 def __gt__(self, other): 

94 if other is None: 

95 return False 

96 return float(self) > float(other) 

97 

98 

99def m_float_or_none(x): 

100 if x is None: 

101 return None 

102 else: 

103 return m_float(x) 

104 

105 

106def make_chunks(items): 

107 ''' 

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

109 ''' 

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

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

112 

113 

114class deg_float(float): 

115 

116 def __str__(self): 

117 return '%4.0f' % self 

118 

119 def __lt__(self, other): 

120 if other is None: 

121 return True 

122 return float(self) < float(other) 

123 

124 def __gt__(self, other): 

125 if other is None: 

126 return False 

127 return float(self) > float(other) 

128 

129 

130def deg_float_or_none(x): 

131 if x is None: 

132 return None 

133 else: 

134 return deg_float(x) 

135 

136 

137class sector_int(int): 

138 

139 def __str__(self): 

140 return '[%i]' % self 

141 

142 def __lt__(self, other): 

143 if other is None: 

144 return True 

145 return int(self) < int(other) 

146 

147 def __gt__(self, other): 

148 if other is None: 

149 return False 

150 return int(self) > int(other) 

151 

152 

153def num_to_html(num): 

154 snum = '%g' % num 

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

156 if m: 

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

158 

159 return snum 

160 

161 

162gap_lap_tolerance = 5. 

163 

164 

165class ViewMode(enum.Enum): 

166 Wiggle = 1 

167 Waterfall = 2 

168 

169 

170class Timer(object): 

171 def __init__(self): 

172 self._start = None 

173 self._stop = None 

174 

175 def start(self): 

176 self._start = os.times() 

177 

178 def stop(self): 

179 self._stop = os.times() 

180 

181 def get(self): 

182 a = self._start 

183 b = self._stop 

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

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

186 else: 

187 return tuple([0.] * 5) 

188 

189 def __sub__(self, other): 

190 a = self.get() 

191 b = other.get() 

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

193 

194 

195class ObjectStyle(object): 

196 def __init__(self, frame_pen, fill_brush): 

197 self.frame_pen = frame_pen 

198 self.fill_brush = fill_brush 

199 

200 

201box_styles = [] 

202box_alpha = 100 

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

204 'scarletred'.split(): 

205 

206 box_styles.append(ObjectStyle( 

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

208 qg.QBrush(qg.QColor( 

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

210 )) 

211 

212box_styles_coverage = {} 

213 

214box_styles_coverage['waveform'] = [ 

215 ObjectStyle( 

216 qg.QPen( 

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

218 1, qc.Qt.DashLine), 

219 qg.QBrush(qg.QColor( 

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

221 ), 

222 ObjectStyle( 

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

224 qg.QBrush(qg.QColor( 

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

226 ), 

227 ObjectStyle( 

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

229 qg.QBrush(qg.QColor( 

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

231 )] 

232 

233box_styles_coverage['waveform_promise'] = [ 

234 ObjectStyle( 

235 qg.QPen( 

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

237 1, qc.Qt.DashLine), 

238 qg.QBrush(qg.QColor( 

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

240 ), 

241 ObjectStyle( 

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

243 qg.QBrush(qg.QColor( 

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

245 ), 

246 ObjectStyle( 

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

248 qg.QBrush(qg.QColor( 

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

250 )] 

251 

252sday = 60*60*24. # \ 

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

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

255 

256acceptable_tincs = num.array([ 

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

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

259 

260 

261working_system_time_range = \ 

262 pyrocko.util.working_system_time_range() 

263 

264initial_time_range = [] 

265 

266try: 

267 initial_time_range.append( 

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

269except Exception: 

270 initial_time_range.append(working_system_time_range[0]) 

271 

272try: 

273 initial_time_range.append( 

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

275except Exception: 

276 initial_time_range.append(working_system_time_range[1]) 

277 

278 

279def is_working_time(t): 

280 return working_system_time_range[0] <= t and \ 

281 t <= working_system_time_range[1] 

282 

283 

284def fancy_time_ax_format(inc): 

285 l0_fmt_brief = '' 

286 l2_fmt = '' 

287 l2_trig = 0 

288 if inc < 0.000001: 

289 l0_fmt = '.%n' 

290 l0_center = False 

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

292 l1_trig = 6 

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

294 l2_trig = 3 

295 elif inc < 0.001: 

296 l0_fmt = '.%u' 

297 l0_center = False 

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

299 l1_trig = 6 

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

301 l2_trig = 3 

302 elif inc < 1: 

303 l0_fmt = '.%r' 

304 l0_center = False 

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

306 l1_trig = 6 

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

308 l2_trig = 3 

309 elif inc < 60: 

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

311 l0_center = False 

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

313 l1_trig = 3 

314 elif inc < 3600: 

315 l0_fmt = '%H:%M' 

316 l0_center = False 

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

318 l1_trig = 3 

319 elif inc < sday: 

320 l0_fmt = '%H:%M' 

321 l0_center = False 

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

323 l1_trig = 3 

324 elif inc < smonth: 

325 l0_fmt = '%a %d' 

326 l0_fmt_brief = '%d' 

327 l0_center = True 

328 l1_fmt = '%b, %Y' 

329 l1_trig = 2 

330 elif inc < syear: 

331 l0_fmt = '%b' 

332 l0_center = True 

333 l1_fmt = '%Y' 

334 l1_trig = 1 

335 else: 

336 l0_fmt = '%Y' 

337 l0_center = False 

338 l1_fmt = '' 

339 l1_trig = 0 

340 

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

342 

343 

344def day_start(timestamp): 

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

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

347 return calendar.timegm(tts) 

348 

349 

350def month_start(timestamp): 

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

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

353 return calendar.timegm(tts) 

354 

355 

356def year_start(timestamp): 

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

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

359 return calendar.timegm(tts) 

360 

361 

362def time_nice_value(inc0): 

363 if inc0 < acceptable_tincs[0]: 

364 return pyrocko.plot.nice_value(inc0) 

365 elif inc0 > acceptable_tincs[-1]: 

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

367 else: 

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

369 return acceptable_tincs[i] 

370 

371 

372class TimeScaler(pyrocko.plot.AutoScaler): 

373 def __init__(self): 

374 pyrocko.plot.AutoScaler.__init__(self) 

375 self.mode = 'min-max' 

376 

377 def make_scale(self, data_range): 

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

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

380 

381 data_min = min(data_range) 

382 data_max = max(data_range) 

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

384 

385 mi, ma = data_min, data_max 

386 nmi = mi 

387 if self.mode != 'off': 

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

389 

390 nma = ma 

391 if self.mode != 'off': 

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

393 

394 mi, ma = nmi, nma 

395 

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

397 mi -= 1.0 

398 ma += 1.0 

399 

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

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

402 

403 # make nice tick increment 

404 if self.inc is not None: 

405 inc = self.inc 

406 else: 

407 if self.approx_ticks > 0.: 

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

409 else: 

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

411 

412 if inc == 0.0: 

413 inc = 1.0 

414 

415 if is_reverse: 

416 return ma, mi, -inc 

417 else: 

418 return mi, ma, inc 

419 

420 def make_ticks(self, data_range): 

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

422 

423 is_reverse = False 

424 if inc < 0: 

425 mi, ma, inc = ma, mi, -inc 

426 is_reverse = True 

427 

428 ticks = [] 

429 

430 if inc < sday: 

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

432 if inc < 0.001: 

433 mi_day = hpfloat(mi_day) 

434 

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

436 if inc < 0.001: 

437 base = hpfloat(base) 

438 

439 base_day = mi_day 

440 i = 0 

441 while True: 

442 tick = base+i*inc 

443 if tick > ma: 

444 break 

445 

446 tick_day = day_start(tick) 

447 if tick_day > base_day: 

448 base_day = tick_day 

449 base = base_day 

450 i = 0 

451 else: 

452 ticks.append(tick) 

453 i += 1 

454 

455 elif inc < smonth: 

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

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

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

459 if mi_day == mi: 

460 dt_base += delta 

461 i = 0 

462 while True: 

463 current = dt_base + i*delta 

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

465 if tick > ma: 

466 break 

467 ticks.append(tick) 

468 i += 1 

469 

470 elif inc < syear: 

471 mi_month = month_start(max( 

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

473 

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

475 while True: 

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

477 m += 1 

478 if m > 12: 

479 y, m = y+1, 1 

480 

481 if tick > ma: 

482 break 

483 

484 if tick >= mi: 

485 ticks.append(tick) 

486 

487 else: 

488 mi_year = year_start(max( 

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

490 

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

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

493 

494 while True: 

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

496 y += incy 

497 if tick > ma: 

498 break 

499 if tick >= mi: 

500 ticks.append(tick) 

501 

502 if is_reverse: 

503 ticks.reverse() 

504 

505 return ticks, inc 

506 

507 

508def need_l1_tick(tt, ms, l1_trig): 

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

510 

511 

512def tick_to_labels(tick, inc): 

513 tt, ms = gmtime_x(tick) 

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

515 fancy_time_ax_format(inc) 

516 

517 l0 = mystrftime(l0_fmt, tt, ms) 

518 l0_brief = mystrftime(l0_fmt_brief, tt, ms) 

519 l1, l2 = None, None 

520 if need_l1_tick(tt, ms, l1_trig): 

521 l1 = mystrftime(l1_fmt, tt, ms) 

522 if need_l1_tick(tt, ms, l2_trig): 

523 l2 = mystrftime(l2_fmt, tt, ms) 

524 

525 return l0, l0_brief, l0_center, l1, l2 

526 

527 

528def l1_l2_tick(tick, inc): 

529 tt, ms = gmtime_x(tick) 

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

531 fancy_time_ax_format(inc) 

532 

533 l1 = mystrftime(l1_fmt, tt, ms) 

534 l2 = mystrftime(l2_fmt, tt, ms) 

535 return l1, l2 

536 

537 

538class TimeAx(TimeScaler): 

539 def __init__(self, *args): 

540 TimeScaler.__init__(self, *args) 

541 

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

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

544 p.setPen(pen) 

545 font = qg.QFont() 

546 font.setBold(True) 

547 p.setFont(font) 

548 fm = p.fontMetrics() 

549 ticklen = 10 

550 pad = 10 

551 tmin, tmax = xprojection.get_in_range() 

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

553 l1_hits = 0 

554 l2_hits = 0 

555 

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

557 uumin, uumax = xprojection.get_out_range() 

558 first_tick_with_label = None 

559 

560 data = [] 

561 for tick in ticks: 

562 umin = xprojection(tick) 

563 

564 umin_approx_next = xprojection(tick+inc) 

565 umax = xprojection(tick) 

566 

567 pinc_approx = umin_approx_next - umin 

568 

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

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

571 

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

573 # hide year at epoch 

574 # (we assume that synthetic data is shown) 

575 if l2: 

576 l2 = None 

577 elif l1: 

578 l1 = None 

579 

580 if l0_center: 

581 ushift = (umin_approx_next-umin)/2. 

582 else: 

583 ushift = 0. 

584 

585 abbr_level = 0 

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

587 label0 = l0x 

588 rect0 = fm.boundingRect(label0) 

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

590 break 

591 

592 abbr_level += 1 

593 

594 data.append(( 

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

596 pinc_approx)) 

597 

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

599 pinc_approx) in data: 

600 

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

602 rect0 = fm.boundingRect(label0) 

603 

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

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

606 

607 if first_tick_with_label is None: 

608 first_tick_with_label = tick 

609 p.drawText(qc.QPointF( 

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

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

612 

613 if l1: 

614 label1 = l1 

615 rect1 = fm.boundingRect(label1) 

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

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

618 

619 p.drawText(qc.QPointF( 

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

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

622 label1) 

623 

624 l1_hits += 1 

625 

626 if l2: 

627 label2 = l2 

628 rect2 = fm.boundingRect(label2) 

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

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

631 

632 p.drawText(qc.QPointF( 

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

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

635 ticklen), label2) 

636 

637 l2_hits += 1 

638 

639 if first_tick_with_label is None: 

640 first_tick_with_label = tmin 

641 

642 l1, l2 = l1_l2_tick(first_tick_with_label, inc) 

643 

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

645 tmax - tmin < 3600*24: 

646 

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

648 if l2: 

649 l2 = None 

650 elif l1: 

651 l1 = None 

652 

653 if l1_hits == 0 and l1: 

654 label1 = l1 

655 rect1 = fm.boundingRect(label1) 

656 p.drawText(qc.QPointF( 

657 uumin+pad, 

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

659 label1) 

660 

661 l1_hits += 1 

662 

663 if l2_hits == 0 and l2: 

664 label2 = l2 

665 rect2 = fm.boundingRect(label2) 

666 p.drawText(qc.QPointF( 

667 uumin+pad, 

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

669 label2) 

670 

671 v = yprojection(0) 

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

673 

674 

675class Projection(object): 

676 def __init__(self): 

677 self.xr = 0., 1. 

678 self.ur = 0., 1. 

679 

680 def set_in_range(self, xmin, xmax): 

681 if xmax == xmin: 

682 xmax = xmin + 1. 

683 

684 self.xr = xmin, xmax 

685 

686 def get_in_range(self): 

687 return self.xr 

688 

689 def set_out_range(self, umin, umax): 

690 if umax == umin: 

691 umax = umin + 1. 

692 

693 self.ur = umin, umax 

694 

695 def get_out_range(self): 

696 return self.ur 

697 

698 def __call__(self, x): 

699 umin, umax = self.ur 

700 xmin, xmax = self.xr 

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

702 

703 def clipped(self, x, umax_pad): 

704 umin, umax = self.ur 

705 xmin, xmax = self.xr 

706 return min( 

707 umax-umax_pad, 

708 max(umin, umin + (x-xmin)*((umax-umin)/(xmax-xmin)))) 

709 

710 def rev(self, u): 

711 umin, umax = self.ur 

712 xmin, xmax = self.xr 

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

714 

715 def copy(self): 

716 return copy.copy(self) 

717 

718 

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

720 group = qw.QActionGroup(menu) 

721 group.setExclusive(True) 

722 menuitems = [] 

723 

724 for name, value, *shortcut in menudef: 

725 action = menu.addAction(name) 

726 action.setCheckable(True) 

727 action.setActionGroup(group) 

728 if shortcut: 

729 action.setShortcut(shortcut[0]) 

730 

731 menuitems.append((action, value)) 

732 if default is not None and ( 

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

734 value == default): 

735 action.setChecked(True) 

736 

737 group.triggered.connect(target) 

738 

739 if default is None: 

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

741 

742 return menuitems 

743 

744 

745def sort_actions(menu): 

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

747 for action in actions: 

748 menu.removeAction(action) 

749 actions.sort(key=lambda x: str(x.text())) 

750 

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

752 if help_action: 

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

754 for action in actions: 

755 menu.addAction(action) 

756 

757 

758fkey_map = dict(zip( 

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

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

761 range(10))) 

762 

763 

764class PileViewerMainException(Exception): 

765 pass 

766 

767 

768class PileViewerMenuBar(qw.QMenuBar): 

769 ... 

770 

771 

772class PileViewerMenu(qw.QMenu): 

773 ... 

774 

775 

776def MakePileViewerMainClass(base): 

777 

778 class PileViewerMain(base): 

779 

780 want_input = qc.pyqtSignal() 

781 about_to_close = qc.pyqtSignal() 

782 pile_has_changed_signal = qc.pyqtSignal() 

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

784 

785 begin_markers_add = qc.pyqtSignal(int, int) 

786 end_markers_add = qc.pyqtSignal() 

787 begin_markers_remove = qc.pyqtSignal(int, int) 

788 end_markers_remove = qc.pyqtSignal() 

789 

790 marker_selection_changed = qc.pyqtSignal(list) 

791 active_event_marker_changed = qc.pyqtSignal() 

792 

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

794 menu=None): 

795 base.__init__(self, *args) 

796 

797 self.pile = pile 

798 self.ax_height = 80 

799 self.panel_parent = panel_parent 

800 

801 self.click_tolerance = 5 

802 

803 self.ntracks_shown_max = ntracks_shown_max 

804 self.initial_ntracks_shown_max = ntracks_shown_max 

805 self.ntracks = 0 

806 self.show_all = True 

807 self.shown_tracks_range = None 

808 self.track_start = None 

809 self.track_trange = None 

810 

811 self.lowpass = None 

812 self.highpass = None 

813 self.gain = 1.0 

814 self.rotate = 0.0 

815 self.picking_down = None 

816 self.picking = None 

817 self.floating_marker = None 

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

819 self.markers_deltat_max = 0. 

820 self.n_selected_markers = 0 

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

822 self.visible_marker_kinds = self.all_marker_kinds 

823 self.active_event_marker = None 

824 self.ignore_releases = 0 

825 self.message = None 

826 self.reloaded = False 

827 self.pile_has_changed = False 

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

829 

830 self.tax = TimeAx() 

831 self.setBackgroundRole(qg.QPalette.Base) 

832 self.setAutoFillBackground(True) 

833 poli = qw.QSizePolicy( 

834 qw.QSizePolicy.Expanding, 

835 qw.QSizePolicy.Expanding) 

836 

837 self.setSizePolicy(poli) 

838 self.setMinimumSize(300, 200) 

839 self.setFocusPolicy(qc.Qt.ClickFocus) 

840 

841 self.menu = menu or PileViewerMenu(self) 

842 

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

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

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

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

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

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

849 

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

851 

852 self.snufflings_menu = self.toggle_panel_menu.addMenu( 

853 'Run Snuffling') 

854 self.toggle_panel_menu.addSeparator() 

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

856 help_menu.addSeparator() 

857 

858 file_menu.addAction( 

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

860 'Open waveform files...', 

861 self.open_waveforms, 

862 qg.QKeySequence.Open) 

863 

864 file_menu.addAction( 

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

866 'Open waveform directory...', 

867 self.open_waveform_directory) 

868 

869 file_menu.addAction( 

870 'Open station files...', 

871 self.open_stations) 

872 

873 file_menu.addAction( 

874 'Open StationXML files...', 

875 self.open_stations_xml) 

876 

877 file_menu.addAction( 

878 'Open event file...', 

879 self.read_events) 

880 

881 file_menu.addSeparator() 

882 file_menu.addAction( 

883 'Open marker file...', 

884 self.read_markers) 

885 

886 file_menu.addAction( 

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

888 'Save markers...', 

889 self.write_markers, 

890 qg.QKeySequence.Save) 

891 

892 file_menu.addAction( 

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

894 'Save selected markers...', 

895 self.write_selected_markers, 

896 qg.QKeySequence.SaveAs) 

897 

898 file_menu.addSeparator() 

899 file_menu.addAction( 

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

901 'Print', 

902 self.printit, 

903 qg.QKeySequence.Print) 

904 

905 file_menu.addAction( 

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

907 'Save as SVG or PNG', 

908 self.savesvg, 

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

910 

911 file_menu.addSeparator() 

912 close = file_menu.addAction( 

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

914 'Close', 

915 self.myclose) 

916 close.setShortcuts( 

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

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

919 

920 # Scale Menu 

921 menudef = [ 

922 ('Individual Scale', 

923 lambda tr: tr.nslc_id, 

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

925 ('Common Scale', 

926 lambda tr: None, 

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

928 ('Common Scale per Station', 

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

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

931 ('Common Scale per Station Location', 

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

933 ('Common Scale per Component', 

934 lambda tr: (tr.channel)), 

935 ] 

936 

937 self.menuitems_scaling = add_radiobuttongroup( 

938 scale_menu, menudef, self.scalingmode_change, 

939 default=self.config.trace_scale) 

940 scale_menu.addSeparator() 

941 

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

943 self.scaling_hooks = {} 

944 self.scalingmode_change() 

945 

946 menudef = [ 

947 ('Scaling based on Minimum and Maximum', 

948 ('minmax', 'minmax')), 

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

950 ('minmax', 'robust')), 

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

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

953 ] 

954 

955 self.menuitems_scaling_base = add_radiobuttongroup( 

956 scale_menu, menudef, self.scaling_base_change) 

957 

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

959 scale_menu.addSeparator() 

960 

961 self.menuitem_fixscalerange = scale_menu.addAction( 

962 'Fix Scale Ranges') 

963 self.menuitem_fixscalerange.setCheckable(True) 

964 

965 # Sort Menu 

966 def sector_dist(sta): 

967 if sta.dist_m is None: 

968 return None, None 

969 else: 

970 return ( 

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

972 m_float(sta.dist_m)) 

973 

974 menudef = [ 

975 ('Sort by Names', 

976 lambda tr: (), 

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

978 ('Sort by Distance', 

979 lambda tr: self.station_attrib( 

980 tr, 

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

982 lambda tr: (None,)), 

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

984 ('Sort by Azimuth', 

985 lambda tr: self.station_attrib( 

986 tr, 

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

988 lambda tr: (None,))), 

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

990 lambda tr: self.station_attrib( 

991 tr, 

992 sector_dist, 

993 lambda tr: (None, None))), 

994 ('Sort by Backazimuth', 

995 lambda tr: self.station_attrib( 

996 tr, 

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

998 lambda tr: (None,))), 

999 ] 

1000 self.menuitems_ssorting = add_radiobuttongroup( 

1001 sort_menu, menudef, self.s_sortingmode_change) 

1002 sort_menu.addSeparator() 

1003 

1004 self._ssort = lambda tr: () 

1005 

1006 self.menu.addSeparator() 

1007 

1008 menudef = [ 

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

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

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

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

1013 ((0, 1, 3, 2), 

1014 lambda tr: tr.channel)), 

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

1016 ((1, 0, 3, 2), 

1017 lambda tr: tr.channel)), 

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

1019 ((2, 0, 1, 3), 

1020 lambda tr: tr.channel)), 

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

1022 ((3, 0, 1, 2), 

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

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

1025 ((0, 1, 3), 

1026 lambda tr: tr.location)), 

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

1028 ((1, 0, 3), 

1029 lambda tr: tr.location)), 

1030 ] 

1031 

1032 self.menuitems_sorting = add_radiobuttongroup( 

1033 sort_menu, menudef, self.sortingmode_change) 

1034 

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

1036 self.config.visible_length_setting] 

1037 

1038 # View menu 

1039 self.menuitems_visible_length = add_radiobuttongroup( 

1040 view_menu, menudef, 

1041 self.visible_length_change) 

1042 view_menu.addSeparator() 

1043 

1044 view_modes = [ 

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

1046 ('Waterfall', ViewMode.Waterfall) 

1047 ] 

1048 

1049 self.menuitems_viewmode = add_radiobuttongroup( 

1050 view_menu, view_modes, 

1051 self.viewmode_change, default=ViewMode.Wiggle) 

1052 view_menu.addSeparator() 

1053 

1054 self.menuitem_cliptraces = view_menu.addAction( 

1055 'Clip Traces') 

1056 self.menuitem_cliptraces.setCheckable(True) 

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

1058 

1059 self.menuitem_showboxes = view_menu.addAction( 

1060 'Show Boxes') 

1061 self.menuitem_showboxes.setCheckable(True) 

1062 self.menuitem_showboxes.setChecked( 

1063 self.config.show_boxes) 

1064 

1065 self.menuitem_colortraces = view_menu.addAction( 

1066 'Color Traces') 

1067 self.menuitem_colortraces.setCheckable(True) 

1068 self.menuitem_antialias = view_menu.addAction( 

1069 'Antialiasing') 

1070 self.menuitem_antialias.setCheckable(True) 

1071 

1072 view_menu.addSeparator() 

1073 self.menuitem_showscalerange = view_menu.addAction( 

1074 'Show Scale Ranges') 

1075 self.menuitem_showscalerange.setCheckable(True) 

1076 self.menuitem_showscalerange.setChecked( 

1077 self.config.show_scale_ranges) 

1078 

1079 self.menuitem_showscaleaxis = view_menu.addAction( 

1080 'Show Scale Axes') 

1081 self.menuitem_showscaleaxis.setCheckable(True) 

1082 self.menuitem_showscaleaxis.setChecked( 

1083 self.config.show_scale_axes) 

1084 

1085 self.menuitem_showzeroline = view_menu.addAction( 

1086 'Show Zero Lines') 

1087 self.menuitem_showzeroline.setCheckable(True) 

1088 

1089 view_menu.addSeparator() 

1090 view_menu.addAction( 

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

1092 'Fullscreen', 

1093 self.toggle_fullscreen, 

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

1095 

1096 # Options Menu 

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

1098 self.menuitem_demean.setCheckable(True) 

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

1100 self.menuitem_demean.setShortcut( 

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

1102 

1103 self.menuitem_distances_3d = options_menu.addAction( 

1104 '3D distances', 

1105 self.distances_3d_changed) 

1106 self.menuitem_distances_3d.setCheckable(True) 

1107 

1108 self.menuitem_allowdownsampling = options_menu.addAction( 

1109 'Allow Downsampling') 

1110 self.menuitem_allowdownsampling.setCheckable(True) 

1111 self.menuitem_allowdownsampling.setChecked(True) 

1112 

1113 self.menuitem_degap = options_menu.addAction( 

1114 'Allow Degapping') 

1115 self.menuitem_degap.setCheckable(True) 

1116 self.menuitem_degap.setChecked(True) 

1117 

1118 options_menu.addSeparator() 

1119 

1120 self.menuitem_fft_filtering = options_menu.addAction( 

1121 'FFT Filtering') 

1122 self.menuitem_fft_filtering.setCheckable(True) 

1123 

1124 self.menuitem_lphp = options_menu.addAction( 

1125 'Bandpass is Low- + Highpass') 

1126 self.menuitem_lphp.setCheckable(True) 

1127 self.menuitem_lphp.setChecked(True) 

1128 

1129 options_menu.addSeparator() 

1130 self.menuitem_watch = options_menu.addAction( 

1131 'Watch Files') 

1132 self.menuitem_watch.setCheckable(True) 

1133 

1134 self.menuitem_liberal_fetch = options_menu.addAction( 

1135 'Liberal Fetch Optimization') 

1136 self.menuitem_liberal_fetch.setCheckable(True) 

1137 

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

1139 

1140 self.snufflings_menu.addAction( 

1141 'Reload Snufflings', 

1142 self.setup_snufflings) 

1143 

1144 # Disable ShadowPileTest 

1145 if False: 

1146 test_action = self.menu.addAction( 

1147 'Test', 

1148 self.toggletest) 

1149 test_action.setCheckable(True) 

1150 

1151 help_menu.addAction( 

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

1153 'Snuffler Controls', 

1154 self.help, 

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

1156 

1157 help_menu.addAction( 

1158 'About', 

1159 self.about) 

1160 

1161 self.time_projection = Projection() 

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

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

1164 

1165 self.gather = None 

1166 

1167 self.trace_filter = None 

1168 self.quick_filter = None 

1169 self.quick_filter_patterns = None, None 

1170 self.blacklist = [] 

1171 

1172 self.track_to_screen = Projection() 

1173 self.track_to_nslc_ids = {} 

1174 

1175 self.cached_vec = None 

1176 self.cached_processed_traces = None 

1177 

1178 self.timer = qc.QTimer(self) 

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

1180 self.timer.setInterval(1000) 

1181 self.timer.start() 

1182 

1183 self._pile_changed = self.pile_changed # need to keep a strong ref 

1184 self.pile.add_listener(self._pile_changed) 

1185 

1186 self.trace_styles = {} 

1187 if self.get_squirrel() is None: 

1188 self.determine_box_styles() 

1189 

1190 self.setMouseTracking(True) 

1191 

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

1193 self.snuffling_modules = {} 

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

1195 self.default_snufflings = None 

1196 self.snufflings = [] 

1197 

1198 self.stations = {} 

1199 

1200 self.timer_draw = Timer() 

1201 self.timer_cutout = Timer() 

1202 self.time_spent_painting = 0.0 

1203 self.time_last_painted = time.time() 

1204 

1205 self.interactive_range_change_time = 0.0 

1206 self.interactive_range_change_delay_time = 10.0 

1207 self.follow_timer = None 

1208 

1209 self.sortingmode_change_time = 0.0 

1210 self.sortingmode_change_delay_time = None 

1211 

1212 self.old_data_ranges = {} 

1213 

1214 self.error_messages = {} 

1215 self.return_tag = None 

1216 self.wheel_pos = 60 

1217 

1218 self.setAcceptDrops(True) 

1219 self._paths_to_load = [] 

1220 

1221 self.tf_cache = {} 

1222 

1223 self.waterfall = TraceWaterfall() 

1224 self.waterfall_cmap = 'viridis' 

1225 self.waterfall_clip_min = 0. 

1226 self.waterfall_clip_max = 1. 

1227 self.waterfall_show_absolute = False 

1228 self.waterfall_integrate = False 

1229 self.view_mode = ViewMode.Wiggle 

1230 

1231 self.automatic_updates = True 

1232 

1233 self.closing = False 

1234 self.in_paint_event = False 

1235 

1236 def fail(self, reason): 

1237 box = qw.QMessageBox(self) 

1238 box.setText(reason) 

1239 box.exec_() 

1240 

1241 def set_trace_filter(self, filter_func): 

1242 self.trace_filter = filter_func 

1243 self.sortingmode_change() 

1244 

1245 def update_trace_filter(self): 

1246 if self.blacklist: 

1247 

1248 def blacklist_func(tr): 

1249 return not pyrocko.util.match_nslc( 

1250 self.blacklist, tr.nslc_id) 

1251 

1252 else: 

1253 blacklist_func = None 

1254 

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

1256 self.set_trace_filter(None) 

1257 elif self.quick_filter is None: 

1258 self.set_trace_filter(blacklist_func) 

1259 elif blacklist_func is None: 

1260 self.set_trace_filter(self.quick_filter) 

1261 else: 

1262 self.set_trace_filter( 

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

1264 

1265 def set_quick_filter(self, filter_func): 

1266 self.quick_filter = filter_func 

1267 self.update_trace_filter() 

1268 

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

1270 if patterns is not None: 

1271 self.set_quick_filter( 

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

1273 else: 

1274 self.set_quick_filter(None) 

1275 

1276 self.quick_filter_patterns = patterns, inputline 

1277 

1278 def get_quick_filter_patterns(self): 

1279 return self.quick_filter_patterns 

1280 

1281 def add_blacklist_pattern(self, pattern): 

1282 if pattern == 'empty': 

1283 keys = set(self.pile.nslc_ids) 

1284 trs = self.pile.all( 

1285 tmin=self.tmin, 

1286 tmax=self.tmax, 

1287 load_data=False, 

1288 degap=False) 

1289 

1290 for tr in trs: 

1291 if tr.nslc_id in keys: 

1292 keys.remove(tr.nslc_id) 

1293 

1294 for key in keys: 

1295 xpattern = '.'.join(key) 

1296 if xpattern not in self.blacklist: 

1297 self.blacklist.append(xpattern) 

1298 

1299 else: 

1300 if pattern in self.blacklist: 

1301 self.blacklist.remove(pattern) 

1302 

1303 self.blacklist.append(pattern) 

1304 

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

1306 self.update_trace_filter() 

1307 

1308 def remove_blacklist_pattern(self, pattern): 

1309 if pattern in self.blacklist: 

1310 self.blacklist.remove(pattern) 

1311 else: 

1312 raise PileViewerMainException( 

1313 'Pattern not found in blacklist.') 

1314 

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

1316 self.update_trace_filter() 

1317 

1318 def clear_blacklist(self): 

1319 self.blacklist = [] 

1320 self.update_trace_filter() 

1321 

1322 def ssort(self, tr): 

1323 return self._ssort(tr) 

1324 

1325 def station_key(self, x): 

1326 return x.network, x.station 

1327 

1328 def station_keys(self, x): 

1329 return [ 

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

1331 (x.network, x.station)] 

1332 

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

1334 for sk in self.station_keys(tr): 

1335 if sk in self.stations: 

1336 station = self.stations[sk] 

1337 return getter(station) 

1338 

1339 return default_getter(tr) 

1340 

1341 def get_station(self, sk): 

1342 return self.stations[sk] 

1343 

1344 def has_station(self, station): 

1345 for sk in self.station_keys(station): 

1346 if sk in self.stations: 

1347 return True 

1348 

1349 return False 

1350 

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

1352 return self.station_attrib( 

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

1354 

1355 def set_stations(self, stations): 

1356 self.stations = {} 

1357 self.add_stations(stations) 

1358 

1359 def add_stations(self, stations): 

1360 for station in stations: 

1361 for sk in self.station_keys(station): 

1362 self.stations[sk] = station 

1363 

1364 ev = self.get_active_event() 

1365 if ev: 

1366 self.set_origin(ev) 

1367 

1368 def add_event(self, event): 

1369 marker = EventMarker(event) 

1370 self.add_marker(marker) 

1371 

1372 def add_events(self, events): 

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

1374 self.add_markers(markers) 

1375 

1376 def set_event_marker_as_origin(self, ignore=None): 

1377 selected = self.selected_markers() 

1378 if not selected: 

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

1380 return 

1381 

1382 m = selected[0] 

1383 if not isinstance(m, EventMarker): 

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

1385 return 

1386 

1387 self.set_active_event_marker(m) 

1388 

1389 def deactivate_event_marker(self): 

1390 if self.active_event_marker: 

1391 self.active_event_marker.active = False 

1392 

1393 self.active_event_marker_changed.emit() 

1394 self.active_event_marker = None 

1395 

1396 def set_active_event_marker(self, event_marker): 

1397 if self.active_event_marker: 

1398 self.active_event_marker.active = False 

1399 

1400 self.active_event_marker = event_marker 

1401 event_marker.active = True 

1402 event = event_marker.get_event() 

1403 self.set_origin(event) 

1404 self.active_event_marker_changed.emit() 

1405 

1406 def set_active_event(self, event): 

1407 for marker in self.markers: 

1408 if isinstance(marker, EventMarker): 

1409 if marker.get_event() is event: 

1410 self.set_active_event_marker(marker) 

1411 

1412 def get_active_event_marker(self): 

1413 return self.active_event_marker 

1414 

1415 def get_active_event(self): 

1416 m = self.get_active_event_marker() 

1417 if m is not None: 

1418 return m.get_event() 

1419 else: 

1420 return None 

1421 

1422 def get_active_markers(self): 

1423 emarker = self.get_active_event_marker() 

1424 if emarker is None: 

1425 return None, [] 

1426 

1427 else: 

1428 ev = emarker.get_event() 

1429 pmarkers = [ 

1430 m for m in self.markers 

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

1432 

1433 return emarker, pmarkers 

1434 

1435 def set_origin(self, location): 

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

1437 station.set_event_relative_data( 

1438 location, 

1439 distance_3d=self.menuitem_distances_3d.isChecked()) 

1440 

1441 self.sortingmode_change() 

1442 

1443 def distances_3d_changed(self): 

1444 ignore = self.menuitem_distances_3d.isChecked() 

1445 self.set_event_marker_as_origin(ignore) 

1446 

1447 def iter_snuffling_modules(self): 

1448 pjoin = os.path.join 

1449 for path in self.snuffling_paths: 

1450 

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

1452 os.mkdir(path) 

1453 

1454 for entry in os.listdir(path): 

1455 directory = path 

1456 fn = entry 

1457 d = pjoin(path, entry) 

1458 if os.path.isdir(d): 

1459 directory = d 

1460 if os.path.isfile( 

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

1462 fn = 'snuffling.py' 

1463 

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

1465 continue 

1466 

1467 name = fn[:-3] 

1468 

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

1470 self.snuffling_modules[directory, name] = \ 

1471 pyrocko.gui.snuffler.snuffling.SnufflingModule( 

1472 directory, name, self) 

1473 

1474 yield self.snuffling_modules[directory, name] 

1475 

1476 def setup_snufflings(self): 

1477 # user snufflings 

1478 from pyrocko.gui.snuffler.snuffling import BrokenSnufflingModule 

1479 for mod in self.iter_snuffling_modules(): 

1480 try: 

1481 mod.load_if_needed() 

1482 except BrokenSnufflingModule as e: 

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

1484 

1485 # load the default snufflings on first run 

1486 if self.default_snufflings is None: 

1487 self.default_snufflings = pyrocko.gui.snuffler\ 

1488 .snufflings.__snufflings__() 

1489 for snuffling in self.default_snufflings: 

1490 self.add_snuffling(snuffling) 

1491 

1492 def set_panel_parent(self, panel_parent): 

1493 self.panel_parent = panel_parent 

1494 

1495 def get_panel_parent(self): 

1496 return self.panel_parent 

1497 

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

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

1500 snuffling.init_gui( 

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

1502 self.snufflings.append(snuffling) 

1503 self.update() 

1504 

1505 def remove_snuffling(self, snuffling): 

1506 snuffling.delete_gui() 

1507 self.update() 

1508 self.snufflings.remove(snuffling) 

1509 snuffling.pre_destroy() 

1510 

1511 def add_snuffling_menuitem(self, item): 

1512 self.snufflings_menu.addAction(item) 

1513 item.setParent(self.snufflings_menu) 

1514 sort_actions(self.snufflings_menu) 

1515 

1516 def remove_snuffling_menuitem(self, item): 

1517 self.snufflings_menu.removeAction(item) 

1518 

1519 def add_snuffling_help_menuitem(self, item): 

1520 self.snuffling_help.addAction(item) 

1521 item.setParent(self.snuffling_help) 

1522 sort_actions(self.snuffling_help) 

1523 

1524 def remove_snuffling_help_menuitem(self, item): 

1525 self.snuffling_help.removeAction(item) 

1526 

1527 def add_panel_toggler(self, item): 

1528 self.toggle_panel_menu.addAction(item) 

1529 item.setParent(self.toggle_panel_menu) 

1530 sort_actions(self.toggle_panel_menu) 

1531 

1532 def remove_panel_toggler(self, item): 

1533 self.toggle_panel_menu.removeAction(item) 

1534 

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

1536 cache_dir=None, force_cache=False): 

1537 

1538 if cache_dir is None: 

1539 cache_dir = pyrocko.config.config().cache_dir 

1540 if isinstance(paths, str): 

1541 paths = [paths] 

1542 

1543 fns = pyrocko.util.select_files( 

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

1545 

1546 if not fns: 

1547 return 

1548 

1549 cache = pyrocko.pile.get_cache(cache_dir) 

1550 

1551 t = [time.time()] 

1552 

1553 def update_bar(label, value): 

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

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

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

1557 else: 

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

1559 

1560 return pbs.set_status(label, value) 

1561 

1562 def update_progress(label, i, n): 

1563 abort = False 

1564 

1565 qw.qApp.processEvents() 

1566 if n != 0: 

1567 perc = i*100/n 

1568 else: 

1569 perc = 100 

1570 abort |= update_bar(label, perc) 

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

1572 

1573 tnow = time.time() 

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

1575 self.update() 

1576 t[0] = tnow 

1577 

1578 return abort 

1579 

1580 self.automatic_updates = False 

1581 

1582 self.pile.load_files( 

1583 sorted(fns), 

1584 filename_attributes=regex, 

1585 cache=cache, 

1586 fileformat=format, 

1587 show_progress=False, 

1588 update_progress=update_progress) 

1589 

1590 self.automatic_updates = True 

1591 self.update() 

1592 

1593 def load_queued(self): 

1594 if not self._paths_to_load: 

1595 return 

1596 paths = self._paths_to_load 

1597 self._paths_to_load = [] 

1598 self.load(paths) 

1599 

1600 def load_soon(self, paths): 

1601 self._paths_to_load.extend(paths) 

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

1603 

1604 def open_waveforms(self): 

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

1606 

1607 fns, _ = qw.QFileDialog.getOpenFileNames( 

1608 self, caption, options=qfiledialog_options) 

1609 

1610 if fns: 

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

1612 

1613 def open_waveform_directory(self): 

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

1615 

1616 dn = qw.QFileDialog.getExistingDirectory( 

1617 self, caption, options=qfiledialog_options) 

1618 

1619 if dn: 

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

1621 

1622 def open_stations(self, fns=None): 

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

1624 

1625 if not fns: 

1626 fns, _ = qw.QFileDialog.getOpenFileNames( 

1627 self, caption, options=qfiledialog_options) 

1628 

1629 try: 

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

1631 for stat in stations: 

1632 self.add_stations(stat) 

1633 

1634 except Exception as e: 

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

1636 

1637 def open_stations_xml(self, fns=None): 

1638 from pyrocko.io import stationxml 

1639 

1640 caption = 'Select one or more StationXML files' 

1641 if not fns: 

1642 fns, _ = qw.QFileDialog.getOpenFileNames( 

1643 self, caption, options=qfiledialog_options, 

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

1645 ';;All files (*)') 

1646 

1647 try: 

1648 stations = [ 

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

1650 for x in fns] 

1651 

1652 for stat in stations: 

1653 self.add_stations(stat) 

1654 

1655 except Exception as e: 

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

1657 

1658 def add_traces(self, traces): 

1659 if traces: 

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

1661 self.pile.add_file(mtf) 

1662 ticket = (self.pile, mtf) 

1663 return ticket 

1664 else: 

1665 return (None, None) 

1666 

1667 def release_data(self, tickets): 

1668 for ticket in tickets: 

1669 pile, mtf = ticket 

1670 if pile is not None: 

1671 pile.remove_file(mtf) 

1672 

1673 def periodical(self): 

1674 if self.menuitem_watch.isChecked(): 

1675 if self.pile.reload_modified(): 

1676 self.update() 

1677 

1678 def get_pile(self): 

1679 return self.pile 

1680 

1681 def pile_changed(self, what, content): 

1682 self.pile_has_changed = True 

1683 self.pile_has_changed_signal.emit() 

1684 if self.automatic_updates: 

1685 self.update() 

1686 

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

1688 

1689 if gather is None: 

1690 def gather_func(tr): 

1691 return tr.nslc_id 

1692 

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

1694 

1695 else: 

1696 def gather_func(tr): 

1697 return ( 

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

1699 

1700 if color is None: 

1701 def color(tr): 

1702 return tr.location 

1703 

1704 self.gather = gather_func 

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

1706 

1707 self.color_gather = color 

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

1709 previous_ntracks = self.ntracks 

1710 self.set_ntracks(len(keys)) 

1711 

1712 if self.shown_tracks_range is None or \ 

1713 previous_ntracks == 0 or \ 

1714 self.show_all: 

1715 

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

1717 key_at_top = None 

1718 n = high-low 

1719 

1720 else: 

1721 low, high = self.shown_tracks_range 

1722 key_at_top = self.track_keys[low] 

1723 n = high-low 

1724 

1725 self.track_keys = sorted(keys) 

1726 

1727 track_patterns = [] 

1728 for k in self.track_keys: 

1729 pat = ['*', '*', '*', '*'] 

1730 for i, j in enumerate(gather): 

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

1732 

1733 track_patterns.append(pat) 

1734 

1735 self.track_patterns = track_patterns 

1736 

1737 if key_at_top is not None: 

1738 try: 

1739 ind = self.track_keys.index(key_at_top) 

1740 low = ind 

1741 high = low+n 

1742 except Exception: 

1743 pass 

1744 

1745 self.set_tracks_range((low, high)) 

1746 

1747 self.key_to_row = dict( 

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

1749 

1750 def inrange(x, r): 

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

1752 

1753 def trace_selector(trace): 

1754 gt = self.gather(trace) 

1755 return ( 

1756 gt in self.key_to_row and 

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

1758 

1759 self.trace_selector = lambda x: \ 

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

1761 and trace_selector(x) 

1762 

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

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

1765 self.show_all: 

1766 

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

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

1769 tlen = (tmax - tmin) 

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

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

1772 

1773 def set_time_range(self, tmin, tmax): 

1774 if tmin is None: 

1775 tmin = initial_time_range[0] 

1776 

1777 if tmax is None: 

1778 tmax = initial_time_range[1] 

1779 

1780 if tmin > tmax: 

1781 tmin, tmax = tmax, tmin 

1782 

1783 if tmin == tmax: 

1784 tmin -= 1. 

1785 tmax += 1. 

1786 

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

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

1789 

1790 min_deltat = self.content_deltat_range()[0] 

1791 if (tmax - tmin < min_deltat): 

1792 m = (tmin + tmax) / 2. 

1793 tmin = m - min_deltat/2. 

1794 tmax = m + min_deltat/2. 

1795 

1796 self.time_projection.set_in_range(tmin, tmax) 

1797 self.tmin, self.tmax = tmin, tmax 

1798 

1799 def get_time_range(self): 

1800 return self.tmin, self.tmax 

1801 

1802 def ypart(self, y): 

1803 if y < self.ax_height: 

1804 return -1 

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

1806 return 1 

1807 else: 

1808 return 0 

1809 

1810 def time_fractional_digits(self): 

1811 min_deltat = self.content_deltat_range()[0] 

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

1813 

1814 def write_markers(self, fn=None): 

1815 caption = 'Choose a file name to write markers' 

1816 if not fn: 

1817 fn, _ = qw.QFileDialog.getSaveFileName( 

1818 self, caption, options=qfiledialog_options) 

1819 if fn: 

1820 try: 

1821 Marker.save_markers( 

1822 self.markers, fn, 

1823 fdigits=self.time_fractional_digits()) 

1824 

1825 except Exception as e: 

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

1827 

1828 def write_selected_markers(self, fn=None): 

1829 caption = 'Choose a file name to write selected markers' 

1830 if not fn: 

1831 fn, _ = qw.QFileDialog.getSaveFileName( 

1832 self, caption, options=qfiledialog_options) 

1833 if fn: 

1834 try: 

1835 Marker.save_markers( 

1836 self.iter_selected_markers(), 

1837 fn, 

1838 fdigits=self.time_fractional_digits()) 

1839 

1840 except Exception as e: 

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

1842 

1843 def read_events(self, fn=None): 

1844 ''' 

1845 Open QFileDialog to open, read and add 

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

1847 representation to the pile viewer. 

1848 ''' 

1849 caption = 'Selet one or more files to open' 

1850 if not fn: 

1851 fn, _ = qw.QFileDialog.getOpenFileName( 

1852 self, caption, options=qfiledialog_options) 

1853 if fn: 

1854 try: 

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

1856 self.associate_phases_to_events() 

1857 

1858 except Exception as e: 

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

1860 

1861 def read_markers(self, fn=None): 

1862 ''' 

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

1864 ''' 

1865 caption = 'Selet one or more marker files to open' 

1866 if not fn: 

1867 fn, _ = qw.QFileDialog.getOpenFileName( 

1868 self, caption, options=qfiledialog_options) 

1869 if fn: 

1870 try: 

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

1872 self.associate_phases_to_events() 

1873 

1874 except Exception as e: 

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

1876 

1877 def associate_phases_to_events(self): 

1878 associate_phases_to_events(self.markers) 

1879 

1880 def add_marker(self, marker): 

1881 # need index to inform QAbstactTableModel about upcoming change, 

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

1883 self.markers.insert(marker) 

1884 i = self.markers.remove(marker) 

1885 

1886 self.begin_markers_add.emit(i, i) 

1887 self.markers.insert(marker) 

1888 self.end_markers_add.emit() 

1889 self.markers_deltat_max = max( 

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

1891 

1892 def add_markers(self, markers): 

1893 if not self.markers: 

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

1895 self.markers.insert_many(markers) 

1896 self.end_markers_add.emit() 

1897 self.update_markers_deltat_max() 

1898 else: 

1899 for marker in markers: 

1900 self.add_marker(marker) 

1901 

1902 def update_markers_deltat_max(self): 

1903 if self.markers: 

1904 self.markers_deltat_max = max( 

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

1906 

1907 def remove_marker(self, marker): 

1908 ''' 

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

1910 

1911 :param marker: 

1912 :py:class:`~pyrocko.gui.snuffler.marker.Marker` (or subclass) 

1913 instance 

1914 ''' 

1915 

1916 if marker is self.active_event_marker: 

1917 self.deactivate_event_marker() 

1918 

1919 try: 

1920 i = self.markers.index(marker) 

1921 self.begin_markers_remove.emit(i, i) 

1922 self.markers.remove_at(i) 

1923 self.end_markers_remove.emit() 

1924 except ValueError: 

1925 pass 

1926 

1927 def remove_markers(self, markers): 

1928 ''' 

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

1930 

1931 :param markers: 

1932 list of :py:class:`~pyrocko.gui.snuffler.marker.Marker` (or 

1933 subclass) instances 

1934 ''' 

1935 

1936 if markers is self.markers: 

1937 markers = list(markers) 

1938 

1939 for marker in markers: 

1940 self.remove_marker(marker) 

1941 

1942 self.update_markers_deltat_max() 

1943 

1944 def remove_selected_markers(self): 

1945 def delete_segment(istart, iend): 

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

1947 for _ in range(iend - istart): 

1948 self.markers.remove_at(istart) 

1949 

1950 self.end_markers_remove.emit() 

1951 

1952 istart = None 

1953 ipos = 0 

1954 markers = self.markers 

1955 nmarkers = len(self.markers) 

1956 while ipos < nmarkers: 

1957 marker = markers[ipos] 

1958 if marker.is_selected(): 

1959 if marker is self.active_event_marker: 

1960 self.deactivate_event_marker() 

1961 

1962 if istart is None: 

1963 istart = ipos 

1964 else: 

1965 if istart is not None: 

1966 delete_segment(istart, ipos) 

1967 nmarkers -= ipos - istart 

1968 ipos = istart - 1 

1969 istart = None 

1970 

1971 ipos += 1 

1972 

1973 if istart is not None: 

1974 delete_segment(istart, ipos) 

1975 

1976 self.update_markers_deltat_max() 

1977 

1978 def selected_markers(self): 

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

1980 

1981 def iter_selected_markers(self): 

1982 for marker in self.markers: 

1983 if marker.is_selected(): 

1984 yield marker 

1985 

1986 def get_markers(self): 

1987 return self.markers 

1988 

1989 def mousePressEvent(self, mouse_ev): 

1990 '' 

1991 self.show_all = False 

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

1993 

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

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

1996 if self.picking: 

1997 if self.picking_down is None: 

1998 self.picking_down = ( 

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

2000 mouse_ev.y()) 

2001 

2002 elif marker is not None: 

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

2004 self.deselect_all() 

2005 marker.selected = True 

2006 self.emit_selected_markers() 

2007 self.update() 

2008 else: 

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

2010 self.track_trange = self.tmin, self.tmax 

2011 

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

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

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

2015 self.update_status() 

2016 

2017 def mouseReleaseEvent(self, mouse_ev): 

2018 '' 

2019 if self.ignore_releases: 

2020 self.ignore_releases -= 1 

2021 return 

2022 

2023 if self.picking: 

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

2025 self.emit_selected_markers() 

2026 

2027 if self.track_start: 

2028 self.update() 

2029 

2030 self.track_start = None 

2031 self.track_trange = None 

2032 self.update_status() 

2033 

2034 def mouseDoubleClickEvent(self, mouse_ev): 

2035 '' 

2036 self.show_all = False 

2037 self.start_picking(None) 

2038 self.ignore_releases = 1 

2039 

2040 def mouseMoveEvent(self, mouse_ev): 

2041 '' 

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

2043 

2044 if self.picking: 

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

2046 

2047 elif self.track_start is not None: 

2048 x0, y0 = self.track_start 

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

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

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

2052 dy = 0 

2053 

2054 tmin0, tmax0 = self.track_trange 

2055 

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

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

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

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

2060 

2061 self.interrupt_following() 

2062 self.set_time_range( 

2063 tmin0 - dt - dtr*frac, 

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

2065 

2066 self.update() 

2067 else: 

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

2069 

2070 self.update_status() 

2071 

2072 def nslc_ids_under_cursor(self, x, y): 

2073 ftrack = self.track_to_screen.rev(y) 

2074 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

2075 return nslc_ids 

2076 

2077 def marker_under_cursor(self, x, y): 

2078 mouset = self.time_projection.rev(x) 

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

2080 relevant_nslc_ids = None 

2081 for marker in self.markers: 

2082 if marker.kind not in self.visible_marker_kinds: 

2083 continue 

2084 

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

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

2087 

2088 if relevant_nslc_ids is None: 

2089 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2090 

2091 marker_nslc_ids = marker.get_nslc_ids() 

2092 if not marker_nslc_ids: 

2093 return marker 

2094 

2095 for nslc_id in marker_nslc_ids: 

2096 if nslc_id in relevant_nslc_ids: 

2097 return marker 

2098 

2099 def hoovering(self, x, y): 

2100 mouset = self.time_projection.rev(x) 

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

2102 needupdate = False 

2103 haveone = False 

2104 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y) 

2105 for marker in self.markers: 

2106 if marker.kind not in self.visible_marker_kinds: 

2107 continue 

2108 

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

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

2111 

2112 if state: 

2113 xstate = False 

2114 

2115 marker_nslc_ids = marker.get_nslc_ids() 

2116 if not marker_nslc_ids: 

2117 xstate = True 

2118 

2119 for nslc in relevant_nslc_ids: 

2120 if marker.match_nslc(nslc): 

2121 xstate = True 

2122 

2123 state = xstate 

2124 

2125 if state: 

2126 haveone = True 

2127 oldstate = marker.is_alerted() 

2128 if oldstate != state: 

2129 needupdate = True 

2130 marker.set_alerted(state) 

2131 if state: 

2132 self.message = marker.hoover_message() 

2133 

2134 if not haveone: 

2135 self.message = None 

2136 

2137 if needupdate: 

2138 self.update() 

2139 

2140 def event(self, event): 

2141 '' 

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

2143 self.keyPressEvent(event) 

2144 return True 

2145 else: 

2146 return base.event(self, event) 

2147 

2148 def keyPressEvent(self, key_event): 

2149 '' 

2150 self.show_all = False 

2151 dt = self.tmax - self.tmin 

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

2153 

2154 key = key_event.key() 

2155 try: 

2156 keytext = str(key_event.text()) 

2157 except UnicodeEncodeError: 

2158 return 

2159 

2160 if key == qc.Qt.Key_Space: 

2161 self.interrupt_following() 

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

2163 

2164 elif key == qc.Qt.Key_Up: 

2165 for m in self.selected_markers(): 

2166 if isinstance(m, PhaseMarker): 

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

2168 p = 0 

2169 else: 

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

2171 m.set_polarity(p) 

2172 

2173 elif key == qc.Qt.Key_Down: 

2174 for m in self.selected_markers(): 

2175 if isinstance(m, PhaseMarker): 

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

2177 p = 0 

2178 else: 

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

2180 m.set_polarity(p) 

2181 

2182 elif key == qc.Qt.Key_B: 

2183 dt = self.tmax - self.tmin 

2184 self.interrupt_following() 

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

2186 

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

2188 self.interrupt_following() 

2189 

2190 tgo = None 

2191 

2192 class TraceDummy(object): 

2193 def __init__(self, marker): 

2194 self._marker = marker 

2195 

2196 @property 

2197 def nslc_id(self): 

2198 return self._marker.one_nslc() 

2199 

2200 def marker_to_itrack(marker): 

2201 try: 

2202 return self.key_to_row.get( 

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

2204 

2205 except MarkerOneNSLCRequired: 

2206 return -1 

2207 

2208 emarker, pmarkers = self.get_active_markers() 

2209 pmarkers = [ 

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

2211 pmarkers.sort(key=lambda m: ( 

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

2213 

2214 if key == qc.Qt.Key_Backtab: 

2215 pmarkers.reverse() 

2216 

2217 smarkers = self.selected_markers() 

2218 iselected = [] 

2219 for sm in smarkers: 

2220 try: 

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

2222 except ValueError: 

2223 pass 

2224 

2225 if iselected: 

2226 icurrent = max(iselected) + 1 

2227 else: 

2228 icurrent = 0 

2229 

2230 if icurrent < len(pmarkers): 

2231 self.deselect_all() 

2232 cmarker = pmarkers[icurrent] 

2233 cmarker.selected = True 

2234 tgo = cmarker.tmin 

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

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

2237 

2238 itrack = marker_to_itrack(cmarker) 

2239 if itrack != -1: 

2240 if itrack < self.shown_tracks_range[0]: 

2241 self.scroll_tracks( 

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

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

2244 self.scroll_tracks( 

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

2246 

2247 if itrack not in self.track_to_nslc_ids: 

2248 self.go_to_selection() 

2249 

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

2251 smarkers = self.selected_markers() 

2252 tgo = None 

2253 dir = str(keytext) 

2254 if smarkers: 

2255 tmid = smarkers[0].tmin 

2256 for smarker in smarkers: 

2257 if dir == 'n': 

2258 tmid = max(smarker.tmin, tmid) 

2259 else: 

2260 tmid = min(smarker.tmin, tmid) 

2261 

2262 tgo = tmid 

2263 

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

2265 for marker in sorted( 

2266 self.markers, 

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

2268 

2269 t = marker.tmin 

2270 if t > tmid and \ 

2271 marker.kind in self.visible_marker_kinds and \ 

2272 (dir == 'n' or 

2273 isinstance(marker, EventMarker)): 

2274 

2275 self.deselect_all() 

2276 marker.selected = True 

2277 tgo = t 

2278 break 

2279 else: 

2280 for marker in sorted( 

2281 self.markers, 

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

2283 reverse=True): 

2284 

2285 t = marker.tmin 

2286 if t < tmid and \ 

2287 marker.kind in self.visible_marker_kinds and \ 

2288 (dir == 'p' or 

2289 isinstance(marker, EventMarker)): 

2290 self.deselect_all() 

2291 marker.selected = True 

2292 tgo = t 

2293 break 

2294 

2295 if tgo is not None: 

2296 self.interrupt_following() 

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

2298 

2299 elif keytext == 'r': 

2300 if self.pile.reload_modified(): 

2301 self.reloaded = True 

2302 

2303 elif keytext == 'R': 

2304 self.setup_snufflings() 

2305 

2306 elif key == qc.Qt.Key_Backspace: 

2307 self.remove_selected_markers() 

2308 

2309 elif keytext == 'a': 

2310 for marker in self.markers: 

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

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

2313 marker.kind in self.visible_marker_kinds): 

2314 marker.selected = True 

2315 else: 

2316 marker.selected = False 

2317 

2318 elif keytext == 'A': 

2319 for marker in self.markers: 

2320 if marker.kind in self.visible_marker_kinds: 

2321 marker.selected = True 

2322 

2323 elif keytext == 'd': 

2324 self.deselect_all() 

2325 

2326 elif keytext == 'E': 

2327 self.deactivate_event_marker() 

2328 

2329 elif keytext == 'e': 

2330 markers = self.selected_markers() 

2331 event_markers_in_spe = [ 

2332 marker for marker in markers 

2333 if not isinstance(marker, PhaseMarker)] 

2334 

2335 phase_markers = [ 

2336 marker for marker in markers 

2337 if isinstance(marker, PhaseMarker)] 

2338 

2339 if len(event_markers_in_spe) == 1: 

2340 event_marker = event_markers_in_spe[0] 

2341 if not isinstance(event_marker, EventMarker): 

2342 nslcs = list(event_marker.nslc_ids) 

2343 lat, lon = 0.0, 0.0 

2344 old = self.get_active_event() 

2345 if len(nslcs) == 1: 

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

2347 elif old is not None: 

2348 lat, lon = old.lat, old.lon 

2349 

2350 event_marker.convert_to_event_marker(lat, lon) 

2351 

2352 self.set_active_event_marker(event_marker) 

2353 event = event_marker.get_event() 

2354 for marker in phase_markers: 

2355 marker.set_event(event) 

2356 

2357 else: 

2358 for marker in event_markers_in_spe: 

2359 marker.convert_to_event_marker() 

2360 

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

2362 for marker in self.selected_markers(): 

2363 marker.set_kind(int(keytext)) 

2364 self.emit_selected_markers() 

2365 

2366 elif key in fkey_map: 

2367 self.handle_fkeys(key) 

2368 

2369 elif key == qc.Qt.Key_Escape: 

2370 if self.picking: 

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

2372 

2373 elif key == qc.Qt.Key_PageDown: 

2374 self.scroll_tracks( 

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

2376 

2377 elif key == qc.Qt.Key_PageUp: 

2378 self.scroll_tracks( 

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

2380 

2381 elif key == qc.Qt.Key_Plus: 

2382 self.zoom_tracks(0., 1.) 

2383 

2384 elif key == qc.Qt.Key_Minus: 

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

2386 

2387 elif key == qc.Qt.Key_Equal: 

2388 ntracks_shown = self.shown_tracks_range[1] - \ 

2389 self.shown_tracks_range[0] 

2390 dtracks = self.initial_ntracks_shown_max - ntracks_shown 

2391 self.zoom_tracks(0., dtracks) 

2392 

2393 elif key == qc.Qt.Key_Colon: 

2394 self.want_input.emit() 

2395 

2396 elif keytext == 'f': 

2397 self.toggle_fullscreen() 

2398 

2399 elif keytext == 'g': 

2400 self.go_to_selection() 

2401 

2402 elif keytext == 'G': 

2403 self.go_to_selection(tight=True) 

2404 

2405 elif keytext == 'm': 

2406 self.toggle_marker_editor() 

2407 

2408 elif keytext == 'c': 

2409 self.toggle_main_controls() 

2410 

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

2412 dir = 1 

2413 amount = 1 

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

2415 dir = -1 

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

2417 amount = 10 

2418 self.nudge_selected_markers(dir*amount) 

2419 else: 

2420 super().keyPressEvent(key_event) 

2421 

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

2423 self.emit_selected_markers() 

2424 

2425 self.update() 

2426 self.update_status() 

2427 

2428 def handle_fkeys(self, key): 

2429 self.set_phase_kind( 

2430 self.selected_markers(), 

2431 fkey_map[key] + 1) 

2432 self.emit_selected_markers() 

2433 

2434 def emit_selected_markers(self): 

2435 ibounds = [] 

2436 last_selected = False 

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

2438 this_selected = marker.is_selected() 

2439 if this_selected != last_selected: 

2440 ibounds.append(imarker) 

2441 

2442 last_selected = this_selected 

2443 

2444 if last_selected: 

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

2446 

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

2448 self.n_selected_markers = sum( 

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

2450 self.marker_selection_changed.emit(chunks) 

2451 

2452 def toggle_marker_editor(self): 

2453 self.panel_parent.toggle_marker_editor() 

2454 

2455 def toggle_main_controls(self): 

2456 self.panel_parent.toggle_main_controls() 

2457 

2458 def nudge_selected_markers(self, npixels): 

2459 a, b = self.time_projection.ur 

2460 c, d = self.time_projection.xr 

2461 for marker in self.selected_markers(): 

2462 if not isinstance(marker, EventMarker): 

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

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

2465 

2466 def toggle_fullscreen(self): 

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

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

2469 self.window().showNormal() 

2470 else: 

2471 if is_macos: 

2472 self.window().showMaximized() 

2473 else: 

2474 self.window().showFullScreen() 

2475 

2476 def about(self): 

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

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

2479 txt = f.read() 

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

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

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

2483 

2484 def help(self): 

2485 class MyScrollArea(qw.QScrollArea): 

2486 

2487 def sizeHint(self): 

2488 s = qc.QSize() 

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

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

2491 return s 

2492 

2493 with open(pyrocko.util.data_file( 

2494 'snuffler_help.html')) as f: 

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

2496 

2497 with open(pyrocko.util.data_file( 

2498 'snuffler_help_epilog.html')) as f: 

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

2500 

2501 for h in [hcheat, hepilog]: 

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

2503 h.setWordWrap(True) 

2504 

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

2506 

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

2508 scroller = qw.QScrollArea() 

2509 frame = qw.QFrame(scroller) 

2510 frame.setLineWidth(0) 

2511 layout = qw.QVBoxLayout() 

2512 layout.setContentsMargins(0, 0, 0, 0) 

2513 layout.setSpacing(0) 

2514 frame.setLayout(layout) 

2515 scroller.setWidget(frame) 

2516 scroller.setWidgetResizable(True) 

2517 frame.setBackgroundRole(qg.QPalette.Base) 

2518 for h in labels: 

2519 h.setParent(frame) 

2520 h.setMargin(3) 

2521 h.setTextInteractionFlags( 

2522 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse) 

2523 h.setBackgroundRole(qg.QPalette.Base) 

2524 layout.addWidget(h) 

2525 h.linkActivated.connect( 

2526 self.open_link) 

2527 

2528 if self.panel_parent is not None: 

2529 if target == 'panel': 

2530 self.panel_parent.add_panel( 

2531 name, scroller, True, volatile=False) 

2532 else: 

2533 self.panel_parent.add_tab(name, scroller) 

2534 

2535 def open_link(self, link): 

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

2537 

2538 def wheelEvent(self, wheel_event): 

2539 '' 

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

2541 

2542 n = self.wheel_pos // 120 

2543 self.wheel_pos = self.wheel_pos % 120 

2544 if n == 0: 

2545 return 

2546 

2547 amount = max( 

2548 1., 

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

2550 wdelta = amount * n 

2551 

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

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

2554 / (trmax-trmin) 

2555 

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

2557 self.zoom_tracks(anchor, wdelta) 

2558 else: 

2559 self.scroll_tracks(-wdelta) 

2560 

2561 def dragEnterEvent(self, event): 

2562 '' 

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

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

2565 event.setDropAction(qc.Qt.LinkAction) 

2566 event.accept() 

2567 

2568 def dropEvent(self, event): 

2569 '' 

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

2571 paths = list( 

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

2573 event.acceptProposedAction() 

2574 self.load(paths) 

2575 

2576 def get_phase_name(self, kind): 

2577 return self.config.get_phase_name(kind) 

2578 

2579 def set_phase_kind(self, markers, kind): 

2580 phasename = self.get_phase_name(kind) 

2581 

2582 for marker in markers: 

2583 if isinstance(marker, PhaseMarker): 

2584 if kind == 10: 

2585 marker.convert_to_marker() 

2586 else: 

2587 marker.set_phasename(phasename) 

2588 marker.set_event(self.get_active_event()) 

2589 

2590 elif isinstance(marker, EventMarker): 

2591 pass 

2592 

2593 else: 

2594 if kind != 10: 

2595 event = self.get_active_event() 

2596 marker.convert_to_phase_marker( 

2597 event, phasename, None, False) 

2598 

2599 def set_ntracks(self, ntracks): 

2600 if self.ntracks != ntracks: 

2601 self.ntracks = ntracks 

2602 if self.shown_tracks_range is not None: 

2603 low, high = self.shown_tracks_range 

2604 else: 

2605 low, high = 0, self.ntracks 

2606 

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

2608 

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

2610 

2611 low, high = range 

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

2613 high = min(self.ntracks, high) 

2614 low = max(0, low) 

2615 high = max(1, high) 

2616 

2617 if start is None: 

2618 start = float(low) 

2619 

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

2621 self.shown_tracks_range = low, high 

2622 self.shown_tracks_start = start 

2623 

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

2625 

2626 def scroll_tracks(self, shift): 

2627 shown = self.shown_tracks_range 

2628 shiftmin = -shown[0] 

2629 shiftmax = self.ntracks-shown[1] 

2630 shift = max(shiftmin, shift) 

2631 shift = min(shiftmax, shift) 

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

2633 

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

2635 

2636 self.update() 

2637 

2638 def zoom_tracks(self, anchor, delta): 

2639 ntracks_shown = self.shown_tracks_range[1] \ 

2640 - self.shown_tracks_range[0] 

2641 

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

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

2644 return 

2645 

2646 ntracks_shown += int(round(delta)) 

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

2648 

2649 u = self.shown_tracks_start 

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

2651 nv = nu + ntracks_shown 

2652 if nv > self.ntracks: 

2653 nu -= nv - self.ntracks 

2654 nv -= nv - self.ntracks 

2655 

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

2657 

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

2659 - self.shown_tracks_range[0] 

2660 

2661 self.update() 

2662 

2663 def content_time_range(self): 

2664 pile = self.get_pile() 

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

2666 if tmin is None: 

2667 tmin = initial_time_range[0] 

2668 if tmax is None: 

2669 tmax = initial_time_range[1] 

2670 

2671 return tmin, tmax 

2672 

2673 def content_deltat_range(self): 

2674 pile = self.get_pile() 

2675 

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

2677 

2678 if deltatmin is None: 

2679 deltatmin = 0.001 

2680 

2681 if deltatmax is None: 

2682 deltatmax = 1000.0 

2683 

2684 return deltatmin, deltatmax 

2685 

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

2687 if tmax < tmin: 

2688 tmin, tmax = tmax, tmin 

2689 

2690 deltatmin = self.content_deltat_range()[0] 

2691 dt = deltatmin * self.visible_length * 0.95 

2692 

2693 if dt == 0.0: 

2694 dt = 1.0 

2695 

2696 if tight: 

2697 if tmax != tmin: 

2698 dtm = tmax - tmin 

2699 tmin -= dtm*0.1 

2700 tmax += dtm*0.1 

2701 return tmin, tmax 

2702 else: 

2703 tcenter = (tmin + tmax) / 2. 

2704 tmin = tcenter - 0.5*dt 

2705 tmax = tcenter + 0.5*dt 

2706 return tmin, tmax 

2707 

2708 if tmax-tmin < dt: 

2709 vmin, vmax = self.get_time_range() 

2710 dt = min(vmax - vmin, dt) 

2711 

2712 tcenter = (tmin+tmax)/2. 

2713 etmin, etmax = tmin, tmax 

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

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

2716 dtm = tmax-tmin 

2717 if etmin == tmin: 

2718 tmin -= dtm*0.1 

2719 if etmax == tmax: 

2720 tmax += dtm*0.1 

2721 

2722 else: 

2723 dtm = tmax-tmin 

2724 tmin -= dtm*0.1 

2725 tmax += dtm*0.1 

2726 

2727 return tmin, tmax 

2728 

2729 def go_to_selection(self, tight=False): 

2730 markers = self.selected_markers() 

2731 if markers: 

2732 tmax, tmin = self.content_time_range() 

2733 for marker in markers: 

2734 tmin = min(tmin, marker.tmin) 

2735 tmax = max(tmax, marker.tmax) 

2736 

2737 else: 

2738 if tight: 

2739 vmin, vmax = self.get_time_range() 

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

2741 else: 

2742 tmin, tmax = self.content_time_range() 

2743 

2744 tmin, tmax = self.make_good_looking_time_range( 

2745 tmin, tmax, tight=tight) 

2746 

2747 self.interrupt_following() 

2748 self.set_time_range(tmin, tmax) 

2749 self.update() 

2750 

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

2752 tmax = t 

2753 if tlen is not None: 

2754 tmax = t+tlen 

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

2756 self.interrupt_following() 

2757 self.set_time_range(tmin, tmax) 

2758 self.update() 

2759 

2760 def go_to_event_by_name(self, name): 

2761 for marker in self.markers: 

2762 if isinstance(marker, EventMarker): 

2763 event = marker.get_event() 

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

2765 tmin, tmax = self.make_good_looking_time_range( 

2766 event.time, event.time) 

2767 

2768 self.interrupt_following() 

2769 self.set_time_range(tmin, tmax) 

2770 

2771 def printit(self): 

2772 from ..qt_compat import qprint 

2773 printer = qprint.QPrinter() 

2774 printer.setOrientation(qprint.QPrinter.Landscape) 

2775 

2776 dialog = qprint.QPrintDialog(printer, self) 

2777 dialog.setWindowTitle('Print') 

2778 

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

2780 return 

2781 

2782 painter = qg.QPainter() 

2783 painter.begin(printer) 

2784 page = printer.pageRect() 

2785 self.drawit( 

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

2787 

2788 painter.end() 

2789 

2790 def savesvg(self, fn=None): 

2791 

2792 if not fn: 

2793 fn, _ = qw.QFileDialog.getSaveFileName( 

2794 self, 

2795 'Save as SVG|PNG', 

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

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

2798 options=qfiledialog_options) 

2799 

2800 if fn == '': 

2801 return 

2802 

2803 fn = str(fn) 

2804 

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

2806 try: 

2807 w, h = 842, 595 

2808 margin = 0.025 

2809 m = max(w, h)*margin 

2810 

2811 generator = qsvg.QSvgGenerator() 

2812 generator.setFileName(fn) 

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

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

2815 

2816 painter = qg.QPainter() 

2817 painter.begin(generator) 

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

2819 painter.end() 

2820 

2821 except Exception as e: 

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

2823 

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

2825 pixmap = self.grab() 

2826 

2827 try: 

2828 pixmap.save(fn) 

2829 

2830 except Exception as e: 

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

2832 

2833 else: 

2834 self.fail( 

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

2836 '".png".') 

2837 

2838 def paintEvent(self, paint_ev): 

2839 ''' 

2840 Called by QT whenever widget needs to be painted. 

2841 ''' 

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

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

2844 if self.in_paint_event: 

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

2846 return 

2847 

2848 self.in_paint_event = True 

2849 

2850 painter = qg.QPainter(self) 

2851 

2852 if self.menuitem_antialias.isChecked(): 

2853 painter.setRenderHint(qg.QPainter.Antialiasing) 

2854 

2855 self.drawit(painter) 

2856 

2857 logger.debug( 

2858 'Time spent drawing: ' 

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

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

2861 (self.timer_draw - self.timer_cutout)) 

2862 

2863 logger.debug( 

2864 'Time spent processing:' 

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

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

2867 self.timer_cutout.get()) 

2868 

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

2870 self.time_last_painted = time.time() 

2871 self.in_paint_event = False 

2872 

2873 def determine_box_styles(self): 

2874 

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

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

2877 istyle = 0 

2878 trace_styles = {} 

2879 for itr, tr in enumerate(traces): 

2880 if itr > 0: 

2881 other = traces[itr-1] 

2882 if not ( 

2883 other.nslc_id == tr.nslc_id 

2884 and other.deltat == tr.deltat 

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

2886 < gap_lap_tolerance*tr.deltat): 

2887 

2888 istyle += 1 

2889 

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

2891 

2892 self.trace_styles = trace_styles 

2893 

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

2895 

2896 for v_projection in track_projections.values(): 

2897 v_projection.set_in_range(0., 1.) 

2898 

2899 def selector(x): 

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

2901 

2902 if self.trace_filter is not None: 

2903 def tselector(x): 

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

2905 

2906 else: 

2907 tselector = selector 

2908 

2909 traces = list(self.pile.iter_traces( 

2910 group_selector=selector, trace_selector=tselector)) 

2911 

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

2913 

2914 def drawbox(itrack, istyle, traces): 

2915 v_projection = track_projections[itrack] 

2916 dvmin = v_projection(0.) 

2917 dvmax = v_projection(1.) 

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

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

2920 

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

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

2923 p.fillRect(rect, style.fill_brush) 

2924 p.setPen(style.frame_pen) 

2925 p.drawRect(rect) 

2926 

2927 traces_by_style = {} 

2928 for itr, tr in enumerate(traces): 

2929 gt = self.gather(tr) 

2930 if gt not in self.key_to_row: 

2931 continue 

2932 

2933 itrack = self.key_to_row[gt] 

2934 if itrack not in track_projections: 

2935 continue 

2936 

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

2938 

2939 if len(traces) < 500: 

2940 drawbox(itrack, istyle, [tr]) 

2941 else: 

2942 if (itrack, istyle) not in traces_by_style: 

2943 traces_by_style[itrack, istyle] = [] 

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

2945 

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

2947 drawbox(itrack, istyle, traces) 

2948 

2949 def draw_visible_markers( 

2950 self, p, vcenter_projection, primary_pen): 

2951 

2952 try: 

2953 markers = self.markers.with_key_in_limited( 

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

2955 

2956 except pyrocko.pile.TooMany: 

2957 tmin = self.markers[0].tmin 

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

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

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

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

2962 v0, _ = vcenter_projection.get_out_range() 

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

2964 

2965 p.save() 

2966 

2967 pen = qg.QPen(primary_pen) 

2968 pen.setWidth(2) 

2969 pen.setStyle(qc.Qt.DotLine) 

2970 # pat = [5., 3.] 

2971 # pen.setDashPattern(pat) 

2972 p.setPen(pen) 

2973 

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

2975 s_selected = ' (all selected)' 

2976 elif self.n_selected_markers > 0: 

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

2978 else: 

2979 s_selected = '' 

2980 

2981 draw_label( 

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

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

2984 label_bg, 'LB') 

2985 

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

2987 p.drawLine(line) 

2988 p.restore() 

2989 

2990 return 

2991 

2992 for marker in markers: 

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

2994 and marker.kind in self.visible_marker_kinds: 

2995 

2996 marker.draw( 

2997 p, self.time_projection, vcenter_projection, 

2998 with_label=True) 

2999 

3000 def get_squirrel(self): 

3001 try: 

3002 return self.pile._squirrel 

3003 except AttributeError: 

3004 return None 

3005 

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

3007 sq = self.get_squirrel() 

3008 if sq is None: 

3009 return 

3010 

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

3012 v_projection = track_projections[itrack] 

3013 dvmin = v_projection(0.) 

3014 dvmax = v_projection(1.) 

3015 dtmin = time_projection.clipped(tmin, 0) 

3016 dtmax = time_projection.clipped(tmax, 1) 

3017 

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

3019 p.fillRect(rect, style.fill_brush) 

3020 p.setPen(style.frame_pen) 

3021 p.drawRect(rect) 

3022 

3023 pattern_list = [] 

3024 pattern_to_itrack = {} 

3025 for key in self.track_keys: 

3026 itrack = self.key_to_row[key] 

3027 if itrack not in track_projections: 

3028 continue 

3029 

3030 pattern = self.track_patterns[itrack] 

3031 pattern_to_itrack[tuple(pattern)] = itrack 

3032 pattern_list.append(tuple(pattern)) 

3033 

3034 vmin, vmax = self.get_time_range() 

3035 

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

3037 for coverage in sq.get_coverage( 

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

3039 itrack = pattern_to_itrack[coverage.pattern.nslc] 

3040 

3041 if coverage.changes is None: 

3042 drawbox( 

3043 itrack, coverage.tmin, coverage.tmax, 

3044 box_styles_coverage[kind][0]) 

3045 else: 

3046 t = None 

3047 pcount = 0 

3048 for tb, count in coverage.changes: 

3049 if t is not None and tb > t: 

3050 if pcount > 0: 

3051 drawbox( 

3052 itrack, t, tb, 

3053 box_styles_coverage[kind][ 

3054 min(len(box_styles_coverage)-1, 

3055 pcount)]) 

3056 

3057 t = tb 

3058 pcount = count 

3059 

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

3061 ''' 

3062 This performs the actual drawing. 

3063 ''' 

3064 

3065 self.timer_draw.start() 

3066 show_boxes = self.menuitem_showboxes.isChecked() 

3067 sq = self.get_squirrel() 

3068 

3069 if self.gather is None: 

3070 self.set_gathering() 

3071 

3072 if self.pile_has_changed: 

3073 

3074 if not self.sortingmode_change_delayed(): 

3075 self.sortingmode_change() 

3076 

3077 if show_boxes and sq is None: 

3078 self.determine_box_styles() 

3079 

3080 self.pile_has_changed = False 

3081 

3082 if h is None: 

3083 h = float(self.height()) 

3084 if w is None: 

3085 w = float(self.width()) 

3086 

3087 if printmode: 

3088 primary_color = (0, 0, 0) 

3089 else: 

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

3091 

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

3093 

3094 ax_h = float(self.ax_height) 

3095 

3096 vbottom_ax_projection = Projection() 

3097 vtop_ax_projection = Projection() 

3098 vcenter_projection = Projection() 

3099 

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

3101 vbottom_ax_projection.set_out_range(h-ax_h, h) 

3102 vtop_ax_projection.set_out_range(0., ax_h) 

3103 vcenter_projection.set_out_range(ax_h, h-ax_h) 

3104 vcenter_projection.set_in_range(0., 1.) 

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

3106 

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

3108 track_projections = {} 

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

3110 proj = Projection() 

3111 proj.set_out_range( 

3112 self.track_to_screen(i+0.05), 

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

3114 

3115 track_projections[i] = proj 

3116 

3117 if self.tmin > self.tmax: 

3118 return 

3119 

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

3121 vbottom_ax_projection.set_in_range(0, ax_h) 

3122 

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

3124 

3125 yscaler = pyrocko.plot.AutoScaler() 

3126 

3127 p.setPen(primary_pen) 

3128 

3129 font = qg.QFont() 

3130 font.setBold(True) 

3131 

3132 axannotfont = qg.QFont() 

3133 axannotfont.setBold(True) 

3134 axannotfont.setPointSize(8) 

3135 

3136 processed_traces = self.prepare_cutout2( 

3137 self.tmin, self.tmax, 

3138 trace_selector=self.trace_selector, 

3139 degap=self.menuitem_degap.isChecked(), 

3140 demean=self.menuitem_demean.isChecked()) 

3141 

3142 if not printmode and show_boxes: 

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

3144 or (self.view_mode is ViewMode.Waterfall 

3145 and not processed_traces): 

3146 

3147 if sq is None: 

3148 self.draw_trace_boxes( 

3149 p, self.time_projection, track_projections) 

3150 

3151 else: 

3152 self.draw_coverage( 

3153 p, self.time_projection, track_projections) 

3154 

3155 p.setFont(font) 

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

3157 

3158 color_lookup = dict( 

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

3160 

3161 self.track_to_nslc_ids = {} 

3162 nticks = 0 

3163 annot_labels = [] 

3164 

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

3166 waterfall = self.waterfall 

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

3168 waterfall.set_traces(processed_traces) 

3169 waterfall.set_cmap(self.waterfall_cmap) 

3170 waterfall.set_integrate(self.waterfall_integrate) 

3171 waterfall.set_clip( 

3172 self.waterfall_clip_min, self.waterfall_clip_max) 

3173 waterfall.show_absolute_values( 

3174 self.waterfall_show_absolute) 

3175 

3176 rect = qc.QRectF( 

3177 0, self.ax_height, 

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

3179 ) 

3180 waterfall.draw_waterfall(p, rect=rect) 

3181 

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

3183 show_scales = self.menuitem_showscalerange.isChecked() \ 

3184 or self.menuitem_showscaleaxis.isChecked() 

3185 

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

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

3188 - self.track_to_screen(0.05) 

3189 

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

3191 

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

3193 if self.menuitem_showscaleaxis.isChecked() \ 

3194 else 15 

3195 

3196 yscaler = pyrocko.plot.AutoScaler( 

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

3198 snap=show_scales 

3199 and not self.menuitem_showscaleaxis.isChecked()) 

3200 

3201 data_ranges = pyrocko.trace.minmax( 

3202 processed_traces, 

3203 key=self.scaling_key, 

3204 mode=self.scaling_base[0], 

3205 outer_mode=self.scaling_base[1]) 

3206 

3207 if not self.menuitem_fixscalerange.isChecked(): 

3208 self.old_data_ranges = data_ranges 

3209 else: 

3210 data_ranges.update(self.old_data_ranges) 

3211 

3212 self.apply_scaling_hooks(data_ranges) 

3213 

3214 trace_to_itrack = {} 

3215 track_scaling_keys = {} 

3216 track_scaling_colors = {} 

3217 for trace in processed_traces: 

3218 gt = self.gather(trace) 

3219 if gt not in self.key_to_row: 

3220 continue 

3221 

3222 itrack = self.key_to_row[gt] 

3223 if itrack not in track_projections: 

3224 continue 

3225 

3226 trace_to_itrack[trace] = itrack 

3227 

3228 if itrack not in self.track_to_nslc_ids: 

3229 self.track_to_nslc_ids[itrack] = set() 

3230 

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

3232 

3233 if itrack not in track_scaling_keys: 

3234 track_scaling_keys[itrack] = set() 

3235 

3236 scaling_key = self.scaling_key(trace) 

3237 track_scaling_keys[itrack].add(scaling_key) 

3238 

3239 color = pyrocko.plot.color( 

3240 color_lookup[self.color_gather(trace)]) 

3241 

3242 k = itrack, scaling_key 

3243 if k not in track_scaling_colors \ 

3244 and self.menuitem_colortraces.isChecked(): 

3245 track_scaling_colors[k] = color 

3246 else: 

3247 track_scaling_colors[k] = primary_color 

3248 

3249 # y axes, zero lines 

3250 trace_projections = {} 

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

3252 if itrack not in track_scaling_keys: 

3253 continue 

3254 uoff = 0 

3255 for scaling_key in track_scaling_keys[itrack]: 

3256 data_range = data_ranges[scaling_key] 

3257 dymin, dymax = data_range 

3258 ymin, ymax, yinc = yscaler.make_scale( 

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

3260 iexp = yscaler.make_exp(yinc) 

3261 factor = 10**iexp 

3262 trace_projection = track_projections[itrack].copy() 

3263 trace_projection.set_in_range(ymax, ymin) 

3264 trace_projections[itrack, scaling_key] = \ 

3265 trace_projection 

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

3267 vmin, vmax = trace_projection.get_out_range() 

3268 umax_zeroline = umax 

3269 uoffnext = uoff 

3270 

3271 if show_scales: 

3272 pen = qg.QPen(primary_pen) 

3273 k = itrack, scaling_key 

3274 if k in track_scaling_colors: 

3275 c = qg.QColor(*track_scaling_colors[ 

3276 itrack, scaling_key]) 

3277 

3278 pen.setColor(c) 

3279 

3280 p.setPen(pen) 

3281 if nlinesavail > 3: 

3282 if self.menuitem_showscaleaxis.isChecked(): 

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

3284 ny_annot = int( 

3285 math.floor(ymax/yinc) 

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

3287 

3288 for iy_annot in range(ny_annot): 

3289 y = ymin_annot + iy_annot*yinc 

3290 v = trace_projection(y) 

3291 line = qc.QLineF( 

3292 umax-10-uoff, v, umax-uoff, v) 

3293 

3294 p.drawLine(line) 

3295 if iy_annot == ny_annot - 1 \ 

3296 and iexp != 0: 

3297 sexp = ' &times; ' \ 

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

3299 else: 

3300 sexp = '' 

3301 

3302 snum = num_to_html(y/factor) 

3303 lab = Label( 

3304 p, 

3305 umax-20-uoff, 

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

3307 label_bg=None, 

3308 anchor='MR', 

3309 font=axannotfont, 

3310 color=c) 

3311 

3312 uoffnext = max( 

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

3314 

3315 annot_labels.append(lab) 

3316 if y == 0.: 

3317 umax_zeroline = \ 

3318 umax - 20 \ 

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

3320 - uoff 

3321 else: 

3322 if not show_boxes: 

3323 qpoints = make_QPolygonF( 

3324 [umax-20-uoff, 

3325 umax-10-uoff, 

3326 umax-10-uoff, 

3327 umax-20-uoff], 

3328 [vmax, vmax, vmin, vmin]) 

3329 p.drawPolyline(qpoints) 

3330 

3331 snum = num_to_html(ymin) 

3332 labmin = Label( 

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

3334 label_bg=None, 

3335 anchor='BR', 

3336 font=axannotfont, 

3337 color=c) 

3338 

3339 annot_labels.append(labmin) 

3340 snum = num_to_html(ymax) 

3341 labmax = Label( 

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

3343 label_bg=None, 

3344 anchor='TR', 

3345 font=axannotfont, 

3346 color=c) 

3347 

3348 annot_labels.append(labmax) 

3349 

3350 for lab in (labmin, labmax): 

3351 uoffnext = max( 

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

3353 

3354 if self.menuitem_showzeroline.isChecked(): 

3355 v = trace_projection(0.) 

3356 if vmin <= v <= vmax: 

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

3358 p.drawLine(line) 

3359 

3360 uoff = uoffnext 

3361 

3362 p.setFont(font) 

3363 p.setPen(primary_pen) 

3364 for trace in processed_traces: 

3365 if self.view_mode is not ViewMode.Wiggle: 

3366 break 

3367 

3368 if trace not in trace_to_itrack: 

3369 continue 

3370 

3371 itrack = trace_to_itrack[trace] 

3372 scaling_key = self.scaling_key(trace) 

3373 trace_projection = trace_projections[ 

3374 itrack, scaling_key] 

3375 

3376 vdata = trace_projection(trace.get_ydata()) 

3377 

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

3379 udata_max = float(self.time_projection( 

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

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

3382 

3383 qpoints = make_QPolygonF(udata, vdata) 

3384 

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

3386 vmin, vmax = trace_projection.get_out_range() 

3387 

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

3389 

3390 if self.menuitem_cliptraces.isChecked(): 

3391 p.setClipRect(trackrect) 

3392 

3393 if self.menuitem_colortraces.isChecked(): 

3394 color = pyrocko.plot.color( 

3395 color_lookup[self.color_gather(trace)]) 

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

3397 p.setPen(pen) 

3398 

3399 p.drawPolyline(qpoints) 

3400 

3401 if self.floating_marker: 

3402 self.floating_marker.draw_trace( 

3403 self, p, trace, 

3404 self.time_projection, trace_projection, 1.0) 

3405 

3406 for marker in self.markers.with_key_in( 

3407 self.tmin - self.markers_deltat_max, 

3408 self.tmax): 

3409 

3410 if marker.tmin < self.tmax \ 

3411 and self.tmin < marker.tmax \ 

3412 and marker.kind \ 

3413 in self.visible_marker_kinds: 

3414 marker.draw_trace( 

3415 self, p, trace, self.time_projection, 

3416 trace_projection, 1.0) 

3417 

3418 p.setPen(primary_pen) 

3419 

3420 if self.menuitem_cliptraces.isChecked(): 

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

3422 

3423 if self.floating_marker: 

3424 self.floating_marker.draw( 

3425 p, self.time_projection, vcenter_projection) 

3426 

3427 self.draw_visible_markers( 

3428 p, vcenter_projection, primary_pen) 

3429 

3430 p.setPen(primary_pen) 

3431 while font.pointSize() > 2: 

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

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

3434 - self.track_to_screen(0.05) 

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

3436 if nlinesavail > 1: 

3437 break 

3438 

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

3440 

3441 p.setFont(font) 

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

3443 

3444 for key in self.track_keys: 

3445 itrack = self.key_to_row[key] 

3446 if itrack in track_projections: 

3447 plabel = ' '.join( 

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

3449 lx = 10 

3450 ly = self.track_to_screen(itrack+0.5) 

3451 

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

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

3454 continue 

3455 

3456 contains_cursor = \ 

3457 self.track_to_screen(itrack) \ 

3458 < mouse_pos.y() \ 

3459 < self.track_to_screen(itrack+1) 

3460 

3461 if not contains_cursor: 

3462 continue 

3463 

3464 font_large = p.font() 

3465 font_large.setPointSize(MIN_LABEL_SIZE_PT) 

3466 p.setFont(font_large) 

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

3468 p.setFont(font) 

3469 

3470 for lab in annot_labels: 

3471 lab.draw() 

3472 

3473 self.timer_draw.stop() 

3474 

3475 def see_data_params(self): 

3476 

3477 min_deltat = self.content_deltat_range()[0] 

3478 

3479 # determine padding and downampling requirements 

3480 if self.lowpass is not None: 

3481 deltat_target = 1./self.lowpass * 0.25 

3482 ndecimate = min( 

3483 50, 

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

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

3486 else: 

3487 ndecimate = 1 

3488 tpad = min_deltat*5. 

3489 

3490 if self.highpass is not None: 

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

3492 

3493 nsee_points_per_trace = 5000*10 

3494 tsee = ndecimate*nsee_points_per_trace*min_deltat 

3495 

3496 return ndecimate, tpad, tsee 

3497 

3498 def clean_update(self): 

3499 self.cached_processed_traces = None 

3500 self.update() 

3501 

3502 def get_adequate_tpad(self): 

3503 tpad = 0. 

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

3505 if f is not None: 

3506 tpad = max(tpad, 1.0/f) 

3507 

3508 for snuffling in self.snufflings: 

3509 if snuffling._post_process_hook_enabled \ 

3510 or snuffling._pre_process_hook_enabled: 

3511 

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

3513 

3514 return tpad 

3515 

3516 def prepare_cutout2( 

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

3518 demean=True, nmax=6000): 

3519 

3520 if self.pile.is_empty(): 

3521 return [] 

3522 

3523 nmax = self.visible_length 

3524 

3525 self.timer_cutout.start() 

3526 

3527 tsee = tmax-tmin 

3528 min_deltat_wo_decimate = tsee/nmax 

3529 min_deltat_w_decimate = min_deltat_wo_decimate / 32. 

3530 

3531 min_deltat_allow = min_deltat_wo_decimate 

3532 if self.lowpass is not None: 

3533 target_deltat_lp = 0.25/self.lowpass 

3534 if target_deltat_lp > min_deltat_wo_decimate: 

3535 min_deltat_allow = min_deltat_w_decimate 

3536 

3537 min_deltat_allow = math.exp( 

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

3539 

3540 tmin_ = tmin 

3541 tmax_ = tmax 

3542 

3543 # fetch more than needed? 

3544 if self.menuitem_liberal_fetch.isChecked(): 

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

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

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

3548 

3549 fft_filtering = self.menuitem_fft_filtering.isChecked() 

3550 lphp = self.menuitem_lphp.isChecked() 

3551 ads = self.menuitem_allowdownsampling.isChecked() 

3552 

3553 tpad = self.get_adequate_tpad() 

3554 tpad = max(tpad, tsee) 

3555 

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

3557 vec = ( 

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

3559 self.highpass, fft_filtering, lphp, 

3560 min_deltat_allow, self.rotate, self.shown_tracks_range, 

3561 ads, self.pile.get_update_count()) 

3562 

3563 if (self.cached_vec 

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

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

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

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

3568 and self.cached_processed_traces is not None): 

3569 

3570 logger.debug('Using cached traces') 

3571 processed_traces = self.cached_processed_traces 

3572 

3573 else: 

3574 processed_traces = [] 

3575 if self.pile.deltatmax >= min_deltat_allow: 

3576 

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

3578 def group_selector(gr): 

3579 return gr.deltatmax >= min_deltat_allow 

3580 

3581 kwargs = dict(group_selector=group_selector) 

3582 else: 

3583 kwargs = {} 

3584 

3585 if trace_selector is not None: 

3586 def trace_selectorx(tr): 

3587 return tr.deltat >= min_deltat_allow \ 

3588 and trace_selector(tr) 

3589 else: 

3590 def trace_selectorx(tr): 

3591 return tr.deltat >= min_deltat_allow 

3592 

3593 for traces in self.pile.chopper( 

3594 tmin=tmin, tmax=tmax, tpad=tpad, 

3595 want_incomplete=True, 

3596 degap=degap, 

3597 maxgap=gap_lap_tolerance, 

3598 maxlap=gap_lap_tolerance, 

3599 keep_current_files_open=True, 

3600 trace_selector=trace_selectorx, 

3601 accessor_id=id(self), 

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

3603 include_last=True, **kwargs): 

3604 

3605 if demean: 

3606 for tr in traces: 

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

3608 continue 

3609 y = tr.get_ydata() 

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

3611 

3612 traces = self.pre_process_hooks(traces) 

3613 

3614 for trace in traces: 

3615 

3616 if not (trace.meta 

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

3618 

3619 if fft_filtering: 

3620 but = pyrocko.response.ButterworthResponse 

3621 multres = pyrocko.response.MultiplyResponse 

3622 if self.lowpass is not None \ 

3623 or self.highpass is not None: 

3624 

3625 it = num.arange( 

3626 trace.data_len(), dtype=float) 

3627 detr_data, m, b = detrend( 

3628 it, trace.get_ydata()) 

3629 

3630 trace.set_ydata(detr_data) 

3631 

3632 freqs, fdata = trace.spectrum( 

3633 pad_to_pow2=True, tfade=None) 

3634 

3635 nfreqs = fdata.size 

3636 

3637 key = (trace.deltat, nfreqs) 

3638 

3639 if key not in self.tf_cache: 

3640 resps = [] 

3641 if self.lowpass is not None: 

3642 resps.append(but( 

3643 order=4, 

3644 corner=self.lowpass, 

3645 type='low')) 

3646 

3647 if self.highpass is not None: 

3648 resps.append(but( 

3649 order=4, 

3650 corner=self.highpass, 

3651 type='high')) 

3652 

3653 resp = multres(resps) 

3654 self.tf_cache[key] = \ 

3655 resp.evaluate(freqs) 

3656 

3657 filtered_data = num.fft.irfft( 

3658 fdata*self.tf_cache[key] 

3659 )[:trace.data_len()] 

3660 

3661 retrended_data = retrend( 

3662 it, filtered_data, m, b) 

3663 

3664 trace.set_ydata(retrended_data) 

3665 

3666 else: 

3667 

3668 if ads and self.lowpass is not None: 

3669 while trace.deltat \ 

3670 < min_deltat_wo_decimate: 

3671 

3672 trace.downsample(2, demean=False) 

3673 

3674 fmax = 0.5/trace.deltat 

3675 if not lphp and ( 

3676 self.lowpass is not None 

3677 and self.highpass is not None 

3678 and self.lowpass < fmax 

3679 and self.highpass < fmax 

3680 and self.highpass < self.lowpass): 

3681 

3682 trace.bandpass( 

3683 2, self.highpass, self.lowpass) 

3684 else: 

3685 if self.lowpass is not None: 

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

3687 trace.lowpass( 

3688 4, self.lowpass, 

3689 demean=False) 

3690 

3691 if self.highpass is not None: 

3692 if self.lowpass is None \ 

3693 or self.highpass \ 

3694 < self.lowpass: 

3695 

3696 if self.highpass < \ 

3697 0.5/trace.deltat: 

3698 trace.highpass( 

3699 4, self.highpass, 

3700 demean=False) 

3701 

3702 processed_traces.append(trace) 

3703 

3704 if self.rotate != 0.0: 

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

3706 cphi = math.cos(phi) 

3707 sphi = math.sin(phi) 

3708 for a in processed_traces: 

3709 for b in processed_traces: 

3710 if (a.network == b.network 

3711 and a.station == b.station 

3712 and a.location == b.location 

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

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

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

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

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

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

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

3720 

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

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

3723 a.set_ydata(aydata) 

3724 b.set_ydata(bydata) 

3725 

3726 processed_traces = self.post_process_hooks(processed_traces) 

3727 

3728 self.cached_processed_traces = processed_traces 

3729 self.cached_vec = vec 

3730 

3731 chopped_traces = [] 

3732 for trace in processed_traces: 

3733 chop_tmin = tmin_ - trace.deltat*4 

3734 chop_tmax = tmax_ + trace.deltat*4 

3735 

3736 try: 

3737 ctrace = trace.chop( 

3738 chop_tmin, chop_tmax, 

3739 inplace=False) 

3740 

3741 except pyrocko.trace.NoData: 

3742 continue 

3743 

3744 if ctrace.data_len() < 2: 

3745 continue 

3746 

3747 chopped_traces.append(ctrace) 

3748 

3749 self.timer_cutout.stop() 

3750 return chopped_traces 

3751 

3752 def pre_process_hooks(self, traces): 

3753 for snuffling in self.snufflings: 

3754 if snuffling._pre_process_hook_enabled: 

3755 traces = snuffling.pre_process_hook(traces) 

3756 

3757 return traces 

3758 

3759 def post_process_hooks(self, traces): 

3760 for snuffling in self.snufflings: 

3761 if snuffling._post_process_hook_enabled: 

3762 traces = snuffling.post_process_hook(traces) 

3763 

3764 return traces 

3765 

3766 def visible_length_change(self, ignore=None): 

3767 for menuitem, vlen in self.menuitems_visible_length: 

3768 if menuitem.isChecked(): 

3769 self.visible_length = vlen 

3770 

3771 def scaling_base_change(self, ignore=None): 

3772 for menuitem, scaling_base in self.menuitems_scaling_base: 

3773 if menuitem.isChecked(): 

3774 self.scaling_base = scaling_base 

3775 

3776 def scalingmode_change(self, ignore=None): 

3777 for menuitem, scaling_key in self.menuitems_scaling: 

3778 if menuitem.isChecked(): 

3779 self.scaling_key = scaling_key 

3780 self.update() 

3781 

3782 def apply_scaling_hooks(self, data_ranges): 

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

3784 hook = self.scaling_hooks[k] 

3785 hook(data_ranges) 

3786 

3787 def viewmode_change(self, ignore=True): 

3788 for item, mode in self.menuitems_viewmode: 

3789 if item.isChecked(): 

3790 self.view_mode = mode 

3791 break 

3792 else: 

3793 raise AttributeError('unknown view mode') 

3794 

3795 items_waterfall_disabled = ( 

3796 self.menuitem_showscaleaxis, 

3797 self.menuitem_showscalerange, 

3798 self.menuitem_showzeroline, 

3799 self.menuitem_colortraces, 

3800 self.menuitem_cliptraces, 

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

3802 ) 

3803 

3804 if self.view_mode is ViewMode.Waterfall: 

3805 self.parent().show_colorbar_ctrl(True) 

3806 self.parent().show_gain_ctrl(False) 

3807 

3808 for item in items_waterfall_disabled: 

3809 item.setDisabled(True) 

3810 

3811 self.visible_length = 180. 

3812 else: 

3813 self.parent().show_colorbar_ctrl(False) 

3814 self.parent().show_gain_ctrl(True) 

3815 

3816 for item in items_waterfall_disabled: 

3817 item.setDisabled(False) 

3818 

3819 self.visible_length_change() 

3820 self.update() 

3821 

3822 def set_scaling_hook(self, k, hook): 

3823 self.scaling_hooks[k] = hook 

3824 

3825 def remove_scaling_hook(self, k): 

3826 del self.scaling_hooks[k] 

3827 

3828 def remove_scaling_hooks(self): 

3829 self.scaling_hooks = {} 

3830 

3831 def s_sortingmode_change(self, ignore=None): 

3832 for menuitem, valfunc in self.menuitems_ssorting: 

3833 if menuitem.isChecked(): 

3834 self._ssort = valfunc 

3835 

3836 self.sortingmode_change() 

3837 

3838 def sortingmode_change(self, ignore=None): 

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

3840 if menuitem.isChecked(): 

3841 self.set_gathering(gather, color) 

3842 

3843 self.sortingmode_change_time = time.time() 

3844 

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

3846 self.lowpass = value 

3847 self.passband_check() 

3848 self.tf_cache = {} 

3849 self.update() 

3850 

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

3852 self.highpass = value 

3853 self.passband_check() 

3854 self.tf_cache = {} 

3855 self.update() 

3856 

3857 def passband_check(self): 

3858 if self.highpass and self.lowpass \ 

3859 and self.highpass >= self.lowpass: 

3860 

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

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

3863 'deactivate the highpass.' 

3864 

3865 self.update_status() 

3866 else: 

3867 oldmess = self.message 

3868 self.message = None 

3869 if oldmess is not None: 

3870 self.update_status() 

3871 

3872 def gain_change(self, value, ignore): 

3873 self.gain = value 

3874 self.update() 

3875 

3876 def rot_change(self, value, ignore): 

3877 self.rotate = value 

3878 self.update() 

3879 

3880 def waterfall_cmap_change(self, cmap): 

3881 self.waterfall_cmap = cmap 

3882 self.update() 

3883 

3884 def waterfall_clip_change(self, clip_min, clip_max): 

3885 self.waterfall_clip_min = clip_min 

3886 self.waterfall_clip_max = clip_max 

3887 self.update() 

3888 

3889 def waterfall_show_absolute_change(self, toggle): 

3890 self.waterfall_show_absolute = toggle 

3891 self.update() 

3892 

3893 def waterfall_set_integrate(self, toggle): 

3894 self.waterfall_integrate = toggle 

3895 self.update() 

3896 

3897 def set_selected_markers(self, markers): 

3898 ''' 

3899 Set a list of markers selected 

3900 

3901 :param markers: list of markers 

3902 ''' 

3903 self.deselect_all() 

3904 for m in markers: 

3905 m.selected = True 

3906 

3907 self.update() 

3908 

3909 def deselect_all(self): 

3910 for marker in self.markers: 

3911 marker.selected = False 

3912 

3913 def animate_picking(self): 

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

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

3916 

3917 def get_nslc_ids_for_track(self, ftrack): 

3918 itrack = int(ftrack) 

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

3920 

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

3922 if self.picking: 

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

3924 self.picking = None 

3925 self.picking_down = None 

3926 self.picking_timer.stop() 

3927 self.picking_timer = None 

3928 if not abort: 

3929 self.add_marker(self.floating_marker) 

3930 self.floating_marker.selected = True 

3931 self.emit_selected_markers() 

3932 

3933 self.floating_marker = None 

3934 

3935 def start_picking(self, ignore): 

3936 

3937 if not self.picking: 

3938 self.deselect_all() 

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

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

3941 

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

3943 self.picking.setGeometry( 

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

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

3946 

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

3948 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3950 self.floating_marker.selected = True 

3951 

3952 self.picking_timer = qc.QTimer() 

3953 self.picking_timer.timeout.connect( 

3954 self.animate_picking) 

3955 

3956 self.picking_timer.setInterval(50) 

3957 self.picking_timer.start() 

3958 

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

3960 if self.picking: 

3961 mouset = self.time_projection.rev(x) 

3962 dt = 0.0 

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

3964 if mouset < self.tmin: 

3965 dt = -(self.tmin - mouset) 

3966 else: 

3967 dt = mouset - self.tmax 

3968 ddt = self.tmax-self.tmin 

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

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

3971 

3972 x0 = x 

3973 if self.picking_down is not None: 

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

3975 

3976 w = abs(x-x0) 

3977 x0 = min(x0, x) 

3978 

3979 tmin, tmax = ( 

3980 self.time_projection.rev(x0), 

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

3982 

3983 tmin, tmax = ( 

3984 max(working_system_time_range[0], tmin), 

3985 min(working_system_time_range[1], tmax)) 

3986 

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

3988 

3989 self.picking.setGeometry( 

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

3991 

3992 ftrack = self.track_to_screen.rev(y) 

3993 nslc_ids = self.get_nslc_ids_for_track(ftrack) 

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

3995 

3996 if dt != 0.0 and doshift: 

3997 self.interrupt_following() 

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

3999 

4000 self.update() 

4001 

4002 def update_status(self): 

4003 

4004 if self.message is None: 

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

4006 

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

4008 if not is_working_time(mouse_t): 

4009 return 

4010 

4011 if self.floating_marker: 

4012 tmi, tma = ( 

4013 self.floating_marker.tmin, 

4014 self.floating_marker.tmax) 

4015 

4016 tt, ms = gmtime_x(tmi) 

4017 

4018 if tmi == tma: 

4019 message = mystrftime( 

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

4021 tt=tt, milliseconds=ms) 

4022 else: 

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

4024 message = mystrftime( 

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

4026 tt=tt, milliseconds=ms) 

4027 else: 

4028 tt, ms = gmtime_x(mouse_t) 

4029 

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

4031 else: 

4032 message = self.message 

4033 

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

4035 sb.clearMessage() 

4036 sb.showMessage(message) 

4037 

4038 def set_sortingmode_change_delay_time(self, dt): 

4039 self.sortingmode_change_delay_time = dt 

4040 

4041 def sortingmode_change_delayed(self): 

4042 now = time.time() 

4043 return ( 

4044 self.sortingmode_change_delay_time is not None 

4045 and now - self.sortingmode_change_time 

4046 < self.sortingmode_change_delay_time) 

4047 

4048 def set_visible_marker_kinds(self, kinds): 

4049 self.deselect_all() 

4050 self.visible_marker_kinds = tuple(kinds) 

4051 self.emit_selected_markers() 

4052 

4053 def following(self): 

4054 return self.follow_timer is not None \ 

4055 and not self.following_interrupted() 

4056 

4057 def interrupt_following(self): 

4058 self.interactive_range_change_time = time.time() 

4059 

4060 def following_interrupted(self, now=None): 

4061 if now is None: 

4062 now = time.time() 

4063 return now - self.interactive_range_change_time \ 

4064 < self.interactive_range_change_delay_time 

4065 

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

4067 if tmax_start is None: 

4068 tmax_start = time.time() 

4069 self.show_all = False 

4070 self.follow_time = tlen 

4071 self.follow_timer = qc.QTimer(self) 

4072 self.follow_timer.timeout.connect( 

4073 self.follow_update) 

4074 self.follow_timer.setInterval(interval) 

4075 self.follow_timer.start() 

4076 self.follow_started = time.time() 

4077 self.follow_lapse = lapse 

4078 self.follow_tshift = self.follow_started - tmax_start 

4079 self.interactive_range_change_time = 0.0 

4080 

4081 def unfollow(self): 

4082 if self.follow_timer is not None: 

4083 self.follow_timer.stop() 

4084 self.follow_timer = None 

4085 self.interactive_range_change_time = 0.0 

4086 

4087 def follow_update(self): 

4088 rnow = time.time() 

4089 if self.follow_lapse is None: 

4090 now = rnow 

4091 else: 

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

4093 * self.follow_lapse 

4094 

4095 if self.following_interrupted(rnow): 

4096 return 

4097 self.set_time_range( 

4098 now-self.follow_time-self.follow_tshift, 

4099 now-self.follow_tshift) 

4100 

4101 self.update() 

4102 

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

4104 self.return_tag = return_tag 

4105 self.window().close() 

4106 

4107 def cleanup(self): 

4108 self.about_to_close.emit() 

4109 self.timer.stop() 

4110 if self.follow_timer is not None: 

4111 self.follow_timer.stop() 

4112 

4113 for snuffling in list(self.snufflings): 

4114 self.remove_snuffling(snuffling) 

4115 

4116 def set_error_message(self, key, value): 

4117 if value is None: 

4118 if key in self.error_messages: 

4119 del self.error_messages[key] 

4120 else: 

4121 self.error_messages[key] = value 

4122 

4123 def inputline_changed(self, text): 

4124 pass 

4125 

4126 def inputline_finished(self, text): 

4127 line = str(text) 

4128 

4129 toks = line.split() 

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

4131 if len(toks) >= 1: 

4132 command = toks[0].lower() 

4133 

4134 try: 

4135 quick_filter_commands = { 

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

4137 's': '*.%s.*.*', 

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

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

4140 

4141 if command in quick_filter_commands: 

4142 if len(toks) >= 2: 

4143 patterns = [ 

4144 quick_filter_commands[toks[0]] % pat 

4145 for pat in toks[1:]] 

4146 self.set_quick_filter_patterns(patterns, line) 

4147 else: 

4148 self.set_quick_filter_patterns(None) 

4149 

4150 self.update() 

4151 

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

4153 if len(toks) >= 2: 

4154 patterns = [] 

4155 if len(toks) == 2: 

4156 patterns = [toks[1]] 

4157 elif len(toks) >= 3: 

4158 x = { 

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

4160 's': '*.%s.*.*', 

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

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

4163 

4164 if toks[1] in x: 

4165 patterns.extend( 

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

4167 

4168 for pattern in patterns: 

4169 if command == 'hide': 

4170 self.add_blacklist_pattern(pattern) 

4171 else: 

4172 self.remove_blacklist_pattern(pattern) 

4173 

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

4175 self.clear_blacklist() 

4176 

4177 clearit = True 

4178 

4179 self.update() 

4180 

4181 elif command == 'markers': 

4182 if len(toks) == 2: 

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

4184 kinds = self.all_marker_kinds 

4185 else: 

4186 kinds = [] 

4187 for x in toks[1]: 

4188 try: 

4189 kinds.append(int(x)) 

4190 except Exception: 

4191 pass 

4192 

4193 self.set_visible_marker_kinds(kinds) 

4194 

4195 elif len(toks) == 1: 

4196 self.set_visible_marker_kinds(()) 

4197 

4198 self.update() 

4199 

4200 elif command == 'scaling': 

4201 if len(toks) == 2: 

4202 hideit = False 

4203 error = 'wrong number of arguments' 

4204 

4205 if len(toks) >= 3: 

4206 vmin, vmax = [ 

4207 pyrocko.model.float_or_none(x) 

4208 for x in toks[-2:]] 

4209 

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

4211 if k in d: 

4212 if vmin is not None: 

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

4214 if vmax is not None: 

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

4216 

4217 if len(toks) == 1: 

4218 self.remove_scaling_hooks() 

4219 

4220 elif len(toks) == 3: 

4221 def hook(data_ranges): 

4222 for k in data_ranges: 

4223 upd(data_ranges, k, vmin, vmax) 

4224 

4225 self.set_scaling_hook('_', hook) 

4226 

4227 elif len(toks) == 4: 

4228 pattern = toks[1] 

4229 

4230 def hook(data_ranges): 

4231 for k in pyrocko.util.match_nslcs( 

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

4233 

4234 upd(data_ranges, k, vmin, vmax) 

4235 

4236 self.set_scaling_hook(pattern, hook) 

4237 

4238 elif command == 'goto': 

4239 toks2 = line.split(None, 1) 

4240 if len(toks2) == 2: 

4241 arg = toks2[1] 

4242 m = re.match( 

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

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

4245 if m: 

4246 tlen = None 

4247 if not m.group(1): 

4248 tlen = 12*32*24*60*60 

4249 elif not m.group(2): 

4250 tlen = 32*24*60*60 

4251 elif not m.group(3): 

4252 tlen = 24*60*60 

4253 elif not m.group(4): 

4254 tlen = 60*60 

4255 elif not m.group(5): 

4256 tlen = 60 

4257 

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

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

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

4261 t = pyrocko.util.str_to_time(arg) 

4262 self.go_to_time(t, tlen=tlen) 

4263 

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

4265 supl = '00:00:00' 

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

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

4268 tmin, tmax = self.get_time_range() 

4269 sdate = pyrocko.util.time_to_str( 

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

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

4272 self.go_to_time(t) 

4273 

4274 elif arg == 'today': 

4275 self.go_to_time( 

4276 day_start( 

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

4278 

4279 elif arg == 'yesterday': 

4280 self.go_to_time( 

4281 day_start( 

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

4283 

4284 else: 

4285 self.go_to_event_by_name(arg) 

4286 

4287 else: 

4288 raise PileViewerMainException( 

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

4290 

4291 except PileViewerMainException as e: 

4292 error = str(e) 

4293 hideit = False 

4294 

4295 return clearit, hideit, error 

4296 

4297 return PileViewerMain 

4298 

4299 

4300PileViewerMain = MakePileViewerMainClass(qw.QWidget) 

4301GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget) 

4302 

4303 

4304class LineEditWithAbort(qw.QLineEdit): 

4305 

4306 aborted = qc.pyqtSignal() 

4307 history_down = qc.pyqtSignal() 

4308 history_up = qc.pyqtSignal() 

4309 

4310 def keyPressEvent(self, key_event): 

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

4312 self.aborted.emit() 

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

4314 self.history_down.emit() 

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

4316 self.history_up.emit() 

4317 else: 

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

4319 

4320 

4321class PileViewer(qw.QFrame): 

4322 ''' 

4323 PileViewerMain + Controls + Inputline 

4324 ''' 

4325 

4326 def __init__( 

4327 self, pile, 

4328 ntracks_shown_max=20, 

4329 marker_editor_sortable=True, 

4330 use_opengl=None, 

4331 panel_parent=None, 

4332 *args): 

4333 

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

4335 

4336 layout = qw.QGridLayout() 

4337 layout.setContentsMargins(0, 0, 0, 0) 

4338 layout.setSpacing(0) 

4339 

4340 self.menu = PileViewerMenuBar(self) 

4341 

4342 if use_opengl is None: 

4343 use_opengl = is_macos 

4344 

4345 if use_opengl: 

4346 self.viewer = GLPileViewerMain( 

4347 pile, 

4348 ntracks_shown_max=ntracks_shown_max, 

4349 panel_parent=panel_parent, 

4350 menu=self.menu) 

4351 else: 

4352 self.viewer = PileViewerMain( 

4353 pile, 

4354 ntracks_shown_max=ntracks_shown_max, 

4355 panel_parent=panel_parent, 

4356 menu=self.menu) 

4357 

4358 self.marker_editor_sortable = marker_editor_sortable 

4359 

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

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

4362 

4363 self.input_area = qw.QFrame(self) 

4364 ia_layout = qw.QGridLayout() 

4365 ia_layout.setContentsMargins(11, 11, 11, 11) 

4366 self.input_area.setLayout(ia_layout) 

4367 

4368 self.inputline = LineEditWithAbort(self.input_area) 

4369 self.inputline.returnPressed.connect( 

4370 self.inputline_returnpressed) 

4371 self.inputline.editingFinished.connect( 

4372 self.inputline_finished) 

4373 self.inputline.aborted.connect( 

4374 self.inputline_aborted) 

4375 

4376 self.inputline.history_down.connect( 

4377 lambda: self.step_through_history(1)) 

4378 self.inputline.history_up.connect( 

4379 lambda: self.step_through_history(-1)) 

4380 

4381 self.inputline.textEdited.connect( 

4382 self.inputline_changed) 

4383 

4384 self.inputline.setPlaceholderText( 

4385 u"Quick commands: e.g. 'c HH?' to select channels. " 

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

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

4388 self.input_area.hide() 

4389 self.history = None 

4390 

4391 self.inputline_error_str = None 

4392 

4393 self.inputline_error = qw.QLabel() 

4394 self.inputline_error.hide() 

4395 

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

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

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

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

4400 

4401 pb = Progressbars(self) 

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

4403 self.progressbars = pb 

4404 

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

4406 self.scrollbar = scrollbar 

4407 layout.addWidget(scrollbar, 1, 1) 

4408 self.scrollbar.valueChanged.connect( 

4409 self.scrollbar_changed) 

4410 

4411 self.block_scrollbar_changes = False 

4412 

4413 self.viewer.want_input.connect( 

4414 self.inputline_show) 

4415 self.viewer.tracks_range_changed.connect( 

4416 self.tracks_range_changed) 

4417 self.viewer.pile_has_changed_signal.connect( 

4418 self.adjust_controls) 

4419 self.viewer.about_to_close.connect( 

4420 self.save_inputline_history) 

4421 

4422 self.setLayout(layout) 

4423 

4424 def cleanup(self): 

4425 self.viewer.cleanup() 

4426 

4427 def get_progressbars(self): 

4428 return self.progressbars 

4429 

4430 def inputline_show(self): 

4431 if not self.history: 

4432 self.load_inputline_history() 

4433 

4434 self.input_area.show() 

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

4436 self.inputline.selectAll() 

4437 

4438 def inputline_set_error(self, string): 

4439 self.inputline_error_str = string 

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

4441 self.inputline.selectAll() 

4442 self.inputline_error.setText(string) 

4443 self.input_area.show() 

4444 self.inputline_error.show() 

4445 

4446 def inputline_clear_error(self): 

4447 if self.inputline_error_str: 

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

4449 self.inputline_error_str = None 

4450 self.inputline_error.clear() 

4451 self.inputline_error.hide() 

4452 

4453 def inputline_changed(self, line): 

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

4455 self.inputline_clear_error() 

4456 

4457 def inputline_returnpressed(self): 

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

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

4460 

4461 if error: 

4462 self.inputline_set_error(error) 

4463 

4464 line = line.strip() 

4465 

4466 if line != '' and not error: 

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

4468 self.history.append(line) 

4469 

4470 if clearit: 

4471 

4472 self.inputline.blockSignals(True) 

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

4474 if qpat is None: 

4475 self.inputline.clear() 

4476 else: 

4477 self.inputline.setText(qinp) 

4478 self.inputline.blockSignals(False) 

4479 

4480 if hideit and not error: 

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

4482 self.input_area.hide() 

4483 

4484 self.hist_ind = len(self.history) 

4485 

4486 def inputline_aborted(self): 

4487 ''' 

4488 Hide the input line. 

4489 ''' 

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

4491 self.hist_ind = len(self.history) 

4492 self.input_area.hide() 

4493 

4494 def save_inputline_history(self): 

4495 ''' 

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

4497 ''' 

4498 if not self.history: 

4499 return 

4500 

4501 conf = pyrocko.config 

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

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

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

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

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

4507 

4508 def load_inputline_history(self): 

4509 ''' 

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

4511 ''' 

4512 conf = pyrocko.config 

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

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

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

4516 f.write('\n') 

4517 

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

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

4520 

4521 self.hist_ind = len(self.history) 

4522 

4523 def step_through_history(self, ud=1): 

4524 ''' 

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

4526 ''' 

4527 n = len(self.history) 

4528 self.hist_ind += ud 

4529 self.hist_ind %= (n + 1) 

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

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

4532 else: 

4533 self.inputline.setText('') 

4534 

4535 def inputline_finished(self): 

4536 pass 

4537 

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

4539 if self.block_scrollbar_changes: 

4540 return 

4541 

4542 self.scrollbar.blockSignals(True) 

4543 self.scrollbar.setPageStep(ihi-ilo) 

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

4545 self.scrollbar.setRange(0, vmax) 

4546 self.scrollbar.setValue(ilo) 

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

4548 self.scrollbar.blockSignals(False) 

4549 

4550 def scrollbar_changed(self, value): 

4551 self.block_scrollbar_changes = True 

4552 ilo = value 

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

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

4555 self.block_scrollbar_changes = False 

4556 self.update_contents() 

4557 

4558 def controls(self): 

4559 frame = qw.QFrame(self) 

4560 layout = qw.QGridLayout() 

4561 frame.setLayout(layout) 

4562 

4563 minfreq = 0.001 

4564 maxfreq = 1000.0 

4565 self.lowpass_control = ValControl(high_is_none=True) 

4566 self.lowpass_control.setup( 

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

4568 self.highpass_control = ValControl(low_is_none=True) 

4569 self.highpass_control.setup( 

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

4571 self.gain_control = ValControl() 

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

4573 self.rot_control = LinValControl() 

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

4575 self.colorbar_control = ColorbarControl(self) 

4576 

4577 self.lowpass_control.valchange.connect( 

4578 self.viewer.lowpass_change) 

4579 self.highpass_control.valchange.connect( 

4580 self.viewer.highpass_change) 

4581 self.gain_control.valchange.connect( 

4582 self.viewer.gain_change) 

4583 self.rot_control.valchange.connect( 

4584 self.viewer.rot_change) 

4585 self.colorbar_control.cmap_changed.connect( 

4586 self.viewer.waterfall_cmap_change 

4587 ) 

4588 self.colorbar_control.clip_changed.connect( 

4589 self.viewer.waterfall_clip_change 

4590 ) 

4591 self.colorbar_control.show_absolute_toggled.connect( 

4592 self.viewer.waterfall_show_absolute_change 

4593 ) 

4594 self.colorbar_control.show_integrate_toggled.connect( 

4595 self.viewer.waterfall_set_integrate 

4596 ) 

4597 

4598 for icontrol, control in enumerate(( 

4599 self.highpass_control, 

4600 self.lowpass_control, 

4601 self.gain_control, 

4602 self.rot_control, 

4603 self.colorbar_control)): 

4604 

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

4606 layout.addWidget(widget, icontrol, iwidget) 

4607 

4608 spacer = qw.QSpacerItem( 

4609 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding) 

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

4611 

4612 self.adjust_controls() 

4613 self.viewer.viewmode_change(ViewMode.Wiggle) 

4614 return frame 

4615 

4616 def marker_editor(self): 

4617 editor = pyrocko.gui.snuffler.marker_editor.MarkerEditor( 

4618 self, sortable=self.marker_editor_sortable) 

4619 

4620 editor.set_viewer(self.get_view()) 

4621 editor.get_marker_model().dataChanged.connect( 

4622 self.update_contents) 

4623 return editor 

4624 

4625 def adjust_controls(self): 

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

4627 maxfreq = 0.5/dtmin 

4628 minfreq = (0.5/dtmax)*0.0001 

4629 self.lowpass_control.set_range(minfreq, maxfreq) 

4630 self.highpass_control.set_range(minfreq, maxfreq) 

4631 

4632 def setup_snufflings(self): 

4633 self.viewer.setup_snufflings() 

4634 

4635 def get_view(self): 

4636 return self.viewer 

4637 

4638 def update_contents(self): 

4639 self.viewer.update() 

4640 

4641 def get_pile(self): 

4642 return self.viewer.get_pile() 

4643 

4644 def show_colorbar_ctrl(self, show): 

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

4646 w.setVisible(show) 

4647 

4648 def show_gain_ctrl(self, show): 

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

4650 w.setVisible(show)