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-04 09:52 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-04 09:52 +0000
1# https://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6import os
7import time
8import calendar
9import datetime
10import re
11import math
12import logging
13import operator
14import copy
15import enum
16from itertools import groupby
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
29from pyrocko.util import hpfloat, gmtime_x, mystrftime
31from .marker import associate_phases_to_events, MarkerOneNSLCRequired
33from ..util import (ValControl, LinValControl, Marker, EventMarker,
34 PhaseMarker, make_QPolygonF, draw_label, Label,
35 Progressbars, ColorbarControl)
37from ..qt_compat import qc, qg, qw, qsvg
39from .pile_viewer_waterfall import TraceWaterfall
41import scipy.stats as sstats
42import platform
44MIN_LABEL_SIZE_PT = 6
46qc.QString = str
48qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \
49 qw.QFileDialog.DontUseSheet
51is_macos = platform.uname()[0] == 'Darwin'
53logger = logging.getLogger('pyrocko.gui.snuffler.pile_viewer')
56def detrend(x, y):
57 slope, offset, _, _, _ = sstats.linregress(x, y)
58 y_detrended = y - slope * x - offset
59 return y_detrended, slope, offset
62def retrend(x, y_detrended, slope, offset):
63 return x * slope + y_detrended + offset
66class Global(object):
67 appOnDemand = None
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
78class m_float(float):
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
88 def __lt__(self, other):
89 if other is None:
90 return True
91 return float(self) < float(other)
93 def __gt__(self, other):
94 if other is None:
95 return False
96 return float(self) > float(other)
99def m_float_or_none(x):
100 if x is None:
101 return None
102 else:
103 return m_float(x)
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]))]
114class deg_float(float):
116 def __str__(self):
117 return '%4.0f' % self
119 def __lt__(self, other):
120 if other is None:
121 return True
122 return float(self) < float(other)
124 def __gt__(self, other):
125 if other is None:
126 return False
127 return float(self) > float(other)
130def deg_float_or_none(x):
131 if x is None:
132 return None
133 else:
134 return deg_float(x)
137class sector_int(int):
139 def __str__(self):
140 return '[%i]' % self
142 def __lt__(self, other):
143 if other is None:
144 return True
145 return int(self) < int(other)
147 def __gt__(self, other):
148 if other is None:
149 return False
150 return int(self) > int(other)
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) + ' × 10<sup>%i</sup>' % int(m.group(2))
159 return snum
162gap_lap_tolerance = 5.
165class ViewMode(enum.Enum):
166 Wiggle = 1
167 Waterfall = 2
170class Timer(object):
171 def __init__(self):
172 self._start = None
173 self._stop = None
175 def start(self):
176 self._start = os.times()
178 def stop(self):
179 self._stop = os.times()
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)
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)])
195class ObjectStyle(object):
196 def __init__(self, frame_pen, fill_brush):
197 self.frame_pen = frame_pen
198 self.fill_brush = fill_brush
201box_styles = []
202box_alpha = 100
203for color in 'orange skyblue butter chameleon chocolate plum ' \
204 'scarletred'.split():
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 ))
212box_styles_coverage = {}
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 )]
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 )]
252sday = 60*60*24. # \
253smonth = 60*60*24*30. # | only used as approx. intervals...
254syear = 60*60*24*365. # /
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)
261working_system_time_range = \
262 pyrocko.util.working_system_time_range()
264initial_time_range = []
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])
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])
279def is_working_time(t):
280 return working_system_time_range[0] <= t and \
281 t <= working_system_time_range[1]
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
341 return l0_fmt, l0_fmt_brief, l0_center, l1_fmt, l1_trig, l2_fmt, l2_trig
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)
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)
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)
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]
372class TimeScaler(pyrocko.plot.AutoScaler):
373 def __init__(self):
374 pyrocko.plot.AutoScaler.__init__(self)
375 self.mode = 'min-max'
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'
381 data_min = min(data_range)
382 data_max = max(data_range)
383 is_reverse = (data_range[0] > data_range[1])
385 mi, ma = data_min, data_max
386 nmi = mi
387 if self.mode != 'off':
388 nmi = mi - self.space*(ma-mi)
390 nma = ma
391 if self.mode != 'off':
392 nma = ma + self.space*(ma-mi)
394 mi, ma = nmi, nma
396 if mi == ma and self.mode != 'off':
397 mi -= 1.0
398 ma += 1.0
400 mi = max(working_system_time_range[0], mi)
401 ma = min(working_system_time_range[1], ma)
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.)
412 if inc == 0.0:
413 inc = 1.0
415 if is_reverse:
416 return ma, mi, -inc
417 else:
418 return mi, ma, inc
420 def make_ticks(self, data_range):
421 mi, ma, inc = self.make_scale(data_range)
423 is_reverse = False
424 if inc < 0:
425 mi, ma, inc = ma, mi, -inc
426 is_reverse = True
428 ticks = []
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)
435 base = mi_day+num.ceil((mi-mi_day)/inc)*inc
436 if inc < 0.001:
437 base = hpfloat(base)
439 base_day = mi_day
440 i = 0
441 while True:
442 tick = base+i*inc
443 if tick > ma:
444 break
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
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
470 elif inc < syear:
471 mi_month = month_start(max(
472 mi, working_system_time_range[0]+smonth*1.5))
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
481 if tick > ma:
482 break
484 if tick >= mi:
485 ticks.append(tick)
487 else:
488 mi_year = year_start(max(
489 mi, working_system_time_range[0]+syear*1.5))
491 incy = int(round(inc/syear))
492 y = int(num.ceil(time.gmtime(mi_year)[0]/incy)*incy)
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)
502 if is_reverse:
503 ticks.reverse()
505 return ticks, inc
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
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)
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)
525 return l0, l0_brief, l0_center, l1, l2
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)
533 l1 = mystrftime(l1_fmt, tt, ms)
534 l2 = mystrftime(l2_fmt, tt, ms)
535 return l1, l2
538class TimeAx(TimeScaler):
539 def __init__(self, *args):
540 TimeScaler.__init__(self, *args)
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
556 vmin, vmax = yprojection(0), yprojection(ticklen)
557 uumin, uumax = xprojection.get_out_range()
558 first_tick_with_label = None
560 data = []
561 for tick in ticks:
562 umin = xprojection(tick)
564 umin_approx_next = xprojection(tick+inc)
565 umax = xprojection(tick)
567 pinc_approx = umin_approx_next - umin
569 p.drawLine(qc.QPointF(umin, vmin), qc.QPointF(umax, vmax))
570 l0, l0_brief, l0_center, l1, l2 = tick_to_labels(tick, inc)
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
580 if l0_center:
581 ushift = (umin_approx_next-umin)/2.
582 else:
583 ushift = 0.
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
592 abbr_level += 1
594 data.append((
595 l0, l0_brief, l0_center, l1, l2, tick, ushift, umin,
596 pinc_approx))
598 for (l0, l0_brief, l0_center, l1, l2, tick, ushift, umin,
599 pinc_approx) in data:
601 label0 = (l0, l0_brief, '')[abbr_level]
602 rect0 = fm.boundingRect(label0)
604 if uumin+pad < umin-rect0.width()/2.+ushift and \
605 umin+rect0.width()/2.+ushift < uumax-pad:
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)
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:
619 p.drawText(qc.QPointF(
620 umin-rect1.width()/2.,
621 vmin+rect0.height()+rect1.height()+ticklen),
622 label1)
624 l1_hits += 1
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:
632 p.drawText(qc.QPointF(
633 umin-rect2.width()/2.,
634 vmin+rect0.height()+rect1.height()+rect2.height() +
635 ticklen), label2)
637 l2_hits += 1
639 if first_tick_with_label is None:
640 first_tick_with_label = tmin
642 l1, l2 = l1_l2_tick(first_tick_with_label, inc)
644 if -3600.*25 < first_tick_with_label <= 3600.*25 and \
645 tmax - tmin < 3600*24:
647 # hide year at epoch (we assume that synthetic data is shown)
648 if l2:
649 l2 = None
650 elif l1:
651 l1 = None
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)
661 l1_hits += 1
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)
671 v = yprojection(0)
672 p.drawLine(qc.QPointF(uumin, v), qc.QPointF(uumax, v))
675class Projection(object):
676 def __init__(self):
677 self.xr = 0., 1.
678 self.ur = 0., 1.
680 def set_in_range(self, xmin, xmax):
681 if xmax == xmin:
682 xmax = xmin + 1.
684 self.xr = xmin, xmax
686 def get_in_range(self):
687 return self.xr
689 def set_out_range(self, umin, umax):
690 if umax == umin:
691 umax = umin + 1.
693 self.ur = umin, umax
695 def get_out_range(self):
696 return self.ur
698 def __call__(self, x):
699 umin, umax = self.ur
700 xmin, xmax = self.xr
701 return umin + (x-xmin)*((umax-umin)/(xmax-xmin))
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))))
710 def rev(self, u):
711 umin, umax = self.ur
712 xmin, xmax = self.xr
713 return xmin + (u-umin)*((xmax-xmin)/(umax-umin))
715 def copy(self):
716 return copy.copy(self)
719def add_radiobuttongroup(menu, menudef, target, default=None):
720 group = qw.QActionGroup(menu)
721 group.setExclusive(True)
722 menuitems = []
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])
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)
737 group.triggered.connect(target)
739 if default is None:
740 menuitems[0][0].setChecked(True)
742 return menuitems
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()))
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)
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)))
764class PileViewerMainException(Exception):
765 pass
768class PileViewerMenuBar(qw.QMenuBar):
769 ...
772class PileViewerMenu(qw.QMenu):
773 ...
776def MakePileViewerMainClass(base):
778 class PileViewerMain(base):
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)
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()
790 marker_selection_changed = qc.pyqtSignal(list)
791 active_event_marker_changed = qc.pyqtSignal()
793 def __init__(self, pile, ntracks_shown_max, panel_parent, *args,
794 menu=None):
795 base.__init__(self, *args)
797 self.pile = pile
798 self.ax_height = 80
799 self.panel_parent = panel_parent
801 self.click_tolerance = 5
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
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')
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)
837 self.setSizePolicy(poli)
838 self.setMinimumSize(300, 200)
839 self.setFocusPolicy(qc.Qt.ClickFocus)
841 self.menu = menu or PileViewerMenu(self)
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')
850 help_menu = self.menu.addMenu('&Help')
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()
858 file_menu.addAction(
859 qg.QIcon.fromTheme('document-open'),
860 'Open waveform files...',
861 self.open_waveforms,
862 qg.QKeySequence.Open)
864 file_menu.addAction(
865 qg.QIcon.fromTheme('document-open'),
866 'Open waveform directory...',
867 self.open_waveform_directory)
869 file_menu.addAction(
870 'Open station files...',
871 self.open_stations)
873 file_menu.addAction(
874 'Open StationXML files...',
875 self.open_stations_xml)
877 file_menu.addAction(
878 'Open event file...',
879 self.read_events)
881 file_menu.addSeparator()
882 file_menu.addAction(
883 'Open marker file...',
884 self.read_markers)
886 file_menu.addAction(
887 qg.QIcon.fromTheme('document-save'),
888 'Save markers...',
889 self.write_markers,
890 qg.QKeySequence.Save)
892 file_menu.addAction(
893 qg.QIcon.fromTheme('document-save-as'),
894 'Save selected markers...',
895 self.write_selected_markers,
896 qg.QKeySequence.SaveAs)
898 file_menu.addSeparator()
899 file_menu.addAction(
900 qg.QIcon.fromTheme('document-print'),
901 'Print',
902 self.printit,
903 qg.QKeySequence.Print)
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))
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)))
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 ]
937 self.menuitems_scaling = add_radiobuttongroup(
938 scale_menu, menudef, self.scalingmode_change,
939 default=self.config.trace_scale)
940 scale_menu.addSeparator()
942 self.scaling_key = self.menuitems_scaling[0][1]
943 self.scaling_hooks = {}
944 self.scalingmode_change()
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 ]
955 self.menuitems_scaling_base = add_radiobuttongroup(
956 scale_menu, menudef, self.scaling_base_change)
958 self.scaling_base = self.menuitems_scaling_base[0][1]
959 scale_menu.addSeparator()
961 self.menuitem_fixscalerange = scale_menu.addAction(
962 'Fix Scale Ranges')
963 self.menuitem_fixscalerange.setCheckable(True)
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))
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()
1004 self._ssort = lambda tr: ()
1006 self.menu.addSeparator()
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 ]
1032 self.menuitems_sorting = add_radiobuttongroup(
1033 sort_menu, menudef, self.sortingmode_change)
1035 menudef = [(x.key, x.value) for x in
1036 self.config.visible_length_setting]
1038 # View menu
1039 self.menuitems_visible_length = add_radiobuttongroup(
1040 view_menu, menudef,
1041 self.visible_length_change)
1042 view_menu.addSeparator()
1044 view_modes = [
1045 ('Wiggle Plot', ViewMode.Wiggle),
1046 ('Waterfall', ViewMode.Waterfall)
1047 ]
1049 self.menuitems_viewmode = add_radiobuttongroup(
1050 view_menu, view_modes,
1051 self.viewmode_change, default=ViewMode.Wiggle)
1052 view_menu.addSeparator()
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)
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)
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)
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)
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)
1085 self.menuitem_showzeroline = view_menu.addAction(
1086 'Show Zero Lines')
1087 self.menuitem_showzeroline.setCheckable(True)
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))
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))
1103 self.menuitem_distances_3d = options_menu.addAction(
1104 '3D distances',
1105 self.distances_3d_changed)
1106 self.menuitem_distances_3d.setCheckable(True)
1108 self.menuitem_allowdownsampling = options_menu.addAction(
1109 'Allow Downsampling')
1110 self.menuitem_allowdownsampling.setCheckable(True)
1111 self.menuitem_allowdownsampling.setChecked(True)
1113 self.menuitem_degap = options_menu.addAction(
1114 'Allow Degapping')
1115 self.menuitem_degap.setCheckable(True)
1116 self.menuitem_degap.setChecked(True)
1118 options_menu.addSeparator()
1120 self.menuitem_fft_filtering = options_menu.addAction(
1121 'FFT Filtering')
1122 self.menuitem_fft_filtering.setCheckable(True)
1124 self.menuitem_lphp = options_menu.addAction(
1125 'Bandpass is Low- + Highpass')
1126 self.menuitem_lphp.setCheckable(True)
1127 self.menuitem_lphp.setChecked(True)
1129 options_menu.addSeparator()
1130 self.menuitem_watch = options_menu.addAction(
1131 'Watch Files')
1132 self.menuitem_watch.setCheckable(True)
1134 self.menuitem_liberal_fetch = options_menu.addAction(
1135 'Liberal Fetch Optimization')
1136 self.menuitem_liberal_fetch.setCheckable(True)
1138 self.visible_length = menudef[0][1]
1140 self.snufflings_menu.addAction(
1141 'Reload Snufflings',
1142 self.setup_snufflings)
1144 # Disable ShadowPileTest
1145 if False:
1146 test_action = self.menu.addAction(
1147 'Test',
1148 self.toggletest)
1149 test_action.setCheckable(True)
1151 help_menu.addAction(
1152 qg.QIcon.fromTheme('preferences-desktop-keyboard'),
1153 'Snuffler Controls',
1154 self.help,
1155 qg.QKeySequence(qc.Qt.Key_Question))
1157 help_menu.addAction(
1158 'About',
1159 self.about)
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())
1165 self.gather = None
1167 self.trace_filter = None
1168 self.quick_filter = None
1169 self.quick_filter_patterns = None, None
1170 self.blacklist = []
1172 self.track_to_screen = Projection()
1173 self.track_to_nslc_ids = {}
1175 self.cached_vec = None
1176 self.cached_processed_traces = None
1178 self.timer = qc.QTimer(self)
1179 self.timer.timeout.connect(self.periodical)
1180 self.timer.setInterval(1000)
1181 self.timer.start()
1183 self._pile_changed = self.pile_changed # need to keep a strong ref
1184 self.pile.add_listener(self._pile_changed)
1186 self.trace_styles = {}
1187 if self.get_squirrel() is None:
1188 self.determine_box_styles()
1190 self.setMouseTracking(True)
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 = []
1198 self.stations = {}
1200 self.timer_draw = Timer()
1201 self.timer_cutout = Timer()
1202 self.time_spent_painting = 0.0
1203 self.time_last_painted = time.time()
1205 self.interactive_range_change_time = 0.0
1206 self.interactive_range_change_delay_time = 10.0
1207 self.follow_timer = None
1209 self.sortingmode_change_time = 0.0
1210 self.sortingmode_change_delay_time = None
1212 self.old_data_ranges = {}
1214 self.error_messages = {}
1215 self.return_tag = None
1216 self.wheel_pos = 60
1218 self.setAcceptDrops(True)
1219 self._paths_to_load = []
1221 self.tf_cache = {}
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
1231 self.automatic_updates = True
1233 self.closing = False
1234 self.in_paint_event = False
1236 def fail(self, reason):
1237 box = qw.QMessageBox(self)
1238 box.setText(reason)
1239 box.exec_()
1241 def set_trace_filter(self, filter_func):
1242 self.trace_filter = filter_func
1243 self.sortingmode_change()
1245 def update_trace_filter(self):
1246 if self.blacklist:
1248 def blacklist_func(tr):
1249 return not pyrocko.util.match_nslc(
1250 self.blacklist, tr.nslc_id)
1252 else:
1253 blacklist_func = None
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))
1265 def set_quick_filter(self, filter_func):
1266 self.quick_filter = filter_func
1267 self.update_trace_filter()
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)
1276 self.quick_filter_patterns = patterns, inputline
1278 def get_quick_filter_patterns(self):
1279 return self.quick_filter_patterns
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)
1290 for tr in trs:
1291 if tr.nslc_id in keys:
1292 keys.remove(tr.nslc_id)
1294 for key in keys:
1295 xpattern = '.'.join(key)
1296 if xpattern not in self.blacklist:
1297 self.blacklist.append(xpattern)
1299 else:
1300 if pattern in self.blacklist:
1301 self.blacklist.remove(pattern)
1303 self.blacklist.append(pattern)
1305 logger.info('Blacklist is [ %s ]' % ', '.join(self.blacklist))
1306 self.update_trace_filter()
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.')
1315 logger.info('Blacklist is [ %s ]' % ', '.join(self.blacklist))
1316 self.update_trace_filter()
1318 def clear_blacklist(self):
1319 self.blacklist = []
1320 self.update_trace_filter()
1322 def ssort(self, tr):
1323 return self._ssort(tr)
1325 def station_key(self, x):
1326 return x.network, x.station
1328 def station_keys(self, x):
1329 return [
1330 (x.network, x.station, x.location),
1331 (x.network, x.station)]
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)
1339 return default_getter(tr)
1341 def get_station(self, sk):
1342 return self.stations[sk]
1344 def has_station(self, station):
1345 for sk in self.station_keys(station):
1346 if sk in self.stations:
1347 return True
1349 return False
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)
1355 def set_stations(self, stations):
1356 self.stations = {}
1357 self.add_stations(stations)
1359 def add_stations(self, stations):
1360 for station in stations:
1361 for sk in self.station_keys(station):
1362 self.stations[sk] = station
1364 ev = self.get_active_event()
1365 if ev:
1366 self.set_origin(ev)
1368 def add_event(self, event):
1369 marker = EventMarker(event)
1370 self.add_marker(marker)
1372 def add_events(self, events):
1373 markers = [EventMarker(e) for e in events]
1374 self.add_markers(markers)
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
1382 m = selected[0]
1383 if not isinstance(m, EventMarker):
1384 self.fail('Selected marker is not an event.')
1385 return
1387 self.set_active_event_marker(m)
1389 def deactivate_event_marker(self):
1390 if self.active_event_marker:
1391 self.active_event_marker.active = False
1393 self.active_event_marker_changed.emit()
1394 self.active_event_marker = None
1396 def set_active_event_marker(self, event_marker):
1397 if self.active_event_marker:
1398 self.active_event_marker.active = False
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()
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)
1412 def get_active_event_marker(self):
1413 return self.active_event_marker
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
1422 def get_active_markers(self):
1423 emarker = self.get_active_event_marker()
1424 if emarker is None:
1425 return None, []
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]
1433 return emarker, pmarkers
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())
1441 self.sortingmode_change()
1443 def distances_3d_changed(self):
1444 ignore = self.menuitem_distances_3d.isChecked()
1445 self.set_event_marker_as_origin(ignore)
1447 def iter_snuffling_modules(self):
1448 pjoin = os.path.join
1449 for path in self.snuffling_paths:
1451 if not os.path.isdir(path):
1452 os.mkdir(path)
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'
1464 if not fn.endswith('.py'):
1465 continue
1467 name = fn[:-3]
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)
1474 yield self.snuffling_modules[directory, name]
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)
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)
1492 def set_panel_parent(self, panel_parent):
1493 self.panel_parent = panel_parent
1495 def get_panel_parent(self):
1496 return self.panel_parent
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()
1505 def remove_snuffling(self, snuffling):
1506 snuffling.delete_gui()
1507 self.update()
1508 self.snufflings.remove(snuffling)
1509 snuffling.pre_destroy()
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)
1516 def remove_snuffling_menuitem(self, item):
1517 self.snufflings_menu.removeAction(item)
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)
1524 def remove_snuffling_help_menuitem(self, item):
1525 self.snuffling_help.removeAction(item)
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)
1532 def remove_panel_toggler(self, item):
1533 self.toggle_panel_menu.removeAction(item)
1535 def load(self, paths, regex=None, format='detect',
1536 cache_dir=None, force_cache=False):
1538 if cache_dir is None:
1539 cache_dir = pyrocko.config.config().cache_dir
1540 if isinstance(paths, str):
1541 paths = [paths]
1543 fns = pyrocko.util.select_files(
1544 paths, selector=None, include=regex, show_progress=False)
1546 if not fns:
1547 return
1549 cache = pyrocko.pile.get_cache(cache_dir)
1551 t = [time.time()]
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)
1560 return pbs.set_status(label, value)
1562 def update_progress(label, i, n):
1563 abort = False
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()
1573 tnow = time.time()
1574 if t[0] + 1. + self.time_spent_painting * 10. < tnow:
1575 self.update()
1576 t[0] = tnow
1578 return abort
1580 self.automatic_updates = False
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)
1590 self.automatic_updates = True
1591 self.update()
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)
1600 def load_soon(self, paths):
1601 self._paths_to_load.extend(paths)
1602 qc.QTimer.singleShot(200, self.load_queued)
1604 def open_waveforms(self):
1605 caption = 'Select one or more files to open'
1607 fns, _ = qw.QFileDialog.getOpenFileNames(
1608 self, caption, options=qfiledialog_options)
1610 if fns:
1611 self.load(list(str(fn) for fn in fns))
1613 def open_waveform_directory(self):
1614 caption = 'Select directory to scan for waveform files'
1616 dn = qw.QFileDialog.getExistingDirectory(
1617 self, caption, options=qfiledialog_options)
1619 if dn:
1620 self.load([str(dn)])
1622 def open_stations(self, fns=None):
1623 caption = 'Select one or more Pyrocko station files to open'
1625 if not fns:
1626 fns, _ = qw.QFileDialog.getOpenFileNames(
1627 self, caption, options=qfiledialog_options)
1629 try:
1630 stations = [pyrocko.model.load_stations(str(x)) for x in fns]
1631 for stat in stations:
1632 self.add_stations(stat)
1634 except Exception as e:
1635 self.fail('Failed to read station file: %s' % str(e))
1637 def open_stations_xml(self, fns=None):
1638 from pyrocko.io import stationxml
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 (*)')
1647 try:
1648 stations = [
1649 stationxml.load_xml(filename=str(x)).get_pyrocko_stations()
1650 for x in fns]
1652 for stat in stations:
1653 self.add_stations(stat)
1655 except Exception as e:
1656 self.fail('Failed to read StationXML file: %s' % str(e))
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)
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)
1673 def periodical(self):
1674 if self.menuitem_watch.isChecked():
1675 if self.pile.reload_modified():
1676 self.update()
1678 def get_pile(self):
1679 return self.pile
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()
1687 def set_gathering(self, gather=None, color=None):
1689 if gather is None:
1690 def gather_func(tr):
1691 return tr.nslc_id
1693 gather = (0, 1, 2, 3)
1695 else:
1696 def gather_func(tr):
1697 return (
1698 self.ssort(tr) + tuple(tr.nslc_id[i] for i in gather))
1700 if color is None:
1701 def color(tr):
1702 return tr.location
1704 self.gather = gather_func
1705 keys = self.pile.gather_keys(gather_func, self.trace_filter)
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))
1712 if self.shown_tracks_range is None or \
1713 previous_ntracks == 0 or \
1714 self.show_all:
1716 low, high = 0, min(self.ntracks_shown_max, self.ntracks)
1717 key_at_top = None
1718 n = high-low
1720 else:
1721 low, high = self.shown_tracks_range
1722 key_at_top = self.track_keys[low]
1723 n = high-low
1725 self.track_keys = sorted(keys)
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]
1733 track_patterns.append(pat)
1735 self.track_patterns = track_patterns
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
1745 self.set_tracks_range((low, high))
1747 self.key_to_row = dict(
1748 [(key, i) for (i, key) in enumerate(self.track_keys)])
1750 def inrange(x, r):
1751 return r[0] <= x and x < r[1]
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))
1759 self.trace_selector = lambda x: \
1760 (self.trace_filter is None or self.trace_filter(x)) \
1761 and trace_selector(x)
1763 if self.tmin == working_system_time_range[0] and \
1764 self.tmax == working_system_time_range[1] or \
1765 self.show_all:
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)
1773 def set_time_range(self, tmin, tmax):
1774 if tmin is None:
1775 tmin = initial_time_range[0]
1777 if tmax is None:
1778 tmax = initial_time_range[1]
1780 if tmin > tmax:
1781 tmin, tmax = tmax, tmin
1783 if tmin == tmax:
1784 tmin -= 1.
1785 tmax += 1.
1787 tmin = max(working_system_time_range[0], tmin)
1788 tmax = min(working_system_time_range[1], tmax)
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.
1796 self.time_projection.set_in_range(tmin, tmax)
1797 self.tmin, self.tmax = tmin, tmax
1799 def get_time_range(self):
1800 return self.tmin, self.tmax
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
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))
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())
1825 except Exception as e:
1826 self.fail('Failed to write marker file: %s' % str(e))
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())
1840 except Exception as e:
1841 self.fail('Failed to write marker file: %s' % str(e))
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()
1858 except Exception as e:
1859 self.fail('Failed to read event file: %s' % str(e))
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()
1874 except Exception as e:
1875 self.fail('Failed to read marker file: %s' % str(e))
1877 def associate_phases_to_events(self):
1878 associate_phases_to_events(self.markers)
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)
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)
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)
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)
1907 def remove_marker(self, marker):
1908 '''
1909 Remove a ``marker`` from the :py:class:`PileViewer`.
1911 :param marker:
1912 :py:class:`~pyrocko.gui.snuffler.marker.Marker` (or subclass)
1913 instance
1914 '''
1916 if marker is self.active_event_marker:
1917 self.deactivate_event_marker()
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
1927 def remove_markers(self, markers):
1928 '''
1929 Remove a list of ``markers`` from the :py:class:`PileViewer`.
1931 :param markers:
1932 list of :py:class:`~pyrocko.gui.snuffler.marker.Marker` (or
1933 subclass) instances
1934 '''
1936 if markers is self.markers:
1937 markers = list(markers)
1939 for marker in markers:
1940 self.remove_marker(marker)
1942 self.update_markers_deltat_max()
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)
1950 self.end_markers_remove.emit()
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()
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
1971 ipos += 1
1973 if istart is not None:
1974 delete_segment(istart, ipos)
1976 self.update_markers_deltat_max()
1978 def selected_markers(self):
1979 return [marker for marker in self.markers if marker.is_selected()]
1981 def iter_selected_markers(self):
1982 for marker in self.markers:
1983 if marker.is_selected():
1984 yield marker
1986 def get_markers(self):
1987 return self.markers
1989 def mousePressEvent(self, mouse_ev):
1990 ''
1991 self.show_all = False
1992 point = self.mapFromGlobal(mouse_ev.globalPos())
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())
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
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()
2017 def mouseReleaseEvent(self, mouse_ev):
2018 ''
2019 if self.ignore_releases:
2020 self.ignore_releases -= 1
2021 return
2023 if self.picking:
2024 self.stop_picking(mouse_ev.x(), mouse_ev.y())
2025 self.emit_selected_markers()
2027 if self.track_start:
2028 self.update()
2030 self.track_start = None
2031 self.track_trange = None
2032 self.update_status()
2034 def mouseDoubleClickEvent(self, mouse_ev):
2035 ''
2036 self.show_all = False
2037 self.start_picking(None)
2038 self.ignore_releases = 1
2040 def mouseMoveEvent(self, mouse_ev):
2041 ''
2042 point = self.mapFromGlobal(mouse_ev.globalPos())
2044 if self.picking:
2045 self.update_picking(point.x(), point.y())
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
2054 tmin0, tmax0 = self.track_trange
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
2061 self.interrupt_following()
2062 self.set_time_range(
2063 tmin0 - dt - dtr*frac,
2064 tmax0 - dt + dtr*(1.-frac))
2066 self.update()
2067 else:
2068 self.hoovering(point.x(), point.y())
2070 self.update_status()
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
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
2085 if (abs(mouset-marker.tmin) < deltat or
2086 abs(mouset-marker.tmax) < deltat):
2088 if relevant_nslc_ids is None:
2089 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y)
2091 marker_nslc_ids = marker.get_nslc_ids()
2092 if not marker_nslc_ids:
2093 return marker
2095 for nslc_id in marker_nslc_ids:
2096 if nslc_id in relevant_nslc_ids:
2097 return marker
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
2109 state = abs(mouset-marker.tmin) < deltat or \
2110 abs(mouset-marker.tmax) < deltat and not haveone
2112 if state:
2113 xstate = False
2115 marker_nslc_ids = marker.get_nslc_ids()
2116 if not marker_nslc_ids:
2117 xstate = True
2119 for nslc in relevant_nslc_ids:
2120 if marker.match_nslc(nslc):
2121 xstate = True
2123 state = xstate
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()
2134 if not haveone:
2135 self.message = None
2137 if needupdate:
2138 self.update()
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)
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.
2154 key = key_event.key()
2155 try:
2156 keytext = str(key_event.text())
2157 except UnicodeEncodeError:
2158 return
2160 if key == qc.Qt.Key_Space:
2161 self.interrupt_following()
2162 self.set_time_range(self.tmin+dt, self.tmax+dt)
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)
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)
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)
2187 elif key in (qc.Qt.Key_Tab, qc.Qt.Key_Backtab):
2188 self.interrupt_following()
2190 tgo = None
2192 class TraceDummy(object):
2193 def __init__(self, marker):
2194 self._marker = marker
2196 @property
2197 def nslc_id(self):
2198 return self._marker.one_nslc()
2200 def marker_to_itrack(marker):
2201 try:
2202 return self.key_to_row.get(
2203 self.gather(TraceDummy(marker)), -1)
2205 except MarkerOneNSLCRequired:
2206 return -1
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))
2214 if key == qc.Qt.Key_Backtab:
2215 pmarkers.reverse()
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
2225 if iselected:
2226 icurrent = max(iselected) + 1
2227 else:
2228 icurrent = 0
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.)
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)
2247 if itrack not in self.track_to_nslc_ids:
2248 self.go_to_selection()
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)
2262 tgo = tmid
2264 if dir.lower() == 'n':
2265 for marker in sorted(
2266 self.markers,
2267 key=operator.attrgetter('tmin')):
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)):
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):
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
2295 if tgo is not None:
2296 self.interrupt_following()
2297 self.set_time_range(tgo-dt/2., tgo+dt/2.)
2299 elif keytext == 'r':
2300 if self.pile.reload_modified():
2301 self.reloaded = True
2303 elif keytext == 'R':
2304 self.setup_snufflings()
2306 elif key == qc.Qt.Key_Backspace:
2307 self.remove_selected_markers()
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
2318 elif keytext == 'A':
2319 for marker in self.markers:
2320 if marker.kind in self.visible_marker_kinds:
2321 marker.selected = True
2323 elif keytext == 'd':
2324 self.deselect_all()
2326 elif keytext == 'E':
2327 self.deactivate_event_marker()
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)]
2335 phase_markers = [
2336 marker for marker in markers
2337 if isinstance(marker, PhaseMarker)]
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
2350 event_marker.convert_to_event_marker(lat, lon)
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)
2357 else:
2358 for marker in event_markers_in_spe:
2359 marker.convert_to_event_marker()
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()
2366 elif key in fkey_map:
2367 self.handle_fkeys(key)
2369 elif key == qc.Qt.Key_Escape:
2370 if self.picking:
2371 self.stop_picking(0, 0, abort=True)
2373 elif key == qc.Qt.Key_PageDown:
2374 self.scroll_tracks(
2375 self.shown_tracks_range[1]-self.shown_tracks_range[0])
2377 elif key == qc.Qt.Key_PageUp:
2378 self.scroll_tracks(
2379 self.shown_tracks_range[0]-self.shown_tracks_range[1])
2381 elif key == qc.Qt.Key_Plus:
2382 self.zoom_tracks(0., 1.)
2384 elif key == qc.Qt.Key_Minus:
2385 self.zoom_tracks(0., -1.)
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)
2393 elif key == qc.Qt.Key_Colon:
2394 self.want_input.emit()
2396 elif keytext == 'f':
2397 self.toggle_fullscreen()
2399 elif keytext == 'g':
2400 self.go_to_selection()
2402 elif keytext == 'G':
2403 self.go_to_selection(tight=True)
2405 elif keytext == 'm':
2406 self.toggle_marker_editor()
2408 elif keytext == 'c':
2409 self.toggle_main_controls()
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)
2422 if keytext != '' and keytext in 'degaApPnN':
2423 self.emit_selected_markers()
2425 self.update()
2426 self.update_status()
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()
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)
2442 last_selected = this_selected
2444 if last_selected:
2445 ibounds.append(len(self.markers))
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)
2452 def toggle_marker_editor(self):
2453 self.panel_parent.toggle_marker_editor()
2455 def toggle_main_controls(self):
2456 self.panel_parent.toggle_main_controls()
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
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()
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')
2484 def help(self):
2485 class MyScrollArea(qw.QScrollArea):
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
2493 with open(pyrocko.util.data_file(
2494 'snuffler_help.html')) as f:
2495 hcheat = qw.QLabel(f.read())
2497 with open(pyrocko.util.data_file(
2498 'snuffler_help_epilog.html')) as f:
2499 hepilog = qw.QLabel(f.read())
2501 for h in [hcheat, hepilog]:
2502 h.setAlignment(qc.Qt.AlignTop | qc.Qt.AlignHCenter)
2503 h.setWordWrap(True)
2505 self.show_doc('Help', [hcheat, hepilog], target='panel')
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)
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)
2535 def open_link(self, link):
2536 qg.QDesktopServices.openUrl(qc.QUrl(link))
2538 def wheelEvent(self, wheel_event):
2539 ''
2540 self.wheel_pos += wheel_event.angleDelta().y()
2542 n = self.wheel_pos // 120
2543 self.wheel_pos = self.wheel_pos % 120
2544 if n == 0:
2545 return
2547 amount = max(
2548 1.,
2549 abs(self.shown_tracks_range[0]-self.shown_tracks_range[1])/5.)
2550 wdelta = amount * n
2552 trmin, trmax = self.track_to_screen.get_in_range()
2553 anchor = (self.track_to_screen.rev(wheel_event.y())-trmin) \
2554 / (trmax-trmin)
2556 if wheel_event.modifiers() & qc.Qt.ControlModifier:
2557 self.zoom_tracks(anchor, wdelta)
2558 else:
2559 self.scroll_tracks(-wdelta)
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()
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)
2576 def get_phase_name(self, kind):
2577 return self.config.get_phase_name(kind)
2579 def set_phase_kind(self, markers, kind):
2580 phasename = self.get_phase_name(kind)
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())
2590 elif isinstance(marker, EventMarker):
2591 pass
2593 else:
2594 if kind != 10:
2595 event = self.get_active_event()
2596 marker.convert_to_phase_marker(
2597 event, phasename, None, False)
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
2607 self.tracks_range_changed.emit(self.ntracks, low, high)
2609 def set_tracks_range(self, range, start=None):
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)
2617 if start is None:
2618 start = float(low)
2620 if self.shown_tracks_range != (low, high):
2621 self.shown_tracks_range = low, high
2622 self.shown_tracks_start = start
2624 self.tracks_range_changed.emit(self.ntracks, low, high)
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
2634 self.set_tracks_range((int(shown[0]), int(shown[1])))
2636 self.update()
2638 def zoom_tracks(self, anchor, delta):
2639 ntracks_shown = self.shown_tracks_range[1] \
2640 - self.shown_tracks_range[0]
2642 if (ntracks_shown == 1 and delta <= 0) or \
2643 (ntracks_shown == self.ntracks and delta >= 0):
2644 return
2646 ntracks_shown += int(round(delta))
2647 ntracks_shown = min(max(1, ntracks_shown), self.ntracks)
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
2656 self.set_tracks_range((int(round(nu)), int(round(nv))), nu)
2658 self.ntracks_shown_max = self.shown_tracks_range[1] \
2659 - self.shown_tracks_range[0]
2661 self.update()
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]
2671 return tmin, tmax
2673 def content_deltat_range(self):
2674 pile = self.get_pile()
2676 deltatmin, deltatmax = pile.get_deltatmin(), pile.get_deltatmax()
2678 if deltatmin is None:
2679 deltatmin = 0.001
2681 if deltatmax is None:
2682 deltatmax = 1000.0
2684 return deltatmin, deltatmax
2686 def make_good_looking_time_range(self, tmin, tmax, tight=False):
2687 if tmax < tmin:
2688 tmin, tmax = tmax, tmin
2690 deltatmin = self.content_deltat_range()[0]
2691 dt = deltatmin * self.visible_length * 0.95
2693 if dt == 0.0:
2694 dt = 1.0
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
2708 if tmax-tmin < dt:
2709 vmin, vmax = self.get_time_range()
2710 dt = min(vmax - vmin, dt)
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
2722 else:
2723 dtm = tmax-tmin
2724 tmin -= dtm*0.1
2725 tmax += dtm*0.1
2727 return tmin, tmax
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)
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()
2744 tmin, tmax = self.make_good_looking_time_range(
2745 tmin, tmax, tight=tight)
2747 self.interrupt_following()
2748 self.set_time_range(tmin, tmax)
2749 self.update()
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()
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)
2768 self.interrupt_following()
2769 self.set_time_range(tmin, tmax)
2771 def printit(self):
2772 from ..qt_compat import qprint
2773 printer = qprint.QPrinter()
2774 printer.setOrientation(qprint.QPrinter.Landscape)
2776 dialog = qprint.QPrintDialog(printer, self)
2777 dialog.setWindowTitle('Print')
2779 if dialog.exec_() != qw.QDialog.Accepted:
2780 return
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())
2788 painter.end()
2790 def savesvg(self, fn=None):
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)
2800 if fn == '':
2801 return
2803 fn = str(fn)
2805 if fn.lower().endswith('.svg'):
2806 try:
2807 w, h = 842, 595
2808 margin = 0.025
2809 m = max(w, h)*margin
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))
2816 painter = qg.QPainter()
2817 painter.begin(generator)
2818 self.drawit(painter, printmode=False, w=w, h=h)
2819 painter.end()
2821 except Exception as e:
2822 self.fail('Failed to write SVG file: %s' % str(e))
2824 elif fn.lower().endswith('.png'):
2825 pixmap = self.grab()
2827 try:
2828 pixmap.save(fn)
2830 except Exception as e:
2831 self.fail('Failed to write PNG file: %s' % str(e))
2833 else:
2834 self.fail(
2835 'Unsupported file type: filename must end with ".svg" or '
2836 '".png".')
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
2848 self.in_paint_event = True
2850 painter = qg.QPainter(self)
2852 if self.menuitem_antialias.isChecked():
2853 painter.setRenderHint(qg.QPainter.Antialiasing)
2855 self.drawit(painter)
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))
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())
2869 self.time_spent_painting = self.timer_draw.get()[-1]
2870 self.time_last_painted = time.time()
2871 self.in_paint_event = False
2873 def determine_box_styles(self):
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):
2888 istyle += 1
2890 trace_styles[tr.full_id, tr.deltat] = istyle
2892 self.trace_styles = trace_styles
2894 def draw_trace_boxes(self, p, time_projection, track_projections):
2896 for v_projection in track_projections.values():
2897 v_projection.set_in_range(0., 1.)
2899 def selector(x):
2900 return x.overlaps(*time_projection.get_in_range())
2902 if self.trace_filter is not None:
2903 def tselector(x):
2904 return selector(x) and self.trace_filter(x)
2906 else:
2907 tselector = selector
2909 traces = list(self.pile.iter_traces(
2910 group_selector=selector, trace_selector=tselector))
2912 traces.sort(key=operator.attrgetter('full_id'))
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)
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)
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
2933 itrack = self.key_to_row[gt]
2934 if itrack not in track_projections:
2935 continue
2937 istyle = self.trace_styles.get((tr.full_id, tr.deltat), 0)
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)
2946 for (itrack, istyle), traces in traces_by_style.items():
2947 drawbox(itrack, istyle, traces)
2949 def draw_visible_markers(
2950 self, p, vcenter_projection, primary_pen):
2952 try:
2953 markers = self.markers.with_key_in_limited(
2954 self.tmin - self.markers_deltat_max, self.tmax, 2000)
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))
2965 p.save()
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)
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 = ''
2981 draw_label(
2982 p, umin+10., v0-10.,
2983 '%i Markers' % len(self.markers) + s_selected,
2984 label_bg, 'LB')
2986 line = qc.QLineF(umin, v0, umax, v0)
2987 p.drawLine(line)
2988 p.restore()
2990 return
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:
2996 marker.draw(
2997 p, self.time_projection, vcenter_projection,
2998 with_label=True)
3000 def get_squirrel(self):
3001 try:
3002 return self.pile._squirrel
3003 except AttributeError:
3004 return None
3006 def draw_coverage(self, p, time_projection, track_projections):
3007 sq = self.get_squirrel()
3008 if sq is None:
3009 return
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)
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)
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
3030 pattern = self.track_patterns[itrack]
3031 pattern_to_itrack[tuple(pattern)] = itrack
3032 pattern_list.append(tuple(pattern))
3034 vmin, vmax = self.get_time_range()
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]
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)])
3057 t = tb
3058 pcount = count
3060 def drawit(self, p, printmode=False, w=None, h=None):
3061 '''
3062 This performs the actual drawing.
3063 '''
3065 self.timer_draw.start()
3066 show_boxes = self.menuitem_showboxes.isChecked()
3067 sq = self.get_squirrel()
3069 if self.gather is None:
3070 self.set_gathering()
3072 if self.pile_has_changed:
3074 if not self.sortingmode_change_delayed():
3075 self.sortingmode_change()
3077 if show_boxes and sq is None:
3078 self.determine_box_styles()
3080 self.pile_has_changed = False
3082 if h is None:
3083 h = float(self.height())
3084 if w is None:
3085 w = float(self.width())
3087 if printmode:
3088 primary_color = (0, 0, 0)
3089 else:
3090 primary_color = pyrocko.plot.tango_colors['aluminium5']
3092 primary_pen = qg.QPen(qg.QColor(*primary_color))
3094 ax_h = float(self.ax_height)
3096 vbottom_ax_projection = Projection()
3097 vtop_ax_projection = Projection()
3098 vcenter_projection = Projection()
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)
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))
3115 track_projections[i] = proj
3117 if self.tmin > self.tmax:
3118 return
3120 self.time_projection.set_in_range(self.tmin, self.tmax)
3121 vbottom_ax_projection.set_in_range(0, ax_h)
3123 self.tax.drawit(p, self.time_projection, vbottom_ax_projection)
3125 yscaler = pyrocko.plot.AutoScaler()
3127 p.setPen(primary_pen)
3129 font = qg.QFont()
3130 font.setBold(True)
3132 axannotfont = qg.QFont()
3133 axannotfont.setBold(True)
3134 axannotfont.setPointSize(8)
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())
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):
3147 if sq is None:
3148 self.draw_trace_boxes(
3149 p, self.time_projection, track_projections)
3151 else:
3152 self.draw_coverage(
3153 p, self.time_projection, track_projections)
3155 p.setFont(font)
3156 label_bg = qg.QBrush(qg.QColor(255, 255, 255, 100))
3158 color_lookup = dict(
3159 [(k, i) for (i, k) in enumerate(self.color_keys)])
3161 self.track_to_nslc_ids = {}
3162 nticks = 0
3163 annot_labels = []
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)
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)
3182 elif self.view_mode is ViewMode.Wiggle and processed_traces:
3183 show_scales = self.menuitem_showscalerange.isChecked() \
3184 or self.menuitem_showscaleaxis.isChecked()
3186 fm = qg.QFontMetrics(axannotfont, p.device())
3187 trackheight = self.track_to_screen(1.-0.05) \
3188 - self.track_to_screen(0.05)
3190 nlinesavail = trackheight/float(fm.lineSpacing())
3192 nticks = max(3, min(nlinesavail * 0.5, 15)) \
3193 if self.menuitem_showscaleaxis.isChecked() \
3194 else 15
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())
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])
3207 if not self.menuitem_fixscalerange.isChecked():
3208 self.old_data_ranges = data_ranges
3209 else:
3210 data_ranges.update(self.old_data_ranges)
3212 self.apply_scaling_hooks(data_ranges)
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
3222 itrack = self.key_to_row[gt]
3223 if itrack not in track_projections:
3224 continue
3226 trace_to_itrack[trace] = itrack
3228 if itrack not in self.track_to_nslc_ids:
3229 self.track_to_nslc_ids[itrack] = set()
3231 self.track_to_nslc_ids[itrack].add(trace.nslc_id)
3233 if itrack not in track_scaling_keys:
3234 track_scaling_keys[itrack] = set()
3236 scaling_key = self.scaling_key(trace)
3237 track_scaling_keys[itrack].add(scaling_key)
3239 color = pyrocko.plot.color(
3240 color_lookup[self.color_gather(trace)])
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
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
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])
3278 pen.setColor(c)
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
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)
3294 p.drawLine(line)
3295 if iy_annot == ny_annot - 1 \
3296 and iexp != 0:
3297 sexp = ' × ' \
3298 '10<sup>%i</sup>' % iexp
3299 else:
3300 sexp = ''
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)
3312 uoffnext = max(
3313 lab.rect.width()+30., uoffnext)
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)
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)
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)
3348 annot_labels.append(labmax)
3350 for lab in (labmin, labmax):
3351 uoffnext = max(
3352 lab.rect.width()+10., uoffnext)
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)
3360 uoff = uoffnext
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
3368 if trace not in trace_to_itrack:
3369 continue
3371 itrack = trace_to_itrack[trace]
3372 scaling_key = self.scaling_key(trace)
3373 trace_projection = trace_projections[
3374 itrack, scaling_key]
3376 vdata = trace_projection(trace.get_ydata())
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)
3383 qpoints = make_QPolygonF(udata, vdata)
3385 umin, umax = self.time_projection.get_out_range()
3386 vmin, vmax = trace_projection.get_out_range()
3388 trackrect = qc.QRectF(umin, vmin, umax-umin, vmax-vmin)
3390 if self.menuitem_cliptraces.isChecked():
3391 p.setClipRect(trackrect)
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)
3399 p.drawPolyline(qpoints)
3401 if self.floating_marker:
3402 self.floating_marker.draw_trace(
3403 self, p, trace,
3404 self.time_projection, trace_projection, 1.0)
3406 for marker in self.markers.with_key_in(
3407 self.tmin - self.markers_deltat_max,
3408 self.tmax):
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)
3418 p.setPen(primary_pen)
3420 if self.menuitem_cliptraces.isChecked():
3421 p.setClipRect(0, 0, int(w), int(h))
3423 if self.floating_marker:
3424 self.floating_marker.draw(
3425 p, self.time_projection, vcenter_projection)
3427 self.draw_visible_markers(
3428 p, vcenter_projection, primary_pen)
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
3439 font.setPointSize(font.pointSize()-1)
3441 p.setFont(font)
3442 mouse_pos = self.mapFromGlobal(qg.QCursor.pos())
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)
3452 if p.font().pointSize() >= MIN_LABEL_SIZE_PT:
3453 draw_label(p, lx, ly, plabel, label_bg, 'ML')
3454 continue
3456 contains_cursor = \
3457 self.track_to_screen(itrack) \
3458 < mouse_pos.y() \
3459 < self.track_to_screen(itrack+1)
3461 if not contains_cursor:
3462 continue
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)
3470 for lab in annot_labels:
3471 lab.draw()
3473 self.timer_draw.stop()
3475 def see_data_params(self):
3477 min_deltat = self.content_deltat_range()[0]
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.
3490 if self.highpass is not None:
3491 tpad = max(1./self.highpass * 2., tpad)
3493 nsee_points_per_trace = 5000*10
3494 tsee = ndecimate*nsee_points_per_trace*min_deltat
3496 return ndecimate, tpad, tsee
3498 def clean_update(self):
3499 self.cached_processed_traces = None
3500 self.update()
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)
3508 for snuffling in self.snufflings:
3509 if snuffling._post_process_hook_enabled \
3510 or snuffling._pre_process_hook_enabled:
3512 tpad = max(tpad, snuffling.get_tpad())
3514 return tpad
3516 def prepare_cutout2(
3517 self, tmin, tmax, trace_selector=None, degap=True,
3518 demean=True, nmax=6000):
3520 if self.pile.is_empty():
3521 return []
3523 nmax = self.visible_length
3525 self.timer_cutout.start()
3527 tsee = tmax-tmin
3528 min_deltat_wo_decimate = tsee/nmax
3529 min_deltat_w_decimate = min_deltat_wo_decimate / 32.
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
3537 min_deltat_allow = math.exp(
3538 int(math.floor(math.log(min_deltat_allow))))
3540 tmin_ = tmin
3541 tmax_ = tmax
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
3549 fft_filtering = self.menuitem_fft_filtering.isChecked()
3550 lphp = self.menuitem_lphp.isChecked()
3551 ads = self.menuitem_allowdownsampling.isChecked()
3553 tpad = self.get_adequate_tpad()
3554 tpad = max(tpad, tsee)
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())
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):
3570 logger.debug('Using cached traces')
3571 processed_traces = self.cached_processed_traces
3573 else:
3574 processed_traces = []
3575 if self.pile.deltatmax >= min_deltat_allow:
3577 if isinstance(self.pile, pyrocko.pile.Pile):
3578 def group_selector(gr):
3579 return gr.deltatmax >= min_deltat_allow
3581 kwargs = dict(group_selector=group_selector)
3582 else:
3583 kwargs = {}
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
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):
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))
3612 traces = self.pre_process_hooks(traces)
3614 for trace in traces:
3616 if not (trace.meta
3617 and trace.meta.get('tabu', False)):
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:
3625 it = num.arange(
3626 trace.data_len(), dtype=float)
3627 detr_data, m, b = detrend(
3628 it, trace.get_ydata())
3630 trace.set_ydata(detr_data)
3632 freqs, fdata = trace.spectrum(
3633 pad_to_pow2=True, tfade=None)
3635 nfreqs = fdata.size
3637 key = (trace.deltat, nfreqs)
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'))
3647 if self.highpass is not None:
3648 resps.append(but(
3649 order=4,
3650 corner=self.highpass,
3651 type='high'))
3653 resp = multres(resps)
3654 self.tf_cache[key] = \
3655 resp.evaluate(freqs)
3657 filtered_data = num.fft.irfft(
3658 fdata*self.tf_cache[key]
3659 )[:trace.data_len()]
3661 retrended_data = retrend(
3662 it, filtered_data, m, b)
3664 trace.set_ydata(retrended_data)
3666 else:
3668 if ads and self.lowpass is not None:
3669 while trace.deltat \
3670 < min_deltat_wo_decimate:
3672 trace.downsample(2, demean=False)
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):
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)
3691 if self.highpass is not None:
3692 if self.lowpass is None \
3693 or self.highpass \
3694 < self.lowpass:
3696 if self.highpass < \
3697 0.5/trace.deltat:
3698 trace.highpass(
3699 4, self.highpass,
3700 demean=False)
3702 processed_traces.append(trace)
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())):
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)
3726 processed_traces = self.post_process_hooks(processed_traces)
3728 self.cached_processed_traces = processed_traces
3729 self.cached_vec = vec
3731 chopped_traces = []
3732 for trace in processed_traces:
3733 chop_tmin = tmin_ - trace.deltat*4
3734 chop_tmax = tmax_ + trace.deltat*4
3736 try:
3737 ctrace = trace.chop(
3738 chop_tmin, chop_tmax,
3739 inplace=False)
3741 except pyrocko.trace.NoData:
3742 continue
3744 if ctrace.data_len() < 2:
3745 continue
3747 chopped_traces.append(ctrace)
3749 self.timer_cutout.stop()
3750 return chopped_traces
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)
3757 return traces
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)
3764 return traces
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
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
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()
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)
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')
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 )
3804 if self.view_mode is ViewMode.Waterfall:
3805 self.parent().show_colorbar_ctrl(True)
3806 self.parent().show_gain_ctrl(False)
3808 for item in items_waterfall_disabled:
3809 item.setDisabled(True)
3811 self.visible_length = 180.
3812 else:
3813 self.parent().show_colorbar_ctrl(False)
3814 self.parent().show_gain_ctrl(True)
3816 for item in items_waterfall_disabled:
3817 item.setDisabled(False)
3819 self.visible_length_change()
3820 self.update()
3822 def set_scaling_hook(self, k, hook):
3823 self.scaling_hooks[k] = hook
3825 def remove_scaling_hook(self, k):
3826 del self.scaling_hooks[k]
3828 def remove_scaling_hooks(self):
3829 self.scaling_hooks = {}
3831 def s_sortingmode_change(self, ignore=None):
3832 for menuitem, valfunc in self.menuitems_ssorting:
3833 if menuitem.isChecked():
3834 self._ssort = valfunc
3836 self.sortingmode_change()
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)
3843 self.sortingmode_change_time = time.time()
3845 def lowpass_change(self, value, ignore=None):
3846 self.lowpass = value
3847 self.passband_check()
3848 self.tf_cache = {}
3849 self.update()
3851 def highpass_change(self, value, ignore=None):
3852 self.highpass = value
3853 self.passband_check()
3854 self.tf_cache = {}
3855 self.update()
3857 def passband_check(self):
3858 if self.highpass and self.lowpass \
3859 and self.highpass >= self.lowpass:
3861 self.message = 'Corner frequency of highpass larger than ' \
3862 'corner frequency of lowpass! I will now ' \
3863 'deactivate the highpass.'
3865 self.update_status()
3866 else:
3867 oldmess = self.message
3868 self.message = None
3869 if oldmess is not None:
3870 self.update_status()
3872 def gain_change(self, value, ignore):
3873 self.gain = value
3874 self.update()
3876 def rot_change(self, value, ignore):
3877 self.rotate = value
3878 self.update()
3880 def waterfall_cmap_change(self, cmap):
3881 self.waterfall_cmap = cmap
3882 self.update()
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()
3889 def waterfall_show_absolute_change(self, toggle):
3890 self.waterfall_show_absolute = toggle
3891 self.update()
3893 def waterfall_set_integrate(self, toggle):
3894 self.waterfall_integrate = toggle
3895 self.update()
3897 def set_selected_markers(self, markers):
3898 '''
3899 Set a list of markers selected
3901 :param markers: list of markers
3902 '''
3903 self.deselect_all()
3904 for m in markers:
3905 m.selected = True
3907 self.update()
3909 def deselect_all(self):
3910 for marker in self.markers:
3911 marker.selected = False
3913 def animate_picking(self):
3914 point = self.mapFromGlobal(qg.QCursor.pos())
3915 self.update_picking(point.x(), point.y(), doshift=True)
3917 def get_nslc_ids_for_track(self, ftrack):
3918 itrack = int(ftrack)
3919 return self.track_to_nslc_ids.get(itrack, [])
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()
3933 self.floating_marker = None
3935 def start_picking(self, ignore):
3937 if not self.picking:
3938 self.deselect_all()
3939 self.picking = qw.QRubberBand(qw.QRubberBand.Rectangle)
3940 point = self.mapFromGlobal(qg.QCursor.pos())
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())
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
3952 self.picking_timer = qc.QTimer()
3953 self.picking_timer.timeout.connect(
3954 self.animate_picking)
3956 self.picking_timer.setInterval(50)
3957 self.picking_timer.start()
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.)
3972 x0 = x
3973 if self.picking_down is not None:
3974 x0 = self.time_projection(self.picking_down[0])
3976 w = abs(x-x0)
3977 x0 = min(x0, x)
3979 tmin, tmax = (
3980 self.time_projection.rev(x0),
3981 self.time_projection.rev(x0+w))
3983 tmin, tmax = (
3984 max(working_system_time_range[0], tmin),
3985 min(working_system_time_range[1], tmax))
3987 p1 = self.mapToGlobal(qc.QPoint(int(round(x0)), 0))
3989 self.picking.setGeometry(
3990 p1.x(), p1.y(), int(round(max(w, 1))), self.height())
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)
3996 if dt != 0.0 and doshift:
3997 self.interrupt_following()
3998 self.set_time_range(self.tmin+dt, self.tmax+dt)
4000 self.update()
4002 def update_status(self):
4004 if self.message is None:
4005 point = self.mapFromGlobal(qg.QCursor.pos())
4007 mouse_t = self.time_projection.rev(point.x())
4008 if not is_working_time(mouse_t):
4009 return
4011 if self.floating_marker:
4012 tmi, tma = (
4013 self.floating_marker.tmin,
4014 self.floating_marker.tmax)
4016 tt, ms = gmtime_x(tmi)
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)
4030 message = mystrftime(fmt=None, tt=tt, milliseconds=ms)
4031 else:
4032 message = self.message
4034 sb = self.window().statusBar()
4035 sb.clearMessage()
4036 sb.showMessage(message)
4038 def set_sortingmode_change_delay_time(self, dt):
4039 self.sortingmode_change_delay_time = dt
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)
4048 def set_visible_marker_kinds(self, kinds):
4049 self.deselect_all()
4050 self.visible_marker_kinds = tuple(kinds)
4051 self.emit_selected_markers()
4053 def following(self):
4054 return self.follow_timer is not None \
4055 and not self.following_interrupted()
4057 def interrupt_following(self):
4058 self.interactive_range_change_time = time.time()
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
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
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
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
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)
4101 self.update()
4103 def myclose(self, return_tag=''):
4104 self.return_tag = return_tag
4105 self.window().close()
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()
4113 for snuffling in list(self.snufflings):
4114 self.remove_snuffling(snuffling)
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
4123 def inputline_changed(self, text):
4124 pass
4126 def inputline_finished(self, text):
4127 line = str(text)
4129 toks = line.split()
4130 clearit, hideit, error = False, True, None
4131 if len(toks) >= 1:
4132 command = toks[0].lower()
4134 try:
4135 quick_filter_commands = {
4136 'n': '%s.*.*.*',
4137 's': '*.%s.*.*',
4138 'l': '*.*.%s.*',
4139 'c': '*.*.*.%s'}
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)
4150 self.update()
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'}
4164 if toks[1] in x:
4165 patterns.extend(
4166 x[toks[1]] % tok for tok in toks[2:])
4168 for pattern in patterns:
4169 if command == 'hide':
4170 self.add_blacklist_pattern(pattern)
4171 else:
4172 self.remove_blacklist_pattern(pattern)
4174 elif command == 'unhide' and len(toks) == 1:
4175 self.clear_blacklist()
4177 clearit = True
4179 self.update()
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
4193 self.set_visible_marker_kinds(kinds)
4195 elif len(toks) == 1:
4196 self.set_visible_marker_kinds(())
4198 self.update()
4200 elif command == 'scaling':
4201 if len(toks) == 2:
4202 hideit = False
4203 error = 'wrong number of arguments'
4205 if len(toks) >= 3:
4206 vmin, vmax = [
4207 pyrocko.model.float_or_none(x)
4208 for x in toks[-2:]]
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
4217 if len(toks) == 1:
4218 self.remove_scaling_hooks()
4220 elif len(toks) == 3:
4221 def hook(data_ranges):
4222 for k in data_ranges:
4223 upd(data_ranges, k, vmin, vmax)
4225 self.set_scaling_hook('_', hook)
4227 elif len(toks) == 4:
4228 pattern = toks[1]
4230 def hook(data_ranges):
4231 for k in pyrocko.util.match_nslcs(
4232 pattern, list(data_ranges.keys())):
4234 upd(data_ranges, k, vmin, vmax)
4236 self.set_scaling_hook(pattern, hook)
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
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)
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)
4274 elif arg == 'today':
4275 self.go_to_time(
4276 day_start(
4277 time.time()), tlen=24*60*60)
4279 elif arg == 'yesterday':
4280 self.go_to_time(
4281 day_start(
4282 time.time()-24*60*60), tlen=24*60*60)
4284 else:
4285 self.go_to_event_by_name(arg)
4287 else:
4288 raise PileViewerMainException(
4289 'No such command: %s' % command)
4291 except PileViewerMainException as e:
4292 error = str(e)
4293 hideit = False
4295 return clearit, hideit, error
4297 return PileViewerMain
4300PileViewerMain = MakePileViewerMainClass(qw.QWidget)
4301GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget)
4304class LineEditWithAbort(qw.QLineEdit):
4306 aborted = qc.pyqtSignal()
4307 history_down = qc.pyqtSignal()
4308 history_up = qc.pyqtSignal()
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)
4321class PileViewer(qw.QFrame):
4322 '''
4323 PileViewerMain + Controls + Inputline
4324 '''
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):
4334 qw.QFrame.__init__(self, *args)
4336 layout = qw.QGridLayout()
4337 layout.setContentsMargins(0, 0, 0, 0)
4338 layout.setSpacing(0)
4340 self.menu = PileViewerMenuBar(self)
4342 if use_opengl is None:
4343 use_opengl = is_macos
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)
4358 self.marker_editor_sortable = marker_editor_sortable
4360 # self.setFrameShape(qw.QFrame.StyledPanel)
4361 # self.setFrameShadow(qw.QFrame.Sunken)
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)
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)
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))
4381 self.inputline.textEdited.connect(
4382 self.inputline_changed)
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
4391 self.inputline_error_str = None
4393 self.inputline_error = qw.QLabel()
4394 self.inputline_error.hide()
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)
4401 pb = Progressbars(self)
4402 layout.addWidget(pb, 2, 0, 1, 2)
4403 self.progressbars = pb
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)
4411 self.block_scrollbar_changes = False
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)
4422 self.setLayout(layout)
4424 def cleanup(self):
4425 self.viewer.cleanup()
4427 def get_progressbars(self):
4428 return self.progressbars
4430 def inputline_show(self):
4431 if not self.history:
4432 self.load_inputline_history()
4434 self.input_area.show()
4435 self.inputline.setFocus(qc.Qt.OtherFocusReason)
4436 self.inputline.selectAll()
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()
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()
4453 def inputline_changed(self, line):
4454 self.viewer.inputline_changed(str(line))
4455 self.inputline_clear_error()
4457 def inputline_returnpressed(self):
4458 line = str(self.inputline.text())
4459 clearit, hideit, error = self.viewer.inputline_finished(line)
4461 if error:
4462 self.inputline_set_error(error)
4464 line = line.strip()
4466 if line != '' and not error:
4467 if not (len(self.history) >= 1 and line == self.history[-1]):
4468 self.history.append(line)
4470 if clearit:
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)
4480 if hideit and not error:
4481 self.viewer.setFocus(qc.Qt.OtherFocusReason)
4482 self.input_area.hide()
4484 self.hist_ind = len(self.history)
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()
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
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)
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')
4518 with open(fn_hist, 'r') as f:
4519 self.history = [line.strip() for line in f.readlines()]
4521 self.hist_ind = len(self.history)
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('')
4535 def inputline_finished(self):
4536 pass
4538 def tracks_range_changed(self, ntracks, ilo, ihi):
4539 if self.block_scrollbar_changes:
4540 return
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)
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()
4558 def controls(self):
4559 frame = qw.QFrame(self)
4560 layout = qw.QGridLayout()
4561 frame.setLayout(layout)
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)
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 )
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)):
4605 for iwidget, widget in enumerate(control.widgets()):
4606 layout.addWidget(widget, icontrol, iwidget)
4608 spacer = qw.QSpacerItem(
4609 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding)
4610 layout.addItem(spacer, 4, 0, 1, 3)
4612 self.adjust_controls()
4613 self.viewer.viewmode_change(ViewMode.Wiggle)
4614 return frame
4616 def marker_editor(self):
4617 editor = pyrocko.gui.snuffler.marker_editor.MarkerEditor(
4618 self, sortable=self.marker_editor_sortable)
4620 editor.set_viewer(self.get_view())
4621 editor.get_marker_model().dataChanged.connect(
4622 self.update_contents)
4623 return editor
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)
4632 def setup_snufflings(self):
4633 self.viewer.setup_snufflings()
4635 def get_view(self):
4636 return self.viewer
4638 def update_contents(self):
4639 self.viewer.update()
4641 def get_pile(self):
4642 return self.viewer.get_pile()
4644 def show_colorbar_ctrl(self, show):
4645 for w in self.colorbar_control.widgets():
4646 w.setVisible(show)
4648 def show_gain_ctrl(self, show):
4649 for w in self.gain_control.widgets():
4650 w.setVisible(show)