Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/gui/snuffler/pile_viewer.py: 71%
2852 statements
« prev ^ index » next coverage.py v6.5.0, created at 2024-03-07 11:54 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2024-03-07 11:54 +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 PileViewerMenuBarButton(qw.QPushButton):
774 def __init__(self, text, *args, **kwargs):
775 qw.QPushButton.__init__(self, text, *args, **kwargs)
776 self.setFlat(True)
777 self.setSizePolicy(
778 qw.QSizePolicy.Preferred, qw.QSizePolicy.Preferred)
779 self.setContentsMargins(0, 0, 0, 0)
780 s = self.fontMetrics().boundingRect(text)
781 self.setMaximumHeight(s.height() + 3)
782 self.setMaximumWidth(max(s.height() + 3, s.width() + 10))
784 def sizeHint(self):
785 s = qw.QPushButton.sizeHint(self)
786 return qc.QSize(max(s.height(), s.width()), s.height())
789def MakePileViewerMainClass(base):
791 class PileViewerMain(base):
793 want_input = qc.pyqtSignal()
794 toggle_input = qc.pyqtSignal()
795 about_to_close = qc.pyqtSignal()
796 pile_has_changed_signal = qc.pyqtSignal()
797 tracks_range_changed = qc.pyqtSignal(int, int, int)
799 begin_markers_add = qc.pyqtSignal(int, int)
800 end_markers_add = qc.pyqtSignal()
801 begin_markers_remove = qc.pyqtSignal(int, int)
802 end_markers_remove = qc.pyqtSignal()
804 marker_selection_changed = qc.pyqtSignal(list)
805 active_event_marker_changed = qc.pyqtSignal()
807 def __init__(self, pile, ntracks_shown_max, panel_parent, *args,
808 menu=None):
809 base.__init__(self, *args)
811 self.pile = pile
812 self.ax_height = 80
813 self.panel_parent = panel_parent
815 self.click_tolerance = 5
817 self.ntracks_shown_max = ntracks_shown_max
818 self.initial_ntracks_shown_max = ntracks_shown_max
819 self.ntracks = 0
820 self.show_all = True
821 self.shown_tracks_range = None
822 self.track_start = None
823 self.track_trange = None
825 self.lowpass = None
826 self.highpass = None
827 self.gain = 1.0
828 self.rotate = 0.0
829 self.picking_down = None
830 self.picking = None
831 self.floating_marker = None
832 self.markers = pyrocko.pile.Sorted([], 'tmin')
833 self.markers_deltat_max = 0.
834 self.n_selected_markers = 0
835 self.all_marker_kinds = (0, 1, 2, 3, 4, 5, 6, 7)
836 self.visible_marker_kinds = self.all_marker_kinds
837 self.active_event_marker = None
838 self.ignore_releases = 0
839 self.reloaded = False
840 self.pile_has_changed = False
841 self.config = pyrocko.config.config('snuffler')
843 self.tax = TimeAx()
844 self.setBackgroundRole(qg.QPalette.Base)
845 self.setAutoFillBackground(True)
846 poli = qw.QSizePolicy(
847 qw.QSizePolicy.Expanding,
848 qw.QSizePolicy.Expanding)
850 self.setSizePolicy(poli)
851 self.setMinimumSize(300, 200)
852 self.setFocusPolicy(qc.Qt.ClickFocus)
854 self.menu = menu
856 file_menu = self.menu.addMenu('&File')
857 view_menu = self.menu.addMenu('&View')
858 options_menu = self.menu.addMenu('&Options')
859 scale_menu = self.menu.addMenu('&Scaling')
860 sort_menu = self.menu.addMenu('Sor&ting')
861 self.toggle_panel_menu = self.menu.addMenu('Sn&ufflings')
863 help_menu = self.menu.addMenu('&Help')
865 self.snufflings_menu = self.toggle_panel_menu.addMenu(
866 'Run Snuffling')
867 self.toggle_panel_menu.addSeparator()
868 self.snuffling_help = help_menu.addMenu('Snuffling Help')
869 help_menu.addSeparator()
871 file_menu.addAction(
872 qg.QIcon.fromTheme('document-open'),
873 'Open waveform files...',
874 self.open_waveforms,
875 qg.QKeySequence.Open)
877 file_menu.addAction(
878 qg.QIcon.fromTheme('document-open'),
879 'Open waveform directory...',
880 self.open_waveform_directory)
882 file_menu.addAction(
883 'Open station files...',
884 self.open_stations)
886 file_menu.addAction(
887 'Open StationXML files...',
888 self.open_stations_xml)
890 file_menu.addAction(
891 'Open event file...',
892 self.read_events)
894 file_menu.addSeparator()
895 file_menu.addAction(
896 'Open marker file...',
897 self.read_markers)
899 file_menu.addAction(
900 qg.QIcon.fromTheme('document-save'),
901 'Save markers...',
902 self.write_markers,
903 qg.QKeySequence.Save)
905 file_menu.addAction(
906 qg.QIcon.fromTheme('document-save-as'),
907 'Save selected markers...',
908 self.write_selected_markers,
909 qg.QKeySequence.SaveAs)
911 file_menu.addSeparator()
912 file_menu.addAction(
913 qg.QIcon.fromTheme('document-print'),
914 'Print',
915 self.printit,
916 qg.QKeySequence.Print)
918 file_menu.addAction(
919 qg.QIcon.fromTheme('insert-image'),
920 'Save as SVG or PNG',
921 self.savesvg,
922 qg.QKeySequence(qc.Qt.CTRL + qc.Qt.Key_E))
924 file_menu.addSeparator()
925 close = file_menu.addAction(
926 qg.QIcon.fromTheme('window-close'),
927 'Close',
928 self.myclose)
929 close.setShortcuts(
930 (qg.QKeySequence(qc.Qt.Key_Q),
931 qg.QKeySequence(qc.Qt.Key_X)))
933 # Scale Menu
934 menudef = [
935 ('Individual Scale',
936 lambda tr: tr.nslc_id,
937 qg.QKeySequence(qc.Qt.Key_S, qc.Qt.Key_I)),
938 ('Common Scale',
939 lambda tr: None,
940 qg.QKeySequence(qc.Qt.Key_S, qc.Qt.Key_C)),
941 ('Common Scale per Station',
942 lambda tr: (tr.network, tr.station),
943 qg.QKeySequence(qc.Qt.Key_S, qc.Qt.Key_S)),
944 ('Common Scale per Station Location',
945 lambda tr: (tr.network, tr.station, tr.location)),
946 ('Common Scale per Component',
947 lambda tr: (tr.channel)),
948 ]
950 self.menuitems_scaling = add_radiobuttongroup(
951 scale_menu, menudef, self.scalingmode_change,
952 default=self.config.trace_scale)
953 scale_menu.addSeparator()
955 self.scaling_key = self.menuitems_scaling[0][1]
956 self.scaling_hooks = {}
957 self.scalingmode_change()
959 menudef = [
960 ('Scaling based on Minimum and Maximum',
961 ('minmax', 'minmax')),
962 ('Scaling based on Minimum and Maximum (Robust)',
963 ('minmax', 'robust')),
964 ('Scaling based on Mean ± 2x Std. Deviation', (2, 'minmax')),
965 ('Scaling based on Mean ± 4x Std. Deviation', (4, 'minmax')),
966 ]
968 self.menuitems_scaling_base = add_radiobuttongroup(
969 scale_menu, menudef, self.scaling_base_change)
971 self.scaling_base = self.menuitems_scaling_base[0][1]
972 scale_menu.addSeparator()
974 self.menuitem_fixscalerange = scale_menu.addAction(
975 'Fix Scale Ranges')
976 self.menuitem_fixscalerange.setCheckable(True)
978 # Sort Menu
979 def sector_dist(sta):
980 if sta.dist_m is None:
981 return None, None
982 else:
983 return (
984 sector_int(round((sta.azimuth+15.)/30.)),
985 m_float(sta.dist_m))
987 menudef = [
988 ('Sort by Names',
989 lambda tr: (),
990 qg.QKeySequence(qc.Qt.Key_S, qc.Qt.Key_N)),
991 ('Sort by Distance',
992 lambda tr: self.station_attrib(
993 tr,
994 lambda sta: (m_float_or_none(sta.dist_m),),
995 lambda tr: (None,)),
996 qg.QKeySequence(qc.Qt.Key_S, qc.Qt.Key_D)),
997 ('Sort by Azimuth',
998 lambda tr: self.station_attrib(
999 tr,
1000 lambda sta: (deg_float_or_none(sta.azimuth),),
1001 lambda tr: (None,))),
1002 ('Sort by Distance in 12 Azimuthal Blocks',
1003 lambda tr: self.station_attrib(
1004 tr,
1005 sector_dist,
1006 lambda tr: (None, None))),
1007 ('Sort by Backazimuth',
1008 lambda tr: self.station_attrib(
1009 tr,
1010 lambda sta: (deg_float_or_none(sta.backazimuth),),
1011 lambda tr: (None,))),
1012 ]
1013 self.menuitems_ssorting = add_radiobuttongroup(
1014 sort_menu, menudef, self.s_sortingmode_change)
1015 sort_menu.addSeparator()
1017 self._ssort = lambda tr: ()
1019 self.menu.addSeparator()
1021 menudef = [
1022 ('Subsort by Network, Station, Location, Channel',
1023 ((0, 1, 2, 3), # gathering
1024 lambda tr: tr.location)), # coloring
1025 ('Subsort by Network, Station, Channel, Location',
1026 ((0, 1, 3, 2),
1027 lambda tr: tr.channel)),
1028 ('Subsort by Station, Network, Channel, Location',
1029 ((1, 0, 3, 2),
1030 lambda tr: tr.channel)),
1031 ('Subsort by Location, Network, Station, Channel',
1032 ((2, 0, 1, 3),
1033 lambda tr: tr.channel)),
1034 ('Subsort by Channel, Network, Station, Location',
1035 ((3, 0, 1, 2),
1036 lambda tr: (tr.network, tr.station, tr.location))),
1037 ('Subsort by Network, Station, Channel (Grouped by Location)',
1038 ((0, 1, 3),
1039 lambda tr: tr.location)),
1040 ('Subsort by Station, Network, Channel (Grouped by Location)',
1041 ((1, 0, 3),
1042 lambda tr: tr.location)),
1043 ]
1045 self.menuitems_sorting = add_radiobuttongroup(
1046 sort_menu, menudef, self.sortingmode_change)
1048 menudef = [(x.key, x.value) for x in
1049 self.config.visible_length_setting]
1051 # View menu
1052 self.menuitems_visible_length = add_radiobuttongroup(
1053 view_menu, menudef,
1054 self.visible_length_change)
1055 view_menu.addSeparator()
1057 view_modes = [
1058 ('Wiggle Plot', ViewMode.Wiggle),
1059 ('Waterfall', ViewMode.Waterfall)
1060 ]
1062 self.menuitems_viewmode = add_radiobuttongroup(
1063 view_menu, view_modes,
1064 self.viewmode_change, default=ViewMode.Wiggle)
1065 view_menu.addSeparator()
1067 self.menuitem_cliptraces = view_menu.addAction(
1068 'Clip Traces')
1069 self.menuitem_cliptraces.setCheckable(True)
1070 self.menuitem_cliptraces.setChecked(self.config.clip_traces)
1072 self.menuitem_showboxes = view_menu.addAction(
1073 'Show Boxes')
1074 self.menuitem_showboxes.setCheckable(True)
1075 self.menuitem_showboxes.setChecked(
1076 self.config.show_boxes)
1078 self.menuitem_colortraces = view_menu.addAction(
1079 'Color Traces')
1080 self.menuitem_colortraces.setCheckable(True)
1081 self.menuitem_antialias = view_menu.addAction(
1082 'Antialiasing')
1083 self.menuitem_antialias.setCheckable(True)
1085 view_menu.addSeparator()
1086 self.menuitem_showscalerange = view_menu.addAction(
1087 'Show Scale Ranges')
1088 self.menuitem_showscalerange.setCheckable(True)
1089 self.menuitem_showscalerange.setChecked(
1090 self.config.show_scale_ranges)
1092 self.menuitem_showscaleaxis = view_menu.addAction(
1093 'Show Scale Axes')
1094 self.menuitem_showscaleaxis.setCheckable(True)
1095 self.menuitem_showscaleaxis.setChecked(
1096 self.config.show_scale_axes)
1098 self.menuitem_showzeroline = view_menu.addAction(
1099 'Show Zero Lines')
1100 self.menuitem_showzeroline.setCheckable(True)
1102 view_menu.addSeparator()
1103 view_menu.addAction(
1104 qg.QIcon.fromTheme('view-fullscreen'),
1105 'Fullscreen',
1106 self.toggle_fullscreen,
1107 qg.QKeySequence(qc.Qt.Key_F11))
1109 # Options Menu
1110 self.menuitem_demean = options_menu.addAction('Demean')
1111 self.menuitem_demean.setCheckable(True)
1112 self.menuitem_demean.setChecked(self.config.demean)
1113 self.menuitem_demean.setShortcut(
1114 qg.QKeySequence(qc.Qt.Key_Underscore))
1116 self.menuitem_distances_3d = options_menu.addAction(
1117 '3D distances',
1118 self.distances_3d_changed)
1119 self.menuitem_distances_3d.setCheckable(True)
1121 self.menuitem_allowdownsampling = options_menu.addAction(
1122 'Allow Downsampling')
1123 self.menuitem_allowdownsampling.setCheckable(True)
1124 self.menuitem_allowdownsampling.setChecked(True)
1126 self.menuitem_degap = options_menu.addAction(
1127 'Allow Degapping')
1128 self.menuitem_degap.setCheckable(True)
1129 self.menuitem_degap.setChecked(True)
1131 options_menu.addSeparator()
1133 self.menuitem_fft_filtering = options_menu.addAction(
1134 'FFT Filtering')
1135 self.menuitem_fft_filtering.setCheckable(True)
1137 self.menuitem_lphp = options_menu.addAction(
1138 'Bandpass is Low- + Highpass')
1139 self.menuitem_lphp.setCheckable(True)
1140 self.menuitem_lphp.setChecked(True)
1142 options_menu.addSeparator()
1143 self.menuitem_watch = options_menu.addAction(
1144 'Watch Files')
1145 self.menuitem_watch.setCheckable(True)
1147 self.menuitem_liberal_fetch = options_menu.addAction(
1148 'Liberal Fetch Optimization')
1149 self.menuitem_liberal_fetch.setCheckable(True)
1151 self.visible_length = menudef[0][1]
1153 self.snufflings_menu.addAction(
1154 'Reload Snufflings',
1155 self.setup_snufflings)
1157 # Disable ShadowPileTest
1158 if False:
1159 test_action = self.menu.addAction(
1160 'Test',
1161 self.toggletest)
1162 test_action.setCheckable(True)
1164 help_menu.addAction(
1165 qg.QIcon.fromTheme('preferences-desktop-keyboard'),
1166 'Snuffler Controls',
1167 self.help,
1168 qg.QKeySequence(qc.Qt.Key_Question))
1170 help_menu.addAction(
1171 'About',
1172 self.about)
1174 toolbar = qw.QFrame(self.menu)
1175 toolbar_layout = qw.QHBoxLayout()
1176 toolbar_layout.setContentsMargins(1, 1, 1, 1)
1177 toolbar.setLayout(toolbar_layout)
1179 def tracks_plus(*args):
1180 self.zoom_tracks(0., 1.)
1182 button = PileViewerMenuBarButton('+')
1183 button.clicked.connect(tracks_plus)
1184 button.setToolTip('Show more traces.')
1185 toolbar_layout.addWidget(button)
1187 def tracks_minus(*args):
1188 self.zoom_tracks(0., -1.)
1190 button = PileViewerMenuBarButton('-')
1191 button.clicked.connect(tracks_minus)
1192 button.setToolTip('Show fewer traces.')
1193 toolbar_layout.addWidget(button)
1195 def toggle_input(*args):
1196 self.toggle_input.emit()
1198 button = PileViewerMenuBarButton(':')
1199 button.setToolTip('Show command line.')
1200 button.clicked.connect(toggle_input)
1201 toolbar_layout.addWidget(button)
1203 self.menu.setCornerWidget(toolbar)
1205 self.time_projection = Projection()
1206 self.set_time_range(self.pile.get_tmin(), self.pile.get_tmax())
1207 self.time_projection.set_out_range(0., self.width())
1209 self.gather = None
1211 self.trace_filter = None
1212 self.quick_filter = None
1213 self.quick_filter_patterns = None, None
1214 self.blacklist = []
1216 self.track_to_screen = Projection()
1217 self.track_to_nslc_ids = {}
1219 self.cached_vec = None
1220 self.cached_processed_traces = None
1222 self.timer = qc.QTimer(self)
1223 self.timer.timeout.connect(self.periodical)
1224 self.timer.setInterval(1000)
1225 self.timer.start()
1227 self._pile_changed = self.pile_changed # need to keep a strong ref
1228 self.pile.add_listener(self._pile_changed)
1230 self.trace_styles = {}
1231 if self.get_squirrel() is None:
1232 self.determine_box_styles()
1234 self.setMouseTracking(True)
1236 user_home_dir = os.path.expanduser('~')
1237 self.snuffling_modules = {}
1238 self.snuffling_paths = [os.path.join(user_home_dir, '.snufflings')]
1239 self.default_snufflings = None
1240 self.snufflings = []
1242 self.stations = {}
1244 self.timer_draw = Timer()
1245 self.timer_cutout = Timer()
1246 self.time_spent_painting = 0.0
1247 self.time_last_painted = time.time()
1249 self.interactive_range_change_time = 0.0
1250 self.interactive_range_change_delay_time = 10.0
1251 self.follow_timer = None
1253 self.sortingmode_change_time = 0.0
1254 self.sortingmode_change_delay_time = None
1256 self.old_data_ranges = {}
1258 self.return_tag = None
1259 self.wheel_pos = 60
1261 self.setAcceptDrops(True)
1262 self._paths_to_load = []
1264 self.tf_cache = {}
1266 self.waterfall = TraceWaterfall()
1267 self.waterfall_cmap = 'viridis'
1268 self.waterfall_clip_min = 0.
1269 self.waterfall_clip_max = 1.
1270 self.waterfall_show_absolute = False
1271 self.waterfall_integrate = False
1272 self.view_mode = ViewMode.Wiggle
1274 self.automatic_updates = True
1276 self.closing = False
1277 self.in_paint_event = False
1279 def fail(self, reason):
1280 box = qw.QMessageBox(self)
1281 box.setText(reason)
1282 box.exec_()
1284 def set_trace_filter(self, filter_func):
1285 self.trace_filter = filter_func
1286 self.sortingmode_change()
1288 def update_trace_filter(self):
1289 if self.blacklist:
1291 def blacklist_func(tr):
1292 return not pyrocko.util.match_nslc(
1293 self.blacklist, tr.nslc_id)
1295 else:
1296 blacklist_func = None
1298 if self.quick_filter is None and blacklist_func is None:
1299 self.set_trace_filter(None)
1300 elif self.quick_filter is None:
1301 self.set_trace_filter(blacklist_func)
1302 elif blacklist_func is None:
1303 self.set_trace_filter(self.quick_filter)
1304 else:
1305 self.set_trace_filter(
1306 lambda tr: blacklist_func(tr) and self.quick_filter(tr))
1308 def set_quick_filter(self, filter_func):
1309 self.quick_filter = filter_func
1310 self.update_trace_filter()
1312 def set_quick_filter_patterns(self, patterns, inputline=None):
1313 if patterns is not None:
1314 self.set_quick_filter(
1315 lambda tr: pyrocko.util.match_nslc(patterns, tr.nslc_id))
1316 else:
1317 self.set_quick_filter(None)
1319 self.quick_filter_patterns = patterns, inputline
1321 def get_quick_filter_patterns(self):
1322 return self.quick_filter_patterns
1324 def add_blacklist_pattern(self, pattern):
1325 if pattern == 'empty':
1326 keys = set(self.pile.nslc_ids)
1327 trs = self.pile.all(
1328 tmin=self.tmin,
1329 tmax=self.tmax,
1330 load_data=False,
1331 degap=False)
1333 for tr in trs:
1334 if tr.nslc_id in keys:
1335 keys.remove(tr.nslc_id)
1337 for key in keys:
1338 xpattern = '.'.join(key)
1339 if xpattern not in self.blacklist:
1340 self.blacklist.append(xpattern)
1342 else:
1343 if pattern in self.blacklist:
1344 self.blacklist.remove(pattern)
1346 self.blacklist.append(pattern)
1348 logger.info('Blacklist is [ %s ]' % ', '.join(self.blacklist))
1349 self.update_trace_filter()
1351 def remove_blacklist_pattern(self, pattern):
1352 if pattern in self.blacklist:
1353 self.blacklist.remove(pattern)
1354 else:
1355 raise PileViewerMainException(
1356 'Pattern not found in blacklist.')
1358 logger.info('Blacklist is [ %s ]' % ', '.join(self.blacklist))
1359 self.update_trace_filter()
1361 def clear_blacklist(self):
1362 self.blacklist = []
1363 self.update_trace_filter()
1365 def ssort(self, tr):
1366 return self._ssort(tr)
1368 def station_key(self, x):
1369 return x.network, x.station
1371 def station_keys(self, x):
1372 return [
1373 (x.network, x.station, x.location),
1374 (x.network, x.station)]
1376 def station_attrib(self, tr, getter, default_getter):
1377 for sk in self.station_keys(tr):
1378 if sk in self.stations:
1379 station = self.stations[sk]
1380 return getter(station)
1382 return default_getter(tr)
1384 def get_station(self, sk):
1385 return self.stations[sk]
1387 def has_station(self, station):
1388 for sk in self.station_keys(station):
1389 if sk in self.stations:
1390 return True
1392 return False
1394 def station_latlon(self, tr, default_getter=lambda tr: (0., 0.)):
1395 return self.station_attrib(
1396 tr, lambda sta: (sta.lat, sta.lon), default_getter)
1398 def set_stations(self, stations):
1399 self.stations = {}
1400 self.add_stations(stations)
1402 def add_stations(self, stations):
1403 for station in stations:
1404 for sk in self.station_keys(station):
1405 self.stations[sk] = station
1407 ev = self.get_active_event()
1408 if ev:
1409 self.set_origin(ev)
1411 def add_event(self, event):
1412 marker = EventMarker(event)
1413 self.add_marker(marker)
1415 def add_events(self, events):
1416 markers = [EventMarker(e) for e in events]
1417 self.add_markers(markers)
1419 def set_event_marker_as_origin(self, ignore=None):
1420 selected = self.selected_markers()
1421 if not selected:
1422 self.fail('An event marker must be selected.')
1423 return
1425 m = selected[0]
1426 if not isinstance(m, EventMarker):
1427 self.fail('Selected marker is not an event.')
1428 return
1430 self.set_active_event_marker(m)
1432 def deactivate_event_marker(self):
1433 if self.active_event_marker:
1434 self.active_event_marker.active = False
1436 self.active_event_marker_changed.emit()
1437 self.active_event_marker = None
1439 def set_active_event_marker(self, event_marker):
1440 if self.active_event_marker:
1441 self.active_event_marker.active = False
1443 self.active_event_marker = event_marker
1444 event_marker.active = True
1445 event = event_marker.get_event()
1446 self.set_origin(event)
1447 self.active_event_marker_changed.emit()
1449 def set_active_event(self, event):
1450 for marker in self.markers:
1451 if isinstance(marker, EventMarker):
1452 if marker.get_event() is event:
1453 self.set_active_event_marker(marker)
1455 def get_active_event_marker(self):
1456 return self.active_event_marker
1458 def get_active_event(self):
1459 m = self.get_active_event_marker()
1460 if m is not None:
1461 return m.get_event()
1462 else:
1463 return None
1465 def get_active_markers(self):
1466 emarker = self.get_active_event_marker()
1467 if emarker is None:
1468 return None, []
1470 else:
1471 ev = emarker.get_event()
1472 pmarkers = [
1473 m for m in self.markers
1474 if isinstance(m, PhaseMarker) and m.get_event() is ev]
1476 return emarker, pmarkers
1478 def set_origin(self, location):
1479 for station in self.stations.values():
1480 station.set_event_relative_data(
1481 location,
1482 distance_3d=self.menuitem_distances_3d.isChecked())
1484 self.sortingmode_change()
1486 def distances_3d_changed(self):
1487 ignore = self.menuitem_distances_3d.isChecked()
1488 self.set_event_marker_as_origin(ignore)
1490 def iter_snuffling_modules(self):
1491 pjoin = os.path.join
1492 for path in self.snuffling_paths:
1494 if not os.path.isdir(path):
1495 os.mkdir(path)
1497 for entry in os.listdir(path):
1498 directory = path
1499 fn = entry
1500 d = pjoin(path, entry)
1501 if os.path.isdir(d):
1502 directory = d
1503 if os.path.isfile(
1504 os.path.join(directory, 'snuffling.py')):
1505 fn = 'snuffling.py'
1507 if not fn.endswith('.py'):
1508 continue
1510 name = fn[:-3]
1512 if (directory, name) not in self.snuffling_modules:
1513 self.snuffling_modules[directory, name] = \
1514 pyrocko.gui.snuffler.snuffling.SnufflingModule(
1515 directory, name, self)
1517 yield self.snuffling_modules[directory, name]
1519 def setup_snufflings(self):
1520 # user snufflings
1521 from pyrocko.gui.snuffler.snuffling import BrokenSnufflingModule
1522 for mod in self.iter_snuffling_modules():
1523 try:
1524 mod.load_if_needed()
1525 except BrokenSnufflingModule as e:
1526 logger.warning('Snuffling module "%s" is broken' % e)
1528 # load the default snufflings on first run
1529 if self.default_snufflings is None:
1530 self.default_snufflings = pyrocko.gui.snuffler\
1531 .snufflings.__snufflings__()
1532 for snuffling in self.default_snufflings:
1533 self.add_snuffling(snuffling)
1535 def set_panel_parent(self, panel_parent):
1536 self.panel_parent = panel_parent
1538 def get_panel_parent(self):
1539 return self.panel_parent
1541 def add_snuffling(self, snuffling, reloaded=False):
1542 logger.debug('Adding snuffling %s' % snuffling.get_name())
1543 snuffling.init_gui(
1544 self, self.get_panel_parent(), self, reloaded=reloaded)
1545 self.snufflings.append(snuffling)
1546 self.update()
1548 def remove_snuffling(self, snuffling):
1549 snuffling.delete_gui()
1550 self.update()
1551 self.snufflings.remove(snuffling)
1552 snuffling.pre_destroy()
1554 def add_snuffling_menuitem(self, item):
1555 self.snufflings_menu.addAction(item)
1556 item.setParent(self.snufflings_menu)
1557 sort_actions(self.snufflings_menu)
1559 def remove_snuffling_menuitem(self, item):
1560 self.snufflings_menu.removeAction(item)
1562 def add_snuffling_help_menuitem(self, item):
1563 self.snuffling_help.addAction(item)
1564 item.setParent(self.snuffling_help)
1565 sort_actions(self.snuffling_help)
1567 def remove_snuffling_help_menuitem(self, item):
1568 self.snuffling_help.removeAction(item)
1570 def add_panel_toggler(self, item):
1571 self.toggle_panel_menu.addAction(item)
1572 item.setParent(self.toggle_panel_menu)
1573 sort_actions(self.toggle_panel_menu)
1575 def remove_panel_toggler(self, item):
1576 self.toggle_panel_menu.removeAction(item)
1578 def load(self, paths, regex=None, format='detect',
1579 cache_dir=None, force_cache=False):
1581 if cache_dir is None:
1582 cache_dir = pyrocko.config.config().cache_dir
1583 if isinstance(paths, str):
1584 paths = [paths]
1586 fns = pyrocko.util.select_files(
1587 paths, selector=None, include=regex, show_progress=False)
1589 if not fns:
1590 return
1592 cache = pyrocko.pile.get_cache(cache_dir)
1594 t = [time.time()]
1596 def update_bar(label, value):
1597 pbs = self.parent().get_progressbars()
1598 if label.lower() == 'looking at files':
1599 label = 'Looking at %i files' % len(fns)
1600 else:
1601 label = 'Scanning %i files' % len(fns)
1603 return pbs.set_status(label, value)
1605 def update_progress(label, i, n):
1606 abort = False
1608 qw.qApp.processEvents()
1609 if n != 0:
1610 perc = i*100/n
1611 else:
1612 perc = 100
1613 abort |= update_bar(label, perc)
1614 abort |= self.window().is_closing()
1616 tnow = time.time()
1617 if t[0] + 1. + self.time_spent_painting * 10. < tnow:
1618 self.update()
1619 t[0] = tnow
1621 return abort
1623 self.automatic_updates = False
1625 self.pile.load_files(
1626 sorted(fns),
1627 filename_attributes=regex,
1628 cache=cache,
1629 fileformat=format,
1630 show_progress=False,
1631 update_progress=update_progress)
1633 self.automatic_updates = True
1634 self.update()
1636 def load_queued(self):
1637 if not self._paths_to_load:
1638 return
1639 paths = self._paths_to_load
1640 self._paths_to_load = []
1641 self.load(paths)
1643 def load_soon(self, paths):
1644 self._paths_to_load.extend(paths)
1645 qc.QTimer.singleShot(200, self.load_queued)
1647 def open_waveforms(self):
1648 caption = 'Select one or more files to open'
1650 fns, _ = qw.QFileDialog.getOpenFileNames(
1651 self, caption, options=qfiledialog_options)
1653 if fns:
1654 self.load(list(str(fn) for fn in fns))
1656 def open_waveform_directory(self):
1657 caption = 'Select directory to scan for waveform files'
1659 dn = qw.QFileDialog.getExistingDirectory(
1660 self, caption, options=qfiledialog_options)
1662 if dn:
1663 self.load([str(dn)])
1665 def open_stations(self, fns=None):
1666 caption = 'Select one or more Pyrocko station files to open'
1668 if not fns:
1669 fns, _ = qw.QFileDialog.getOpenFileNames(
1670 self, caption, options=qfiledialog_options)
1672 try:
1673 stations = [pyrocko.model.load_stations(str(x)) for x in fns]
1674 for stat in stations:
1675 self.add_stations(stat)
1677 except Exception as e:
1678 self.fail('Failed to read station file: %s' % str(e))
1680 def open_stations_xml(self, fns=None):
1681 from pyrocko.io import stationxml
1683 caption = 'Select one or more StationXML files'
1684 if not fns:
1685 fns, _ = qw.QFileDialog.getOpenFileNames(
1686 self, caption, options=qfiledialog_options,
1687 filter='StationXML (*.xml *.XML *.stationxml *.stationXML)'
1688 ';;All files (*)')
1690 try:
1691 stations = [
1692 stationxml.load_xml(filename=str(x)).get_pyrocko_stations()
1693 for x in fns]
1695 for stat in stations:
1696 self.add_stations(stat)
1698 except Exception as e:
1699 self.fail('Failed to read StationXML file: %s' % str(e))
1701 def add_traces(self, traces):
1702 if traces:
1703 mtf = pyrocko.pile.MemTracesFile(None, traces)
1704 self.pile.add_file(mtf)
1705 ticket = (self.pile, mtf)
1706 return ticket
1707 else:
1708 return (None, None)
1710 def release_data(self, tickets):
1711 for ticket in tickets:
1712 pile, mtf = ticket
1713 if pile is not None:
1714 pile.remove_file(mtf)
1716 def periodical(self):
1717 if self.menuitem_watch.isChecked():
1718 if self.pile.reload_modified():
1719 self.update()
1721 def get_pile(self):
1722 return self.pile
1724 def pile_changed(self, what, content):
1725 self.pile_has_changed = True
1726 self.pile_has_changed_signal.emit()
1727 if self.automatic_updates:
1728 self.update()
1730 def set_gathering(self, gather=None, color=None):
1732 if gather is None:
1733 def gather_func(tr):
1734 return tr.nslc_id
1736 gather = (0, 1, 2, 3)
1738 else:
1739 def gather_func(tr):
1740 return (
1741 self.ssort(tr) + tuple(tr.nslc_id[i] for i in gather))
1743 if color is None:
1744 def color(tr):
1745 return tr.location
1747 self.gather = gather_func
1748 keys = self.pile.gather_keys(gather_func, self.trace_filter)
1750 self.color_gather = color
1751 self.color_keys = self.pile.gather_keys(color)
1752 previous_ntracks = self.ntracks
1753 self.set_ntracks(len(keys))
1755 if self.shown_tracks_range is None or \
1756 previous_ntracks == 0 or \
1757 self.show_all:
1759 low, high = 0, min(self.ntracks_shown_max, self.ntracks)
1760 key_at_top = None
1761 n = high-low
1763 else:
1764 low, high = self.shown_tracks_range
1765 key_at_top = self.track_keys[low]
1766 n = high-low
1768 self.track_keys = sorted(keys)
1770 track_patterns = []
1771 for k in self.track_keys:
1772 pat = ['*', '*', '*', '*']
1773 for i, j in enumerate(gather):
1774 pat[j] = k[-len(gather)+i]
1776 track_patterns.append(pat)
1778 self.track_patterns = track_patterns
1780 if key_at_top is not None:
1781 try:
1782 ind = self.track_keys.index(key_at_top)
1783 low = ind
1784 high = low+n
1785 except Exception:
1786 pass
1788 self.set_tracks_range((low, high))
1790 self.key_to_row = dict(
1791 [(key, i) for (i, key) in enumerate(self.track_keys)])
1793 def inrange(x, r):
1794 return r[0] <= x and x < r[1]
1796 def trace_selector(trace):
1797 gt = self.gather(trace)
1798 return (
1799 gt in self.key_to_row and
1800 inrange(self.key_to_row[gt], self.shown_tracks_range))
1802 self.trace_selector = lambda x: \
1803 (self.trace_filter is None or self.trace_filter(x)) \
1804 and trace_selector(x)
1806 if self.tmin == working_system_time_range[0] and \
1807 self.tmax == working_system_time_range[1] or \
1808 self.show_all:
1810 tmin, tmax = self.pile.get_tmin(), self.pile.get_tmax()
1811 if tmin is not None and tmax is not None:
1812 tlen = (tmax - tmin)
1813 tpad = tlen * 5./self.width()
1814 self.set_time_range(tmin-tpad, tmax+tpad)
1816 def set_time_range(self, tmin, tmax):
1817 if tmin is None:
1818 tmin = initial_time_range[0]
1820 if tmax is None:
1821 tmax = initial_time_range[1]
1823 if tmin > tmax:
1824 tmin, tmax = tmax, tmin
1826 if tmin == tmax:
1827 tmin -= 1.
1828 tmax += 1.
1830 tmin = max(working_system_time_range[0], tmin)
1831 tmax = min(working_system_time_range[1], tmax)
1833 min_deltat = self.content_deltat_range()[0]
1834 if (tmax - tmin < min_deltat):
1835 m = (tmin + tmax) / 2.
1836 tmin = m - min_deltat/2.
1837 tmax = m + min_deltat/2.
1839 self.time_projection.set_in_range(tmin, tmax)
1840 self.tmin, self.tmax = tmin, tmax
1842 def get_time_range(self):
1843 return self.tmin, self.tmax
1845 def ypart(self, y):
1846 if y < self.ax_height:
1847 return -1
1848 elif y > self.height()-self.ax_height:
1849 return 1
1850 else:
1851 return 0
1853 def time_fractional_digits(self):
1854 min_deltat = self.content_deltat_range()[0]
1855 return min(9, max(1, int(-math.floor(math.log10(min_deltat)))+2))
1857 def write_markers(self, fn=None):
1858 caption = 'Choose a file name to write markers'
1859 if not fn:
1860 fn, _ = qw.QFileDialog.getSaveFileName(
1861 self, caption, options=qfiledialog_options)
1862 if fn:
1863 try:
1864 Marker.save_markers(
1865 self.markers, fn,
1866 fdigits=self.time_fractional_digits())
1868 except Exception as e:
1869 self.fail('Failed to write marker file: %s' % str(e))
1871 def write_selected_markers(self, fn=None):
1872 caption = 'Choose a file name to write selected markers'
1873 if not fn:
1874 fn, _ = qw.QFileDialog.getSaveFileName(
1875 self, caption, options=qfiledialog_options)
1876 if fn:
1877 try:
1878 Marker.save_markers(
1879 self.iter_selected_markers(),
1880 fn,
1881 fdigits=self.time_fractional_digits())
1883 except Exception as e:
1884 self.fail('Failed to write marker file: %s' % str(e))
1886 def read_events(self, fn=None):
1887 '''
1888 Open QFileDialog to open, read and add
1889 :py:class:`pyrocko.model.Event` instances and their marker
1890 representation to the pile viewer.
1891 '''
1892 caption = 'Selet one or more files to open'
1893 if not fn:
1894 fn, _ = qw.QFileDialog.getOpenFileName(
1895 self, caption, options=qfiledialog_options)
1896 if fn:
1897 try:
1898 self.add_events(pyrocko.model.load_events(fn))
1899 self.associate_phases_to_events()
1901 except Exception as e:
1902 self.fail('Failed to read event file: %s' % str(e))
1904 def read_markers(self, fn=None):
1905 '''
1906 Open QFileDialog to open, read and add markers to the pile viewer.
1907 '''
1908 caption = 'Selet one or more marker files to open'
1909 if not fn:
1910 fn, _ = qw.QFileDialog.getOpenFileName(
1911 self, caption, options=qfiledialog_options)
1912 if fn:
1913 try:
1914 self.add_markers(Marker.load_markers(fn))
1915 self.associate_phases_to_events()
1917 except Exception as e:
1918 self.fail('Failed to read marker file: %s' % str(e))
1920 def associate_phases_to_events(self):
1921 associate_phases_to_events(self.markers)
1923 def add_marker(self, marker):
1924 # need index to inform QAbstactTableModel about upcoming change,
1925 # but have to restore current state in order to not cause problems
1926 self.markers.insert(marker)
1927 i = self.markers.remove(marker)
1929 self.begin_markers_add.emit(i, i)
1930 self.markers.insert(marker)
1931 self.end_markers_add.emit()
1932 self.markers_deltat_max = max(
1933 self.markers_deltat_max, marker.tmax - marker.tmin)
1935 def add_markers(self, markers):
1936 if not self.markers:
1937 self.begin_markers_add.emit(0, len(markers) - 1)
1938 self.markers.insert_many(markers)
1939 self.end_markers_add.emit()
1940 self.update_markers_deltat_max()
1941 else:
1942 for marker in markers:
1943 self.add_marker(marker)
1945 def update_markers_deltat_max(self):
1946 if self.markers:
1947 self.markers_deltat_max = max(
1948 marker.tmax - marker.tmin for marker in self.markers)
1950 def remove_marker(self, marker):
1951 '''
1952 Remove a ``marker`` from the :py:class:`PileViewer`.
1954 :param marker:
1955 :py:class:`~pyrocko.gui.snuffler.marker.Marker` (or subclass)
1956 instance
1957 '''
1959 if marker is self.active_event_marker:
1960 self.deactivate_event_marker()
1962 try:
1963 i = self.markers.index(marker)
1964 self.begin_markers_remove.emit(i, i)
1965 self.markers.remove_at(i)
1966 self.end_markers_remove.emit()
1967 except ValueError:
1968 pass
1970 def remove_markers(self, markers):
1971 '''
1972 Remove a list of ``markers`` from the :py:class:`PileViewer`.
1974 :param markers:
1975 list of :py:class:`~pyrocko.gui.snuffler.marker.Marker` (or
1976 subclass) instances
1977 '''
1979 if markers is self.markers:
1980 markers = list(markers)
1982 for marker in markers:
1983 self.remove_marker(marker)
1985 self.update_markers_deltat_max()
1987 def remove_selected_markers(self):
1988 def delete_segment(istart, iend):
1989 self.begin_markers_remove.emit(istart, iend-1)
1990 for _ in range(iend - istart):
1991 self.markers.remove_at(istart)
1993 self.end_markers_remove.emit()
1995 istart = None
1996 ipos = 0
1997 markers = self.markers
1998 nmarkers = len(self.markers)
1999 while ipos < nmarkers:
2000 marker = markers[ipos]
2001 if marker.is_selected():
2002 if marker is self.active_event_marker:
2003 self.deactivate_event_marker()
2005 if istart is None:
2006 istart = ipos
2007 else:
2008 if istart is not None:
2009 delete_segment(istart, ipos)
2010 nmarkers -= ipos - istart
2011 ipos = istart - 1
2012 istart = None
2014 ipos += 1
2016 if istart is not None:
2017 delete_segment(istart, ipos)
2019 self.update_markers_deltat_max()
2021 def selected_markers(self):
2022 return [marker for marker in self.markers if marker.is_selected()]
2024 def iter_selected_markers(self):
2025 for marker in self.markers:
2026 if marker.is_selected():
2027 yield marker
2029 def get_markers(self):
2030 return self.markers
2032 def mousePressEvent(self, mouse_ev):
2033 ''
2034 self.show_all = False
2035 point = self.mapFromGlobal(mouse_ev.globalPos())
2037 if mouse_ev.button() == qc.Qt.LeftButton:
2038 marker = self.marker_under_cursor(point.x(), point.y())
2039 if self.picking:
2040 if self.picking_down is None:
2041 self.picking_down = (
2042 self.time_projection.rev(mouse_ev.x()),
2043 mouse_ev.y())
2045 elif marker is not None:
2046 if not (mouse_ev.modifiers() & qc.Qt.ShiftModifier):
2047 self.deselect_all()
2048 marker.selected = True
2049 self.emit_selected_markers()
2050 self.update()
2051 else:
2052 self.track_start = mouse_ev.x(), mouse_ev.y()
2053 self.track_trange = self.tmin, self.tmax
2055 if mouse_ev.button() == qc.Qt.RightButton \
2056 and isinstance(self.menu, qw.QMenu):
2057 self.menu.exec_(qg.QCursor.pos())
2058 self.update_status()
2060 def mouseReleaseEvent(self, mouse_ev):
2061 ''
2062 if self.ignore_releases:
2063 self.ignore_releases -= 1
2064 return
2066 if self.picking:
2067 self.stop_picking(mouse_ev.x(), mouse_ev.y())
2068 self.emit_selected_markers()
2070 if self.track_start:
2071 self.update()
2073 self.track_start = None
2074 self.track_trange = None
2075 self.update_status()
2077 def mouseDoubleClickEvent(self, mouse_ev):
2078 ''
2079 self.show_all = False
2080 self.start_picking(None)
2081 self.ignore_releases = 1
2083 def mouseMoveEvent(self, mouse_ev):
2084 ''
2085 point = self.mapFromGlobal(mouse_ev.globalPos())
2087 if self.picking:
2088 self.update_picking(point.x(), point.y())
2090 elif self.track_start is not None:
2091 x0, y0 = self.track_start
2092 dx = (point.x() - x0)/float(self.width())
2093 dy = (point.y() - y0)/float(self.height())
2094 if self.ypart(y0) == 1:
2095 dy = 0
2097 tmin0, tmax0 = self.track_trange
2099 scale = math.exp(-dy*5.)
2100 dtr = scale*(tmax0-tmin0) - (tmax0-tmin0)
2101 frac = x0/float(self.width())
2102 dt = dx*(tmax0-tmin0)*scale
2104 self.interrupt_following()
2105 self.set_time_range(
2106 tmin0 - dt - dtr*frac,
2107 tmax0 - dt + dtr*(1.-frac))
2109 self.update()
2110 else:
2111 self.hoovering(point.x(), point.y())
2113 self.update_status()
2115 def nslc_ids_under_cursor(self, x, y):
2116 ftrack = self.track_to_screen.rev(y)
2117 nslc_ids = self.get_nslc_ids_for_track(ftrack)
2118 return nslc_ids
2120 def marker_under_cursor(self, x, y):
2121 mouset = self.time_projection.rev(x)
2122 deltat = (self.tmax-self.tmin)*self.click_tolerance/self.width()
2123 relevant_nslc_ids = None
2124 for marker in self.markers:
2125 if marker.kind not in self.visible_marker_kinds:
2126 continue
2128 if (abs(mouset-marker.tmin) < deltat or
2129 abs(mouset-marker.tmax) < deltat):
2131 if relevant_nslc_ids is None:
2132 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y)
2134 marker_nslc_ids = marker.get_nslc_ids()
2135 if not marker_nslc_ids:
2136 return marker
2138 for nslc_id in marker_nslc_ids:
2139 if nslc_id in relevant_nslc_ids:
2140 return marker
2142 def hoovering(self, x, y):
2143 mouset = self.time_projection.rev(x)
2144 deltat = (self.tmax-self.tmin)*self.click_tolerance/self.width()
2145 needupdate = False
2146 haveone = False
2147 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y)
2148 for marker in self.markers:
2149 if marker.kind not in self.visible_marker_kinds:
2150 continue
2152 state = abs(mouset-marker.tmin) < deltat or \
2153 abs(mouset-marker.tmax) < deltat and not haveone
2155 if state:
2156 xstate = False
2158 marker_nslc_ids = marker.get_nslc_ids()
2159 if not marker_nslc_ids:
2160 xstate = True
2162 for nslc in relevant_nslc_ids:
2163 if marker.match_nslc(nslc):
2164 xstate = True
2166 state = xstate
2168 if state:
2169 haveone = True
2170 oldstate = marker.is_alerted()
2171 if oldstate != state:
2172 needupdate = True
2173 marker.set_alerted(state)
2174 if state:
2175 self.window().status_messages.set(
2176 'marker', marker.hoover_message())
2178 if needupdate:
2179 self.update()
2181 self.update_status()
2183 def event(self, event):
2184 ''
2185 if event.type() == qc.QEvent.KeyPress:
2186 self.keyPressEvent(event)
2187 return True
2188 else:
2189 return base.event(self, event)
2191 def keyPressEvent(self, key_event):
2192 ''
2193 self.show_all = False
2194 dt = self.tmax - self.tmin
2195 tmid = (self.tmin + self.tmax) / 2.
2197 key = key_event.key()
2198 try:
2199 keytext = str(key_event.text())
2200 except UnicodeEncodeError:
2201 return
2203 if key == qc.Qt.Key_Space:
2204 self.interrupt_following()
2205 self.set_time_range(self.tmin+dt, self.tmax+dt)
2207 elif key == qc.Qt.Key_Up:
2208 for m in self.selected_markers():
2209 if isinstance(m, PhaseMarker):
2210 if key_event.modifiers() & qc.Qt.ShiftModifier:
2211 p = 0
2212 else:
2213 p = 1 if m.get_polarity() != 1 else None
2214 m.set_polarity(p)
2216 elif key == qc.Qt.Key_Down:
2217 for m in self.selected_markers():
2218 if isinstance(m, PhaseMarker):
2219 if key_event.modifiers() & qc.Qt.ShiftModifier:
2220 p = 0
2221 else:
2222 p = -1 if m.get_polarity() != -1 else None
2223 m.set_polarity(p)
2225 elif key == qc.Qt.Key_B:
2226 dt = self.tmax - self.tmin
2227 self.interrupt_following()
2228 self.set_time_range(self.tmin-dt, self.tmax-dt)
2230 elif key in (qc.Qt.Key_Tab, qc.Qt.Key_Backtab):
2231 self.interrupt_following()
2233 tgo = None
2235 class TraceDummy(object):
2236 def __init__(self, marker):
2237 self._marker = marker
2239 @property
2240 def nslc_id(self):
2241 return self._marker.one_nslc()
2243 def marker_to_itrack(marker):
2244 try:
2245 return self.key_to_row.get(
2246 self.gather(TraceDummy(marker)), -1)
2248 except MarkerOneNSLCRequired:
2249 return -1
2251 emarker, pmarkers = self.get_active_markers()
2252 pmarkers = [
2253 m for m in pmarkers if m.kind in self.visible_marker_kinds]
2254 pmarkers.sort(key=lambda m: (
2255 marker_to_itrack(m), (m.tmin + m.tmax) / 2.0))
2257 if key == qc.Qt.Key_Backtab:
2258 pmarkers.reverse()
2260 smarkers = self.selected_markers()
2261 iselected = []
2262 for sm in smarkers:
2263 try:
2264 iselected.append(pmarkers.index(sm))
2265 except ValueError:
2266 pass
2268 if iselected:
2269 icurrent = max(iselected) + 1
2270 else:
2271 icurrent = 0
2273 if icurrent < len(pmarkers):
2274 self.deselect_all()
2275 cmarker = pmarkers[icurrent]
2276 cmarker.selected = True
2277 tgo = cmarker.tmin
2278 if not self.tmin < tgo < self.tmax:
2279 self.set_time_range(tgo-dt/2., tgo+dt/2.)
2281 itrack = marker_to_itrack(cmarker)
2282 if itrack != -1:
2283 if itrack < self.shown_tracks_range[0]:
2284 self.scroll_tracks(
2285 - (self.shown_tracks_range[0] - itrack))
2286 elif self.shown_tracks_range[1] <= itrack:
2287 self.scroll_tracks(
2288 itrack - self.shown_tracks_range[1]+1)
2290 if itrack not in self.track_to_nslc_ids:
2291 self.go_to_selection()
2293 elif keytext in ('p', 'n', 'P', 'N'):
2294 smarkers = self.selected_markers()
2295 tgo = None
2296 dir = str(keytext)
2297 if smarkers:
2298 tmid = smarkers[0].tmin
2299 for smarker in smarkers:
2300 if dir == 'n':
2301 tmid = max(smarker.tmin, tmid)
2302 else:
2303 tmid = min(smarker.tmin, tmid)
2305 tgo = tmid
2307 if dir.lower() == 'n':
2308 for marker in sorted(
2309 self.markers,
2310 key=operator.attrgetter('tmin')):
2312 t = marker.tmin
2313 if t > tmid and \
2314 marker.kind in self.visible_marker_kinds and \
2315 (dir == 'n' or
2316 isinstance(marker, EventMarker)):
2318 self.deselect_all()
2319 marker.selected = True
2320 tgo = t
2321 break
2322 else:
2323 for marker in sorted(
2324 self.markers,
2325 key=operator.attrgetter('tmin'),
2326 reverse=True):
2328 t = marker.tmin
2329 if t < tmid and \
2330 marker.kind in self.visible_marker_kinds and \
2331 (dir == 'p' or
2332 isinstance(marker, EventMarker)):
2333 self.deselect_all()
2334 marker.selected = True
2335 tgo = t
2336 break
2338 if tgo is not None:
2339 self.interrupt_following()
2340 self.set_time_range(tgo-dt/2., tgo+dt/2.)
2342 elif keytext == 'r':
2343 if self.pile.reload_modified():
2344 self.reloaded = True
2346 elif keytext == 'R':
2347 self.setup_snufflings()
2349 elif key == qc.Qt.Key_Backspace:
2350 self.remove_selected_markers()
2352 elif keytext == 'a':
2353 for marker in self.markers:
2354 if ((self.tmin <= marker.tmin <= self.tmax or
2355 self.tmin <= marker.tmax <= self.tmax) and
2356 marker.kind in self.visible_marker_kinds):
2357 marker.selected = True
2358 else:
2359 marker.selected = False
2361 elif keytext == 'A':
2362 for marker in self.markers:
2363 if marker.kind in self.visible_marker_kinds:
2364 marker.selected = True
2366 elif keytext == 'd':
2367 self.deselect_all()
2369 elif keytext == 'E':
2370 self.deactivate_event_marker()
2372 elif keytext == 'e':
2373 markers = self.selected_markers()
2374 event_markers_in_spe = [
2375 marker for marker in markers
2376 if not isinstance(marker, PhaseMarker)]
2378 phase_markers = [
2379 marker for marker in markers
2380 if isinstance(marker, PhaseMarker)]
2382 if len(event_markers_in_spe) == 1:
2383 event_marker = event_markers_in_spe[0]
2384 if not isinstance(event_marker, EventMarker):
2385 nslcs = list(event_marker.nslc_ids)
2386 lat, lon = 0.0, 0.0
2387 old = self.get_active_event()
2388 if len(nslcs) == 1:
2389 lat, lon = self.station_latlon(NSLC(*nslcs[0]))
2390 elif old is not None:
2391 lat, lon = old.lat, old.lon
2393 event_marker.convert_to_event_marker(lat, lon)
2395 self.set_active_event_marker(event_marker)
2396 event = event_marker.get_event()
2397 for marker in phase_markers:
2398 marker.set_event(event)
2400 else:
2401 for marker in event_markers_in_spe:
2402 marker.convert_to_event_marker()
2404 elif keytext in ('0', '1', '2', '3', '4', '5', '6', '7'):
2405 for marker in self.selected_markers():
2406 marker.set_kind(int(keytext))
2407 self.emit_selected_markers()
2409 elif key in fkey_map:
2410 self.handle_fkeys(key)
2412 elif key == qc.Qt.Key_Escape:
2413 if self.picking:
2414 self.stop_picking(0, 0, abort=True)
2416 elif key == qc.Qt.Key_PageDown:
2417 self.scroll_tracks(
2418 self.shown_tracks_range[1]-self.shown_tracks_range[0])
2420 elif key == qc.Qt.Key_PageUp:
2421 self.scroll_tracks(
2422 self.shown_tracks_range[0]-self.shown_tracks_range[1])
2424 elif key == qc.Qt.Key_Plus:
2425 self.zoom_tracks(0., 1.)
2427 elif key == qc.Qt.Key_Minus:
2428 self.zoom_tracks(0., -1.)
2430 elif key == qc.Qt.Key_Equal:
2431 ntracks_shown = self.shown_tracks_range[1] - \
2432 self.shown_tracks_range[0]
2433 dtracks = self.initial_ntracks_shown_max - ntracks_shown
2434 self.zoom_tracks(0., dtracks)
2436 elif key == qc.Qt.Key_Colon:
2437 self.want_input.emit()
2439 elif keytext == 'f':
2440 self.toggle_fullscreen()
2442 elif keytext == 'g':
2443 self.go_to_selection()
2445 elif keytext == 'G':
2446 self.go_to_selection(tight=True)
2448 elif keytext == 'm':
2449 self.toggle_marker_editor()
2451 elif keytext == 'c':
2452 self.toggle_main_controls()
2454 elif key_event.key() in (qc.Qt.Key_Left, qc.Qt.Key_Right):
2455 dir = 1
2456 amount = 1
2457 if key_event.key() == qc.Qt.Key_Left:
2458 dir = -1
2459 if key_event.modifiers() & qc.Qt.ShiftModifier:
2460 amount = 10
2461 self.nudge_selected_markers(dir*amount)
2462 else:
2463 super().keyPressEvent(key_event)
2465 if keytext != '' and keytext in 'degaApPnN':
2466 self.emit_selected_markers()
2468 self.update()
2469 self.update_status()
2471 def handle_fkeys(self, key):
2472 self.set_phase_kind(
2473 self.selected_markers(),
2474 fkey_map[key] + 1)
2475 self.emit_selected_markers()
2477 def emit_selected_markers(self):
2478 ibounds = []
2479 last_selected = False
2480 for imarker, marker in enumerate(self.markers):
2481 this_selected = marker.is_selected()
2482 if this_selected != last_selected:
2483 ibounds.append(imarker)
2485 last_selected = this_selected
2487 if last_selected:
2488 ibounds.append(len(self.markers))
2490 chunks = list(zip(ibounds[::2], ibounds[1::2]))
2491 self.n_selected_markers = sum(
2492 chunk[1] - chunk[0] for chunk in chunks)
2493 self.marker_selection_changed.emit(chunks)
2495 def toggle_marker_editor(self):
2496 self.panel_parent.toggle_marker_editor()
2498 def toggle_main_controls(self):
2499 self.panel_parent.toggle_main_controls()
2501 def nudge_selected_markers(self, npixels):
2502 a, b = self.time_projection.ur
2503 c, d = self.time_projection.xr
2504 for marker in self.selected_markers():
2505 if not isinstance(marker, EventMarker):
2506 marker.tmin += npixels * (d-c)/b
2507 marker.tmax += npixels * (d-c)/b
2509 def toggle_fullscreen(self):
2510 if self.window().windowState() & qc.Qt.WindowFullScreen or \
2511 self.window().windowState() & qc.Qt.WindowMaximized:
2512 self.window().showNormal()
2513 else:
2514 if is_macos:
2515 self.window().showMaximized()
2516 else:
2517 self.window().showFullScreen()
2519 def about(self):
2520 fn = pyrocko.util.data_file('snuffler.png')
2521 with open(pyrocko.util.data_file('snuffler_about.html')) as f:
2522 txt = f.read()
2523 label = qw.QLabel(txt % {'logo': fn})
2524 label.setAlignment(qc.Qt.AlignVCenter | qc.Qt.AlignHCenter)
2525 self.show_doc('About', [label], target='tab')
2527 def help(self):
2528 class MyScrollArea(qw.QScrollArea):
2530 def sizeHint(self):
2531 s = qc.QSize()
2532 s.setWidth(self.widget().sizeHint().width())
2533 s.setHeight(self.widget().sizeHint().height())
2534 return s
2536 with open(pyrocko.util.data_file(
2537 'snuffler_help.html')) as f:
2538 hcheat = qw.QLabel(f.read())
2540 with open(pyrocko.util.data_file(
2541 'snuffler_help_epilog.html')) as f:
2542 hepilog = qw.QLabel(f.read())
2544 for h in [hcheat, hepilog]:
2545 h.setAlignment(qc.Qt.AlignTop | qc.Qt.AlignHCenter)
2546 h.setWordWrap(True)
2548 self.show_doc('Help', [hcheat, hepilog], target='panel')
2550 def show_doc(self, name, labels, target='panel'):
2551 scroller = qw.QScrollArea()
2552 frame = qw.QFrame(scroller)
2553 frame.setLineWidth(0)
2554 layout = qw.QVBoxLayout()
2555 layout.setContentsMargins(0, 0, 0, 0)
2556 layout.setSpacing(0)
2557 frame.setLayout(layout)
2558 scroller.setWidget(frame)
2559 scroller.setWidgetResizable(True)
2560 frame.setBackgroundRole(qg.QPalette.Base)
2561 for h in labels:
2562 h.setParent(frame)
2563 h.setMargin(3)
2564 h.setTextInteractionFlags(
2565 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse)
2566 h.setBackgroundRole(qg.QPalette.Base)
2567 layout.addWidget(h)
2568 h.linkActivated.connect(
2569 self.open_link)
2571 if self.panel_parent is not None:
2572 if target == 'panel':
2573 self.panel_parent.add_panel(
2574 name, scroller, True, volatile=False)
2575 else:
2576 self.panel_parent.add_tab(name, scroller)
2578 def open_link(self, link):
2579 qg.QDesktopServices.openUrl(qc.QUrl(link))
2581 def wheelEvent(self, wheel_event):
2582 ''
2583 self.wheel_pos += wheel_event.angleDelta().y()
2585 n = self.wheel_pos // 120
2586 self.wheel_pos = self.wheel_pos % 120
2587 if n == 0:
2588 return
2590 amount = max(
2591 1.,
2592 abs(self.shown_tracks_range[0]-self.shown_tracks_range[1])/5.)
2593 wdelta = amount * n
2595 trmin, trmax = self.track_to_screen.get_in_range()
2596 anchor = (self.track_to_screen.rev(wheel_event.y())-trmin) \
2597 / (trmax-trmin)
2599 if wheel_event.modifiers() & qc.Qt.ControlModifier:
2600 self.zoom_tracks(anchor, wdelta)
2601 else:
2602 self.scroll_tracks(-wdelta)
2604 def dragEnterEvent(self, event):
2605 ''
2606 if event.mimeData().hasUrls():
2607 if any(url.toLocalFile() for url in event.mimeData().urls()):
2608 event.setDropAction(qc.Qt.LinkAction)
2609 event.accept()
2611 def dropEvent(self, event):
2612 ''
2613 if event.mimeData().hasUrls():
2614 paths = list(
2615 str(url.toLocalFile()) for url in event.mimeData().urls())
2616 event.acceptProposedAction()
2617 self.load(paths)
2619 def get_phase_name(self, kind):
2620 return self.config.get_phase_name(kind)
2622 def set_phase_kind(self, markers, kind):
2623 phasename = self.get_phase_name(kind)
2625 for marker in markers:
2626 if isinstance(marker, PhaseMarker):
2627 if kind == 10:
2628 marker.convert_to_marker()
2629 else:
2630 marker.set_phasename(phasename)
2631 marker.set_event(self.get_active_event())
2633 elif isinstance(marker, EventMarker):
2634 pass
2636 else:
2637 if kind != 10:
2638 event = self.get_active_event()
2639 marker.convert_to_phase_marker(
2640 event, phasename, None, False)
2642 def set_ntracks(self, ntracks):
2643 if self.ntracks != ntracks:
2644 self.ntracks = ntracks
2645 if self.shown_tracks_range is not None:
2646 low, high = self.shown_tracks_range
2647 else:
2648 low, high = 0, self.ntracks
2650 self.tracks_range_changed.emit(self.ntracks, low, high)
2652 def set_tracks_range(self, range, start=None):
2654 low, high = range
2655 low = min(self.ntracks-1, low)
2656 high = min(self.ntracks, high)
2657 low = max(0, low)
2658 high = max(1, high)
2660 if start is None:
2661 start = float(low)
2663 if self.shown_tracks_range != (low, high):
2664 self.shown_tracks_range = low, high
2665 self.shown_tracks_start = start
2667 self.tracks_range_changed.emit(self.ntracks, low, high)
2669 def scroll_tracks(self, shift):
2670 shown = self.shown_tracks_range
2671 shiftmin = -shown[0]
2672 shiftmax = self.ntracks-shown[1]
2673 shift = max(shiftmin, shift)
2674 shift = min(shiftmax, shift)
2675 shown = shown[0] + shift, shown[1] + shift
2677 self.set_tracks_range((int(shown[0]), int(shown[1])))
2679 self.update()
2681 def zoom_tracks(self, anchor, delta):
2682 ntracks_shown = self.shown_tracks_range[1] \
2683 - self.shown_tracks_range[0]
2685 if (ntracks_shown == 1 and delta <= 0) or \
2686 (ntracks_shown == self.ntracks and delta >= 0):
2687 return
2689 ntracks_shown += int(round(delta))
2690 ntracks_shown = min(max(1, ntracks_shown), self.ntracks)
2692 u = self.shown_tracks_start
2693 nu = max(0., u-anchor*delta)
2694 nv = nu + ntracks_shown
2695 if nv > self.ntracks:
2696 nu -= nv - self.ntracks
2697 nv -= nv - self.ntracks
2699 self.set_tracks_range((int(round(nu)), int(round(nv))), nu)
2701 self.ntracks_shown_max = self.shown_tracks_range[1] \
2702 - self.shown_tracks_range[0]
2704 self.update()
2706 def content_time_range(self):
2707 pile = self.get_pile()
2708 tmin, tmax = pile.get_tmin(), pile.get_tmax()
2709 if tmin is None:
2710 tmin = initial_time_range[0]
2711 if tmax is None:
2712 tmax = initial_time_range[1]
2714 return tmin, tmax
2716 def content_deltat_range(self):
2717 pile = self.get_pile()
2719 deltatmin, deltatmax = pile.get_deltatmin(), pile.get_deltatmax()
2721 if deltatmin is None:
2722 deltatmin = 0.001
2724 if deltatmax is None:
2725 deltatmax = 1000.0
2727 return deltatmin, deltatmax
2729 def make_good_looking_time_range(self, tmin, tmax, tight=False):
2730 if tmax < tmin:
2731 tmin, tmax = tmax, tmin
2733 deltatmin = self.content_deltat_range()[0]
2734 dt = deltatmin * self.visible_length * 0.95
2736 if dt == 0.0:
2737 dt = 1.0
2739 if tight:
2740 if tmax != tmin:
2741 dtm = tmax - tmin
2742 tmin -= dtm*0.1
2743 tmax += dtm*0.1
2744 return tmin, tmax
2745 else:
2746 tcenter = (tmin + tmax) / 2.
2747 tmin = tcenter - 0.5*dt
2748 tmax = tcenter + 0.5*dt
2749 return tmin, tmax
2751 if tmax-tmin < dt:
2752 vmin, vmax = self.get_time_range()
2753 dt = min(vmax - vmin, dt)
2755 tcenter = (tmin+tmax)/2.
2756 etmin, etmax = tmin, tmax
2757 tmin = min(etmin, tcenter - 0.5*dt)
2758 tmax = max(etmax, tcenter + 0.5*dt)
2759 dtm = tmax-tmin
2760 if etmin == tmin:
2761 tmin -= dtm*0.1
2762 if etmax == tmax:
2763 tmax += dtm*0.1
2765 else:
2766 dtm = tmax-tmin
2767 tmin -= dtm*0.1
2768 tmax += dtm*0.1
2770 return tmin, tmax
2772 def go_to_selection(self, tight=False):
2773 markers = self.selected_markers()
2774 if markers:
2775 tmax, tmin = self.content_time_range()
2776 for marker in markers:
2777 tmin = min(tmin, marker.tmin)
2778 tmax = max(tmax, marker.tmax)
2780 else:
2781 if tight:
2782 vmin, vmax = self.get_time_range()
2783 tmin = tmax = (vmin + vmax) / 2.
2784 else:
2785 tmin, tmax = self.content_time_range()
2787 tmin, tmax = self.make_good_looking_time_range(
2788 tmin, tmax, tight=tight)
2790 self.interrupt_following()
2791 self.set_time_range(tmin, tmax)
2792 self.update()
2794 def go_to_time(self, t, tlen=None):
2795 tmax = t
2796 if tlen is not None:
2797 tmax = t+tlen
2798 tmin, tmax = self.make_good_looking_time_range(t, tmax)
2799 self.interrupt_following()
2800 self.set_time_range(tmin, tmax)
2801 self.update()
2803 def go_to_event_by_name(self, name):
2804 for marker in self.markers:
2805 if isinstance(marker, EventMarker):
2806 event = marker.get_event()
2807 if event.name and event.name.lower() == name.lower():
2808 tmin, tmax = self.make_good_looking_time_range(
2809 event.time, event.time)
2811 self.interrupt_following()
2812 self.set_time_range(tmin, tmax)
2814 def printit(self):
2815 from ..qt_compat import qprint
2816 printer = qprint.QPrinter()
2817 printer.setOrientation(qprint.QPrinter.Landscape)
2819 dialog = qprint.QPrintDialog(printer, self)
2820 dialog.setWindowTitle('Print')
2822 if dialog.exec_() != qw.QDialog.Accepted:
2823 return
2825 painter = qg.QPainter()
2826 painter.begin(printer)
2827 page = printer.pageRect()
2828 self.drawit(
2829 painter, printmode=False, w=page.width(), h=page.height())
2831 painter.end()
2833 def savesvg(self, fn=None):
2835 if not fn:
2836 fn, _ = qw.QFileDialog.getSaveFileName(
2837 self,
2838 'Save as SVG|PNG',
2839 os.path.expanduser(os.path.join('~', 'untitled.svg')),
2840 'SVG|PNG (*.svg *.png)',
2841 options=qfiledialog_options)
2843 if fn == '':
2844 return
2846 fn = str(fn)
2848 if fn.lower().endswith('.svg'):
2849 try:
2850 w, h = 842, 595
2851 margin = 0.025
2852 m = max(w, h)*margin
2854 generator = qsvg.QSvgGenerator()
2855 generator.setFileName(fn)
2856 generator.setSize(qc.QSize(w, h))
2857 generator.setViewBox(qc.QRectF(-m, -m, w+2*m, h+2*m))
2859 painter = qg.QPainter()
2860 painter.begin(generator)
2861 self.drawit(painter, printmode=False, w=w, h=h)
2862 painter.end()
2864 except Exception as e:
2865 self.fail('Failed to write SVG file: %s' % str(e))
2867 elif fn.lower().endswith('.png'):
2868 pixmap = self.grab()
2870 try:
2871 pixmap.save(fn)
2873 except Exception as e:
2874 self.fail('Failed to write PNG file: %s' % str(e))
2876 else:
2877 self.fail(
2878 'Unsupported file type: filename must end with ".svg" or '
2879 '".png".')
2881 def paintEvent(self, paint_ev):
2882 '''
2883 Called by QT whenever widget needs to be painted.
2884 '''
2885 # Prevent a problem on macos with QOpenGLWidget, where paintEvent
2886 # was called twice (by different threads?), causing segfaults.
2887 if self.in_paint_event:
2888 logger.warning('Blocking reentrant call to paintEvent().')
2889 return
2891 self.in_paint_event = True
2893 painter = qg.QPainter(self)
2895 if self.menuitem_antialias.isChecked():
2896 painter.setRenderHint(qg.QPainter.Antialiasing)
2898 self.drawit(painter)
2900 logger.debug(
2901 'Time spent drawing: '
2902 ' user:%.3f sys:%.3f children_user:%.3f'
2903 ' childred_sys:%.3f elapsed:%.3f' %
2904 (self.timer_draw - self.timer_cutout))
2906 logger.debug(
2907 'Time spent processing:'
2908 ' user:%.3f sys:%.3f children_user:%.3f'
2909 ' childred_sys:%.3f elapsed:%.3f' %
2910 self.timer_cutout.get())
2912 self.time_spent_painting = self.timer_draw.get()[-1]
2913 self.time_last_painted = time.time()
2914 self.in_paint_event = False
2916 def determine_box_styles(self):
2918 traces = list(self.pile.iter_traces())
2919 traces.sort(key=operator.attrgetter('full_id'))
2920 istyle = 0
2921 trace_styles = {}
2922 for itr, tr in enumerate(traces):
2923 if itr > 0:
2924 other = traces[itr-1]
2925 if not (
2926 other.nslc_id == tr.nslc_id
2927 and other.deltat == tr.deltat
2928 and abs(other.tmax - tr.tmin)
2929 < gap_lap_tolerance*tr.deltat):
2931 istyle += 1
2933 trace_styles[tr.full_id, tr.deltat] = istyle
2935 self.trace_styles = trace_styles
2937 def draw_trace_boxes(self, p, time_projection, track_projections):
2939 for v_projection in track_projections.values():
2940 v_projection.set_in_range(0., 1.)
2942 def selector(x):
2943 return x.overlaps(*time_projection.get_in_range())
2945 if self.trace_filter is not None:
2946 def tselector(x):
2947 return selector(x) and self.trace_filter(x)
2949 else:
2950 tselector = selector
2952 traces = list(self.pile.iter_traces(
2953 group_selector=selector, trace_selector=tselector))
2955 traces.sort(key=operator.attrgetter('full_id'))
2957 def drawbox(itrack, istyle, traces):
2958 v_projection = track_projections[itrack]
2959 dvmin = v_projection(0.)
2960 dvmax = v_projection(1.)
2961 dtmin = time_projection.clipped(traces[0].tmin, 0)
2962 dtmax = time_projection.clipped(traces[-1].tmax, 1)
2964 style = box_styles[istyle % len(box_styles)]
2965 rect = qc.QRectF(dtmin, dvmin, float(dtmax-dtmin), dvmax-dvmin)
2966 p.fillRect(rect, style.fill_brush)
2967 p.setPen(style.frame_pen)
2968 p.drawRect(rect)
2970 traces_by_style = {}
2971 for itr, tr in enumerate(traces):
2972 gt = self.gather(tr)
2973 if gt not in self.key_to_row:
2974 continue
2976 itrack = self.key_to_row[gt]
2977 if itrack not in track_projections:
2978 continue
2980 istyle = self.trace_styles.get((tr.full_id, tr.deltat), 0)
2982 if len(traces) < 500:
2983 drawbox(itrack, istyle, [tr])
2984 else:
2985 if (itrack, istyle) not in traces_by_style:
2986 traces_by_style[itrack, istyle] = []
2987 traces_by_style[itrack, istyle].append(tr)
2989 for (itrack, istyle), traces in traces_by_style.items():
2990 drawbox(itrack, istyle, traces)
2992 def draw_visible_markers(
2993 self, p, vcenter_projection, primary_pen):
2995 try:
2996 markers = self.markers.with_key_in_limited(
2997 self.tmin - self.markers_deltat_max, self.tmax, 2000)
2999 except pyrocko.pile.TooMany:
3000 tmin = self.markers[0].tmin
3001 tmax = self.markers[-1].tmax
3002 umin_view, umax_view = self.time_projection.get_out_range()
3003 umin = max(umin_view, self.time_projection(tmin))
3004 umax = min(umax_view, self.time_projection(tmax))
3005 v0, _ = vcenter_projection.get_out_range()
3006 label_bg = qg.QBrush(qg.QColor(255, 255, 255))
3008 p.save()
3010 pen = qg.QPen(primary_pen)
3011 pen.setWidth(2)
3012 pen.setStyle(qc.Qt.DotLine)
3013 # pat = [5., 3.]
3014 # pen.setDashPattern(pat)
3015 p.setPen(pen)
3017 if self.n_selected_markers == len(self.markers):
3018 s_selected = ' (all selected)'
3019 elif self.n_selected_markers > 0:
3020 s_selected = ' (%i selected)' % self.n_selected_markers
3021 else:
3022 s_selected = ''
3024 draw_label(
3025 p, umin+10., v0-10.,
3026 '%i Markers' % len(self.markers) + s_selected,
3027 label_bg, 'LB')
3029 line = qc.QLineF(umin, v0, umax, v0)
3030 p.drawLine(line)
3031 p.restore()
3033 return
3035 for marker in markers:
3036 if marker.tmin < self.tmax and self.tmin < marker.tmax \
3037 and marker.kind in self.visible_marker_kinds:
3039 marker.draw(
3040 p, self.time_projection, vcenter_projection,
3041 with_label=True)
3043 def get_squirrel(self):
3044 try:
3045 return self.pile._squirrel
3046 except AttributeError:
3047 return None
3049 def draw_coverage(self, p, time_projection, track_projections):
3050 sq = self.get_squirrel()
3051 if sq is None:
3052 return
3054 def drawbox(itrack, tmin, tmax, style):
3055 v_projection = track_projections[itrack]
3056 dvmin = v_projection(0.)
3057 dvmax = v_projection(1.)
3058 dtmin = time_projection.clipped(tmin, 0)
3059 dtmax = time_projection.clipped(tmax, 1)
3061 rect = qc.QRectF(dtmin, dvmin, float(dtmax-dtmin), dvmax-dvmin)
3062 p.fillRect(rect, style.fill_brush)
3063 p.setPen(style.frame_pen)
3064 p.drawRect(rect)
3066 pattern_list = []
3067 pattern_to_itrack = {}
3068 for key in self.track_keys:
3069 itrack = self.key_to_row[key]
3070 if itrack not in track_projections:
3071 continue
3073 pattern = self.track_patterns[itrack]
3074 pattern_to_itrack[tuple(pattern)] = itrack
3075 pattern_list.append(tuple(pattern))
3077 vmin, vmax = self.get_time_range()
3079 for kind in ['waveform', 'waveform_promise']:
3080 for coverage in sq.get_coverage(
3081 kind, vmin, vmax, pattern_list, limit=500):
3082 itrack = pattern_to_itrack[coverage.pattern.nslc]
3084 if coverage.changes is None:
3085 drawbox(
3086 itrack, coverage.tmin, coverage.tmax,
3087 box_styles_coverage[kind][0])
3088 else:
3089 t = None
3090 pcount = 0
3091 for tb, count in coverage.changes:
3092 if t is not None and tb > t:
3093 if pcount > 0:
3094 drawbox(
3095 itrack, t, tb,
3096 box_styles_coverage[kind][
3097 min(len(box_styles_coverage)-1,
3098 pcount)])
3100 t = tb
3101 pcount = count
3103 def drawit(self, p, printmode=False, w=None, h=None):
3104 '''
3105 This performs the actual drawing.
3106 '''
3108 self.timer_draw.start()
3109 show_boxes = self.menuitem_showboxes.isChecked()
3110 sq = self.get_squirrel()
3112 if self.gather is None:
3113 self.set_gathering()
3115 if self.pile_has_changed:
3117 if not self.sortingmode_change_delayed():
3118 self.sortingmode_change()
3120 if show_boxes and sq is None:
3121 self.determine_box_styles()
3123 self.pile_has_changed = False
3125 if h is None:
3126 h = float(self.height())
3127 if w is None:
3128 w = float(self.width())
3130 if printmode:
3131 primary_color = (0, 0, 0)
3132 else:
3133 primary_color = pyrocko.plot.tango_colors['aluminium5']
3135 primary_pen = qg.QPen(qg.QColor(*primary_color))
3137 ax_h = float(self.ax_height)
3139 vbottom_ax_projection = Projection()
3140 vtop_ax_projection = Projection()
3141 vcenter_projection = Projection()
3143 self.time_projection.set_out_range(0., w)
3144 vbottom_ax_projection.set_out_range(h-ax_h, h)
3145 vtop_ax_projection.set_out_range(0., ax_h)
3146 vcenter_projection.set_out_range(ax_h, h-ax_h)
3147 vcenter_projection.set_in_range(0., 1.)
3148 self.track_to_screen.set_out_range(ax_h, h-ax_h)
3150 self.track_to_screen.set_in_range(*self.shown_tracks_range)
3151 track_projections = {}
3152 for i in range(*self.shown_tracks_range):
3153 proj = Projection()
3154 proj.set_out_range(
3155 self.track_to_screen(i+0.05),
3156 self.track_to_screen(i+1.-0.05))
3158 track_projections[i] = proj
3160 if self.tmin > self.tmax:
3161 return
3163 self.time_projection.set_in_range(self.tmin, self.tmax)
3164 vbottom_ax_projection.set_in_range(0, ax_h)
3166 self.tax.drawit(p, self.time_projection, vbottom_ax_projection)
3168 yscaler = pyrocko.plot.AutoScaler()
3170 p.setPen(primary_pen)
3172 font = qg.QFont()
3173 font.setBold(True)
3175 axannotfont = qg.QFont()
3176 axannotfont.setBold(True)
3177 axannotfont.setPointSize(8)
3179 processed_traces = self.prepare_cutout2(
3180 self.tmin, self.tmax,
3181 trace_selector=self.trace_selector,
3182 degap=self.menuitem_degap.isChecked(),
3183 demean=self.menuitem_demean.isChecked())
3185 if not printmode and show_boxes:
3186 if (self.view_mode is ViewMode.Wiggle) \
3187 or (self.view_mode is ViewMode.Waterfall
3188 and not processed_traces):
3190 if sq is None:
3191 self.draw_trace_boxes(
3192 p, self.time_projection, track_projections)
3194 else:
3195 self.draw_coverage(
3196 p, self.time_projection, track_projections)
3198 p.setFont(font)
3199 label_bg = qg.QBrush(qg.QColor(255, 255, 255, 100))
3201 color_lookup = dict(
3202 [(k, i) for (i, k) in enumerate(self.color_keys)])
3204 self.track_to_nslc_ids = {}
3205 nticks = 0
3206 annot_labels = []
3208 if self.view_mode is ViewMode.Waterfall and processed_traces:
3209 waterfall = self.waterfall
3210 waterfall.set_time_range(self.tmin, self.tmax)
3211 waterfall.set_traces(processed_traces)
3212 waterfall.set_cmap(self.waterfall_cmap)
3213 waterfall.set_integrate(self.waterfall_integrate)
3214 waterfall.set_clip(
3215 self.waterfall_clip_min, self.waterfall_clip_max)
3216 waterfall.show_absolute_values(
3217 self.waterfall_show_absolute)
3219 rect = qc.QRectF(
3220 0, self.ax_height,
3221 self.width(), self.height() - self.ax_height*2
3222 )
3223 waterfall.draw_waterfall(p, rect=rect)
3225 elif self.view_mode is ViewMode.Wiggle and processed_traces:
3226 show_scales = self.menuitem_showscalerange.isChecked() \
3227 or self.menuitem_showscaleaxis.isChecked()
3229 fm = qg.QFontMetrics(axannotfont, p.device())
3230 trackheight = self.track_to_screen(1.-0.05) \
3231 - self.track_to_screen(0.05)
3233 nlinesavail = trackheight/float(fm.lineSpacing())
3235 nticks = max(3, min(nlinesavail * 0.5, 15)) \
3236 if self.menuitem_showscaleaxis.isChecked() \
3237 else 15
3239 yscaler = pyrocko.plot.AutoScaler(
3240 no_exp_interval=(-3, 2), approx_ticks=nticks,
3241 snap=show_scales
3242 and not self.menuitem_showscaleaxis.isChecked())
3244 data_ranges = pyrocko.trace.minmax(
3245 processed_traces,
3246 key=self.scaling_key,
3247 mode=self.scaling_base[0],
3248 outer_mode=self.scaling_base[1])
3250 if not self.menuitem_fixscalerange.isChecked():
3251 self.old_data_ranges = data_ranges
3252 else:
3253 data_ranges.update(self.old_data_ranges)
3255 self.apply_scaling_hooks(data_ranges)
3257 trace_to_itrack = {}
3258 track_scaling_keys = {}
3259 track_scaling_colors = {}
3260 for trace in processed_traces:
3261 gt = self.gather(trace)
3262 if gt not in self.key_to_row:
3263 continue
3265 itrack = self.key_to_row[gt]
3266 if itrack not in track_projections:
3267 continue
3269 trace_to_itrack[trace] = itrack
3271 if itrack not in self.track_to_nslc_ids:
3272 self.track_to_nslc_ids[itrack] = set()
3274 self.track_to_nslc_ids[itrack].add(trace.nslc_id)
3276 if itrack not in track_scaling_keys:
3277 track_scaling_keys[itrack] = set()
3279 scaling_key = self.scaling_key(trace)
3280 track_scaling_keys[itrack].add(scaling_key)
3282 color = pyrocko.plot.color(
3283 color_lookup[self.color_gather(trace)])
3285 k = itrack, scaling_key
3286 if k not in track_scaling_colors \
3287 and self.menuitem_colortraces.isChecked():
3288 track_scaling_colors[k] = color
3289 else:
3290 track_scaling_colors[k] = primary_color
3292 # y axes, zero lines
3293 trace_projections = {}
3294 for itrack in list(track_projections.keys()):
3295 if itrack not in track_scaling_keys:
3296 continue
3297 uoff = 0
3298 for scaling_key in track_scaling_keys[itrack]:
3299 data_range = data_ranges[scaling_key]
3300 dymin, dymax = data_range
3301 ymin, ymax, yinc = yscaler.make_scale(
3302 (dymin/self.gain, dymax/self.gain))
3303 iexp = yscaler.make_exp(yinc)
3304 factor = 10**iexp
3305 trace_projection = track_projections[itrack].copy()
3306 trace_projection.set_in_range(ymax, ymin)
3307 trace_projections[itrack, scaling_key] = \
3308 trace_projection
3309 umin, umax = self.time_projection.get_out_range()
3310 vmin, vmax = trace_projection.get_out_range()
3311 umax_zeroline = umax
3312 uoffnext = uoff
3314 if show_scales:
3315 pen = qg.QPen(primary_pen)
3316 k = itrack, scaling_key
3317 if k in track_scaling_colors:
3318 c = qg.QColor(*track_scaling_colors[
3319 itrack, scaling_key])
3321 pen.setColor(c)
3323 p.setPen(pen)
3324 if nlinesavail > 3:
3325 if self.menuitem_showscaleaxis.isChecked():
3326 ymin_annot = math.ceil(ymin/yinc)*yinc
3327 ny_annot = int(
3328 math.floor(ymax/yinc)
3329 - math.ceil(ymin/yinc)) + 1
3331 for iy_annot in range(ny_annot):
3332 y = ymin_annot + iy_annot*yinc
3333 v = trace_projection(y)
3334 line = qc.QLineF(
3335 umax-10-uoff, v, umax-uoff, v)
3337 p.drawLine(line)
3338 if iy_annot == ny_annot - 1 \
3339 and iexp != 0:
3340 sexp = ' × ' \
3341 '10<sup>%i</sup>' % iexp
3342 else:
3343 sexp = ''
3345 snum = num_to_html(y/factor)
3346 lab = Label(
3347 p,
3348 umax-20-uoff,
3349 v, '%s%s' % (snum, sexp),
3350 label_bg=None,
3351 anchor='MR',
3352 font=axannotfont,
3353 color=c)
3355 uoffnext = max(
3356 lab.rect.width()+30., uoffnext)
3358 annot_labels.append(lab)
3359 if y == 0.:
3360 umax_zeroline = \
3361 umax - 20 \
3362 - lab.rect.width() - 10 \
3363 - uoff
3364 else:
3365 if not show_boxes:
3366 qpoints = make_QPolygonF(
3367 [umax-20-uoff,
3368 umax-10-uoff,
3369 umax-10-uoff,
3370 umax-20-uoff],
3371 [vmax, vmax, vmin, vmin])
3372 p.drawPolyline(qpoints)
3374 snum = num_to_html(ymin)
3375 labmin = Label(
3376 p, umax-15-uoff, vmax, snum,
3377 label_bg=None,
3378 anchor='BR',
3379 font=axannotfont,
3380 color=c)
3382 annot_labels.append(labmin)
3383 snum = num_to_html(ymax)
3384 labmax = Label(
3385 p, umax-15-uoff, vmin, snum,
3386 label_bg=None,
3387 anchor='TR',
3388 font=axannotfont,
3389 color=c)
3391 annot_labels.append(labmax)
3393 for lab in (labmin, labmax):
3394 uoffnext = max(
3395 lab.rect.width()+10., uoffnext)
3397 if self.menuitem_showzeroline.isChecked():
3398 v = trace_projection(0.)
3399 if vmin <= v <= vmax:
3400 line = qc.QLineF(umin, v, umax_zeroline, v)
3401 p.drawLine(line)
3403 uoff = uoffnext
3405 p.setFont(font)
3406 p.setPen(primary_pen)
3407 for trace in processed_traces:
3408 if self.view_mode is not ViewMode.Wiggle:
3409 break
3411 if trace not in trace_to_itrack:
3412 continue
3414 itrack = trace_to_itrack[trace]
3415 scaling_key = self.scaling_key(trace)
3416 trace_projection = trace_projections[
3417 itrack, scaling_key]
3419 vdata = trace_projection(trace.get_ydata())
3421 udata_min = float(self.time_projection(trace.tmin))
3422 udata_max = float(self.time_projection(
3423 trace.tmin+trace.deltat*(vdata.size-1)))
3424 udata = num.linspace(udata_min, udata_max, vdata.size)
3426 qpoints = make_QPolygonF(udata, vdata)
3428 umin, umax = self.time_projection.get_out_range()
3429 vmin, vmax = trace_projection.get_out_range()
3431 trackrect = qc.QRectF(umin, vmin, umax-umin, vmax-vmin)
3433 if self.menuitem_cliptraces.isChecked():
3434 p.setClipRect(trackrect)
3436 if self.menuitem_colortraces.isChecked():
3437 color = pyrocko.plot.color(
3438 color_lookup[self.color_gather(trace)])
3439 pen = qg.QPen(qg.QColor(*color), 1)
3440 p.setPen(pen)
3442 p.drawPolyline(qpoints)
3444 if self.floating_marker:
3445 self.floating_marker.draw_trace(
3446 self, p, trace,
3447 self.time_projection, trace_projection, 1.0)
3449 for marker in self.markers.with_key_in(
3450 self.tmin - self.markers_deltat_max,
3451 self.tmax):
3453 if marker.tmin < self.tmax \
3454 and self.tmin < marker.tmax \
3455 and marker.kind \
3456 in self.visible_marker_kinds:
3457 marker.draw_trace(
3458 self, p, trace, self.time_projection,
3459 trace_projection, 1.0)
3461 p.setPen(primary_pen)
3463 if self.menuitem_cliptraces.isChecked():
3464 p.setClipRect(0, 0, int(w), int(h))
3466 if self.floating_marker:
3467 self.floating_marker.draw(
3468 p, self.time_projection, vcenter_projection)
3470 self.draw_visible_markers(
3471 p, vcenter_projection, primary_pen)
3473 p.setPen(primary_pen)
3474 while font.pointSize() > 2:
3475 fm = qg.QFontMetrics(font, p.device())
3476 trackheight = self.track_to_screen(1.-0.05) \
3477 - self.track_to_screen(0.05)
3478 nlinesavail = trackheight/float(fm.lineSpacing())
3479 if nlinesavail > 1:
3480 break
3482 font.setPointSize(font.pointSize()-1)
3484 p.setFont(font)
3485 mouse_pos = self.mapFromGlobal(qg.QCursor.pos())
3487 for key in self.track_keys:
3488 itrack = self.key_to_row[key]
3489 if itrack in track_projections:
3490 plabel = ' '.join(
3491 [str(x) for x in key if x is not None])
3492 lx = 10
3493 ly = self.track_to_screen(itrack+0.5)
3495 if p.font().pointSize() >= MIN_LABEL_SIZE_PT:
3496 draw_label(p, lx, ly, plabel, label_bg, 'ML')
3497 continue
3499 contains_cursor = \
3500 self.track_to_screen(itrack) \
3501 < mouse_pos.y() \
3502 < self.track_to_screen(itrack+1)
3504 if not contains_cursor:
3505 continue
3507 font_large = p.font()
3508 font_large.setPointSize(MIN_LABEL_SIZE_PT)
3509 p.setFont(font_large)
3510 draw_label(p, lx, ly, plabel, label_bg, 'ML')
3511 p.setFont(font)
3513 for lab in annot_labels:
3514 lab.draw()
3516 self.timer_draw.stop()
3518 def see_data_params(self):
3520 min_deltat = self.content_deltat_range()[0]
3522 # determine padding and downampling requirements
3523 if self.lowpass is not None:
3524 deltat_target = 1./self.lowpass * 0.25
3525 ndecimate = min(
3526 50,
3527 max(1, int(round(deltat_target / min_deltat))))
3528 tpad = 1./self.lowpass * 2.
3529 else:
3530 ndecimate = 1
3531 tpad = min_deltat*5.
3533 if self.highpass is not None:
3534 tpad = max(1./self.highpass * 2., tpad)
3536 nsee_points_per_trace = 5000*10
3537 tsee = ndecimate*nsee_points_per_trace*min_deltat
3539 return ndecimate, tpad, tsee
3541 def clean_update(self):
3542 self.cached_processed_traces = None
3543 self.update()
3545 def get_adequate_tpad(self):
3546 tpad = 0.
3547 for f in [self.highpass, self.lowpass]:
3548 if f is not None:
3549 tpad = max(tpad, 1.0/f)
3551 for snuffling in self.snufflings:
3552 if snuffling._post_process_hook_enabled \
3553 or snuffling._pre_process_hook_enabled:
3555 tpad = max(tpad, snuffling.get_tpad())
3557 return tpad
3559 def prepare_cutout2(
3560 self, tmin, tmax, trace_selector=None, degap=True,
3561 demean=True, nmax=6000):
3563 if self.pile.is_empty():
3564 return []
3566 nmax = self.visible_length
3568 self.timer_cutout.start()
3570 tsee = tmax-tmin
3571 min_deltat_wo_decimate = tsee/nmax
3572 min_deltat_w_decimate = min_deltat_wo_decimate / 32.
3574 min_deltat_allow = min_deltat_wo_decimate
3575 if self.lowpass is not None:
3576 target_deltat_lp = 0.25/self.lowpass
3577 if target_deltat_lp > min_deltat_wo_decimate:
3578 min_deltat_allow = min_deltat_w_decimate
3580 min_deltat_allow = math.exp(
3581 int(math.floor(math.log(min_deltat_allow))))
3583 tmin_ = tmin
3584 tmax_ = tmax
3586 # fetch more than needed?
3587 if self.menuitem_liberal_fetch.isChecked():
3588 tlen = pyrocko.trace.nextpow2((tmax-tmin)*1.5)
3589 tmin = math.floor(tmin/tlen) * tlen
3590 tmax = math.ceil(tmax/tlen) * tlen
3592 fft_filtering = self.menuitem_fft_filtering.isChecked()
3593 lphp = self.menuitem_lphp.isChecked()
3594 ads = self.menuitem_allowdownsampling.isChecked()
3596 tpad = self.get_adequate_tpad()
3597 tpad = max(tpad, tsee)
3599 # state vector to decide if cached traces can be used
3600 vec = (
3601 tmin, tmax, tpad, trace_selector, degap, demean, self.lowpass,
3602 self.highpass, fft_filtering, lphp,
3603 min_deltat_allow, self.rotate, self.shown_tracks_range,
3604 ads, self.pile.get_update_count())
3606 if (self.cached_vec
3607 and self.cached_vec[0] <= vec[0]
3608 and vec[1] <= self.cached_vec[1]
3609 and vec[2:] == self.cached_vec[2:]
3610 and not (self.reloaded or self.menuitem_watch.isChecked())
3611 and self.cached_processed_traces is not None):
3613 logger.debug('Using cached traces')
3614 processed_traces = self.cached_processed_traces
3616 else:
3617 processed_traces = []
3618 if self.pile.deltatmax >= min_deltat_allow:
3620 if isinstance(self.pile, pyrocko.pile.Pile):
3621 def group_selector(gr):
3622 return gr.deltatmax >= min_deltat_allow
3624 kwargs = dict(group_selector=group_selector)
3625 else:
3626 kwargs = {}
3628 if trace_selector is not None:
3629 def trace_selectorx(tr):
3630 return tr.deltat >= min_deltat_allow \
3631 and trace_selector(tr)
3632 else:
3633 def trace_selectorx(tr):
3634 return tr.deltat >= min_deltat_allow
3636 for traces in self.pile.chopper(
3637 tmin=tmin, tmax=tmax, tpad=tpad,
3638 want_incomplete=True,
3639 degap=degap,
3640 maxgap=gap_lap_tolerance,
3641 maxlap=gap_lap_tolerance,
3642 keep_current_files_open=True,
3643 trace_selector=trace_selectorx,
3644 accessor_id=id(self),
3645 snap=(math.floor, math.ceil),
3646 include_last=True, **kwargs):
3648 if demean:
3649 for tr in traces:
3650 if (tr.meta and tr.meta.get('tabu', False)):
3651 continue
3652 y = tr.get_ydata()
3653 tr.set_ydata(y - num.mean(y))
3655 traces = self.pre_process_hooks(traces)
3657 for trace in traces:
3659 if not (trace.meta
3660 and trace.meta.get('tabu', False)):
3662 if fft_filtering:
3663 but = pyrocko.response.ButterworthResponse
3664 multres = pyrocko.response.MultiplyResponse
3665 if self.lowpass is not None \
3666 or self.highpass is not None:
3668 it = num.arange(
3669 trace.data_len(), dtype=float)
3670 detr_data, m, b = detrend(
3671 it, trace.get_ydata())
3673 trace.set_ydata(detr_data)
3675 freqs, fdata = trace.spectrum(
3676 pad_to_pow2=True, tfade=None)
3678 nfreqs = fdata.size
3680 key = (trace.deltat, nfreqs)
3682 if key not in self.tf_cache:
3683 resps = []
3684 if self.lowpass is not None:
3685 resps.append(but(
3686 order=4,
3687 corner=self.lowpass,
3688 type='low'))
3690 if self.highpass is not None:
3691 resps.append(but(
3692 order=4,
3693 corner=self.highpass,
3694 type='high'))
3696 resp = multres(resps)
3697 self.tf_cache[key] = \
3698 resp.evaluate(freqs)
3700 filtered_data = num.fft.irfft(
3701 fdata*self.tf_cache[key]
3702 )[:trace.data_len()]
3704 retrended_data = retrend(
3705 it, filtered_data, m, b)
3707 trace.set_ydata(retrended_data)
3709 else:
3711 if ads and self.lowpass is not None:
3712 while trace.deltat \
3713 < min_deltat_wo_decimate:
3715 trace.downsample(2, demean=False)
3717 fmax = 0.5/trace.deltat
3718 if not lphp and (
3719 self.lowpass is not None
3720 and self.highpass is not None
3721 and self.lowpass < fmax
3722 and self.highpass < fmax
3723 and self.highpass < self.lowpass):
3725 trace.bandpass(
3726 2, self.highpass, self.lowpass)
3727 else:
3728 if self.lowpass is not None:
3729 if self.lowpass < 0.5/trace.deltat:
3730 trace.lowpass(
3731 4, self.lowpass,
3732 demean=False)
3734 if self.highpass is not None:
3735 if self.lowpass is None \
3736 or self.highpass \
3737 < self.lowpass:
3739 if self.highpass < \
3740 0.5/trace.deltat:
3741 trace.highpass(
3742 4, self.highpass,
3743 demean=False)
3745 processed_traces.append(trace)
3747 if self.rotate != 0.0:
3748 phi = self.rotate/180.*math.pi
3749 cphi = math.cos(phi)
3750 sphi = math.sin(phi)
3751 for a in processed_traces:
3752 for b in processed_traces:
3753 if (a.network == b.network
3754 and a.station == b.station
3755 and a.location == b.location
3756 and ((a.channel.lower().endswith('n')
3757 and b.channel.lower().endswith('e'))
3758 or (a.channel.endswith('1')
3759 and b.channel.endswith('2')))
3760 and abs(a.deltat-b.deltat) < a.deltat*0.001
3761 and abs(a.tmin-b.tmin) < a.deltat*0.01 and
3762 len(a.get_ydata()) == len(b.get_ydata())):
3764 aydata = a.get_ydata()*cphi+b.get_ydata()*sphi
3765 bydata = -a.get_ydata()*sphi+b.get_ydata()*cphi
3766 a.set_ydata(aydata)
3767 b.set_ydata(bydata)
3769 processed_traces = self.post_process_hooks(processed_traces)
3771 self.cached_processed_traces = processed_traces
3772 self.cached_vec = vec
3774 chopped_traces = []
3775 for trace in processed_traces:
3776 chop_tmin = tmin_ - trace.deltat*4
3777 chop_tmax = tmax_ + trace.deltat*4
3779 try:
3780 ctrace = trace.chop(
3781 chop_tmin, chop_tmax,
3782 inplace=False)
3784 except pyrocko.trace.NoData:
3785 continue
3787 if ctrace.data_len() < 2:
3788 continue
3790 chopped_traces.append(ctrace)
3792 self.timer_cutout.stop()
3793 return chopped_traces
3795 def pre_process_hooks(self, traces):
3796 for snuffling in self.snufflings:
3797 if snuffling._pre_process_hook_enabled:
3798 traces = snuffling.pre_process_hook(traces)
3800 return traces
3802 def post_process_hooks(self, traces):
3803 for snuffling in self.snufflings:
3804 if snuffling._post_process_hook_enabled:
3805 traces = snuffling.post_process_hook(traces)
3807 return traces
3809 def visible_length_change(self, ignore=None):
3810 for menuitem, vlen in self.menuitems_visible_length:
3811 if menuitem.isChecked():
3812 self.visible_length = vlen
3814 def scaling_base_change(self, ignore=None):
3815 for menuitem, scaling_base in self.menuitems_scaling_base:
3816 if menuitem.isChecked():
3817 self.scaling_base = scaling_base
3819 def scalingmode_change(self, ignore=None):
3820 for menuitem, scaling_key in self.menuitems_scaling:
3821 if menuitem.isChecked():
3822 self.scaling_key = scaling_key
3823 self.update()
3825 def apply_scaling_hooks(self, data_ranges):
3826 for k in sorted(self.scaling_hooks.keys()):
3827 hook = self.scaling_hooks[k]
3828 hook(data_ranges)
3830 def viewmode_change(self, ignore=True):
3831 for item, mode in self.menuitems_viewmode:
3832 if item.isChecked():
3833 self.view_mode = mode
3834 break
3835 else:
3836 raise AttributeError('unknown view mode')
3838 items_waterfall_disabled = (
3839 self.menuitem_showscaleaxis,
3840 self.menuitem_showscalerange,
3841 self.menuitem_showzeroline,
3842 self.menuitem_colortraces,
3843 self.menuitem_cliptraces,
3844 *(itm[0] for itm in self.menuitems_visible_length)
3845 )
3847 if self.view_mode is ViewMode.Waterfall:
3848 self.parent().show_colorbar_ctrl(True)
3849 self.parent().show_gain_ctrl(False)
3851 for item in items_waterfall_disabled:
3852 item.setDisabled(True)
3854 self.visible_length = 180.
3855 else:
3856 self.parent().show_colorbar_ctrl(False)
3857 self.parent().show_gain_ctrl(True)
3859 for item in items_waterfall_disabled:
3860 item.setDisabled(False)
3862 self.visible_length_change()
3863 self.update()
3865 def set_scaling_hook(self, k, hook):
3866 self.scaling_hooks[k] = hook
3868 def remove_scaling_hook(self, k):
3869 del self.scaling_hooks[k]
3871 def remove_scaling_hooks(self):
3872 self.scaling_hooks = {}
3874 def s_sortingmode_change(self, ignore=None):
3875 for menuitem, valfunc in self.menuitems_ssorting:
3876 if menuitem.isChecked():
3877 self._ssort = valfunc
3879 self.sortingmode_change()
3881 def sortingmode_change(self, ignore=None):
3882 for menuitem, (gather, color) in self.menuitems_sorting:
3883 if menuitem.isChecked():
3884 self.set_gathering(gather, color)
3886 self.sortingmode_change_time = time.time()
3888 def lowpass_change(self, value, ignore=None):
3889 self.lowpass = value
3890 self.passband_check()
3891 self.tf_cache = {}
3892 self.update()
3894 def highpass_change(self, value, ignore=None):
3895 self.highpass = value
3896 self.passband_check()
3897 self.tf_cache = {}
3898 self.update()
3900 def passband_check(self):
3901 if self.highpass and self.lowpass \
3902 and self.highpass >= self.lowpass:
3904 self.window().status_messages.set(
3905 'filter_error',
3906 'Corner frequency of highpass greater than '
3907 'corner frequency of lowpass. Highpass deactivated.')
3908 else:
3909 self.window().status_messages.clear('filter_error')
3911 def gain_change(self, value, ignore):
3912 self.gain = value
3913 self.update()
3915 def rot_change(self, value, ignore):
3916 self.rotate = value
3917 self.update()
3919 def waterfall_cmap_change(self, cmap):
3920 self.waterfall_cmap = cmap
3921 self.update()
3923 def waterfall_clip_change(self, clip_min, clip_max):
3924 self.waterfall_clip_min = clip_min
3925 self.waterfall_clip_max = clip_max
3926 self.update()
3928 def waterfall_show_absolute_change(self, toggle):
3929 self.waterfall_show_absolute = toggle
3930 self.update()
3932 def waterfall_set_integrate(self, toggle):
3933 self.waterfall_integrate = toggle
3934 self.update()
3936 def set_selected_markers(self, markers):
3937 '''
3938 Set a list of markers selected
3940 :param markers: list of markers
3941 '''
3942 self.deselect_all()
3943 for m in markers:
3944 m.selected = True
3946 self.update()
3948 def deselect_all(self):
3949 for marker in self.markers:
3950 marker.selected = False
3952 def animate_picking(self):
3953 point = self.mapFromGlobal(qg.QCursor.pos())
3954 self.update_picking(point.x(), point.y(), doshift=True)
3956 def get_nslc_ids_for_track(self, ftrack):
3957 itrack = int(ftrack)
3958 return self.track_to_nslc_ids.get(itrack, [])
3960 def stop_picking(self, x, y, abort=False):
3961 if self.picking:
3962 self.update_picking(x, y, doshift=False)
3963 self.picking = None
3964 self.picking_down = None
3965 self.picking_timer.stop()
3966 self.picking_timer = None
3967 if not abort:
3968 self.add_marker(self.floating_marker)
3969 self.floating_marker.selected = True
3970 self.emit_selected_markers()
3972 self.floating_marker = None
3974 def start_picking(self, ignore):
3976 if not self.picking:
3977 self.deselect_all()
3978 self.picking = qw.QRubberBand(qw.QRubberBand.Rectangle)
3979 point = self.mapFromGlobal(qg.QCursor.pos())
3981 gpoint = self.mapToGlobal(qc.QPoint(point.x(), 0))
3982 self.picking.setGeometry(
3983 gpoint.x(), gpoint.y(), 1, self.height())
3984 t = self.time_projection.rev(point.x())
3986 ftrack = self.track_to_screen.rev(point.y())
3987 nslc_ids = self.get_nslc_ids_for_track(ftrack)
3988 self.floating_marker = Marker(nslc_ids, t, t)
3989 self.floating_marker.selected = True
3991 self.picking_timer = qc.QTimer()
3992 self.picking_timer.timeout.connect(
3993 self.animate_picking)
3995 self.picking_timer.setInterval(50)
3996 self.picking_timer.start()
3998 def update_picking(self, x, y, doshift=False):
3999 if self.picking:
4000 mouset = self.time_projection.rev(x)
4001 dt = 0.0
4002 if mouset < self.tmin or mouset > self.tmax:
4003 if mouset < self.tmin:
4004 dt = -(self.tmin - mouset)
4005 else:
4006 dt = mouset - self.tmax
4007 ddt = self.tmax-self.tmin
4008 dt = max(dt, -ddt/10.)
4009 dt = min(dt, ddt/10.)
4011 x0 = x
4012 if self.picking_down is not None:
4013 x0 = self.time_projection(self.picking_down[0])
4015 w = abs(x-x0)
4016 x0 = min(x0, x)
4018 tmin, tmax = (
4019 self.time_projection.rev(x0),
4020 self.time_projection.rev(x0+w))
4022 tmin, tmax = (
4023 max(working_system_time_range[0], tmin),
4024 min(working_system_time_range[1], tmax))
4026 p1 = self.mapToGlobal(qc.QPoint(int(round(x0)), 0))
4028 self.picking.setGeometry(
4029 p1.x(), p1.y(), int(round(max(w, 1))), self.height())
4031 ftrack = self.track_to_screen.rev(y)
4032 nslc_ids = self.get_nslc_ids_for_track(ftrack)
4033 self.floating_marker.set(nslc_ids, tmin, tmax)
4035 if dt != 0.0 and doshift:
4036 self.interrupt_following()
4037 self.set_time_range(self.tmin+dt, self.tmax+dt)
4039 self.update()
4041 def update_status(self):
4043 point = self.mapFromGlobal(qg.QCursor.pos())
4045 mouse_t = self.time_projection.rev(point.x())
4046 if not is_working_time(mouse_t):
4047 return
4049 if self.floating_marker:
4050 tmi, tma = (
4051 self.floating_marker.tmin,
4052 self.floating_marker.tmax)
4054 tt, ms = gmtime_x(tmi)
4056 if tmi == tma:
4057 message = mystrftime(
4058 fmt='Pick: %Y-%m-%d %H:%M:%S .%r',
4059 tt=tt, milliseconds=ms)
4060 else:
4061 srange = '%g s' % (tma-tmi)
4062 message = mystrftime(
4063 fmt='Start: %Y-%m-%d %H:%M:%S .%r Length: '+srange,
4064 tt=tt, milliseconds=ms)
4065 else:
4066 tt, ms = gmtime_x(mouse_t)
4068 message = mystrftime(fmt=None, tt=tt, milliseconds=ms)
4070 sb = self.window().statusBar()
4071 sb.clearMessage()
4072 sb.showMessage(message)
4074 def set_sortingmode_change_delay_time(self, dt):
4075 self.sortingmode_change_delay_time = dt
4077 def sortingmode_change_delayed(self):
4078 now = time.time()
4079 return (
4080 self.sortingmode_change_delay_time is not None
4081 and now - self.sortingmode_change_time
4082 < self.sortingmode_change_delay_time)
4084 def set_visible_marker_kinds(self, kinds):
4085 self.deselect_all()
4086 self.visible_marker_kinds = tuple(kinds)
4087 self.emit_selected_markers()
4089 def following(self):
4090 return self.follow_timer is not None \
4091 and not self.following_interrupted()
4093 def interrupt_following(self):
4094 self.interactive_range_change_time = time.time()
4096 def following_interrupted(self, now=None):
4097 if now is None:
4098 now = time.time()
4099 return now - self.interactive_range_change_time \
4100 < self.interactive_range_change_delay_time
4102 def follow(self, tlen, interval=50, lapse=None, tmax_start=None):
4103 if tmax_start is None:
4104 tmax_start = time.time()
4105 self.show_all = False
4106 self.follow_time = tlen
4107 self.follow_timer = qc.QTimer(self)
4108 self.follow_timer.timeout.connect(
4109 self.follow_update)
4110 self.follow_timer.setInterval(interval)
4111 self.follow_timer.start()
4112 self.follow_started = time.time()
4113 self.follow_lapse = lapse
4114 self.follow_tshift = self.follow_started - tmax_start
4115 self.interactive_range_change_time = 0.0
4117 def unfollow(self):
4118 if self.follow_timer is not None:
4119 self.follow_timer.stop()
4120 self.follow_timer = None
4121 self.interactive_range_change_time = 0.0
4123 def follow_update(self):
4124 rnow = time.time()
4125 if self.follow_lapse is None:
4126 now = rnow
4127 else:
4128 now = self.follow_started + (rnow - self.follow_started) \
4129 * self.follow_lapse
4131 if self.following_interrupted(rnow):
4132 return
4133 self.set_time_range(
4134 now-self.follow_time-self.follow_tshift,
4135 now-self.follow_tshift)
4137 self.update()
4139 def myclose(self, return_tag=''):
4140 self.return_tag = return_tag
4141 self.window().close()
4143 def cleanup(self):
4144 self.about_to_close.emit()
4145 self.timer.stop()
4146 if self.follow_timer is not None:
4147 self.follow_timer.stop()
4149 for snuffling in list(self.snufflings):
4150 self.remove_snuffling(snuffling)
4152 def inputline_changed(self, text):
4153 pass
4155 def inputline_finished(self, text):
4156 line = str(text)
4158 toks = line.split()
4159 clearit, hideit, error = False, True, None
4160 if len(toks) >= 1:
4161 command = toks[0].lower()
4163 try:
4164 quick_filter_commands = {
4165 'n': '%s.*.*.*',
4166 's': '*.%s.*.*',
4167 'l': '*.*.%s.*',
4168 'c': '*.*.*.%s'}
4170 if command in quick_filter_commands:
4171 if len(toks) >= 2:
4172 patterns = [
4173 quick_filter_commands[toks[0]] % pat
4174 for pat in toks[1:]]
4175 self.set_quick_filter_patterns(patterns, line)
4176 else:
4177 self.set_quick_filter_patterns(None)
4179 self.update()
4181 elif command in ('hide', 'unhide'):
4182 if len(toks) >= 2:
4183 patterns = []
4184 if len(toks) == 2:
4185 patterns = [toks[1]]
4186 elif len(toks) >= 3:
4187 x = {
4188 'n': '%s.*.*.*',
4189 's': '*.%s.*.*',
4190 'l': '*.*.%s.*',
4191 'c': '*.*.*.%s'}
4193 if toks[1] in x:
4194 patterns.extend(
4195 x[toks[1]] % tok for tok in toks[2:])
4197 for pattern in patterns:
4198 if command == 'hide':
4199 self.add_blacklist_pattern(pattern)
4200 else:
4201 self.remove_blacklist_pattern(pattern)
4203 elif command == 'unhide' and len(toks) == 1:
4204 self.clear_blacklist()
4206 clearit = True
4208 self.update()
4210 elif command == 'markers':
4211 if len(toks) == 2:
4212 if toks[1] == 'all':
4213 kinds = self.all_marker_kinds
4214 else:
4215 kinds = []
4216 for x in toks[1]:
4217 try:
4218 kinds.append(int(x))
4219 except Exception:
4220 pass
4222 self.set_visible_marker_kinds(kinds)
4224 elif len(toks) == 1:
4225 self.set_visible_marker_kinds(())
4227 self.update()
4229 elif command == 'scaling':
4230 if len(toks) == 2:
4231 hideit = False
4232 error = 'wrong number of arguments'
4234 if len(toks) >= 3:
4235 vmin, vmax = [
4236 pyrocko.model.float_or_none(x)
4237 for x in toks[-2:]]
4239 def upd(d, k, vmin, vmax):
4240 if k in d:
4241 if vmin is not None:
4242 d[k] = vmin, d[k][1]
4243 if vmax is not None:
4244 d[k] = d[k][0], vmax
4246 if len(toks) == 1:
4247 self.remove_scaling_hooks()
4249 elif len(toks) == 3:
4250 def hook(data_ranges):
4251 for k in data_ranges:
4252 upd(data_ranges, k, vmin, vmax)
4254 self.set_scaling_hook('_', hook)
4256 elif len(toks) == 4:
4257 pattern = toks[1]
4259 def hook(data_ranges):
4260 for k in pyrocko.util.match_nslcs(
4261 pattern, list(data_ranges.keys())):
4263 upd(data_ranges, k, vmin, vmax)
4265 self.set_scaling_hook(pattern, hook)
4267 elif command == 'goto':
4268 toks2 = line.split(None, 1)
4269 if len(toks2) == 2:
4270 arg = toks2[1]
4271 m = re.match(
4272 r'^\d\d\d\d(-\d\d(-\d\d( \d\d(:\d\d'
4273 r'(:\d\d(\.\d+)?)?)?)?)?)?$', arg)
4274 if m:
4275 tlen = None
4276 if not m.group(1):
4277 tlen = 12*32*24*60*60
4278 elif not m.group(2):
4279 tlen = 32*24*60*60
4280 elif not m.group(3):
4281 tlen = 24*60*60
4282 elif not m.group(4):
4283 tlen = 60*60
4284 elif not m.group(5):
4285 tlen = 60
4287 supl = '1970-01-01 00:00:00'
4288 if len(supl) > len(arg):
4289 arg = arg + supl[-(len(supl)-len(arg)):]
4290 t = pyrocko.util.str_to_time(arg)
4291 self.go_to_time(t, tlen=tlen)
4293 elif re.match(r'^\d\d:\d\d(:\d\d(\.\d+)?)?$', arg):
4294 supl = '00:00:00'
4295 if len(supl) > len(arg):
4296 arg = arg + supl[-(len(supl)-len(arg)):]
4297 tmin, tmax = self.get_time_range()
4298 sdate = pyrocko.util.time_to_str(
4299 tmin/2.+tmax/2., format='%Y-%m-%d')
4300 t = pyrocko.util.str_to_time(sdate + ' ' + arg)
4301 self.go_to_time(t)
4303 elif arg == 'today':
4304 self.go_to_time(
4305 day_start(
4306 time.time()), tlen=24*60*60)
4308 elif arg == 'yesterday':
4309 self.go_to_time(
4310 day_start(
4311 time.time()-24*60*60), tlen=24*60*60)
4313 else:
4314 self.go_to_event_by_name(arg)
4316 else:
4317 raise PileViewerMainException(
4318 'No such command: %s' % command)
4320 except PileViewerMainException as e:
4321 error = str(e)
4322 hideit = False
4324 return clearit, hideit, error
4326 return PileViewerMain
4329PileViewerMain = MakePileViewerMainClass(qw.QWidget)
4330GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget)
4333class LineEditWithAbort(qw.QLineEdit):
4335 aborted = qc.pyqtSignal()
4336 history_down = qc.pyqtSignal()
4337 history_up = qc.pyqtSignal()
4339 def keyPressEvent(self, key_event):
4340 if key_event.key() == qc.Qt.Key_Escape:
4341 self.aborted.emit()
4342 elif key_event.key() == qc.Qt.Key_Down:
4343 self.history_down.emit()
4344 elif key_event.key() == qc.Qt.Key_Up:
4345 self.history_up.emit()
4346 else:
4347 return qw.QLineEdit.keyPressEvent(self, key_event)
4350class PileViewer(qw.QFrame):
4351 '''
4352 PileViewerMain + Controls + Inputline
4353 '''
4355 def __init__(
4356 self, pile,
4357 ntracks_shown_max=20,
4358 marker_editor_sortable=True,
4359 use_opengl=None,
4360 panel_parent=None,
4361 *args):
4363 qw.QFrame.__init__(self, *args)
4365 layout = qw.QGridLayout()
4366 layout.setContentsMargins(0, 0, 0, 0)
4367 layout.setSpacing(0)
4369 self.menu = PileViewerMenuBar(self)
4371 if use_opengl is None:
4372 use_opengl = is_macos
4374 if use_opengl:
4375 self.viewer = GLPileViewerMain(
4376 pile,
4377 ntracks_shown_max=ntracks_shown_max,
4378 panel_parent=panel_parent,
4379 menu=self.menu)
4380 else:
4381 self.viewer = PileViewerMain(
4382 pile,
4383 ntracks_shown_max=ntracks_shown_max,
4384 panel_parent=panel_parent,
4385 menu=self.menu)
4387 self.marker_editor_sortable = marker_editor_sortable
4389 # self.setFrameShape(qw.QFrame.StyledPanel)
4390 # self.setFrameShadow(qw.QFrame.Sunken)
4392 self.input_area = qw.QFrame(self)
4393 ia_layout = qw.QGridLayout()
4394 ia_layout.setContentsMargins(11, 11, 11, 11)
4395 self.input_area.setLayout(ia_layout)
4397 self.inputline = LineEditWithAbort(self.input_area)
4398 self.inputline.returnPressed.connect(
4399 self.inputline_returnpressed)
4400 self.inputline.editingFinished.connect(
4401 self.inputline_finished)
4402 self.inputline.aborted.connect(
4403 self.inputline_aborted)
4405 self.inputline.history_down.connect(
4406 lambda: self.step_through_history(1))
4407 self.inputline.history_up.connect(
4408 lambda: self.step_through_history(-1))
4410 self.inputline.textEdited.connect(
4411 self.inputline_changed)
4413 self.inputline.setPlaceholderText(
4414 u"Quick commands: e.g. 'c HH?' to select channels. "
4415 u'Use ↑ or ↓ to navigate.')
4416 self.inputline.setFocusPolicy(qc.Qt.ClickFocus)
4417 self.input_area.hide()
4418 self.history = None
4420 self.inputline_error_str = None
4422 self.inputline_error = qw.QLabel()
4423 self.inputline_error.hide()
4425 ia_layout.addWidget(self.inputline, 0, 0)
4426 ia_layout.addWidget(self.inputline_error, 1, 0)
4427 layout.addWidget(self.input_area, 0, 0, 1, 2)
4428 layout.addWidget(self.viewer, 1, 0)
4430 pb = Progressbars(self)
4431 layout.addWidget(pb, 2, 0, 1, 2)
4432 self.progressbars = pb
4434 scrollbar = qw.QScrollBar(qc.Qt.Vertical)
4435 self.scrollbar = scrollbar
4436 layout.addWidget(scrollbar, 1, 1)
4437 self.scrollbar.valueChanged.connect(
4438 self.scrollbar_changed)
4440 self.block_scrollbar_changes = False
4442 self.viewer.want_input.connect(
4443 self.inputline_show)
4444 self.viewer.toggle_input.connect(
4445 self.inputline_toggle)
4446 self.viewer.tracks_range_changed.connect(
4447 self.tracks_range_changed)
4448 self.viewer.pile_has_changed_signal.connect(
4449 self.adjust_controls)
4450 self.viewer.about_to_close.connect(
4451 self.save_inputline_history)
4453 self.setLayout(layout)
4455 def cleanup(self):
4456 self.viewer.cleanup()
4458 def get_progressbars(self):
4459 return self.progressbars
4461 def inputline_show(self):
4462 if not self.history:
4463 self.load_inputline_history()
4465 self.input_area.show()
4466 self.inputline.setFocus(qc.Qt.OtherFocusReason)
4467 self.inputline.selectAll()
4469 def inputline_set_error(self, string):
4470 self.inputline_error_str = string
4471 self.inputline.setPalette(pyrocko.gui.util.get_err_palette())
4472 self.inputline.selectAll()
4473 self.inputline_error.setText(string)
4474 self.input_area.show()
4475 self.inputline_error.show()
4477 def inputline_clear_error(self):
4478 if self.inputline_error_str:
4479 self.inputline.setPalette(qw.QApplication.palette())
4480 self.inputline_error_str = None
4481 self.inputline_error.clear()
4482 self.inputline_error.hide()
4484 def inputline_changed(self, line):
4485 self.viewer.inputline_changed(str(line))
4486 self.inputline_clear_error()
4488 def inputline_returnpressed(self):
4489 line = str(self.inputline.text())
4490 clearit, hideit, error = self.viewer.inputline_finished(line)
4492 if error:
4493 self.inputline_set_error(error)
4495 line = line.strip()
4497 if line != '' and not error:
4498 if not (len(self.history) >= 1 and line == self.history[-1]):
4499 self.history.append(line)
4501 if clearit:
4503 self.inputline.blockSignals(True)
4504 qpat, qinp = self.viewer.get_quick_filter_patterns()
4505 if qpat is None:
4506 self.inputline.clear()
4507 else:
4508 self.inputline.setText(qinp)
4509 self.inputline.blockSignals(False)
4511 if hideit and not error:
4512 self.viewer.setFocus(qc.Qt.OtherFocusReason)
4513 self.input_area.hide()
4515 self.hist_ind = len(self.history)
4517 def inputline_aborted(self):
4518 '''
4519 Hide the input line.
4520 '''
4521 self.viewer.setFocus(qc.Qt.OtherFocusReason)
4522 self.hist_ind = len(self.history)
4523 self.input_area.hide()
4525 def inputline_toggle(self):
4526 if self.input_area.isVisible():
4527 self.inputline_aborted()
4528 else:
4529 self.inputline_show()
4531 def save_inputline_history(self):
4532 '''
4533 Save input line history to "$HOME/.pyrocko/.snuffler_history.pf"
4534 '''
4535 if not self.history:
4536 return
4538 conf = pyrocko.config
4539 fn_hist = conf.expand(conf.make_conf_path_tmpl('.snuffler_history'))
4540 with open(fn_hist, 'w') as f:
4541 i = min(100, len(self.history))
4542 for c in self.history[-i:]:
4543 f.write('%s\n' % c)
4545 def load_inputline_history(self):
4546 '''
4547 Load input line history from "$HOME/.pyrocko/.snuffler_history.pf"
4548 '''
4549 conf = pyrocko.config
4550 fn_hist = conf.expand(conf.make_conf_path_tmpl('.snuffler_history'))
4551 if not os.path.exists(fn_hist):
4552 with open(fn_hist, 'w+') as f:
4553 f.write('\n')
4555 with open(fn_hist, 'r') as f:
4556 self.history = [line.strip() for line in f.readlines()]
4558 self.hist_ind = len(self.history)
4560 def step_through_history(self, ud=1):
4561 '''
4562 Step through input line history and set the input line text.
4563 '''
4564 n = len(self.history)
4565 self.hist_ind += ud
4566 self.hist_ind %= (n + 1)
4567 if len(self.history) != 0 and self.hist_ind != n:
4568 self.inputline.setText(self.history[self.hist_ind])
4569 else:
4570 self.inputline.setText('')
4572 def inputline_finished(self):
4573 pass
4575 def tracks_range_changed(self, ntracks, ilo, ihi):
4576 if self.block_scrollbar_changes:
4577 return
4579 self.scrollbar.blockSignals(True)
4580 self.scrollbar.setPageStep(ihi-ilo)
4581 vmax = max(0, ntracks-(ihi-ilo))
4582 self.scrollbar.setRange(0, vmax)
4583 self.scrollbar.setValue(ilo)
4584 self.scrollbar.setHidden(vmax == 0)
4585 self.scrollbar.blockSignals(False)
4587 def scrollbar_changed(self, value):
4588 self.block_scrollbar_changes = True
4589 ilo = value
4590 ihi = ilo + self.scrollbar.pageStep()
4591 self.viewer.set_tracks_range((ilo, ihi))
4592 self.block_scrollbar_changes = False
4593 self.update_contents()
4595 def controls(self):
4596 frame = qw.QFrame(self)
4597 layout = qw.QGridLayout()
4598 frame.setLayout(layout)
4600 minfreq = 0.001
4601 maxfreq = 1000.0
4602 self.lowpass_control = ValControl(high_is_none=True)
4603 self.lowpass_control.setup(
4604 'Lowpass [Hz]:', minfreq, maxfreq, maxfreq, 0)
4605 self.highpass_control = ValControl(low_is_none=True)
4606 self.highpass_control.setup(
4607 'Highpass [Hz]:', minfreq, maxfreq, minfreq, 1)
4608 self.gain_control = ValControl()
4609 self.gain_control.setup('Gain:', 0.001, 1000., 1., 2)
4610 self.rot_control = LinValControl()
4611 self.rot_control.setup('Rotate [deg]:', -180., 180., 0., 3)
4612 self.colorbar_control = ColorbarControl(self)
4614 self.lowpass_control.valchange.connect(
4615 self.viewer.lowpass_change)
4616 self.highpass_control.valchange.connect(
4617 self.viewer.highpass_change)
4618 self.gain_control.valchange.connect(
4619 self.viewer.gain_change)
4620 self.rot_control.valchange.connect(
4621 self.viewer.rot_change)
4622 self.colorbar_control.cmap_changed.connect(
4623 self.viewer.waterfall_cmap_change
4624 )
4625 self.colorbar_control.clip_changed.connect(
4626 self.viewer.waterfall_clip_change
4627 )
4628 self.colorbar_control.show_absolute_toggled.connect(
4629 self.viewer.waterfall_show_absolute_change
4630 )
4631 self.colorbar_control.show_integrate_toggled.connect(
4632 self.viewer.waterfall_set_integrate
4633 )
4635 for icontrol, control in enumerate((
4636 self.highpass_control,
4637 self.lowpass_control,
4638 self.gain_control,
4639 self.rot_control,
4640 self.colorbar_control)):
4642 for iwidget, widget in enumerate(control.widgets()):
4643 layout.addWidget(widget, icontrol, iwidget)
4645 spacer = qw.QSpacerItem(
4646 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding)
4647 layout.addItem(spacer, 4, 0, 1, 3)
4649 self.adjust_controls()
4650 self.viewer.viewmode_change(ViewMode.Wiggle)
4651 return frame
4653 def marker_editor(self):
4654 editor = pyrocko.gui.snuffler.marker_editor.MarkerEditor(
4655 self, sortable=self.marker_editor_sortable)
4657 editor.set_viewer(self.get_view())
4658 editor.get_marker_model().dataChanged.connect(
4659 self.update_contents)
4660 return editor
4662 def adjust_controls(self):
4663 dtmin, dtmax = self.viewer.content_deltat_range()
4664 maxfreq = 0.5/dtmin
4665 minfreq = (0.5/dtmax)*0.0001
4666 self.lowpass_control.set_range(minfreq, maxfreq)
4667 self.highpass_control.set_range(minfreq, maxfreq)
4669 def setup_snufflings(self):
4670 self.viewer.setup_snufflings()
4672 def get_view(self):
4673 return self.viewer
4675 def update_contents(self):
4676 self.viewer.update()
4678 def get_pile(self):
4679 return self.viewer.get_pile()
4681 def show_colorbar_ctrl(self, show):
4682 for w in self.colorbar_control.widgets():
4683 w.setVisible(show)
4685 def show_gain_ctrl(self, show):
4686 for w in self.gain_control.widgets():
4687 w.setVisible(show)