Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/gui/snuffler/pile_viewer.py: 70%
2845 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-10 09:02 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-10 09:02 +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 about_to_close = qc.pyqtSignal()
795 pile_has_changed_signal = qc.pyqtSignal()
796 tracks_range_changed = qc.pyqtSignal(int, int, int)
798 begin_markers_add = qc.pyqtSignal(int, int)
799 end_markers_add = qc.pyqtSignal()
800 begin_markers_remove = qc.pyqtSignal(int, int)
801 end_markers_remove = qc.pyqtSignal()
803 marker_selection_changed = qc.pyqtSignal(list)
804 active_event_marker_changed = qc.pyqtSignal()
806 def __init__(self, pile, ntracks_shown_max, panel_parent, *args,
807 menu=None):
808 base.__init__(self, *args)
810 self.pile = pile
811 self.ax_height = 80
812 self.panel_parent = panel_parent
814 self.click_tolerance = 5
816 self.ntracks_shown_max = ntracks_shown_max
817 self.initial_ntracks_shown_max = ntracks_shown_max
818 self.ntracks = 0
819 self.show_all = True
820 self.shown_tracks_range = None
821 self.track_start = None
822 self.track_trange = None
824 self.lowpass = None
825 self.highpass = None
826 self.gain = 1.0
827 self.rotate = 0.0
828 self.picking_down = None
829 self.picking = None
830 self.floating_marker = None
831 self.markers = pyrocko.pile.Sorted([], 'tmin')
832 self.markers_deltat_max = 0.
833 self.n_selected_markers = 0
834 self.all_marker_kinds = (0, 1, 2, 3, 4, 5, 6, 7)
835 self.visible_marker_kinds = self.all_marker_kinds
836 self.active_event_marker = None
837 self.ignore_releases = 0
838 self.message = None
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)
1178 toolbar_layout.addWidget(PileViewerMenuBarButton('+'))
1179 toolbar_layout.addWidget(PileViewerMenuBarButton('-'))
1180 toolbar_layout.addWidget(PileViewerMenuBarButton('>>'))
1182 self.menu.setCornerWidget(toolbar)
1184 self.time_projection = Projection()
1185 self.set_time_range(self.pile.get_tmin(), self.pile.get_tmax())
1186 self.time_projection.set_out_range(0., self.width())
1188 self.gather = None
1190 self.trace_filter = None
1191 self.quick_filter = None
1192 self.quick_filter_patterns = None, None
1193 self.blacklist = []
1195 self.track_to_screen = Projection()
1196 self.track_to_nslc_ids = {}
1198 self.cached_vec = None
1199 self.cached_processed_traces = None
1201 self.timer = qc.QTimer(self)
1202 self.timer.timeout.connect(self.periodical)
1203 self.timer.setInterval(1000)
1204 self.timer.start()
1206 self._pile_changed = self.pile_changed # need to keep a strong ref
1207 self.pile.add_listener(self._pile_changed)
1209 self.trace_styles = {}
1210 if self.get_squirrel() is None:
1211 self.determine_box_styles()
1213 self.setMouseTracking(True)
1215 user_home_dir = os.path.expanduser('~')
1216 self.snuffling_modules = {}
1217 self.snuffling_paths = [os.path.join(user_home_dir, '.snufflings')]
1218 self.default_snufflings = None
1219 self.snufflings = []
1221 self.stations = {}
1223 self.timer_draw = Timer()
1224 self.timer_cutout = Timer()
1225 self.time_spent_painting = 0.0
1226 self.time_last_painted = time.time()
1228 self.interactive_range_change_time = 0.0
1229 self.interactive_range_change_delay_time = 10.0
1230 self.follow_timer = None
1232 self.sortingmode_change_time = 0.0
1233 self.sortingmode_change_delay_time = None
1235 self.old_data_ranges = {}
1237 self.error_messages = {}
1238 self.return_tag = None
1239 self.wheel_pos = 60
1241 self.setAcceptDrops(True)
1242 self._paths_to_load = []
1244 self.tf_cache = {}
1246 self.waterfall = TraceWaterfall()
1247 self.waterfall_cmap = 'viridis'
1248 self.waterfall_clip_min = 0.
1249 self.waterfall_clip_max = 1.
1250 self.waterfall_show_absolute = False
1251 self.waterfall_integrate = False
1252 self.view_mode = ViewMode.Wiggle
1254 self.automatic_updates = True
1256 self.closing = False
1257 self.in_paint_event = False
1259 def fail(self, reason):
1260 box = qw.QMessageBox(self)
1261 box.setText(reason)
1262 box.exec_()
1264 def set_trace_filter(self, filter_func):
1265 self.trace_filter = filter_func
1266 self.sortingmode_change()
1268 def update_trace_filter(self):
1269 if self.blacklist:
1271 def blacklist_func(tr):
1272 return not pyrocko.util.match_nslc(
1273 self.blacklist, tr.nslc_id)
1275 else:
1276 blacklist_func = None
1278 if self.quick_filter is None and blacklist_func is None:
1279 self.set_trace_filter(None)
1280 elif self.quick_filter is None:
1281 self.set_trace_filter(blacklist_func)
1282 elif blacklist_func is None:
1283 self.set_trace_filter(self.quick_filter)
1284 else:
1285 self.set_trace_filter(
1286 lambda tr: blacklist_func(tr) and self.quick_filter(tr))
1288 def set_quick_filter(self, filter_func):
1289 self.quick_filter = filter_func
1290 self.update_trace_filter()
1292 def set_quick_filter_patterns(self, patterns, inputline=None):
1293 if patterns is not None:
1294 self.set_quick_filter(
1295 lambda tr: pyrocko.util.match_nslc(patterns, tr.nslc_id))
1296 else:
1297 self.set_quick_filter(None)
1299 self.quick_filter_patterns = patterns, inputline
1301 def get_quick_filter_patterns(self):
1302 return self.quick_filter_patterns
1304 def add_blacklist_pattern(self, pattern):
1305 if pattern == 'empty':
1306 keys = set(self.pile.nslc_ids)
1307 trs = self.pile.all(
1308 tmin=self.tmin,
1309 tmax=self.tmax,
1310 load_data=False,
1311 degap=False)
1313 for tr in trs:
1314 if tr.nslc_id in keys:
1315 keys.remove(tr.nslc_id)
1317 for key in keys:
1318 xpattern = '.'.join(key)
1319 if xpattern not in self.blacklist:
1320 self.blacklist.append(xpattern)
1322 else:
1323 if pattern in self.blacklist:
1324 self.blacklist.remove(pattern)
1326 self.blacklist.append(pattern)
1328 logger.info('Blacklist is [ %s ]' % ', '.join(self.blacklist))
1329 self.update_trace_filter()
1331 def remove_blacklist_pattern(self, pattern):
1332 if pattern in self.blacklist:
1333 self.blacklist.remove(pattern)
1334 else:
1335 raise PileViewerMainException(
1336 'Pattern not found in blacklist.')
1338 logger.info('Blacklist is [ %s ]' % ', '.join(self.blacklist))
1339 self.update_trace_filter()
1341 def clear_blacklist(self):
1342 self.blacklist = []
1343 self.update_trace_filter()
1345 def ssort(self, tr):
1346 return self._ssort(tr)
1348 def station_key(self, x):
1349 return x.network, x.station
1351 def station_keys(self, x):
1352 return [
1353 (x.network, x.station, x.location),
1354 (x.network, x.station)]
1356 def station_attrib(self, tr, getter, default_getter):
1357 for sk in self.station_keys(tr):
1358 if sk in self.stations:
1359 station = self.stations[sk]
1360 return getter(station)
1362 return default_getter(tr)
1364 def get_station(self, sk):
1365 return self.stations[sk]
1367 def has_station(self, station):
1368 for sk in self.station_keys(station):
1369 if sk in self.stations:
1370 return True
1372 return False
1374 def station_latlon(self, tr, default_getter=lambda tr: (0., 0.)):
1375 return self.station_attrib(
1376 tr, lambda sta: (sta.lat, sta.lon), default_getter)
1378 def set_stations(self, stations):
1379 self.stations = {}
1380 self.add_stations(stations)
1382 def add_stations(self, stations):
1383 for station in stations:
1384 for sk in self.station_keys(station):
1385 self.stations[sk] = station
1387 ev = self.get_active_event()
1388 if ev:
1389 self.set_origin(ev)
1391 def add_event(self, event):
1392 marker = EventMarker(event)
1393 self.add_marker(marker)
1395 def add_events(self, events):
1396 markers = [EventMarker(e) for e in events]
1397 self.add_markers(markers)
1399 def set_event_marker_as_origin(self, ignore=None):
1400 selected = self.selected_markers()
1401 if not selected:
1402 self.fail('An event marker must be selected.')
1403 return
1405 m = selected[0]
1406 if not isinstance(m, EventMarker):
1407 self.fail('Selected marker is not an event.')
1408 return
1410 self.set_active_event_marker(m)
1412 def deactivate_event_marker(self):
1413 if self.active_event_marker:
1414 self.active_event_marker.active = False
1416 self.active_event_marker_changed.emit()
1417 self.active_event_marker = None
1419 def set_active_event_marker(self, event_marker):
1420 if self.active_event_marker:
1421 self.active_event_marker.active = False
1423 self.active_event_marker = event_marker
1424 event_marker.active = True
1425 event = event_marker.get_event()
1426 self.set_origin(event)
1427 self.active_event_marker_changed.emit()
1429 def set_active_event(self, event):
1430 for marker in self.markers:
1431 if isinstance(marker, EventMarker):
1432 if marker.get_event() is event:
1433 self.set_active_event_marker(marker)
1435 def get_active_event_marker(self):
1436 return self.active_event_marker
1438 def get_active_event(self):
1439 m = self.get_active_event_marker()
1440 if m is not None:
1441 return m.get_event()
1442 else:
1443 return None
1445 def get_active_markers(self):
1446 emarker = self.get_active_event_marker()
1447 if emarker is None:
1448 return None, []
1450 else:
1451 ev = emarker.get_event()
1452 pmarkers = [
1453 m for m in self.markers
1454 if isinstance(m, PhaseMarker) and m.get_event() is ev]
1456 return emarker, pmarkers
1458 def set_origin(self, location):
1459 for station in self.stations.values():
1460 station.set_event_relative_data(
1461 location,
1462 distance_3d=self.menuitem_distances_3d.isChecked())
1464 self.sortingmode_change()
1466 def distances_3d_changed(self):
1467 ignore = self.menuitem_distances_3d.isChecked()
1468 self.set_event_marker_as_origin(ignore)
1470 def iter_snuffling_modules(self):
1471 pjoin = os.path.join
1472 for path in self.snuffling_paths:
1474 if not os.path.isdir(path):
1475 os.mkdir(path)
1477 for entry in os.listdir(path):
1478 directory = path
1479 fn = entry
1480 d = pjoin(path, entry)
1481 if os.path.isdir(d):
1482 directory = d
1483 if os.path.isfile(
1484 os.path.join(directory, 'snuffling.py')):
1485 fn = 'snuffling.py'
1487 if not fn.endswith('.py'):
1488 continue
1490 name = fn[:-3]
1492 if (directory, name) not in self.snuffling_modules:
1493 self.snuffling_modules[directory, name] = \
1494 pyrocko.gui.snuffler.snuffling.SnufflingModule(
1495 directory, name, self)
1497 yield self.snuffling_modules[directory, name]
1499 def setup_snufflings(self):
1500 # user snufflings
1501 from pyrocko.gui.snuffler.snuffling import BrokenSnufflingModule
1502 for mod in self.iter_snuffling_modules():
1503 try:
1504 mod.load_if_needed()
1505 except BrokenSnufflingModule as e:
1506 logger.warning('Snuffling module "%s" is broken' % e)
1508 # load the default snufflings on first run
1509 if self.default_snufflings is None:
1510 self.default_snufflings = pyrocko.gui.snuffler\
1511 .snufflings.__snufflings__()
1512 for snuffling in self.default_snufflings:
1513 self.add_snuffling(snuffling)
1515 def set_panel_parent(self, panel_parent):
1516 self.panel_parent = panel_parent
1518 def get_panel_parent(self):
1519 return self.panel_parent
1521 def add_snuffling(self, snuffling, reloaded=False):
1522 logger.debug('Adding snuffling %s' % snuffling.get_name())
1523 snuffling.init_gui(
1524 self, self.get_panel_parent(), self, reloaded=reloaded)
1525 self.snufflings.append(snuffling)
1526 self.update()
1528 def remove_snuffling(self, snuffling):
1529 snuffling.delete_gui()
1530 self.update()
1531 self.snufflings.remove(snuffling)
1532 snuffling.pre_destroy()
1534 def add_snuffling_menuitem(self, item):
1535 self.snufflings_menu.addAction(item)
1536 item.setParent(self.snufflings_menu)
1537 sort_actions(self.snufflings_menu)
1539 def remove_snuffling_menuitem(self, item):
1540 self.snufflings_menu.removeAction(item)
1542 def add_snuffling_help_menuitem(self, item):
1543 self.snuffling_help.addAction(item)
1544 item.setParent(self.snuffling_help)
1545 sort_actions(self.snuffling_help)
1547 def remove_snuffling_help_menuitem(self, item):
1548 self.snuffling_help.removeAction(item)
1550 def add_panel_toggler(self, item):
1551 self.toggle_panel_menu.addAction(item)
1552 item.setParent(self.toggle_panel_menu)
1553 sort_actions(self.toggle_panel_menu)
1555 def remove_panel_toggler(self, item):
1556 self.toggle_panel_menu.removeAction(item)
1558 def load(self, paths, regex=None, format='detect',
1559 cache_dir=None, force_cache=False):
1561 if cache_dir is None:
1562 cache_dir = pyrocko.config.config().cache_dir
1563 if isinstance(paths, str):
1564 paths = [paths]
1566 fns = pyrocko.util.select_files(
1567 paths, selector=None, include=regex, show_progress=False)
1569 if not fns:
1570 return
1572 cache = pyrocko.pile.get_cache(cache_dir)
1574 t = [time.time()]
1576 def update_bar(label, value):
1577 pbs = self.parent().get_progressbars()
1578 if label.lower() == 'looking at files':
1579 label = 'Looking at %i files' % len(fns)
1580 else:
1581 label = 'Scanning %i files' % len(fns)
1583 return pbs.set_status(label, value)
1585 def update_progress(label, i, n):
1586 abort = False
1588 qw.qApp.processEvents()
1589 if n != 0:
1590 perc = i*100/n
1591 else:
1592 perc = 100
1593 abort |= update_bar(label, perc)
1594 abort |= self.window().is_closing()
1596 tnow = time.time()
1597 if t[0] + 1. + self.time_spent_painting * 10. < tnow:
1598 self.update()
1599 t[0] = tnow
1601 return abort
1603 self.automatic_updates = False
1605 self.pile.load_files(
1606 sorted(fns),
1607 filename_attributes=regex,
1608 cache=cache,
1609 fileformat=format,
1610 show_progress=False,
1611 update_progress=update_progress)
1613 self.automatic_updates = True
1614 self.update()
1616 def load_queued(self):
1617 if not self._paths_to_load:
1618 return
1619 paths = self._paths_to_load
1620 self._paths_to_load = []
1621 self.load(paths)
1623 def load_soon(self, paths):
1624 self._paths_to_load.extend(paths)
1625 qc.QTimer.singleShot(200, self.load_queued)
1627 def open_waveforms(self):
1628 caption = 'Select one or more files to open'
1630 fns, _ = qw.QFileDialog.getOpenFileNames(
1631 self, caption, options=qfiledialog_options)
1633 if fns:
1634 self.load(list(str(fn) for fn in fns))
1636 def open_waveform_directory(self):
1637 caption = 'Select directory to scan for waveform files'
1639 dn = qw.QFileDialog.getExistingDirectory(
1640 self, caption, options=qfiledialog_options)
1642 if dn:
1643 self.load([str(dn)])
1645 def open_stations(self, fns=None):
1646 caption = 'Select one or more Pyrocko station files to open'
1648 if not fns:
1649 fns, _ = qw.QFileDialog.getOpenFileNames(
1650 self, caption, options=qfiledialog_options)
1652 try:
1653 stations = [pyrocko.model.load_stations(str(x)) for x in fns]
1654 for stat in stations:
1655 self.add_stations(stat)
1657 except Exception as e:
1658 self.fail('Failed to read station file: %s' % str(e))
1660 def open_stations_xml(self, fns=None):
1661 from pyrocko.io import stationxml
1663 caption = 'Select one or more StationXML files'
1664 if not fns:
1665 fns, _ = qw.QFileDialog.getOpenFileNames(
1666 self, caption, options=qfiledialog_options,
1667 filter='StationXML (*.xml *.XML *.stationxml *.stationXML)'
1668 ';;All files (*)')
1670 try:
1671 stations = [
1672 stationxml.load_xml(filename=str(x)).get_pyrocko_stations()
1673 for x in fns]
1675 for stat in stations:
1676 self.add_stations(stat)
1678 except Exception as e:
1679 self.fail('Failed to read StationXML file: %s' % str(e))
1681 def add_traces(self, traces):
1682 if traces:
1683 mtf = pyrocko.pile.MemTracesFile(None, traces)
1684 self.pile.add_file(mtf)
1685 ticket = (self.pile, mtf)
1686 return ticket
1687 else:
1688 return (None, None)
1690 def release_data(self, tickets):
1691 for ticket in tickets:
1692 pile, mtf = ticket
1693 if pile is not None:
1694 pile.remove_file(mtf)
1696 def periodical(self):
1697 if self.menuitem_watch.isChecked():
1698 if self.pile.reload_modified():
1699 self.update()
1701 def get_pile(self):
1702 return self.pile
1704 def pile_changed(self, what, content):
1705 self.pile_has_changed = True
1706 self.pile_has_changed_signal.emit()
1707 if self.automatic_updates:
1708 self.update()
1710 def set_gathering(self, gather=None, color=None):
1712 if gather is None:
1713 def gather_func(tr):
1714 return tr.nslc_id
1716 gather = (0, 1, 2, 3)
1718 else:
1719 def gather_func(tr):
1720 return (
1721 self.ssort(tr) + tuple(tr.nslc_id[i] for i in gather))
1723 if color is None:
1724 def color(tr):
1725 return tr.location
1727 self.gather = gather_func
1728 keys = self.pile.gather_keys(gather_func, self.trace_filter)
1730 self.color_gather = color
1731 self.color_keys = self.pile.gather_keys(color)
1732 previous_ntracks = self.ntracks
1733 self.set_ntracks(len(keys))
1735 if self.shown_tracks_range is None or \
1736 previous_ntracks == 0 or \
1737 self.show_all:
1739 low, high = 0, min(self.ntracks_shown_max, self.ntracks)
1740 key_at_top = None
1741 n = high-low
1743 else:
1744 low, high = self.shown_tracks_range
1745 key_at_top = self.track_keys[low]
1746 n = high-low
1748 self.track_keys = sorted(keys)
1750 track_patterns = []
1751 for k in self.track_keys:
1752 pat = ['*', '*', '*', '*']
1753 for i, j in enumerate(gather):
1754 pat[j] = k[-len(gather)+i]
1756 track_patterns.append(pat)
1758 self.track_patterns = track_patterns
1760 if key_at_top is not None:
1761 try:
1762 ind = self.track_keys.index(key_at_top)
1763 low = ind
1764 high = low+n
1765 except Exception:
1766 pass
1768 self.set_tracks_range((low, high))
1770 self.key_to_row = dict(
1771 [(key, i) for (i, key) in enumerate(self.track_keys)])
1773 def inrange(x, r):
1774 return r[0] <= x and x < r[1]
1776 def trace_selector(trace):
1777 gt = self.gather(trace)
1778 return (
1779 gt in self.key_to_row and
1780 inrange(self.key_to_row[gt], self.shown_tracks_range))
1782 self.trace_selector = lambda x: \
1783 (self.trace_filter is None or self.trace_filter(x)) \
1784 and trace_selector(x)
1786 if self.tmin == working_system_time_range[0] and \
1787 self.tmax == working_system_time_range[1] or \
1788 self.show_all:
1790 tmin, tmax = self.pile.get_tmin(), self.pile.get_tmax()
1791 if tmin is not None and tmax is not None:
1792 tlen = (tmax - tmin)
1793 tpad = tlen * 5./self.width()
1794 self.set_time_range(tmin-tpad, tmax+tpad)
1796 def set_time_range(self, tmin, tmax):
1797 if tmin is None:
1798 tmin = initial_time_range[0]
1800 if tmax is None:
1801 tmax = initial_time_range[1]
1803 if tmin > tmax:
1804 tmin, tmax = tmax, tmin
1806 if tmin == tmax:
1807 tmin -= 1.
1808 tmax += 1.
1810 tmin = max(working_system_time_range[0], tmin)
1811 tmax = min(working_system_time_range[1], tmax)
1813 min_deltat = self.content_deltat_range()[0]
1814 if (tmax - tmin < min_deltat):
1815 m = (tmin + tmax) / 2.
1816 tmin = m - min_deltat/2.
1817 tmax = m + min_deltat/2.
1819 self.time_projection.set_in_range(tmin, tmax)
1820 self.tmin, self.tmax = tmin, tmax
1822 def get_time_range(self):
1823 return self.tmin, self.tmax
1825 def ypart(self, y):
1826 if y < self.ax_height:
1827 return -1
1828 elif y > self.height()-self.ax_height:
1829 return 1
1830 else:
1831 return 0
1833 def time_fractional_digits(self):
1834 min_deltat = self.content_deltat_range()[0]
1835 return min(9, max(1, int(-math.floor(math.log10(min_deltat)))+2))
1837 def write_markers(self, fn=None):
1838 caption = 'Choose a file name to write markers'
1839 if not fn:
1840 fn, _ = qw.QFileDialog.getSaveFileName(
1841 self, caption, options=qfiledialog_options)
1842 if fn:
1843 try:
1844 Marker.save_markers(
1845 self.markers, fn,
1846 fdigits=self.time_fractional_digits())
1848 except Exception as e:
1849 self.fail('Failed to write marker file: %s' % str(e))
1851 def write_selected_markers(self, fn=None):
1852 caption = 'Choose a file name to write selected markers'
1853 if not fn:
1854 fn, _ = qw.QFileDialog.getSaveFileName(
1855 self, caption, options=qfiledialog_options)
1856 if fn:
1857 try:
1858 Marker.save_markers(
1859 self.iter_selected_markers(),
1860 fn,
1861 fdigits=self.time_fractional_digits())
1863 except Exception as e:
1864 self.fail('Failed to write marker file: %s' % str(e))
1866 def read_events(self, fn=None):
1867 '''
1868 Open QFileDialog to open, read and add
1869 :py:class:`pyrocko.model.Event` instances and their marker
1870 representation to the pile viewer.
1871 '''
1872 caption = 'Selet one or more files to open'
1873 if not fn:
1874 fn, _ = qw.QFileDialog.getOpenFileName(
1875 self, caption, options=qfiledialog_options)
1876 if fn:
1877 try:
1878 self.add_events(pyrocko.model.load_events(fn))
1879 self.associate_phases_to_events()
1881 except Exception as e:
1882 self.fail('Failed to read event file: %s' % str(e))
1884 def read_markers(self, fn=None):
1885 '''
1886 Open QFileDialog to open, read and add markers to the pile viewer.
1887 '''
1888 caption = 'Selet one or more marker files to open'
1889 if not fn:
1890 fn, _ = qw.QFileDialog.getOpenFileName(
1891 self, caption, options=qfiledialog_options)
1892 if fn:
1893 try:
1894 self.add_markers(Marker.load_markers(fn))
1895 self.associate_phases_to_events()
1897 except Exception as e:
1898 self.fail('Failed to read marker file: %s' % str(e))
1900 def associate_phases_to_events(self):
1901 associate_phases_to_events(self.markers)
1903 def add_marker(self, marker):
1904 # need index to inform QAbstactTableModel about upcoming change,
1905 # but have to restore current state in order to not cause problems
1906 self.markers.insert(marker)
1907 i = self.markers.remove(marker)
1909 self.begin_markers_add.emit(i, i)
1910 self.markers.insert(marker)
1911 self.end_markers_add.emit()
1912 self.markers_deltat_max = max(
1913 self.markers_deltat_max, marker.tmax - marker.tmin)
1915 def add_markers(self, markers):
1916 if not self.markers:
1917 self.begin_markers_add.emit(0, len(markers) - 1)
1918 self.markers.insert_many(markers)
1919 self.end_markers_add.emit()
1920 self.update_markers_deltat_max()
1921 else:
1922 for marker in markers:
1923 self.add_marker(marker)
1925 def update_markers_deltat_max(self):
1926 if self.markers:
1927 self.markers_deltat_max = max(
1928 marker.tmax - marker.tmin for marker in self.markers)
1930 def remove_marker(self, marker):
1931 '''
1932 Remove a ``marker`` from the :py:class:`PileViewer`.
1934 :param marker:
1935 :py:class:`~pyrocko.gui.snuffler.marker.Marker` (or subclass)
1936 instance
1937 '''
1939 if marker is self.active_event_marker:
1940 self.deactivate_event_marker()
1942 try:
1943 i = self.markers.index(marker)
1944 self.begin_markers_remove.emit(i, i)
1945 self.markers.remove_at(i)
1946 self.end_markers_remove.emit()
1947 except ValueError:
1948 pass
1950 def remove_markers(self, markers):
1951 '''
1952 Remove a list of ``markers`` from the :py:class:`PileViewer`.
1954 :param markers:
1955 list of :py:class:`~pyrocko.gui.snuffler.marker.Marker` (or
1956 subclass) instances
1957 '''
1959 if markers is self.markers:
1960 markers = list(markers)
1962 for marker in markers:
1963 self.remove_marker(marker)
1965 self.update_markers_deltat_max()
1967 def remove_selected_markers(self):
1968 def delete_segment(istart, iend):
1969 self.begin_markers_remove.emit(istart, iend-1)
1970 for _ in range(iend - istart):
1971 self.markers.remove_at(istart)
1973 self.end_markers_remove.emit()
1975 istart = None
1976 ipos = 0
1977 markers = self.markers
1978 nmarkers = len(self.markers)
1979 while ipos < nmarkers:
1980 marker = markers[ipos]
1981 if marker.is_selected():
1982 if marker is self.active_event_marker:
1983 self.deactivate_event_marker()
1985 if istart is None:
1986 istart = ipos
1987 else:
1988 if istart is not None:
1989 delete_segment(istart, ipos)
1990 nmarkers -= ipos - istart
1991 ipos = istart - 1
1992 istart = None
1994 ipos += 1
1996 if istart is not None:
1997 delete_segment(istart, ipos)
1999 self.update_markers_deltat_max()
2001 def selected_markers(self):
2002 return [marker for marker in self.markers if marker.is_selected()]
2004 def iter_selected_markers(self):
2005 for marker in self.markers:
2006 if marker.is_selected():
2007 yield marker
2009 def get_markers(self):
2010 return self.markers
2012 def mousePressEvent(self, mouse_ev):
2013 ''
2014 self.show_all = False
2015 point = self.mapFromGlobal(mouse_ev.globalPos())
2017 if mouse_ev.button() == qc.Qt.LeftButton:
2018 marker = self.marker_under_cursor(point.x(), point.y())
2019 if self.picking:
2020 if self.picking_down is None:
2021 self.picking_down = (
2022 self.time_projection.rev(mouse_ev.x()),
2023 mouse_ev.y())
2025 elif marker is not None:
2026 if not (mouse_ev.modifiers() & qc.Qt.ShiftModifier):
2027 self.deselect_all()
2028 marker.selected = True
2029 self.emit_selected_markers()
2030 self.update()
2031 else:
2032 self.track_start = mouse_ev.x(), mouse_ev.y()
2033 self.track_trange = self.tmin, self.tmax
2035 if mouse_ev.button() == qc.Qt.RightButton \
2036 and isinstance(self.menu, qw.QMenu):
2037 self.menu.exec_(qg.QCursor.pos())
2038 self.update_status()
2040 def mouseReleaseEvent(self, mouse_ev):
2041 ''
2042 if self.ignore_releases:
2043 self.ignore_releases -= 1
2044 return
2046 if self.picking:
2047 self.stop_picking(mouse_ev.x(), mouse_ev.y())
2048 self.emit_selected_markers()
2050 if self.track_start:
2051 self.update()
2053 self.track_start = None
2054 self.track_trange = None
2055 self.update_status()
2057 def mouseDoubleClickEvent(self, mouse_ev):
2058 ''
2059 self.show_all = False
2060 self.start_picking(None)
2061 self.ignore_releases = 1
2063 def mouseMoveEvent(self, mouse_ev):
2064 ''
2065 point = self.mapFromGlobal(mouse_ev.globalPos())
2067 if self.picking:
2068 self.update_picking(point.x(), point.y())
2070 elif self.track_start is not None:
2071 x0, y0 = self.track_start
2072 dx = (point.x() - x0)/float(self.width())
2073 dy = (point.y() - y0)/float(self.height())
2074 if self.ypart(y0) == 1:
2075 dy = 0
2077 tmin0, tmax0 = self.track_trange
2079 scale = math.exp(-dy*5.)
2080 dtr = scale*(tmax0-tmin0) - (tmax0-tmin0)
2081 frac = x0/float(self.width())
2082 dt = dx*(tmax0-tmin0)*scale
2084 self.interrupt_following()
2085 self.set_time_range(
2086 tmin0 - dt - dtr*frac,
2087 tmax0 - dt + dtr*(1.-frac))
2089 self.update()
2090 else:
2091 self.hoovering(point.x(), point.y())
2093 self.update_status()
2095 def nslc_ids_under_cursor(self, x, y):
2096 ftrack = self.track_to_screen.rev(y)
2097 nslc_ids = self.get_nslc_ids_for_track(ftrack)
2098 return nslc_ids
2100 def marker_under_cursor(self, x, y):
2101 mouset = self.time_projection.rev(x)
2102 deltat = (self.tmax-self.tmin)*self.click_tolerance/self.width()
2103 relevant_nslc_ids = None
2104 for marker in self.markers:
2105 if marker.kind not in self.visible_marker_kinds:
2106 continue
2108 if (abs(mouset-marker.tmin) < deltat or
2109 abs(mouset-marker.tmax) < deltat):
2111 if relevant_nslc_ids is None:
2112 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y)
2114 marker_nslc_ids = marker.get_nslc_ids()
2115 if not marker_nslc_ids:
2116 return marker
2118 for nslc_id in marker_nslc_ids:
2119 if nslc_id in relevant_nslc_ids:
2120 return marker
2122 def hoovering(self, x, y):
2123 mouset = self.time_projection.rev(x)
2124 deltat = (self.tmax-self.tmin)*self.click_tolerance/self.width()
2125 needupdate = False
2126 haveone = False
2127 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y)
2128 for marker in self.markers:
2129 if marker.kind not in self.visible_marker_kinds:
2130 continue
2132 state = abs(mouset-marker.tmin) < deltat or \
2133 abs(mouset-marker.tmax) < deltat and not haveone
2135 if state:
2136 xstate = False
2138 marker_nslc_ids = marker.get_nslc_ids()
2139 if not marker_nslc_ids:
2140 xstate = True
2142 for nslc in relevant_nslc_ids:
2143 if marker.match_nslc(nslc):
2144 xstate = True
2146 state = xstate
2148 if state:
2149 haveone = True
2150 oldstate = marker.is_alerted()
2151 if oldstate != state:
2152 needupdate = True
2153 marker.set_alerted(state)
2154 if state:
2155 self.message = marker.hoover_message()
2157 if not haveone:
2158 self.message = None
2160 if needupdate:
2161 self.update()
2163 def event(self, event):
2164 ''
2165 if event.type() == qc.QEvent.KeyPress:
2166 self.keyPressEvent(event)
2167 return True
2168 else:
2169 return base.event(self, event)
2171 def keyPressEvent(self, key_event):
2172 ''
2173 self.show_all = False
2174 dt = self.tmax - self.tmin
2175 tmid = (self.tmin + self.tmax) / 2.
2177 key = key_event.key()
2178 try:
2179 keytext = str(key_event.text())
2180 except UnicodeEncodeError:
2181 return
2183 if key == qc.Qt.Key_Space:
2184 self.interrupt_following()
2185 self.set_time_range(self.tmin+dt, self.tmax+dt)
2187 elif key == qc.Qt.Key_Up:
2188 for m in self.selected_markers():
2189 if isinstance(m, PhaseMarker):
2190 if key_event.modifiers() & qc.Qt.ShiftModifier:
2191 p = 0
2192 else:
2193 p = 1 if m.get_polarity() != 1 else None
2194 m.set_polarity(p)
2196 elif key == qc.Qt.Key_Down:
2197 for m in self.selected_markers():
2198 if isinstance(m, PhaseMarker):
2199 if key_event.modifiers() & qc.Qt.ShiftModifier:
2200 p = 0
2201 else:
2202 p = -1 if m.get_polarity() != -1 else None
2203 m.set_polarity(p)
2205 elif key == qc.Qt.Key_B:
2206 dt = self.tmax - self.tmin
2207 self.interrupt_following()
2208 self.set_time_range(self.tmin-dt, self.tmax-dt)
2210 elif key in (qc.Qt.Key_Tab, qc.Qt.Key_Backtab):
2211 self.interrupt_following()
2213 tgo = None
2215 class TraceDummy(object):
2216 def __init__(self, marker):
2217 self._marker = marker
2219 @property
2220 def nslc_id(self):
2221 return self._marker.one_nslc()
2223 def marker_to_itrack(marker):
2224 try:
2225 return self.key_to_row.get(
2226 self.gather(TraceDummy(marker)), -1)
2228 except MarkerOneNSLCRequired:
2229 return -1
2231 emarker, pmarkers = self.get_active_markers()
2232 pmarkers = [
2233 m for m in pmarkers if m.kind in self.visible_marker_kinds]
2234 pmarkers.sort(key=lambda m: (
2235 marker_to_itrack(m), (m.tmin + m.tmax) / 2.0))
2237 if key == qc.Qt.Key_Backtab:
2238 pmarkers.reverse()
2240 smarkers = self.selected_markers()
2241 iselected = []
2242 for sm in smarkers:
2243 try:
2244 iselected.append(pmarkers.index(sm))
2245 except ValueError:
2246 pass
2248 if iselected:
2249 icurrent = max(iselected) + 1
2250 else:
2251 icurrent = 0
2253 if icurrent < len(pmarkers):
2254 self.deselect_all()
2255 cmarker = pmarkers[icurrent]
2256 cmarker.selected = True
2257 tgo = cmarker.tmin
2258 if not self.tmin < tgo < self.tmax:
2259 self.set_time_range(tgo-dt/2., tgo+dt/2.)
2261 itrack = marker_to_itrack(cmarker)
2262 if itrack != -1:
2263 if itrack < self.shown_tracks_range[0]:
2264 self.scroll_tracks(
2265 - (self.shown_tracks_range[0] - itrack))
2266 elif self.shown_tracks_range[1] <= itrack:
2267 self.scroll_tracks(
2268 itrack - self.shown_tracks_range[1]+1)
2270 if itrack not in self.track_to_nslc_ids:
2271 self.go_to_selection()
2273 elif keytext in ('p', 'n', 'P', 'N'):
2274 smarkers = self.selected_markers()
2275 tgo = None
2276 dir = str(keytext)
2277 if smarkers:
2278 tmid = smarkers[0].tmin
2279 for smarker in smarkers:
2280 if dir == 'n':
2281 tmid = max(smarker.tmin, tmid)
2282 else:
2283 tmid = min(smarker.tmin, tmid)
2285 tgo = tmid
2287 if dir.lower() == 'n':
2288 for marker in sorted(
2289 self.markers,
2290 key=operator.attrgetter('tmin')):
2292 t = marker.tmin
2293 if t > tmid and \
2294 marker.kind in self.visible_marker_kinds and \
2295 (dir == 'n' or
2296 isinstance(marker, EventMarker)):
2298 self.deselect_all()
2299 marker.selected = True
2300 tgo = t
2301 break
2302 else:
2303 for marker in sorted(
2304 self.markers,
2305 key=operator.attrgetter('tmin'),
2306 reverse=True):
2308 t = marker.tmin
2309 if t < tmid and \
2310 marker.kind in self.visible_marker_kinds and \
2311 (dir == 'p' or
2312 isinstance(marker, EventMarker)):
2313 self.deselect_all()
2314 marker.selected = True
2315 tgo = t
2316 break
2318 if tgo is not None:
2319 self.interrupt_following()
2320 self.set_time_range(tgo-dt/2., tgo+dt/2.)
2322 elif keytext == 'r':
2323 if self.pile.reload_modified():
2324 self.reloaded = True
2326 elif keytext == 'R':
2327 self.setup_snufflings()
2329 elif key == qc.Qt.Key_Backspace:
2330 self.remove_selected_markers()
2332 elif keytext == 'a':
2333 for marker in self.markers:
2334 if ((self.tmin <= marker.tmin <= self.tmax or
2335 self.tmin <= marker.tmax <= self.tmax) and
2336 marker.kind in self.visible_marker_kinds):
2337 marker.selected = True
2338 else:
2339 marker.selected = False
2341 elif keytext == 'A':
2342 for marker in self.markers:
2343 if marker.kind in self.visible_marker_kinds:
2344 marker.selected = True
2346 elif keytext == 'd':
2347 self.deselect_all()
2349 elif keytext == 'E':
2350 self.deactivate_event_marker()
2352 elif keytext == 'e':
2353 markers = self.selected_markers()
2354 event_markers_in_spe = [
2355 marker for marker in markers
2356 if not isinstance(marker, PhaseMarker)]
2358 phase_markers = [
2359 marker for marker in markers
2360 if isinstance(marker, PhaseMarker)]
2362 if len(event_markers_in_spe) == 1:
2363 event_marker = event_markers_in_spe[0]
2364 if not isinstance(event_marker, EventMarker):
2365 nslcs = list(event_marker.nslc_ids)
2366 lat, lon = 0.0, 0.0
2367 old = self.get_active_event()
2368 if len(nslcs) == 1:
2369 lat, lon = self.station_latlon(NSLC(*nslcs[0]))
2370 elif old is not None:
2371 lat, lon = old.lat, old.lon
2373 event_marker.convert_to_event_marker(lat, lon)
2375 self.set_active_event_marker(event_marker)
2376 event = event_marker.get_event()
2377 for marker in phase_markers:
2378 marker.set_event(event)
2380 else:
2381 for marker in event_markers_in_spe:
2382 marker.convert_to_event_marker()
2384 elif keytext in ('0', '1', '2', '3', '4', '5', '6', '7'):
2385 for marker in self.selected_markers():
2386 marker.set_kind(int(keytext))
2387 self.emit_selected_markers()
2389 elif key in fkey_map:
2390 self.handle_fkeys(key)
2392 elif key == qc.Qt.Key_Escape:
2393 if self.picking:
2394 self.stop_picking(0, 0, abort=True)
2396 elif key == qc.Qt.Key_PageDown:
2397 self.scroll_tracks(
2398 self.shown_tracks_range[1]-self.shown_tracks_range[0])
2400 elif key == qc.Qt.Key_PageUp:
2401 self.scroll_tracks(
2402 self.shown_tracks_range[0]-self.shown_tracks_range[1])
2404 elif key == qc.Qt.Key_Plus:
2405 self.zoom_tracks(0., 1.)
2407 elif key == qc.Qt.Key_Minus:
2408 self.zoom_tracks(0., -1.)
2410 elif key == qc.Qt.Key_Equal:
2411 ntracks_shown = self.shown_tracks_range[1] - \
2412 self.shown_tracks_range[0]
2413 dtracks = self.initial_ntracks_shown_max - ntracks_shown
2414 self.zoom_tracks(0., dtracks)
2416 elif key == qc.Qt.Key_Colon:
2417 self.want_input.emit()
2419 elif keytext == 'f':
2420 self.toggle_fullscreen()
2422 elif keytext == 'g':
2423 self.go_to_selection()
2425 elif keytext == 'G':
2426 self.go_to_selection(tight=True)
2428 elif keytext == 'm':
2429 self.toggle_marker_editor()
2431 elif keytext == 'c':
2432 self.toggle_main_controls()
2434 elif key_event.key() in (qc.Qt.Key_Left, qc.Qt.Key_Right):
2435 dir = 1
2436 amount = 1
2437 if key_event.key() == qc.Qt.Key_Left:
2438 dir = -1
2439 if key_event.modifiers() & qc.Qt.ShiftModifier:
2440 amount = 10
2441 self.nudge_selected_markers(dir*amount)
2442 else:
2443 super().keyPressEvent(key_event)
2445 if keytext != '' and keytext in 'degaApPnN':
2446 self.emit_selected_markers()
2448 self.update()
2449 self.update_status()
2451 def handle_fkeys(self, key):
2452 self.set_phase_kind(
2453 self.selected_markers(),
2454 fkey_map[key] + 1)
2455 self.emit_selected_markers()
2457 def emit_selected_markers(self):
2458 ibounds = []
2459 last_selected = False
2460 for imarker, marker in enumerate(self.markers):
2461 this_selected = marker.is_selected()
2462 if this_selected != last_selected:
2463 ibounds.append(imarker)
2465 last_selected = this_selected
2467 if last_selected:
2468 ibounds.append(len(self.markers))
2470 chunks = list(zip(ibounds[::2], ibounds[1::2]))
2471 self.n_selected_markers = sum(
2472 chunk[1] - chunk[0] for chunk in chunks)
2473 self.marker_selection_changed.emit(chunks)
2475 def toggle_marker_editor(self):
2476 self.panel_parent.toggle_marker_editor()
2478 def toggle_main_controls(self):
2479 self.panel_parent.toggle_main_controls()
2481 def nudge_selected_markers(self, npixels):
2482 a, b = self.time_projection.ur
2483 c, d = self.time_projection.xr
2484 for marker in self.selected_markers():
2485 if not isinstance(marker, EventMarker):
2486 marker.tmin += npixels * (d-c)/b
2487 marker.tmax += npixels * (d-c)/b
2489 def toggle_fullscreen(self):
2490 if self.window().windowState() & qc.Qt.WindowFullScreen or \
2491 self.window().windowState() & qc.Qt.WindowMaximized:
2492 self.window().showNormal()
2493 else:
2494 if is_macos:
2495 self.window().showMaximized()
2496 else:
2497 self.window().showFullScreen()
2499 def about(self):
2500 fn = pyrocko.util.data_file('snuffler.png')
2501 with open(pyrocko.util.data_file('snuffler_about.html')) as f:
2502 txt = f.read()
2503 label = qw.QLabel(txt % {'logo': fn})
2504 label.setAlignment(qc.Qt.AlignVCenter | qc.Qt.AlignHCenter)
2505 self.show_doc('About', [label], target='tab')
2507 def help(self):
2508 class MyScrollArea(qw.QScrollArea):
2510 def sizeHint(self):
2511 s = qc.QSize()
2512 s.setWidth(self.widget().sizeHint().width())
2513 s.setHeight(self.widget().sizeHint().height())
2514 return s
2516 with open(pyrocko.util.data_file(
2517 'snuffler_help.html')) as f:
2518 hcheat = qw.QLabel(f.read())
2520 with open(pyrocko.util.data_file(
2521 'snuffler_help_epilog.html')) as f:
2522 hepilog = qw.QLabel(f.read())
2524 for h in [hcheat, hepilog]:
2525 h.setAlignment(qc.Qt.AlignTop | qc.Qt.AlignHCenter)
2526 h.setWordWrap(True)
2528 self.show_doc('Help', [hcheat, hepilog], target='panel')
2530 def show_doc(self, name, labels, target='panel'):
2531 scroller = qw.QScrollArea()
2532 frame = qw.QFrame(scroller)
2533 frame.setLineWidth(0)
2534 layout = qw.QVBoxLayout()
2535 layout.setContentsMargins(0, 0, 0, 0)
2536 layout.setSpacing(0)
2537 frame.setLayout(layout)
2538 scroller.setWidget(frame)
2539 scroller.setWidgetResizable(True)
2540 frame.setBackgroundRole(qg.QPalette.Base)
2541 for h in labels:
2542 h.setParent(frame)
2543 h.setMargin(3)
2544 h.setTextInteractionFlags(
2545 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse)
2546 h.setBackgroundRole(qg.QPalette.Base)
2547 layout.addWidget(h)
2548 h.linkActivated.connect(
2549 self.open_link)
2551 if self.panel_parent is not None:
2552 if target == 'panel':
2553 self.panel_parent.add_panel(
2554 name, scroller, True, volatile=False)
2555 else:
2556 self.panel_parent.add_tab(name, scroller)
2558 def open_link(self, link):
2559 qg.QDesktopServices.openUrl(qc.QUrl(link))
2561 def wheelEvent(self, wheel_event):
2562 ''
2563 self.wheel_pos += wheel_event.angleDelta().y()
2565 n = self.wheel_pos // 120
2566 self.wheel_pos = self.wheel_pos % 120
2567 if n == 0:
2568 return
2570 amount = max(
2571 1.,
2572 abs(self.shown_tracks_range[0]-self.shown_tracks_range[1])/5.)
2573 wdelta = amount * n
2575 trmin, trmax = self.track_to_screen.get_in_range()
2576 anchor = (self.track_to_screen.rev(wheel_event.y())-trmin) \
2577 / (trmax-trmin)
2579 if wheel_event.modifiers() & qc.Qt.ControlModifier:
2580 self.zoom_tracks(anchor, wdelta)
2581 else:
2582 self.scroll_tracks(-wdelta)
2584 def dragEnterEvent(self, event):
2585 ''
2586 if event.mimeData().hasUrls():
2587 if any(url.toLocalFile() for url in event.mimeData().urls()):
2588 event.setDropAction(qc.Qt.LinkAction)
2589 event.accept()
2591 def dropEvent(self, event):
2592 ''
2593 if event.mimeData().hasUrls():
2594 paths = list(
2595 str(url.toLocalFile()) for url in event.mimeData().urls())
2596 event.acceptProposedAction()
2597 self.load(paths)
2599 def get_phase_name(self, kind):
2600 return self.config.get_phase_name(kind)
2602 def set_phase_kind(self, markers, kind):
2603 phasename = self.get_phase_name(kind)
2605 for marker in markers:
2606 if isinstance(marker, PhaseMarker):
2607 if kind == 10:
2608 marker.convert_to_marker()
2609 else:
2610 marker.set_phasename(phasename)
2611 marker.set_event(self.get_active_event())
2613 elif isinstance(marker, EventMarker):
2614 pass
2616 else:
2617 if kind != 10:
2618 event = self.get_active_event()
2619 marker.convert_to_phase_marker(
2620 event, phasename, None, False)
2622 def set_ntracks(self, ntracks):
2623 if self.ntracks != ntracks:
2624 self.ntracks = ntracks
2625 if self.shown_tracks_range is not None:
2626 low, high = self.shown_tracks_range
2627 else:
2628 low, high = 0, self.ntracks
2630 self.tracks_range_changed.emit(self.ntracks, low, high)
2632 def set_tracks_range(self, range, start=None):
2634 low, high = range
2635 low = min(self.ntracks-1, low)
2636 high = min(self.ntracks, high)
2637 low = max(0, low)
2638 high = max(1, high)
2640 if start is None:
2641 start = float(low)
2643 if self.shown_tracks_range != (low, high):
2644 self.shown_tracks_range = low, high
2645 self.shown_tracks_start = start
2647 self.tracks_range_changed.emit(self.ntracks, low, high)
2649 def scroll_tracks(self, shift):
2650 shown = self.shown_tracks_range
2651 shiftmin = -shown[0]
2652 shiftmax = self.ntracks-shown[1]
2653 shift = max(shiftmin, shift)
2654 shift = min(shiftmax, shift)
2655 shown = shown[0] + shift, shown[1] + shift
2657 self.set_tracks_range((int(shown[0]), int(shown[1])))
2659 self.update()
2661 def zoom_tracks(self, anchor, delta):
2662 ntracks_shown = self.shown_tracks_range[1] \
2663 - self.shown_tracks_range[0]
2665 if (ntracks_shown == 1 and delta <= 0) or \
2666 (ntracks_shown == self.ntracks and delta >= 0):
2667 return
2669 ntracks_shown += int(round(delta))
2670 ntracks_shown = min(max(1, ntracks_shown), self.ntracks)
2672 u = self.shown_tracks_start
2673 nu = max(0., u-anchor*delta)
2674 nv = nu + ntracks_shown
2675 if nv > self.ntracks:
2676 nu -= nv - self.ntracks
2677 nv -= nv - self.ntracks
2679 self.set_tracks_range((int(round(nu)), int(round(nv))), nu)
2681 self.ntracks_shown_max = self.shown_tracks_range[1] \
2682 - self.shown_tracks_range[0]
2684 self.update()
2686 def content_time_range(self):
2687 pile = self.get_pile()
2688 tmin, tmax = pile.get_tmin(), pile.get_tmax()
2689 if tmin is None:
2690 tmin = initial_time_range[0]
2691 if tmax is None:
2692 tmax = initial_time_range[1]
2694 return tmin, tmax
2696 def content_deltat_range(self):
2697 pile = self.get_pile()
2699 deltatmin, deltatmax = pile.get_deltatmin(), pile.get_deltatmax()
2701 if deltatmin is None:
2702 deltatmin = 0.001
2704 if deltatmax is None:
2705 deltatmax = 1000.0
2707 return deltatmin, deltatmax
2709 def make_good_looking_time_range(self, tmin, tmax, tight=False):
2710 if tmax < tmin:
2711 tmin, tmax = tmax, tmin
2713 deltatmin = self.content_deltat_range()[0]
2714 dt = deltatmin * self.visible_length * 0.95
2716 if dt == 0.0:
2717 dt = 1.0
2719 if tight:
2720 if tmax != tmin:
2721 dtm = tmax - tmin
2722 tmin -= dtm*0.1
2723 tmax += dtm*0.1
2724 return tmin, tmax
2725 else:
2726 tcenter = (tmin + tmax) / 2.
2727 tmin = tcenter - 0.5*dt
2728 tmax = tcenter + 0.5*dt
2729 return tmin, tmax
2731 if tmax-tmin < dt:
2732 vmin, vmax = self.get_time_range()
2733 dt = min(vmax - vmin, dt)
2735 tcenter = (tmin+tmax)/2.
2736 etmin, etmax = tmin, tmax
2737 tmin = min(etmin, tcenter - 0.5*dt)
2738 tmax = max(etmax, tcenter + 0.5*dt)
2739 dtm = tmax-tmin
2740 if etmin == tmin:
2741 tmin -= dtm*0.1
2742 if etmax == tmax:
2743 tmax += dtm*0.1
2745 else:
2746 dtm = tmax-tmin
2747 tmin -= dtm*0.1
2748 tmax += dtm*0.1
2750 return tmin, tmax
2752 def go_to_selection(self, tight=False):
2753 markers = self.selected_markers()
2754 if markers:
2755 tmax, tmin = self.content_time_range()
2756 for marker in markers:
2757 tmin = min(tmin, marker.tmin)
2758 tmax = max(tmax, marker.tmax)
2760 else:
2761 if tight:
2762 vmin, vmax = self.get_time_range()
2763 tmin = tmax = (vmin + vmax) / 2.
2764 else:
2765 tmin, tmax = self.content_time_range()
2767 tmin, tmax = self.make_good_looking_time_range(
2768 tmin, tmax, tight=tight)
2770 self.interrupt_following()
2771 self.set_time_range(tmin, tmax)
2772 self.update()
2774 def go_to_time(self, t, tlen=None):
2775 tmax = t
2776 if tlen is not None:
2777 tmax = t+tlen
2778 tmin, tmax = self.make_good_looking_time_range(t, tmax)
2779 self.interrupt_following()
2780 self.set_time_range(tmin, tmax)
2781 self.update()
2783 def go_to_event_by_name(self, name):
2784 for marker in self.markers:
2785 if isinstance(marker, EventMarker):
2786 event = marker.get_event()
2787 if event.name and event.name.lower() == name.lower():
2788 tmin, tmax = self.make_good_looking_time_range(
2789 event.time, event.time)
2791 self.interrupt_following()
2792 self.set_time_range(tmin, tmax)
2794 def printit(self):
2795 from ..qt_compat import qprint
2796 printer = qprint.QPrinter()
2797 printer.setOrientation(qprint.QPrinter.Landscape)
2799 dialog = qprint.QPrintDialog(printer, self)
2800 dialog.setWindowTitle('Print')
2802 if dialog.exec_() != qw.QDialog.Accepted:
2803 return
2805 painter = qg.QPainter()
2806 painter.begin(printer)
2807 page = printer.pageRect()
2808 self.drawit(
2809 painter, printmode=False, w=page.width(), h=page.height())
2811 painter.end()
2813 def savesvg(self, fn=None):
2815 if not fn:
2816 fn, _ = qw.QFileDialog.getSaveFileName(
2817 self,
2818 'Save as SVG|PNG',
2819 os.path.expanduser(os.path.join('~', 'untitled.svg')),
2820 'SVG|PNG (*.svg *.png)',
2821 options=qfiledialog_options)
2823 if fn == '':
2824 return
2826 fn = str(fn)
2828 if fn.lower().endswith('.svg'):
2829 try:
2830 w, h = 842, 595
2831 margin = 0.025
2832 m = max(w, h)*margin
2834 generator = qsvg.QSvgGenerator()
2835 generator.setFileName(fn)
2836 generator.setSize(qc.QSize(w, h))
2837 generator.setViewBox(qc.QRectF(-m, -m, w+2*m, h+2*m))
2839 painter = qg.QPainter()
2840 painter.begin(generator)
2841 self.drawit(painter, printmode=False, w=w, h=h)
2842 painter.end()
2844 except Exception as e:
2845 self.fail('Failed to write SVG file: %s' % str(e))
2847 elif fn.lower().endswith('.png'):
2848 pixmap = self.grab()
2850 try:
2851 pixmap.save(fn)
2853 except Exception as e:
2854 self.fail('Failed to write PNG file: %s' % str(e))
2856 else:
2857 self.fail(
2858 'Unsupported file type: filename must end with ".svg" or '
2859 '".png".')
2861 def paintEvent(self, paint_ev):
2862 '''
2863 Called by QT whenever widget needs to be painted.
2864 '''
2865 # Prevent a problem on macos with QOpenGLWidget, where paintEvent
2866 # was called twice (by different threads?), causing segfaults.
2867 if self.in_paint_event:
2868 logger.warning('Blocking reentrant call to paintEvent().')
2869 return
2871 self.in_paint_event = True
2873 painter = qg.QPainter(self)
2875 if self.menuitem_antialias.isChecked():
2876 painter.setRenderHint(qg.QPainter.Antialiasing)
2878 self.drawit(painter)
2880 logger.debug(
2881 'Time spent drawing: '
2882 ' user:%.3f sys:%.3f children_user:%.3f'
2883 ' childred_sys:%.3f elapsed:%.3f' %
2884 (self.timer_draw - self.timer_cutout))
2886 logger.debug(
2887 'Time spent processing:'
2888 ' user:%.3f sys:%.3f children_user:%.3f'
2889 ' childred_sys:%.3f elapsed:%.3f' %
2890 self.timer_cutout.get())
2892 self.time_spent_painting = self.timer_draw.get()[-1]
2893 self.time_last_painted = time.time()
2894 self.in_paint_event = False
2896 def determine_box_styles(self):
2898 traces = list(self.pile.iter_traces())
2899 traces.sort(key=operator.attrgetter('full_id'))
2900 istyle = 0
2901 trace_styles = {}
2902 for itr, tr in enumerate(traces):
2903 if itr > 0:
2904 other = traces[itr-1]
2905 if not (
2906 other.nslc_id == tr.nslc_id
2907 and other.deltat == tr.deltat
2908 and abs(other.tmax - tr.tmin)
2909 < gap_lap_tolerance*tr.deltat):
2911 istyle += 1
2913 trace_styles[tr.full_id, tr.deltat] = istyle
2915 self.trace_styles = trace_styles
2917 def draw_trace_boxes(self, p, time_projection, track_projections):
2919 for v_projection in track_projections.values():
2920 v_projection.set_in_range(0., 1.)
2922 def selector(x):
2923 return x.overlaps(*time_projection.get_in_range())
2925 if self.trace_filter is not None:
2926 def tselector(x):
2927 return selector(x) and self.trace_filter(x)
2929 else:
2930 tselector = selector
2932 traces = list(self.pile.iter_traces(
2933 group_selector=selector, trace_selector=tselector))
2935 traces.sort(key=operator.attrgetter('full_id'))
2937 def drawbox(itrack, istyle, traces):
2938 v_projection = track_projections[itrack]
2939 dvmin = v_projection(0.)
2940 dvmax = v_projection(1.)
2941 dtmin = time_projection.clipped(traces[0].tmin, 0)
2942 dtmax = time_projection.clipped(traces[-1].tmax, 1)
2944 style = box_styles[istyle % len(box_styles)]
2945 rect = qc.QRectF(dtmin, dvmin, float(dtmax-dtmin), dvmax-dvmin)
2946 p.fillRect(rect, style.fill_brush)
2947 p.setPen(style.frame_pen)
2948 p.drawRect(rect)
2950 traces_by_style = {}
2951 for itr, tr in enumerate(traces):
2952 gt = self.gather(tr)
2953 if gt not in self.key_to_row:
2954 continue
2956 itrack = self.key_to_row[gt]
2957 if itrack not in track_projections:
2958 continue
2960 istyle = self.trace_styles.get((tr.full_id, tr.deltat), 0)
2962 if len(traces) < 500:
2963 drawbox(itrack, istyle, [tr])
2964 else:
2965 if (itrack, istyle) not in traces_by_style:
2966 traces_by_style[itrack, istyle] = []
2967 traces_by_style[itrack, istyle].append(tr)
2969 for (itrack, istyle), traces in traces_by_style.items():
2970 drawbox(itrack, istyle, traces)
2972 def draw_visible_markers(
2973 self, p, vcenter_projection, primary_pen):
2975 try:
2976 markers = self.markers.with_key_in_limited(
2977 self.tmin - self.markers_deltat_max, self.tmax, 2000)
2979 except pyrocko.pile.TooMany:
2980 tmin = self.markers[0].tmin
2981 tmax = self.markers[-1].tmax
2982 umin_view, umax_view = self.time_projection.get_out_range()
2983 umin = max(umin_view, self.time_projection(tmin))
2984 umax = min(umax_view, self.time_projection(tmax))
2985 v0, _ = vcenter_projection.get_out_range()
2986 label_bg = qg.QBrush(qg.QColor(255, 255, 255))
2988 p.save()
2990 pen = qg.QPen(primary_pen)
2991 pen.setWidth(2)
2992 pen.setStyle(qc.Qt.DotLine)
2993 # pat = [5., 3.]
2994 # pen.setDashPattern(pat)
2995 p.setPen(pen)
2997 if self.n_selected_markers == len(self.markers):
2998 s_selected = ' (all selected)'
2999 elif self.n_selected_markers > 0:
3000 s_selected = ' (%i selected)' % self.n_selected_markers
3001 else:
3002 s_selected = ''
3004 draw_label(
3005 p, umin+10., v0-10.,
3006 '%i Markers' % len(self.markers) + s_selected,
3007 label_bg, 'LB')
3009 line = qc.QLineF(umin, v0, umax, v0)
3010 p.drawLine(line)
3011 p.restore()
3013 return
3015 for marker in markers:
3016 if marker.tmin < self.tmax and self.tmin < marker.tmax \
3017 and marker.kind in self.visible_marker_kinds:
3019 marker.draw(
3020 p, self.time_projection, vcenter_projection,
3021 with_label=True)
3023 def get_squirrel(self):
3024 try:
3025 return self.pile._squirrel
3026 except AttributeError:
3027 return None
3029 def draw_coverage(self, p, time_projection, track_projections):
3030 sq = self.get_squirrel()
3031 if sq is None:
3032 return
3034 def drawbox(itrack, tmin, tmax, style):
3035 v_projection = track_projections[itrack]
3036 dvmin = v_projection(0.)
3037 dvmax = v_projection(1.)
3038 dtmin = time_projection.clipped(tmin, 0)
3039 dtmax = time_projection.clipped(tmax, 1)
3041 rect = qc.QRectF(dtmin, dvmin, float(dtmax-dtmin), dvmax-dvmin)
3042 p.fillRect(rect, style.fill_brush)
3043 p.setPen(style.frame_pen)
3044 p.drawRect(rect)
3046 pattern_list = []
3047 pattern_to_itrack = {}
3048 for key in self.track_keys:
3049 itrack = self.key_to_row[key]
3050 if itrack not in track_projections:
3051 continue
3053 pattern = self.track_patterns[itrack]
3054 pattern_to_itrack[tuple(pattern)] = itrack
3055 pattern_list.append(tuple(pattern))
3057 vmin, vmax = self.get_time_range()
3059 for kind in ['waveform', 'waveform_promise']:
3060 for coverage in sq.get_coverage(
3061 kind, vmin, vmax, pattern_list, limit=500):
3062 itrack = pattern_to_itrack[coverage.pattern.nslc]
3064 if coverage.changes is None:
3065 drawbox(
3066 itrack, coverage.tmin, coverage.tmax,
3067 box_styles_coverage[kind][0])
3068 else:
3069 t = None
3070 pcount = 0
3071 for tb, count in coverage.changes:
3072 if t is not None and tb > t:
3073 if pcount > 0:
3074 drawbox(
3075 itrack, t, tb,
3076 box_styles_coverage[kind][
3077 min(len(box_styles_coverage)-1,
3078 pcount)])
3080 t = tb
3081 pcount = count
3083 def drawit(self, p, printmode=False, w=None, h=None):
3084 '''
3085 This performs the actual drawing.
3086 '''
3088 self.timer_draw.start()
3089 show_boxes = self.menuitem_showboxes.isChecked()
3090 sq = self.get_squirrel()
3092 if self.gather is None:
3093 self.set_gathering()
3095 if self.pile_has_changed:
3097 if not self.sortingmode_change_delayed():
3098 self.sortingmode_change()
3100 if show_boxes and sq is None:
3101 self.determine_box_styles()
3103 self.pile_has_changed = False
3105 if h is None:
3106 h = float(self.height())
3107 if w is None:
3108 w = float(self.width())
3110 if printmode:
3111 primary_color = (0, 0, 0)
3112 else:
3113 primary_color = pyrocko.plot.tango_colors['aluminium5']
3115 primary_pen = qg.QPen(qg.QColor(*primary_color))
3117 ax_h = float(self.ax_height)
3119 vbottom_ax_projection = Projection()
3120 vtop_ax_projection = Projection()
3121 vcenter_projection = Projection()
3123 self.time_projection.set_out_range(0., w)
3124 vbottom_ax_projection.set_out_range(h-ax_h, h)
3125 vtop_ax_projection.set_out_range(0., ax_h)
3126 vcenter_projection.set_out_range(ax_h, h-ax_h)
3127 vcenter_projection.set_in_range(0., 1.)
3128 self.track_to_screen.set_out_range(ax_h, h-ax_h)
3130 self.track_to_screen.set_in_range(*self.shown_tracks_range)
3131 track_projections = {}
3132 for i in range(*self.shown_tracks_range):
3133 proj = Projection()
3134 proj.set_out_range(
3135 self.track_to_screen(i+0.05),
3136 self.track_to_screen(i+1.-0.05))
3138 track_projections[i] = proj
3140 if self.tmin > self.tmax:
3141 return
3143 self.time_projection.set_in_range(self.tmin, self.tmax)
3144 vbottom_ax_projection.set_in_range(0, ax_h)
3146 self.tax.drawit(p, self.time_projection, vbottom_ax_projection)
3148 yscaler = pyrocko.plot.AutoScaler()
3150 p.setPen(primary_pen)
3152 font = qg.QFont()
3153 font.setBold(True)
3155 axannotfont = qg.QFont()
3156 axannotfont.setBold(True)
3157 axannotfont.setPointSize(8)
3159 processed_traces = self.prepare_cutout2(
3160 self.tmin, self.tmax,
3161 trace_selector=self.trace_selector,
3162 degap=self.menuitem_degap.isChecked(),
3163 demean=self.menuitem_demean.isChecked())
3165 if not printmode and show_boxes:
3166 if (self.view_mode is ViewMode.Wiggle) \
3167 or (self.view_mode is ViewMode.Waterfall
3168 and not processed_traces):
3170 if sq is None:
3171 self.draw_trace_boxes(
3172 p, self.time_projection, track_projections)
3174 else:
3175 self.draw_coverage(
3176 p, self.time_projection, track_projections)
3178 p.setFont(font)
3179 label_bg = qg.QBrush(qg.QColor(255, 255, 255, 100))
3181 color_lookup = dict(
3182 [(k, i) for (i, k) in enumerate(self.color_keys)])
3184 self.track_to_nslc_ids = {}
3185 nticks = 0
3186 annot_labels = []
3188 if self.view_mode is ViewMode.Waterfall and processed_traces:
3189 waterfall = self.waterfall
3190 waterfall.set_time_range(self.tmin, self.tmax)
3191 waterfall.set_traces(processed_traces)
3192 waterfall.set_cmap(self.waterfall_cmap)
3193 waterfall.set_integrate(self.waterfall_integrate)
3194 waterfall.set_clip(
3195 self.waterfall_clip_min, self.waterfall_clip_max)
3196 waterfall.show_absolute_values(
3197 self.waterfall_show_absolute)
3199 rect = qc.QRectF(
3200 0, self.ax_height,
3201 self.width(), self.height() - self.ax_height*2
3202 )
3203 waterfall.draw_waterfall(p, rect=rect)
3205 elif self.view_mode is ViewMode.Wiggle and processed_traces:
3206 show_scales = self.menuitem_showscalerange.isChecked() \
3207 or self.menuitem_showscaleaxis.isChecked()
3209 fm = qg.QFontMetrics(axannotfont, p.device())
3210 trackheight = self.track_to_screen(1.-0.05) \
3211 - self.track_to_screen(0.05)
3213 nlinesavail = trackheight/float(fm.lineSpacing())
3215 nticks = max(3, min(nlinesavail * 0.5, 15)) \
3216 if self.menuitem_showscaleaxis.isChecked() \
3217 else 15
3219 yscaler = pyrocko.plot.AutoScaler(
3220 no_exp_interval=(-3, 2), approx_ticks=nticks,
3221 snap=show_scales
3222 and not self.menuitem_showscaleaxis.isChecked())
3224 data_ranges = pyrocko.trace.minmax(
3225 processed_traces,
3226 key=self.scaling_key,
3227 mode=self.scaling_base[0],
3228 outer_mode=self.scaling_base[1])
3230 if not self.menuitem_fixscalerange.isChecked():
3231 self.old_data_ranges = data_ranges
3232 else:
3233 data_ranges.update(self.old_data_ranges)
3235 self.apply_scaling_hooks(data_ranges)
3237 trace_to_itrack = {}
3238 track_scaling_keys = {}
3239 track_scaling_colors = {}
3240 for trace in processed_traces:
3241 gt = self.gather(trace)
3242 if gt not in self.key_to_row:
3243 continue
3245 itrack = self.key_to_row[gt]
3246 if itrack not in track_projections:
3247 continue
3249 trace_to_itrack[trace] = itrack
3251 if itrack not in self.track_to_nslc_ids:
3252 self.track_to_nslc_ids[itrack] = set()
3254 self.track_to_nslc_ids[itrack].add(trace.nslc_id)
3256 if itrack not in track_scaling_keys:
3257 track_scaling_keys[itrack] = set()
3259 scaling_key = self.scaling_key(trace)
3260 track_scaling_keys[itrack].add(scaling_key)
3262 color = pyrocko.plot.color(
3263 color_lookup[self.color_gather(trace)])
3265 k = itrack, scaling_key
3266 if k not in track_scaling_colors \
3267 and self.menuitem_colortraces.isChecked():
3268 track_scaling_colors[k] = color
3269 else:
3270 track_scaling_colors[k] = primary_color
3272 # y axes, zero lines
3273 trace_projections = {}
3274 for itrack in list(track_projections.keys()):
3275 if itrack not in track_scaling_keys:
3276 continue
3277 uoff = 0
3278 for scaling_key in track_scaling_keys[itrack]:
3279 data_range = data_ranges[scaling_key]
3280 dymin, dymax = data_range
3281 ymin, ymax, yinc = yscaler.make_scale(
3282 (dymin/self.gain, dymax/self.gain))
3283 iexp = yscaler.make_exp(yinc)
3284 factor = 10**iexp
3285 trace_projection = track_projections[itrack].copy()
3286 trace_projection.set_in_range(ymax, ymin)
3287 trace_projections[itrack, scaling_key] = \
3288 trace_projection
3289 umin, umax = self.time_projection.get_out_range()
3290 vmin, vmax = trace_projection.get_out_range()
3291 umax_zeroline = umax
3292 uoffnext = uoff
3294 if show_scales:
3295 pen = qg.QPen(primary_pen)
3296 k = itrack, scaling_key
3297 if k in track_scaling_colors:
3298 c = qg.QColor(*track_scaling_colors[
3299 itrack, scaling_key])
3301 pen.setColor(c)
3303 p.setPen(pen)
3304 if nlinesavail > 3:
3305 if self.menuitem_showscaleaxis.isChecked():
3306 ymin_annot = math.ceil(ymin/yinc)*yinc
3307 ny_annot = int(
3308 math.floor(ymax/yinc)
3309 - math.ceil(ymin/yinc)) + 1
3311 for iy_annot in range(ny_annot):
3312 y = ymin_annot + iy_annot*yinc
3313 v = trace_projection(y)
3314 line = qc.QLineF(
3315 umax-10-uoff, v, umax-uoff, v)
3317 p.drawLine(line)
3318 if iy_annot == ny_annot - 1 \
3319 and iexp != 0:
3320 sexp = ' × ' \
3321 '10<sup>%i</sup>' % iexp
3322 else:
3323 sexp = ''
3325 snum = num_to_html(y/factor)
3326 lab = Label(
3327 p,
3328 umax-20-uoff,
3329 v, '%s%s' % (snum, sexp),
3330 label_bg=None,
3331 anchor='MR',
3332 font=axannotfont,
3333 color=c)
3335 uoffnext = max(
3336 lab.rect.width()+30., uoffnext)
3338 annot_labels.append(lab)
3339 if y == 0.:
3340 umax_zeroline = \
3341 umax - 20 \
3342 - lab.rect.width() - 10 \
3343 - uoff
3344 else:
3345 if not show_boxes:
3346 qpoints = make_QPolygonF(
3347 [umax-20-uoff,
3348 umax-10-uoff,
3349 umax-10-uoff,
3350 umax-20-uoff],
3351 [vmax, vmax, vmin, vmin])
3352 p.drawPolyline(qpoints)
3354 snum = num_to_html(ymin)
3355 labmin = Label(
3356 p, umax-15-uoff, vmax, snum,
3357 label_bg=None,
3358 anchor='BR',
3359 font=axannotfont,
3360 color=c)
3362 annot_labels.append(labmin)
3363 snum = num_to_html(ymax)
3364 labmax = Label(
3365 p, umax-15-uoff, vmin, snum,
3366 label_bg=None,
3367 anchor='TR',
3368 font=axannotfont,
3369 color=c)
3371 annot_labels.append(labmax)
3373 for lab in (labmin, labmax):
3374 uoffnext = max(
3375 lab.rect.width()+10., uoffnext)
3377 if self.menuitem_showzeroline.isChecked():
3378 v = trace_projection(0.)
3379 if vmin <= v <= vmax:
3380 line = qc.QLineF(umin, v, umax_zeroline, v)
3381 p.drawLine(line)
3383 uoff = uoffnext
3385 p.setFont(font)
3386 p.setPen(primary_pen)
3387 for trace in processed_traces:
3388 if self.view_mode is not ViewMode.Wiggle:
3389 break
3391 if trace not in trace_to_itrack:
3392 continue
3394 itrack = trace_to_itrack[trace]
3395 scaling_key = self.scaling_key(trace)
3396 trace_projection = trace_projections[
3397 itrack, scaling_key]
3399 vdata = trace_projection(trace.get_ydata())
3401 udata_min = float(self.time_projection(trace.tmin))
3402 udata_max = float(self.time_projection(
3403 trace.tmin+trace.deltat*(vdata.size-1)))
3404 udata = num.linspace(udata_min, udata_max, vdata.size)
3406 qpoints = make_QPolygonF(udata, vdata)
3408 umin, umax = self.time_projection.get_out_range()
3409 vmin, vmax = trace_projection.get_out_range()
3411 trackrect = qc.QRectF(umin, vmin, umax-umin, vmax-vmin)
3413 if self.menuitem_cliptraces.isChecked():
3414 p.setClipRect(trackrect)
3416 if self.menuitem_colortraces.isChecked():
3417 color = pyrocko.plot.color(
3418 color_lookup[self.color_gather(trace)])
3419 pen = qg.QPen(qg.QColor(*color), 1)
3420 p.setPen(pen)
3422 p.drawPolyline(qpoints)
3424 if self.floating_marker:
3425 self.floating_marker.draw_trace(
3426 self, p, trace,
3427 self.time_projection, trace_projection, 1.0)
3429 for marker in self.markers.with_key_in(
3430 self.tmin - self.markers_deltat_max,
3431 self.tmax):
3433 if marker.tmin < self.tmax \
3434 and self.tmin < marker.tmax \
3435 and marker.kind \
3436 in self.visible_marker_kinds:
3437 marker.draw_trace(
3438 self, p, trace, self.time_projection,
3439 trace_projection, 1.0)
3441 p.setPen(primary_pen)
3443 if self.menuitem_cliptraces.isChecked():
3444 p.setClipRect(0, 0, int(w), int(h))
3446 if self.floating_marker:
3447 self.floating_marker.draw(
3448 p, self.time_projection, vcenter_projection)
3450 self.draw_visible_markers(
3451 p, vcenter_projection, primary_pen)
3453 p.setPen(primary_pen)
3454 while font.pointSize() > 2:
3455 fm = qg.QFontMetrics(font, p.device())
3456 trackheight = self.track_to_screen(1.-0.05) \
3457 - self.track_to_screen(0.05)
3458 nlinesavail = trackheight/float(fm.lineSpacing())
3459 if nlinesavail > 1:
3460 break
3462 font.setPointSize(font.pointSize()-1)
3464 p.setFont(font)
3465 mouse_pos = self.mapFromGlobal(qg.QCursor.pos())
3467 for key in self.track_keys:
3468 itrack = self.key_to_row[key]
3469 if itrack in track_projections:
3470 plabel = ' '.join(
3471 [str(x) for x in key if x is not None])
3472 lx = 10
3473 ly = self.track_to_screen(itrack+0.5)
3475 if p.font().pointSize() >= MIN_LABEL_SIZE_PT:
3476 draw_label(p, lx, ly, plabel, label_bg, 'ML')
3477 continue
3479 contains_cursor = \
3480 self.track_to_screen(itrack) \
3481 < mouse_pos.y() \
3482 < self.track_to_screen(itrack+1)
3484 if not contains_cursor:
3485 continue
3487 font_large = p.font()
3488 font_large.setPointSize(MIN_LABEL_SIZE_PT)
3489 p.setFont(font_large)
3490 draw_label(p, lx, ly, plabel, label_bg, 'ML')
3491 p.setFont(font)
3493 for lab in annot_labels:
3494 lab.draw()
3496 self.timer_draw.stop()
3498 def see_data_params(self):
3500 min_deltat = self.content_deltat_range()[0]
3502 # determine padding and downampling requirements
3503 if self.lowpass is not None:
3504 deltat_target = 1./self.lowpass * 0.25
3505 ndecimate = min(
3506 50,
3507 max(1, int(round(deltat_target / min_deltat))))
3508 tpad = 1./self.lowpass * 2.
3509 else:
3510 ndecimate = 1
3511 tpad = min_deltat*5.
3513 if self.highpass is not None:
3514 tpad = max(1./self.highpass * 2., tpad)
3516 nsee_points_per_trace = 5000*10
3517 tsee = ndecimate*nsee_points_per_trace*min_deltat
3519 return ndecimate, tpad, tsee
3521 def clean_update(self):
3522 self.cached_processed_traces = None
3523 self.update()
3525 def get_adequate_tpad(self):
3526 tpad = 0.
3527 for f in [self.highpass, self.lowpass]:
3528 if f is not None:
3529 tpad = max(tpad, 1.0/f)
3531 for snuffling in self.snufflings:
3532 if snuffling._post_process_hook_enabled \
3533 or snuffling._pre_process_hook_enabled:
3535 tpad = max(tpad, snuffling.get_tpad())
3537 return tpad
3539 def prepare_cutout2(
3540 self, tmin, tmax, trace_selector=None, degap=True,
3541 demean=True, nmax=6000):
3543 if self.pile.is_empty():
3544 return []
3546 nmax = self.visible_length
3548 self.timer_cutout.start()
3550 tsee = tmax-tmin
3551 min_deltat_wo_decimate = tsee/nmax
3552 min_deltat_w_decimate = min_deltat_wo_decimate / 32.
3554 min_deltat_allow = min_deltat_wo_decimate
3555 if self.lowpass is not None:
3556 target_deltat_lp = 0.25/self.lowpass
3557 if target_deltat_lp > min_deltat_wo_decimate:
3558 min_deltat_allow = min_deltat_w_decimate
3560 min_deltat_allow = math.exp(
3561 int(math.floor(math.log(min_deltat_allow))))
3563 tmin_ = tmin
3564 tmax_ = tmax
3566 # fetch more than needed?
3567 if self.menuitem_liberal_fetch.isChecked():
3568 tlen = pyrocko.trace.nextpow2((tmax-tmin)*1.5)
3569 tmin = math.floor(tmin/tlen) * tlen
3570 tmax = math.ceil(tmax/tlen) * tlen
3572 fft_filtering = self.menuitem_fft_filtering.isChecked()
3573 lphp = self.menuitem_lphp.isChecked()
3574 ads = self.menuitem_allowdownsampling.isChecked()
3576 tpad = self.get_adequate_tpad()
3577 tpad = max(tpad, tsee)
3579 # state vector to decide if cached traces can be used
3580 vec = (
3581 tmin, tmax, tpad, trace_selector, degap, demean, self.lowpass,
3582 self.highpass, fft_filtering, lphp,
3583 min_deltat_allow, self.rotate, self.shown_tracks_range,
3584 ads, self.pile.get_update_count())
3586 if (self.cached_vec
3587 and self.cached_vec[0] <= vec[0]
3588 and vec[1] <= self.cached_vec[1]
3589 and vec[2:] == self.cached_vec[2:]
3590 and not (self.reloaded or self.menuitem_watch.isChecked())
3591 and self.cached_processed_traces is not None):
3593 logger.debug('Using cached traces')
3594 processed_traces = self.cached_processed_traces
3596 else:
3597 processed_traces = []
3598 if self.pile.deltatmax >= min_deltat_allow:
3600 if isinstance(self.pile, pyrocko.pile.Pile):
3601 def group_selector(gr):
3602 return gr.deltatmax >= min_deltat_allow
3604 kwargs = dict(group_selector=group_selector)
3605 else:
3606 kwargs = {}
3608 if trace_selector is not None:
3609 def trace_selectorx(tr):
3610 return tr.deltat >= min_deltat_allow \
3611 and trace_selector(tr)
3612 else:
3613 def trace_selectorx(tr):
3614 return tr.deltat >= min_deltat_allow
3616 for traces in self.pile.chopper(
3617 tmin=tmin, tmax=tmax, tpad=tpad,
3618 want_incomplete=True,
3619 degap=degap,
3620 maxgap=gap_lap_tolerance,
3621 maxlap=gap_lap_tolerance,
3622 keep_current_files_open=True,
3623 trace_selector=trace_selectorx,
3624 accessor_id=id(self),
3625 snap=(math.floor, math.ceil),
3626 include_last=True, **kwargs):
3628 if demean:
3629 for tr in traces:
3630 if (tr.meta and tr.meta.get('tabu', False)):
3631 continue
3632 y = tr.get_ydata()
3633 tr.set_ydata(y - num.mean(y))
3635 traces = self.pre_process_hooks(traces)
3637 for trace in traces:
3639 if not (trace.meta
3640 and trace.meta.get('tabu', False)):
3642 if fft_filtering:
3643 but = pyrocko.response.ButterworthResponse
3644 multres = pyrocko.response.MultiplyResponse
3645 if self.lowpass is not None \
3646 or self.highpass is not None:
3648 it = num.arange(
3649 trace.data_len(), dtype=float)
3650 detr_data, m, b = detrend(
3651 it, trace.get_ydata())
3653 trace.set_ydata(detr_data)
3655 freqs, fdata = trace.spectrum(
3656 pad_to_pow2=True, tfade=None)
3658 nfreqs = fdata.size
3660 key = (trace.deltat, nfreqs)
3662 if key not in self.tf_cache:
3663 resps = []
3664 if self.lowpass is not None:
3665 resps.append(but(
3666 order=4,
3667 corner=self.lowpass,
3668 type='low'))
3670 if self.highpass is not None:
3671 resps.append(but(
3672 order=4,
3673 corner=self.highpass,
3674 type='high'))
3676 resp = multres(resps)
3677 self.tf_cache[key] = \
3678 resp.evaluate(freqs)
3680 filtered_data = num.fft.irfft(
3681 fdata*self.tf_cache[key]
3682 )[:trace.data_len()]
3684 retrended_data = retrend(
3685 it, filtered_data, m, b)
3687 trace.set_ydata(retrended_data)
3689 else:
3691 if ads and self.lowpass is not None:
3692 while trace.deltat \
3693 < min_deltat_wo_decimate:
3695 trace.downsample(2, demean=False)
3697 fmax = 0.5/trace.deltat
3698 if not lphp and (
3699 self.lowpass is not None
3700 and self.highpass is not None
3701 and self.lowpass < fmax
3702 and self.highpass < fmax
3703 and self.highpass < self.lowpass):
3705 trace.bandpass(
3706 2, self.highpass, self.lowpass)
3707 else:
3708 if self.lowpass is not None:
3709 if self.lowpass < 0.5/trace.deltat:
3710 trace.lowpass(
3711 4, self.lowpass,
3712 demean=False)
3714 if self.highpass is not None:
3715 if self.lowpass is None \
3716 or self.highpass \
3717 < self.lowpass:
3719 if self.highpass < \
3720 0.5/trace.deltat:
3721 trace.highpass(
3722 4, self.highpass,
3723 demean=False)
3725 processed_traces.append(trace)
3727 if self.rotate != 0.0:
3728 phi = self.rotate/180.*math.pi
3729 cphi = math.cos(phi)
3730 sphi = math.sin(phi)
3731 for a in processed_traces:
3732 for b in processed_traces:
3733 if (a.network == b.network
3734 and a.station == b.station
3735 and a.location == b.location
3736 and ((a.channel.lower().endswith('n')
3737 and b.channel.lower().endswith('e'))
3738 or (a.channel.endswith('1')
3739 and b.channel.endswith('2')))
3740 and abs(a.deltat-b.deltat) < a.deltat*0.001
3741 and abs(a.tmin-b.tmin) < a.deltat*0.01 and
3742 len(a.get_ydata()) == len(b.get_ydata())):
3744 aydata = a.get_ydata()*cphi+b.get_ydata()*sphi
3745 bydata = -a.get_ydata()*sphi+b.get_ydata()*cphi
3746 a.set_ydata(aydata)
3747 b.set_ydata(bydata)
3749 processed_traces = self.post_process_hooks(processed_traces)
3751 self.cached_processed_traces = processed_traces
3752 self.cached_vec = vec
3754 chopped_traces = []
3755 for trace in processed_traces:
3756 chop_tmin = tmin_ - trace.deltat*4
3757 chop_tmax = tmax_ + trace.deltat*4
3759 try:
3760 ctrace = trace.chop(
3761 chop_tmin, chop_tmax,
3762 inplace=False)
3764 except pyrocko.trace.NoData:
3765 continue
3767 if ctrace.data_len() < 2:
3768 continue
3770 chopped_traces.append(ctrace)
3772 self.timer_cutout.stop()
3773 return chopped_traces
3775 def pre_process_hooks(self, traces):
3776 for snuffling in self.snufflings:
3777 if snuffling._pre_process_hook_enabled:
3778 traces = snuffling.pre_process_hook(traces)
3780 return traces
3782 def post_process_hooks(self, traces):
3783 for snuffling in self.snufflings:
3784 if snuffling._post_process_hook_enabled:
3785 traces = snuffling.post_process_hook(traces)
3787 return traces
3789 def visible_length_change(self, ignore=None):
3790 for menuitem, vlen in self.menuitems_visible_length:
3791 if menuitem.isChecked():
3792 self.visible_length = vlen
3794 def scaling_base_change(self, ignore=None):
3795 for menuitem, scaling_base in self.menuitems_scaling_base:
3796 if menuitem.isChecked():
3797 self.scaling_base = scaling_base
3799 def scalingmode_change(self, ignore=None):
3800 for menuitem, scaling_key in self.menuitems_scaling:
3801 if menuitem.isChecked():
3802 self.scaling_key = scaling_key
3803 self.update()
3805 def apply_scaling_hooks(self, data_ranges):
3806 for k in sorted(self.scaling_hooks.keys()):
3807 hook = self.scaling_hooks[k]
3808 hook(data_ranges)
3810 def viewmode_change(self, ignore=True):
3811 for item, mode in self.menuitems_viewmode:
3812 if item.isChecked():
3813 self.view_mode = mode
3814 break
3815 else:
3816 raise AttributeError('unknown view mode')
3818 items_waterfall_disabled = (
3819 self.menuitem_showscaleaxis,
3820 self.menuitem_showscalerange,
3821 self.menuitem_showzeroline,
3822 self.menuitem_colortraces,
3823 self.menuitem_cliptraces,
3824 *(itm[0] for itm in self.menuitems_visible_length)
3825 )
3827 if self.view_mode is ViewMode.Waterfall:
3828 self.parent().show_colorbar_ctrl(True)
3829 self.parent().show_gain_ctrl(False)
3831 for item in items_waterfall_disabled:
3832 item.setDisabled(True)
3834 self.visible_length = 180.
3835 else:
3836 self.parent().show_colorbar_ctrl(False)
3837 self.parent().show_gain_ctrl(True)
3839 for item in items_waterfall_disabled:
3840 item.setDisabled(False)
3842 self.visible_length_change()
3843 self.update()
3845 def set_scaling_hook(self, k, hook):
3846 self.scaling_hooks[k] = hook
3848 def remove_scaling_hook(self, k):
3849 del self.scaling_hooks[k]
3851 def remove_scaling_hooks(self):
3852 self.scaling_hooks = {}
3854 def s_sortingmode_change(self, ignore=None):
3855 for menuitem, valfunc in self.menuitems_ssorting:
3856 if menuitem.isChecked():
3857 self._ssort = valfunc
3859 self.sortingmode_change()
3861 def sortingmode_change(self, ignore=None):
3862 for menuitem, (gather, color) in self.menuitems_sorting:
3863 if menuitem.isChecked():
3864 self.set_gathering(gather, color)
3866 self.sortingmode_change_time = time.time()
3868 def lowpass_change(self, value, ignore=None):
3869 self.lowpass = value
3870 self.passband_check()
3871 self.tf_cache = {}
3872 self.update()
3874 def highpass_change(self, value, ignore=None):
3875 self.highpass = value
3876 self.passband_check()
3877 self.tf_cache = {}
3878 self.update()
3880 def passband_check(self):
3881 if self.highpass and self.lowpass \
3882 and self.highpass >= self.lowpass:
3884 self.message = 'Corner frequency of highpass larger than ' \
3885 'corner frequency of lowpass! I will now ' \
3886 'deactivate the highpass.'
3888 self.update_status()
3889 else:
3890 oldmess = self.message
3891 self.message = None
3892 if oldmess is not None:
3893 self.update_status()
3895 def gain_change(self, value, ignore):
3896 self.gain = value
3897 self.update()
3899 def rot_change(self, value, ignore):
3900 self.rotate = value
3901 self.update()
3903 def waterfall_cmap_change(self, cmap):
3904 self.waterfall_cmap = cmap
3905 self.update()
3907 def waterfall_clip_change(self, clip_min, clip_max):
3908 self.waterfall_clip_min = clip_min
3909 self.waterfall_clip_max = clip_max
3910 self.update()
3912 def waterfall_show_absolute_change(self, toggle):
3913 self.waterfall_show_absolute = toggle
3914 self.update()
3916 def waterfall_set_integrate(self, toggle):
3917 self.waterfall_integrate = toggle
3918 self.update()
3920 def set_selected_markers(self, markers):
3921 '''
3922 Set a list of markers selected
3924 :param markers: list of markers
3925 '''
3926 self.deselect_all()
3927 for m in markers:
3928 m.selected = True
3930 self.update()
3932 def deselect_all(self):
3933 for marker in self.markers:
3934 marker.selected = False
3936 def animate_picking(self):
3937 point = self.mapFromGlobal(qg.QCursor.pos())
3938 self.update_picking(point.x(), point.y(), doshift=True)
3940 def get_nslc_ids_for_track(self, ftrack):
3941 itrack = int(ftrack)
3942 return self.track_to_nslc_ids.get(itrack, [])
3944 def stop_picking(self, x, y, abort=False):
3945 if self.picking:
3946 self.update_picking(x, y, doshift=False)
3947 self.picking = None
3948 self.picking_down = None
3949 self.picking_timer.stop()
3950 self.picking_timer = None
3951 if not abort:
3952 self.add_marker(self.floating_marker)
3953 self.floating_marker.selected = True
3954 self.emit_selected_markers()
3956 self.floating_marker = None
3958 def start_picking(self, ignore):
3960 if not self.picking:
3961 self.deselect_all()
3962 self.picking = qw.QRubberBand(qw.QRubberBand.Rectangle)
3963 point = self.mapFromGlobal(qg.QCursor.pos())
3965 gpoint = self.mapToGlobal(qc.QPoint(point.x(), 0))
3966 self.picking.setGeometry(
3967 gpoint.x(), gpoint.y(), 1, self.height())
3968 t = self.time_projection.rev(point.x())
3970 ftrack = self.track_to_screen.rev(point.y())
3971 nslc_ids = self.get_nslc_ids_for_track(ftrack)
3972 self.floating_marker = Marker(nslc_ids, t, t)
3973 self.floating_marker.selected = True
3975 self.picking_timer = qc.QTimer()
3976 self.picking_timer.timeout.connect(
3977 self.animate_picking)
3979 self.picking_timer.setInterval(50)
3980 self.picking_timer.start()
3982 def update_picking(self, x, y, doshift=False):
3983 if self.picking:
3984 mouset = self.time_projection.rev(x)
3985 dt = 0.0
3986 if mouset < self.tmin or mouset > self.tmax:
3987 if mouset < self.tmin:
3988 dt = -(self.tmin - mouset)
3989 else:
3990 dt = mouset - self.tmax
3991 ddt = self.tmax-self.tmin
3992 dt = max(dt, -ddt/10.)
3993 dt = min(dt, ddt/10.)
3995 x0 = x
3996 if self.picking_down is not None:
3997 x0 = self.time_projection(self.picking_down[0])
3999 w = abs(x-x0)
4000 x0 = min(x0, x)
4002 tmin, tmax = (
4003 self.time_projection.rev(x0),
4004 self.time_projection.rev(x0+w))
4006 tmin, tmax = (
4007 max(working_system_time_range[0], tmin),
4008 min(working_system_time_range[1], tmax))
4010 p1 = self.mapToGlobal(qc.QPoint(int(round(x0)), 0))
4012 self.picking.setGeometry(
4013 p1.x(), p1.y(), int(round(max(w, 1))), self.height())
4015 ftrack = self.track_to_screen.rev(y)
4016 nslc_ids = self.get_nslc_ids_for_track(ftrack)
4017 self.floating_marker.set(nslc_ids, tmin, tmax)
4019 if dt != 0.0 and doshift:
4020 self.interrupt_following()
4021 self.set_time_range(self.tmin+dt, self.tmax+dt)
4023 self.update()
4025 def update_status(self):
4027 if self.message is None:
4028 point = self.mapFromGlobal(qg.QCursor.pos())
4030 mouse_t = self.time_projection.rev(point.x())
4031 if not is_working_time(mouse_t):
4032 return
4034 if self.floating_marker:
4035 tmi, tma = (
4036 self.floating_marker.tmin,
4037 self.floating_marker.tmax)
4039 tt, ms = gmtime_x(tmi)
4041 if tmi == tma:
4042 message = mystrftime(
4043 fmt='Pick: %Y-%m-%d %H:%M:%S .%r',
4044 tt=tt, milliseconds=ms)
4045 else:
4046 srange = '%g s' % (tma-tmi)
4047 message = mystrftime(
4048 fmt='Start: %Y-%m-%d %H:%M:%S .%r Length: '+srange,
4049 tt=tt, milliseconds=ms)
4050 else:
4051 tt, ms = gmtime_x(mouse_t)
4053 message = mystrftime(fmt=None, tt=tt, milliseconds=ms)
4054 else:
4055 message = self.message
4057 sb = self.window().statusBar()
4058 sb.clearMessage()
4059 sb.showMessage(message)
4061 def set_sortingmode_change_delay_time(self, dt):
4062 self.sortingmode_change_delay_time = dt
4064 def sortingmode_change_delayed(self):
4065 now = time.time()
4066 return (
4067 self.sortingmode_change_delay_time is not None
4068 and now - self.sortingmode_change_time
4069 < self.sortingmode_change_delay_time)
4071 def set_visible_marker_kinds(self, kinds):
4072 self.deselect_all()
4073 self.visible_marker_kinds = tuple(kinds)
4074 self.emit_selected_markers()
4076 def following(self):
4077 return self.follow_timer is not None \
4078 and not self.following_interrupted()
4080 def interrupt_following(self):
4081 self.interactive_range_change_time = time.time()
4083 def following_interrupted(self, now=None):
4084 if now is None:
4085 now = time.time()
4086 return now - self.interactive_range_change_time \
4087 < self.interactive_range_change_delay_time
4089 def follow(self, tlen, interval=50, lapse=None, tmax_start=None):
4090 if tmax_start is None:
4091 tmax_start = time.time()
4092 self.show_all = False
4093 self.follow_time = tlen
4094 self.follow_timer = qc.QTimer(self)
4095 self.follow_timer.timeout.connect(
4096 self.follow_update)
4097 self.follow_timer.setInterval(interval)
4098 self.follow_timer.start()
4099 self.follow_started = time.time()
4100 self.follow_lapse = lapse
4101 self.follow_tshift = self.follow_started - tmax_start
4102 self.interactive_range_change_time = 0.0
4104 def unfollow(self):
4105 if self.follow_timer is not None:
4106 self.follow_timer.stop()
4107 self.follow_timer = None
4108 self.interactive_range_change_time = 0.0
4110 def follow_update(self):
4111 rnow = time.time()
4112 if self.follow_lapse is None:
4113 now = rnow
4114 else:
4115 now = self.follow_started + (rnow - self.follow_started) \
4116 * self.follow_lapse
4118 if self.following_interrupted(rnow):
4119 return
4120 self.set_time_range(
4121 now-self.follow_time-self.follow_tshift,
4122 now-self.follow_tshift)
4124 self.update()
4126 def myclose(self, return_tag=''):
4127 self.return_tag = return_tag
4128 self.window().close()
4130 def cleanup(self):
4131 self.about_to_close.emit()
4132 self.timer.stop()
4133 if self.follow_timer is not None:
4134 self.follow_timer.stop()
4136 for snuffling in list(self.snufflings):
4137 self.remove_snuffling(snuffling)
4139 def set_error_message(self, key, value):
4140 if value is None:
4141 if key in self.error_messages:
4142 del self.error_messages[key]
4143 else:
4144 self.error_messages[key] = value
4146 def inputline_changed(self, text):
4147 pass
4149 def inputline_finished(self, text):
4150 line = str(text)
4152 toks = line.split()
4153 clearit, hideit, error = False, True, None
4154 if len(toks) >= 1:
4155 command = toks[0].lower()
4157 try:
4158 quick_filter_commands = {
4159 'n': '%s.*.*.*',
4160 's': '*.%s.*.*',
4161 'l': '*.*.%s.*',
4162 'c': '*.*.*.%s'}
4164 if command in quick_filter_commands:
4165 if len(toks) >= 2:
4166 patterns = [
4167 quick_filter_commands[toks[0]] % pat
4168 for pat in toks[1:]]
4169 self.set_quick_filter_patterns(patterns, line)
4170 else:
4171 self.set_quick_filter_patterns(None)
4173 self.update()
4175 elif command in ('hide', 'unhide'):
4176 if len(toks) >= 2:
4177 patterns = []
4178 if len(toks) == 2:
4179 patterns = [toks[1]]
4180 elif len(toks) >= 3:
4181 x = {
4182 'n': '%s.*.*.*',
4183 's': '*.%s.*.*',
4184 'l': '*.*.%s.*',
4185 'c': '*.*.*.%s'}
4187 if toks[1] in x:
4188 patterns.extend(
4189 x[toks[1]] % tok for tok in toks[2:])
4191 for pattern in patterns:
4192 if command == 'hide':
4193 self.add_blacklist_pattern(pattern)
4194 else:
4195 self.remove_blacklist_pattern(pattern)
4197 elif command == 'unhide' and len(toks) == 1:
4198 self.clear_blacklist()
4200 clearit = True
4202 self.update()
4204 elif command == 'markers':
4205 if len(toks) == 2:
4206 if toks[1] == 'all':
4207 kinds = self.all_marker_kinds
4208 else:
4209 kinds = []
4210 for x in toks[1]:
4211 try:
4212 kinds.append(int(x))
4213 except Exception:
4214 pass
4216 self.set_visible_marker_kinds(kinds)
4218 elif len(toks) == 1:
4219 self.set_visible_marker_kinds(())
4221 self.update()
4223 elif command == 'scaling':
4224 if len(toks) == 2:
4225 hideit = False
4226 error = 'wrong number of arguments'
4228 if len(toks) >= 3:
4229 vmin, vmax = [
4230 pyrocko.model.float_or_none(x)
4231 for x in toks[-2:]]
4233 def upd(d, k, vmin, vmax):
4234 if k in d:
4235 if vmin is not None:
4236 d[k] = vmin, d[k][1]
4237 if vmax is not None:
4238 d[k] = d[k][0], vmax
4240 if len(toks) == 1:
4241 self.remove_scaling_hooks()
4243 elif len(toks) == 3:
4244 def hook(data_ranges):
4245 for k in data_ranges:
4246 upd(data_ranges, k, vmin, vmax)
4248 self.set_scaling_hook('_', hook)
4250 elif len(toks) == 4:
4251 pattern = toks[1]
4253 def hook(data_ranges):
4254 for k in pyrocko.util.match_nslcs(
4255 pattern, list(data_ranges.keys())):
4257 upd(data_ranges, k, vmin, vmax)
4259 self.set_scaling_hook(pattern, hook)
4261 elif command == 'goto':
4262 toks2 = line.split(None, 1)
4263 if len(toks2) == 2:
4264 arg = toks2[1]
4265 m = re.match(
4266 r'^\d\d\d\d(-\d\d(-\d\d( \d\d(:\d\d'
4267 r'(:\d\d(\.\d+)?)?)?)?)?)?$', arg)
4268 if m:
4269 tlen = None
4270 if not m.group(1):
4271 tlen = 12*32*24*60*60
4272 elif not m.group(2):
4273 tlen = 32*24*60*60
4274 elif not m.group(3):
4275 tlen = 24*60*60
4276 elif not m.group(4):
4277 tlen = 60*60
4278 elif not m.group(5):
4279 tlen = 60
4281 supl = '1970-01-01 00:00:00'
4282 if len(supl) > len(arg):
4283 arg = arg + supl[-(len(supl)-len(arg)):]
4284 t = pyrocko.util.str_to_time(arg)
4285 self.go_to_time(t, tlen=tlen)
4287 elif re.match(r'^\d\d:\d\d(:\d\d(\.\d+)?)?$', arg):
4288 supl = '00:00:00'
4289 if len(supl) > len(arg):
4290 arg = arg + supl[-(len(supl)-len(arg)):]
4291 tmin, tmax = self.get_time_range()
4292 sdate = pyrocko.util.time_to_str(
4293 tmin/2.+tmax/2., format='%Y-%m-%d')
4294 t = pyrocko.util.str_to_time(sdate + ' ' + arg)
4295 self.go_to_time(t)
4297 elif arg == 'today':
4298 self.go_to_time(
4299 day_start(
4300 time.time()), tlen=24*60*60)
4302 elif arg == 'yesterday':
4303 self.go_to_time(
4304 day_start(
4305 time.time()-24*60*60), tlen=24*60*60)
4307 else:
4308 self.go_to_event_by_name(arg)
4310 else:
4311 raise PileViewerMainException(
4312 'No such command: %s' % command)
4314 except PileViewerMainException as e:
4315 error = str(e)
4316 hideit = False
4318 return clearit, hideit, error
4320 return PileViewerMain
4323PileViewerMain = MakePileViewerMainClass(qw.QWidget)
4324GLPileViewerMain = MakePileViewerMainClass(qw.QOpenGLWidget)
4327class LineEditWithAbort(qw.QLineEdit):
4329 aborted = qc.pyqtSignal()
4330 history_down = qc.pyqtSignal()
4331 history_up = qc.pyqtSignal()
4333 def keyPressEvent(self, key_event):
4334 if key_event.key() == qc.Qt.Key_Escape:
4335 self.aborted.emit()
4336 elif key_event.key() == qc.Qt.Key_Down:
4337 self.history_down.emit()
4338 elif key_event.key() == qc.Qt.Key_Up:
4339 self.history_up.emit()
4340 else:
4341 return qw.QLineEdit.keyPressEvent(self, key_event)
4344class PileViewer(qw.QFrame):
4345 '''
4346 PileViewerMain + Controls + Inputline
4347 '''
4349 def __init__(
4350 self, pile,
4351 ntracks_shown_max=20,
4352 marker_editor_sortable=True,
4353 use_opengl=None,
4354 panel_parent=None,
4355 *args):
4357 qw.QFrame.__init__(self, *args)
4359 layout = qw.QGridLayout()
4360 layout.setContentsMargins(0, 0, 0, 0)
4361 layout.setSpacing(0)
4363 self.menu = PileViewerMenuBar(self)
4365 if use_opengl is None:
4366 use_opengl = is_macos
4368 if use_opengl:
4369 self.viewer = GLPileViewerMain(
4370 pile,
4371 ntracks_shown_max=ntracks_shown_max,
4372 panel_parent=panel_parent,
4373 menu=self.menu)
4374 else:
4375 self.viewer = PileViewerMain(
4376 pile,
4377 ntracks_shown_max=ntracks_shown_max,
4378 panel_parent=panel_parent,
4379 menu=self.menu)
4381 self.marker_editor_sortable = marker_editor_sortable
4383 # self.setFrameShape(qw.QFrame.StyledPanel)
4384 # self.setFrameShadow(qw.QFrame.Sunken)
4386 self.input_area = qw.QFrame(self)
4387 ia_layout = qw.QGridLayout()
4388 ia_layout.setContentsMargins(11, 11, 11, 11)
4389 self.input_area.setLayout(ia_layout)
4391 self.inputline = LineEditWithAbort(self.input_area)
4392 self.inputline.returnPressed.connect(
4393 self.inputline_returnpressed)
4394 self.inputline.editingFinished.connect(
4395 self.inputline_finished)
4396 self.inputline.aborted.connect(
4397 self.inputline_aborted)
4399 self.inputline.history_down.connect(
4400 lambda: self.step_through_history(1))
4401 self.inputline.history_up.connect(
4402 lambda: self.step_through_history(-1))
4404 self.inputline.textEdited.connect(
4405 self.inputline_changed)
4407 self.inputline.setPlaceholderText(
4408 u"Quick commands: e.g. 'c HH?' to select channels. "
4409 u'Use ↑ or ↓ to navigate.')
4410 self.inputline.setFocusPolicy(qc.Qt.ClickFocus)
4411 self.input_area.hide()
4412 self.history = None
4414 self.inputline_error_str = None
4416 self.inputline_error = qw.QLabel()
4417 self.inputline_error.hide()
4419 ia_layout.addWidget(self.inputline, 0, 0)
4420 ia_layout.addWidget(self.inputline_error, 1, 0)
4421 layout.addWidget(self.input_area, 0, 0, 1, 2)
4422 layout.addWidget(self.viewer, 1, 0)
4424 pb = Progressbars(self)
4425 layout.addWidget(pb, 2, 0, 1, 2)
4426 self.progressbars = pb
4428 scrollbar = qw.QScrollBar(qc.Qt.Vertical)
4429 self.scrollbar = scrollbar
4430 layout.addWidget(scrollbar, 1, 1)
4431 self.scrollbar.valueChanged.connect(
4432 self.scrollbar_changed)
4434 self.block_scrollbar_changes = False
4436 self.viewer.want_input.connect(
4437 self.inputline_show)
4438 self.viewer.tracks_range_changed.connect(
4439 self.tracks_range_changed)
4440 self.viewer.pile_has_changed_signal.connect(
4441 self.adjust_controls)
4442 self.viewer.about_to_close.connect(
4443 self.save_inputline_history)
4445 self.setLayout(layout)
4447 def cleanup(self):
4448 self.viewer.cleanup()
4450 def get_progressbars(self):
4451 return self.progressbars
4453 def inputline_show(self):
4454 if not self.history:
4455 self.load_inputline_history()
4457 self.input_area.show()
4458 self.inputline.setFocus(qc.Qt.OtherFocusReason)
4459 self.inputline.selectAll()
4461 def inputline_set_error(self, string):
4462 self.inputline_error_str = string
4463 self.inputline.setPalette(pyrocko.gui.util.get_err_palette())
4464 self.inputline.selectAll()
4465 self.inputline_error.setText(string)
4466 self.input_area.show()
4467 self.inputline_error.show()
4469 def inputline_clear_error(self):
4470 if self.inputline_error_str:
4471 self.inputline.setPalette(qw.QApplication.palette())
4472 self.inputline_error_str = None
4473 self.inputline_error.clear()
4474 self.inputline_error.hide()
4476 def inputline_changed(self, line):
4477 self.viewer.inputline_changed(str(line))
4478 self.inputline_clear_error()
4480 def inputline_returnpressed(self):
4481 line = str(self.inputline.text())
4482 clearit, hideit, error = self.viewer.inputline_finished(line)
4484 if error:
4485 self.inputline_set_error(error)
4487 line = line.strip()
4489 if line != '' and not error:
4490 if not (len(self.history) >= 1 and line == self.history[-1]):
4491 self.history.append(line)
4493 if clearit:
4495 self.inputline.blockSignals(True)
4496 qpat, qinp = self.viewer.get_quick_filter_patterns()
4497 if qpat is None:
4498 self.inputline.clear()
4499 else:
4500 self.inputline.setText(qinp)
4501 self.inputline.blockSignals(False)
4503 if hideit and not error:
4504 self.viewer.setFocus(qc.Qt.OtherFocusReason)
4505 self.input_area.hide()
4507 self.hist_ind = len(self.history)
4509 def inputline_aborted(self):
4510 '''
4511 Hide the input line.
4512 '''
4513 self.viewer.setFocus(qc.Qt.OtherFocusReason)
4514 self.hist_ind = len(self.history)
4515 self.input_area.hide()
4517 def save_inputline_history(self):
4518 '''
4519 Save input line history to "$HOME/.pyrocko/.snuffler_history.pf"
4520 '''
4521 if not self.history:
4522 return
4524 conf = pyrocko.config
4525 fn_hist = conf.expand(conf.make_conf_path_tmpl('.snuffler_history'))
4526 with open(fn_hist, 'w') as f:
4527 i = min(100, len(self.history))
4528 for c in self.history[-i:]:
4529 f.write('%s\n' % c)
4531 def load_inputline_history(self):
4532 '''
4533 Load input line history from "$HOME/.pyrocko/.snuffler_history.pf"
4534 '''
4535 conf = pyrocko.config
4536 fn_hist = conf.expand(conf.make_conf_path_tmpl('.snuffler_history'))
4537 if not os.path.exists(fn_hist):
4538 with open(fn_hist, 'w+') as f:
4539 f.write('\n')
4541 with open(fn_hist, 'r') as f:
4542 self.history = [line.strip() for line in f.readlines()]
4544 self.hist_ind = len(self.history)
4546 def step_through_history(self, ud=1):
4547 '''
4548 Step through input line history and set the input line text.
4549 '''
4550 n = len(self.history)
4551 self.hist_ind += ud
4552 self.hist_ind %= (n + 1)
4553 if len(self.history) != 0 and self.hist_ind != n:
4554 self.inputline.setText(self.history[self.hist_ind])
4555 else:
4556 self.inputline.setText('')
4558 def inputline_finished(self):
4559 pass
4561 def tracks_range_changed(self, ntracks, ilo, ihi):
4562 if self.block_scrollbar_changes:
4563 return
4565 self.scrollbar.blockSignals(True)
4566 self.scrollbar.setPageStep(ihi-ilo)
4567 vmax = max(0, ntracks-(ihi-ilo))
4568 self.scrollbar.setRange(0, vmax)
4569 self.scrollbar.setValue(ilo)
4570 self.scrollbar.setHidden(vmax == 0)
4571 self.scrollbar.blockSignals(False)
4573 def scrollbar_changed(self, value):
4574 self.block_scrollbar_changes = True
4575 ilo = value
4576 ihi = ilo + self.scrollbar.pageStep()
4577 self.viewer.set_tracks_range((ilo, ihi))
4578 self.block_scrollbar_changes = False
4579 self.update_contents()
4581 def controls(self):
4582 frame = qw.QFrame(self)
4583 layout = qw.QGridLayout()
4584 frame.setLayout(layout)
4586 minfreq = 0.001
4587 maxfreq = 1000.0
4588 self.lowpass_control = ValControl(high_is_none=True)
4589 self.lowpass_control.setup(
4590 'Lowpass [Hz]:', minfreq, maxfreq, maxfreq, 0)
4591 self.highpass_control = ValControl(low_is_none=True)
4592 self.highpass_control.setup(
4593 'Highpass [Hz]:', minfreq, maxfreq, minfreq, 1)
4594 self.gain_control = ValControl()
4595 self.gain_control.setup('Gain:', 0.001, 1000., 1., 2)
4596 self.rot_control = LinValControl()
4597 self.rot_control.setup('Rotate [deg]:', -180., 180., 0., 3)
4598 self.colorbar_control = ColorbarControl(self)
4600 self.lowpass_control.valchange.connect(
4601 self.viewer.lowpass_change)
4602 self.highpass_control.valchange.connect(
4603 self.viewer.highpass_change)
4604 self.gain_control.valchange.connect(
4605 self.viewer.gain_change)
4606 self.rot_control.valchange.connect(
4607 self.viewer.rot_change)
4608 self.colorbar_control.cmap_changed.connect(
4609 self.viewer.waterfall_cmap_change
4610 )
4611 self.colorbar_control.clip_changed.connect(
4612 self.viewer.waterfall_clip_change
4613 )
4614 self.colorbar_control.show_absolute_toggled.connect(
4615 self.viewer.waterfall_show_absolute_change
4616 )
4617 self.colorbar_control.show_integrate_toggled.connect(
4618 self.viewer.waterfall_set_integrate
4619 )
4621 for icontrol, control in enumerate((
4622 self.highpass_control,
4623 self.lowpass_control,
4624 self.gain_control,
4625 self.rot_control,
4626 self.colorbar_control)):
4628 for iwidget, widget in enumerate(control.widgets()):
4629 layout.addWidget(widget, icontrol, iwidget)
4631 spacer = qw.QSpacerItem(
4632 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding)
4633 layout.addItem(spacer, 4, 0, 1, 3)
4635 self.adjust_controls()
4636 self.viewer.viewmode_change(ViewMode.Wiggle)
4637 return frame
4639 def marker_editor(self):
4640 editor = pyrocko.gui.snuffler.marker_editor.MarkerEditor(
4641 self, sortable=self.marker_editor_sortable)
4643 editor.set_viewer(self.get_view())
4644 editor.get_marker_model().dataChanged.connect(
4645 self.update_contents)
4646 return editor
4648 def adjust_controls(self):
4649 dtmin, dtmax = self.viewer.content_deltat_range()
4650 maxfreq = 0.5/dtmin
4651 minfreq = (0.5/dtmax)*0.0001
4652 self.lowpass_control.set_range(minfreq, maxfreq)
4653 self.highpass_control.set_range(minfreq, maxfreq)
4655 def setup_snufflings(self):
4656 self.viewer.setup_snufflings()
4658 def get_view(self):
4659 return self.viewer
4661 def update_contents(self):
4662 self.viewer.update()
4664 def get_pile(self):
4665 return self.viewer.get_pile()
4667 def show_colorbar_ctrl(self, show):
4668 for w in self.colorbar_control.widgets():
4669 w.setVisible(show)
4671 def show_gain_ctrl(self, show):
4672 for w in self.gain_control.widgets():
4673 w.setVisible(show)