1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
5from __future__ import absolute_import, print_function
7import sys
8import os
9import time
10import calendar
11import datetime
12import re
13import math
14import logging
15import operator
16import copy
17from itertools import groupby
19import numpy as num
20import pyrocko.model
21import pyrocko.pile
22import pyrocko.shadow_pile
23import pyrocko.trace
24import pyrocko.util
25import pyrocko.plot
26import pyrocko.gui.snuffling
27import pyrocko.gui.snufflings
28import pyrocko.gui.marker_editor
30from pyrocko.util import hpfloat, gmtime_x, mystrftime
32from .marker import associate_phases_to_events, MarkerOneNSLCRequired
34from .util import (ValControl, LinValControl, Marker, EventMarker,
35 PhaseMarker, make_QPolygonF, draw_label, Label,
36 Progressbars)
38from .qt_compat import qc, qg, qw, qgl, qsvg, use_pyqt5
40import scipy.stats as sstats
41import platform
43try:
44 newstr = unicode
45except NameError:
46 newstr = str
49def fnpatch(x):
50 if use_pyqt5:
51 return x
52 else:
53 return x, None
56if sys.version_info[0] >= 3:
57 qc.QString = str
59qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \
60 qw.QFileDialog.DontUseSheet
62if platform.mac_ver() != ('', ('', '', ''), ''):
63 macosx = True
64else:
65 macosx = False
67logger = logging.getLogger('pyrocko.gui.pile_viewer')
70def detrend(x, y):
71 slope, offset, _, _, _ = sstats.linregress(x, y)
72 y_detrended = y - slope * x - offset
73 return y_detrended, slope, offset
76def retrend(x, y_detrended, slope, offset):
77 return x * slope + y_detrended + offset
80class Global(object):
81 appOnDemand = None
84class NSLC(object):
85 def __init__(self, n, s, l=None, c=None): # noqa
86 self.network = n
87 self.station = s
88 self.location = l
89 self.channel = c
92class m_float(float):
94 def __str__(self):
95 if abs(self) >= 10000.:
96 return '%g km' % round(self/1000., 0)
97 elif abs(self) >= 1000.:
98 return '%g km' % round(self/1000., 1)
99 else:
100 return '%.5g m' % self
102 def __lt__(self, other):
103 if other is None:
104 return True
105 return float(self) < float(other)
107 def __gt__(self, other):
108 if other is None:
109 return False
110 return float(self) > float(other)
113def m_float_or_none(x):
114 if x is None:
115 return None
116 else:
117 return m_float(x)
120def make_chunks(items):
121 '''
122 Split a list of integers into sublists of consecutive elements.
123 '''
124 return [list(map(operator.itemgetter(1), g)) for k, g in groupby(
125 enumerate(items), (lambda x: x[1]-x[0]))]
128class deg_float(float):
130 def __str__(self):
131 return '%4.0f' % self
134def deg_float_or_none(x):
135 if x is None:
136 return None
137 else:
138 return deg_float(x)
141class sector_int(int):
143 def __str__(self):
144 return '[%i]' % self
147def num_to_html(num):
148 snum = '%g' % num
149 m = re.match(r'(.+)[eE]([+-]?\d+)$', snum)
150 if m:
151 snum = m.group(1) + ' × 10<sup>%i</sup>' % int(m.group(2))
153 return snum
156gap_lap_tolerance = 5.
159class Timer(object):
160 def __init__(self):
161 self._start = None
162 self._stop = None
164 def start(self):
165 self._start = os.times()
167 def stop(self):
168 self._stop = os.times()
170 def get(self):
171 a = self._start
172 b = self._stop
173 if a is not None and b is not None:
174 return tuple([b[i] - a[i] for i in range(5)])
175 else:
176 return tuple([0.] * 5)
178 def __sub__(self, other):
179 a = self.get()
180 b = other.get()
181 return tuple([a[i] - b[i] for i in range(5)])
184class Integrator(pyrocko.shadow_pile.ShadowPile):
186 def process(self, iblock, tmin, tmax, traces):
187 for trace in traces:
188 trace.ydata = trace.ydata - trace.ydata.mean()
189 trace.ydata = num.cumsum(trace.ydata)
191 return traces
194class ObjectStyle(object):
195 def __init__(self, frame_pen, fill_brush):
196 self.frame_pen = frame_pen
197 self.fill_brush = fill_brush
200box_styles = []
201box_alpha = 100
202for color in 'orange skyblue butter chameleon chocolate plum ' \
203 'scarletred'.split():
205 box_styles.append(ObjectStyle(
206 qg.QPen(qg.QColor(*pyrocko.plot.tango_colors[color+'3'])),
207 qg.QBrush(qg.QColor(
208 *(pyrocko.plot.tango_colors[color+'1'] + (box_alpha,)))),
209 ))
211sday = 60*60*24. # \
212smonth = 60*60*24*30. # | only used as approx. intervals...
213syear = 60*60*24*365. # /
215acceptable_tincs = num.array([
216 1, 2, 5, 10, 20, 30, 60, 60*5, 60*10, 60*20, 60*30, 60*60, 60*60*3,
217 60*60*6, 60*60*12, sday, smonth, syear], dtype=float)
220working_system_time_range = \
221 pyrocko.util.working_system_time_range()
223initial_time_range = []
225try:
226 initial_time_range.append(
227 calendar.timegm((1950, 1, 1, 0, 0, 0)))
228except Exception:
229 initial_time_range.append(working_system_time_range[0])
231try:
232 initial_time_range.append(
233 calendar.timegm((time.gmtime().tm_year + 11, 1, 1, 0, 0, 0)))
234except Exception:
235 initial_time_range.append(working_system_time_range[1])
238def is_working_time(t):
239 return working_system_time_range[0] <= t and \
240 t <= working_system_time_range[1]
243def fancy_time_ax_format(inc):
244 l0_fmt_brief = ''
245 l2_fmt = ''
246 l2_trig = 0
247 if inc < 0.000001:
248 l0_fmt = '.%n'
249 l0_center = False
250 l1_fmt = '%H:%M:%S'
251 l1_trig = 6
252 l2_fmt = '%b %d, %Y'
253 l2_trig = 3
254 elif inc < 0.001:
255 l0_fmt = '.%u'
256 l0_center = False
257 l1_fmt = '%H:%M:%S'
258 l1_trig = 6
259 l2_fmt = '%b %d, %Y'
260 l2_trig = 3
261 elif inc < 1:
262 l0_fmt = '.%r'
263 l0_center = False
264 l1_fmt = '%H:%M:%S'
265 l1_trig = 6
266 l2_fmt = '%b %d, %Y'
267 l2_trig = 3
268 elif inc < 60:
269 l0_fmt = '%H:%M:%S'
270 l0_center = False
271 l1_fmt = '%b %d, %Y'
272 l1_trig = 3
273 elif inc < 3600:
274 l0_fmt = '%H:%M'
275 l0_center = False
276 l1_fmt = '%b %d, %Y'
277 l1_trig = 3
278 elif inc < sday:
279 l0_fmt = '%H:%M'
280 l0_center = False
281 l1_fmt = '%b %d, %Y'
282 l1_trig = 3
283 elif inc < smonth:
284 l0_fmt = '%a %d'
285 l0_fmt_brief = '%d'
286 l0_center = True
287 l1_fmt = '%b, %Y'
288 l1_trig = 2
289 elif inc < syear:
290 l0_fmt = '%b'
291 l0_center = True
292 l1_fmt = '%Y'
293 l1_trig = 1
294 else:
295 l0_fmt = '%Y'
296 l0_center = False
297 l1_fmt = ''
298 l1_trig = 0
300 return l0_fmt, l0_fmt_brief, l0_center, l1_fmt, l1_trig, l2_fmt, l2_trig
303def day_start(timestamp):
304 tt = time.gmtime(int(timestamp))
305 tts = tt[0:3] + (0, 0, 0) + tt[6:9]
306 return calendar.timegm(tts)
309def month_start(timestamp):
310 tt = time.gmtime(int(timestamp))
311 tts = tt[0:2] + (1, 0, 0, 0) + tt[6:9]
312 return calendar.timegm(tts)
315def year_start(timestamp):
316 tt = time.gmtime(int(timestamp))
317 tts = tt[0:1] + (1, 1, 0, 0, 0) + tt[6:9]
318 return calendar.timegm(tts)
321def time_nice_value(inc0):
322 if inc0 < acceptable_tincs[0]:
323 return pyrocko.plot.nice_value(inc0)
324 elif inc0 > acceptable_tincs[-1]:
325 return pyrocko.plot.nice_value(inc0/syear)*syear
326 else:
327 i = num.argmin(num.abs(acceptable_tincs-inc0))
328 return acceptable_tincs[i]
331class TimeScaler(pyrocko.plot.AutoScaler):
332 def __init__(self):
333 pyrocko.plot.AutoScaler.__init__(self)
334 self.mode = 'min-max'
336 def make_scale(self, data_range):
337 assert self.mode in ('min-max', 'off'), \
338 'mode must be "min-max" or "off" for TimeScaler'
340 data_min = min(data_range)
341 data_max = max(data_range)
342 is_reverse = (data_range[0] > data_range[1])
344 mi, ma = data_min, data_max
345 nmi = mi
346 if self.mode != 'off':
347 nmi = mi - self.space*(ma-mi)
349 nma = ma
350 if self.mode != 'off':
351 nma = ma + self.space*(ma-mi)
353 mi, ma = nmi, nma
355 if mi == ma and self.mode != 'off':
356 mi -= 1.0
357 ma += 1.0
359 mi = max(working_system_time_range[0], mi)
360 ma = min(working_system_time_range[1], ma)
362 # make nice tick increment
363 if self.inc is not None:
364 inc = self.inc
365 else:
366 if self.approx_ticks > 0.:
367 inc = time_nice_value((ma-mi)/self.approx_ticks)
368 else:
369 inc = time_nice_value((ma-mi)*10.)
371 if inc == 0.0:
372 inc = 1.0
374 if is_reverse:
375 return ma, mi, -inc
376 else:
377 return mi, ma, inc
379 def make_ticks(self, data_range):
380 mi, ma, inc = self.make_scale(data_range)
382 is_reverse = False
383 if inc < 0:
384 mi, ma, inc = ma, mi, -inc
385 is_reverse = True
387 ticks = []
389 if inc < sday:
390 mi_day = day_start(max(mi, working_system_time_range[0]+sday*1.5))
391 if inc < 0.001:
392 mi_day = hpfloat(mi_day)
394 base = mi_day+num.ceil((mi-mi_day)/inc)*inc
395 if inc < 0.001:
396 base = hpfloat(base)
398 base_day = mi_day
399 i = 0
400 while True:
401 tick = base+i*inc
402 if tick > ma:
403 break
405 tick_day = day_start(tick)
406 if tick_day > base_day:
407 base_day = tick_day
408 base = base_day
409 i = 0
410 else:
411 ticks.append(tick)
412 i += 1
414 elif inc < smonth:
415 mi_day = day_start(max(mi, working_system_time_range[0]+sday*1.5))
416 dt_base = datetime.datetime(*time.gmtime(mi_day)[:6])
417 delta = datetime.timedelta(days=int(round(inc/sday)))
418 if mi_day == mi:
419 dt_base += delta
420 i = 0
421 while True:
422 current = dt_base + i*delta
423 tick = calendar.timegm(current.timetuple())
424 if tick > ma:
425 break
426 ticks.append(tick)
427 i += 1
429 elif inc < syear:
430 mi_month = month_start(max(
431 mi, working_system_time_range[0]+smonth*1.5))
433 y, m = time.gmtime(mi_month)[:2]
434 while True:
435 tick = calendar.timegm((y, m, 1, 0, 0, 0))
436 m += 1
437 if m > 12:
438 y, m = y+1, 1
440 if tick > ma:
441 break
443 if tick >= mi:
444 ticks.append(tick)
446 else:
447 mi_year = year_start(max(
448 mi, working_system_time_range[0]+syear*1.5))
450 incy = int(round(inc/syear))
451 y = int(num.ceil(time.gmtime(mi_year)[0]/incy)*incy)
453 while True:
454 tick = calendar.timegm((y, 1, 1, 0, 0, 0))
455 y += incy
456 if tick > ma:
457 break
458 if tick >= mi:
459 ticks.append(tick)
461 if is_reverse:
462 ticks.reverse()
464 return ticks, inc
467def need_l1_tick(tt, ms, l1_trig):
468 return (0, 1, 1, 0, 0, 0)[l1_trig:] == tt[l1_trig:6] and ms == 0.0
471def tick_to_labels(tick, inc):
472 tt, ms = gmtime_x(tick)
473 l0_fmt, l0_fmt_brief, l0_center, l1_fmt, l1_trig, l2_fmt, l2_trig = \
474 fancy_time_ax_format(inc)
476 l0 = mystrftime(l0_fmt, tt, ms)
477 l0_brief = mystrftime(l0_fmt_brief, tt, ms)
478 l1, l2 = None, None
479 if need_l1_tick(tt, ms, l1_trig):
480 l1 = mystrftime(l1_fmt, tt, ms)
481 if need_l1_tick(tt, ms, l2_trig):
482 l2 = mystrftime(l2_fmt, tt, ms)
484 return l0, l0_brief, l0_center, l1, l2
487def l1_l2_tick(tick, inc):
488 tt, ms = gmtime_x(tick)
489 l0_fmt, l0_fmt_brief, l0_center, l1_fmt, l1_trig, l2_fmt, l2_trig = \
490 fancy_time_ax_format(inc)
492 l1 = mystrftime(l1_fmt, tt, ms)
493 l2 = mystrftime(l2_fmt, tt, ms)
494 return l1, l2
497class TimeAx(TimeScaler):
498 def __init__(self, *args):
499 TimeScaler.__init__(self, *args)
501 def drawit(self, p, xprojection, yprojection):
502 pen = qg.QPen(qg.QColor(*pyrocko.plot.tango_colors['aluminium5']), 1)
503 p.setPen(pen)
504 font = qg.QFont()
505 font.setBold(True)
506 p.setFont(font)
507 fm = p.fontMetrics()
508 ticklen = 10
509 pad = 10
510 tmin, tmax = xprojection.get_in_range()
511 ticks, inc = self.make_ticks((tmin, tmax))
512 l1_hits = 0
513 l2_hits = 0
515 vmin, vmax = yprojection(0), yprojection(ticklen)
516 uumin, uumax = xprojection.get_out_range()
517 first_tick_with_label = None
518 for tick in ticks:
519 umin = xprojection(tick)
521 umin_approx_next = xprojection(tick+inc)
522 umax = xprojection(tick)
524 pinc_approx = umin_approx_next - umin
526 p.drawLine(qc.QPointF(umin, vmin), qc.QPointF(umax, vmax))
527 l0, l0_brief, l0_center, l1, l2 = tick_to_labels(tick, inc)
529 if tick == 0.0 and tmax - tmin < 3600*24:
530 # hide year at epoch (we assume that synthetic data is shown)
531 if l2:
532 l2 = None
533 elif l1:
534 l1 = None
536 if l0_center:
537 ushift = (umin_approx_next-umin)/2.
538 else:
539 ushift = 0.
541 for l0x in (l0, l0_brief, ''):
542 label0 = l0x
543 rect0 = fm.boundingRect(label0)
544 if rect0.width() <= pinc_approx*0.9:
545 break
547 if uumin+pad < umin-rect0.width()/2.+ushift and \
548 umin+rect0.width()/2.+ushift < uumax-pad:
550 if first_tick_with_label is None:
551 first_tick_with_label = tick
552 p.drawText(qc.QPointF(
553 umin-rect0.width()/2.+ushift,
554 vmin+rect0.height()+ticklen), label0)
556 if l1:
557 label1 = l1
558 rect1 = fm.boundingRect(label1)
559 if uumin+pad < umin-rect1.width()/2. and \
560 umin+rect1.width()/2. < uumax-pad:
562 p.drawText(qc.QPointF(
563 umin-rect1.width()/2.,
564 vmin+rect0.height()+rect1.height()+ticklen),
565 label1)
567 l1_hits += 1
569 if l2:
570 label2 = l2
571 rect2 = fm.boundingRect(label2)
572 if uumin+pad < umin-rect2.width()/2. and \
573 umin+rect2.width()/2. < uumax-pad:
575 p.drawText(qc.QPointF(
576 umin-rect2.width()/2.,
577 vmin+rect0.height()+rect1.height()+rect2.height() +
578 ticklen), label2)
580 l2_hits += 1
582 if first_tick_with_label is None:
583 first_tick_with_label = tmin
585 l1, l2 = l1_l2_tick(first_tick_with_label, inc)
587 if -3600.*25 < first_tick_with_label <= 3600.*25 and \
588 tmax - tmin < 3600*24:
590 # hide year at epoch (we assume that synthetic data is shown)
591 if l2:
592 l2 = None
593 elif l1:
594 l1 = None
596 if l1_hits == 0 and l1:
597 label1 = l1
598 rect1 = fm.boundingRect(label1)
599 p.drawText(qc.QPointF(
600 uumin+pad,
601 vmin+rect0.height()+rect1.height()+ticklen),
602 label1)
604 l1_hits += 1
606 if l2_hits == 0 and l2:
607 label2 = l2
608 rect2 = fm.boundingRect(label2)
609 p.drawText(qc.QPointF(
610 uumin+pad,
611 vmin+rect0.height()+rect1.height()+rect2.height()+ticklen),
612 label2)
614 v = yprojection(0)
615 p.drawLine(qc.QPointF(uumin, v), qc.QPointF(uumax, v))
618class Projection(object):
619 def __init__(self):
620 self.xr = 0., 1.
621 self.ur = 0., 1.
623 def set_in_range(self, xmin, xmax):
624 if xmax == xmin:
625 xmax = xmin + 1.
627 self.xr = xmin, xmax
629 def get_in_range(self):
630 return self.xr
632 def set_out_range(self, umin, umax):
633 if umax == umin:
634 umax = umin + 1.
636 self.ur = umin, umax
638 def get_out_range(self):
639 return self.ur
641 def __call__(self, x):
642 umin, umax = self.ur
643 xmin, xmax = self.xr
644 return umin + (x-xmin)*((umax-umin)/(xmax-xmin))
646 def clipped(self, x):
647 umin, umax = self.ur
648 xmin, xmax = self.xr
649 return min(umax, max(umin, umin + (x-xmin)*((umax-umin)/(xmax-xmin))))
651 def rev(self, u):
652 umin, umax = self.ur
653 xmin, xmax = self.xr
654 return xmin + (u-umin)*((xmax-xmin)/(umax-umin))
656 def copy(self):
657 return copy.copy(self)
660def add_radiobuttongroup(menu, menudef, obj, target, default=None):
661 group = qw.QActionGroup(menu)
662 menuitems = []
663 for name, v in menudef:
664 k = qw.QAction(name, menu)
665 group.addAction(k)
666 menu.addAction(k)
667 k.setCheckable(True)
668 group.triggered.connect(target)
669 menuitems.append((k, v))
670 if default is not None:
671 if name.lower().replace(' ', '_') == default:
672 k.setChecked(True)
674 if default is None:
675 menuitems[0][0].setChecked(True)
676 return menuitems
679def sort_actions(menu):
680 actions = menu.actions()
681 for action in actions:
682 menu.removeAction(action)
683 actions.sort(key=lambda x: newstr(x.text()))
685 help_action = [a for a in actions if a.text() == 'Snuffler Controls']
686 if help_action:
687 actions.insert(0, actions.pop(actions.index(help_action[0])))
688 for action in actions:
689 menu.addAction(action)
692fkey_map = dict(zip(
693 (qc.Qt.Key_F1, qc.Qt.Key_F2, qc.Qt.Key_F3, qc.Qt.Key_F4, qc.Qt.Key_F5,
694 qc.Qt.Key_F6, qc.Qt.Key_F7, qc.Qt.Key_F8, qc.Qt.Key_F9, qc.Qt.Key_F10,
695 qc.Qt.Key_F11, qc.Qt.Key_F12),
696 range(12)))
699class PileViewerMainException(Exception):
700 pass
703def MakePileViewerMainClass(base):
705 class PileViewerMain(base):
707 want_input = qc.pyqtSignal()
708 about_to_close = qc.pyqtSignal()
709 pile_has_changed_signal = qc.pyqtSignal()
710 tracks_range_changed = qc.pyqtSignal(int, int, int)
712 begin_markers_add = qc.pyqtSignal(int, int)
713 end_markers_add = qc.pyqtSignal()
714 begin_markers_remove = qc.pyqtSignal(int, int)
715 end_markers_remove = qc.pyqtSignal()
717 marker_selection_changed = qc.pyqtSignal(list)
718 active_event_marker_changed = qc.pyqtSignal()
720 def __init__(self, pile, ntracks_shown_max, panel_parent, *args):
721 if base == qgl.QGLWidget:
722 from OpenGL import GL # noqa
724 base.__init__(
725 self, qgl.QGLFormat(qgl.QGL.SampleBuffers), *args)
726 else:
727 base.__init__(self, *args)
729 self.pile = pile
730 self.ax_height = 80
731 self.panel_parent = panel_parent
733 self.click_tolerance = 5
735 self.ntracks_shown_max = ntracks_shown_max
736 self.initial_ntracks_shown_max = ntracks_shown_max
737 self.ntracks = 0
738 self.show_all = True
739 self.shown_tracks_range = None
740 self.track_start = None
741 self.track_trange = None
743 self.lowpass = None
744 self.highpass = None
745 self.gain = 1.0
746 self.rotate = 0.0
747 self.picking_down = None
748 self.picking = None
749 self.floating_marker = None
750 self.markers = pyrocko.pile.Sorted([], 'tmin')
751 self.markers_deltat_max = 0.
752 self.n_selected_markers = 0
753 self.all_marker_kinds = (0, 1, 2, 3, 4, 5)
754 self.visible_marker_kinds = self.all_marker_kinds
755 self.active_event_marker = None
756 self.ignore_releases = 0
757 self.message = None
758 self.reloaded = False
759 self.pile_has_changed = False
760 self.config = pyrocko.config.config('snuffler')
762 self.tax = TimeAx()
763 self.setBackgroundRole(qg.QPalette.Base)
764 self.setAutoFillBackground(True)
765 poli = qw.QSizePolicy(
766 qw.QSizePolicy.Expanding,
767 qw.QSizePolicy.Expanding)
769 self.setSizePolicy(poli)
770 self.setMinimumSize(300, 200)
771 self.setFocusPolicy(qc.Qt.ClickFocus)
773 self.menu = qw.QMenu(self)
775 mi = qw.QAction('Open waveform files...', self.menu)
776 self.menu.addAction(mi)
777 mi.triggered.connect(self.open_waveforms)
779 mi = qw.QAction('Open waveform directory...', self.menu)
780 self.menu.addAction(mi)
781 mi.triggered.connect(self.open_waveform_directory)
783 mi = qw.QAction('Open station files...', self.menu)
784 self.menu.addAction(mi)
785 mi.triggered.connect(self.open_stations)
787 mi = qw.QAction('Open StationXML files...', self.menu)
788 self.menu.addAction(mi)
789 mi.triggered.connect(self.open_stations_xml)
791 mi = qw.QAction('Save markers...', self.menu)
792 self.menu.addAction(mi)
793 mi.triggered.connect(self.write_markers)
795 mi = qw.QAction('Save selected markers...', self.menu)
796 self.menu.addAction(mi)
797 mi.triggered.connect(self.write_selected_markers)
799 mi = qw.QAction('Open marker file...', self.menu)
800 self.menu.addAction(mi)
801 mi.triggered.connect(self.read_markers)
803 mi = qw.QAction('Open event file...', self.menu)
804 self.menu.addAction(mi)
805 mi.triggered.connect(self.read_events)
807 self.menu.addSeparator()
809 menudef = [
810 ('Individual Scale',
811 lambda tr: tr.nslc_id),
812 ('Common Scale',
813 lambda tr: None),
814 ('Common Scale per Station',
815 lambda tr: (tr.network, tr.station)),
816 ('Common Scale per Station Location',
817 lambda tr: (tr.network, tr.station, tr.location)),
818 ('Common Scale per Component',
819 lambda tr: (tr.channel)),
820 ]
822 self.menuitems_scaling = add_radiobuttongroup(
823 self.menu, menudef, self, self.scalingmode_change,
824 default=self.config.trace_scale)
826 self.scaling_key = self.menuitems_scaling[0][1]
827 self.scaling_hooks = {}
828 self.scalingmode_change()
830 self.menu.addSeparator()
832 menudef = [
833 ('Scaling based on Minimum and Maximum', 'minmax'),
834 ('Scaling based on Mean +- 2 x Std. Deviation', 2),
835 ('Scaling based on Mean +- 4 x Std. Deviation', 4),
836 ]
838 self.menuitems_scaling_base = add_radiobuttongroup(
839 self.menu, menudef, self, self.scaling_base_change)
841 self.scaling_base = self.menuitems_scaling_base[0][1]
843 self.menu.addSeparator()
845 def sector_dist(sta):
846 if sta.dist_m is None:
847 return None, None
848 else:
849 return (
850 sector_int(round((sta.azimuth+15.)/30.)),
851 m_float(sta.dist_m))
853 menudef = [
854 ('Sort by Names',
855 lambda tr: ()),
856 ('Sort by Distance',
857 lambda tr: self.station_attrib(
858 tr,
859 lambda sta: (m_float_or_none(sta.dist_m),),
860 lambda tr: (None,))),
861 ('Sort by Azimuth',
862 lambda tr: self.station_attrib(
863 tr,
864 lambda sta: (deg_float_or_none(sta.azimuth),),
865 lambda tr: (None,))),
866 ('Sort by Distance in 12 Azimuthal Blocks',
867 lambda tr: self.station_attrib(
868 tr,
869 sector_dist,
870 lambda tr: (None, None))),
871 ('Sort by Backazimuth',
872 lambda tr: self.station_attrib(
873 tr,
874 lambda sta: (deg_float_or_none(sta.backazimuth),),
875 lambda tr: (None,))),
876 ]
877 self.menuitems_ssorting = add_radiobuttongroup(
878 self.menu, menudef, self, self.s_sortingmode_change)
880 self._ssort = lambda tr: ()
882 self.menuitem_distances_3d = qw.QAction('3D distances', self.menu)
883 self.menuitem_distances_3d.setCheckable(True)
884 self.menuitem_distances_3d.setChecked(False)
885 self.menuitem_distances_3d.toggled.connect(
886 self.distances_3d_changed)
888 self.menu.addAction(self.menuitem_distances_3d)
890 self.menu.addSeparator()
892 menudef = [
893 ('Subsort by Network, Station, Location, Channel',
894 (lambda tr: self.ssort(tr) + tr.nslc_id, # gathering
895 lambda a: a, # sorting
896 lambda tr: tr.location)), # coloring
897 ('Subsort by Network, Station, Channel, Location',
898 (lambda tr: self.ssort(tr) + (
899 tr.network, tr.station, tr.channel, tr.location),
900 lambda a: a,
901 lambda tr: tr.channel)),
902 ('Subsort by Station, Network, Channel, Location',
903 (lambda tr: self.ssort(tr) + (
904 tr.station, tr.network, tr.channel, tr.location),
905 lambda a: a,
906 lambda tr: tr.channel)),
907 ('Subsort by Location, Network, Station, Channel',
908 (lambda tr: self.ssort(tr) + (
909 tr.location, tr.network, tr.station, tr.channel),
910 lambda a: a,
911 lambda tr: tr.channel)),
912 ('Subsort by Channel, Network, Station, Location',
913 (lambda tr: self.ssort(tr) + (
914 tr.channel, tr.network, tr.station, tr.location),
915 lambda a: a,
916 lambda tr: (tr.network, tr.station, tr.location))),
917 ('Subsort by Network, Station, Channel (Grouped by Location)',
918 (lambda tr: self.ssort(tr) + (
919 tr.network, tr.station, tr.channel),
920 lambda a: a,
921 lambda tr: tr.location)),
922 ('Subsort by Station, Network, Channel (Grouped by Location)',
923 (lambda tr: self.ssort(tr) + (
924 tr.station, tr.network, tr.channel),
925 lambda a: a,
926 lambda tr: tr.location)),
927 ]
929 self.menuitems_sorting = add_radiobuttongroup(
930 self.menu, menudef, self, self.sortingmode_change)
932 self.menu.addSeparator()
934 self.menuitem_antialias = qw.QAction('Antialiasing', self.menu)
935 self.menuitem_antialias.setCheckable(True)
936 self.menu.addAction(self.menuitem_antialias)
938 self.menuitem_liberal_fetch = qw.QAction(
939 'Liberal Fetch Optimization', self.menu)
940 self.menuitem_liberal_fetch.setCheckable(True)
941 self.menu.addAction(self.menuitem_liberal_fetch)
943 self.menuitem_cliptraces = qw.QAction('Clip Traces', self.menu)
944 self.menuitem_cliptraces.setCheckable(True)
945 self.menuitem_cliptraces.setChecked(self.config.clip_traces)
946 self.menu.addAction(self.menuitem_cliptraces)
948 self.menuitem_showboxes = qw.QAction('Show Boxes', self.menu)
949 self.menuitem_showboxes.setCheckable(True)
950 self.menuitem_showboxes.setChecked(
951 self.config.show_boxes)
952 self.menu.addAction(self.menuitem_showboxes)
954 self.menuitem_colortraces = qw.QAction('Color Traces', self.menu)
955 self.menuitem_colortraces.setCheckable(True)
956 self.menuitem_colortraces.setChecked(False)
957 self.menu.addAction(self.menuitem_colortraces)
959 self.menuitem_showscalerange = qw.QAction(
960 'Show Scale Ranges', self.menu)
961 self.menuitem_showscalerange.setCheckable(True)
962 self.menuitem_showscalerange.setChecked(
963 self.config.show_scale_ranges)
964 self.menu.addAction(self.menuitem_showscalerange)
966 self.menuitem_showscaleaxis = qw.QAction(
967 'Show Scale Axes', self.menu)
968 self.menuitem_showscaleaxis.setCheckable(True)
969 self.menuitem_showscaleaxis.setChecked(
970 self.config.show_scale_axes)
971 self.menu.addAction(self.menuitem_showscaleaxis)
973 self.menuitem_showzeroline = qw.QAction(
974 'Show Zero Lines', self.menu)
975 self.menuitem_showzeroline.setCheckable(True)
976 self.menu.addAction(self.menuitem_showzeroline)
978 self.menuitem_fixscalerange = qw.QAction(
979 'Fix Scale Ranges', self.menu)
980 self.menuitem_fixscalerange.setCheckable(True)
981 self.menu.addAction(self.menuitem_fixscalerange)
983 self.menuitem_allowdownsampling = qw.QAction(
984 'Allow Downsampling', self.menu)
985 self.menuitem_allowdownsampling.setCheckable(True)
986 self.menuitem_allowdownsampling.setChecked(True)
987 self.menu.addAction(self.menuitem_allowdownsampling)
989 self.menuitem_degap = qw.QAction('Allow Degapping', self.menu)
990 self.menuitem_degap.setCheckable(True)
991 self.menuitem_degap.setChecked(True)
992 self.menu.addAction(self.menuitem_degap)
994 self.menuitem_demean = qw.QAction('Demean', self.menu)
995 self.menuitem_demean.setCheckable(True)
996 self.menuitem_demean.setChecked(self.config.demean)
997 self.menu.addAction(self.menuitem_demean)
999 self.menuitem_fft_filtering = qw.QAction(
1000 'FFT Filtering', self.menu)
1001 self.menuitem_fft_filtering.setCheckable(True)
1002 self.menuitem_fft_filtering.setChecked(False)
1003 self.menu.addAction(self.menuitem_fft_filtering)
1005 self.menuitem_lphp = qw.QAction(
1006 'Bandpass is Lowpass + Highpass', self.menu)
1007 self.menuitem_lphp.setCheckable(True)
1008 self.menuitem_lphp.setChecked(True)
1009 self.menu.addAction(self.menuitem_lphp)
1011 self.menuitem_watch = qw.QAction('Watch Files', self.menu)
1012 self.menuitem_watch.setCheckable(True)
1013 self.menuitem_watch.setChecked(False)
1014 self.menu.addAction(self.menuitem_watch)
1016 self.visible_length_menu = qw.QMenu('Visible Length', self.menu)
1018 menudef = [(x.key, x.value) for x in
1019 self.config.visible_length_setting]
1021 self.menuitems_visible_length = add_radiobuttongroup(
1022 self.visible_length_menu, menudef, self,
1023 self.visible_length_change)
1025 self.visible_length = menudef[0][1]
1026 self.menu.addMenu(self.visible_length_menu)
1027 self.menu.addSeparator()
1029 self.snufflings_menu = qw.QMenu('Run Snuffling', self.menu)
1030 self.menu.addMenu(self.snufflings_menu)
1032 self.toggle_panel_menu = qw.QMenu('Panels', self.menu)
1033 self.menu.addMenu(self.toggle_panel_menu)
1035 self.menuitem_reload = qw.QAction('Reload Snufflings', self.menu)
1036 self.menu.addAction(self.menuitem_reload)
1037 self.menuitem_reload.triggered.connect(
1038 self.setup_snufflings)
1040 self.menu.addSeparator()
1042 # Disable ShadowPileTest
1043 if False:
1044 self.menuitem_test = qw.QAction('Test', self.menu)
1045 self.menuitem_test.setCheckable(True)
1046 self.menuitem_test.setChecked(False)
1047 self.menu.addAction(self.menuitem_test)
1048 self.menuitem_test.triggered.connect(
1049 self.toggletest)
1051 self.menuitem_print = qw.QAction('Print', self.menu)
1052 self.menu.addAction(self.menuitem_print)
1053 self.menuitem_print.triggered.connect(
1054 self.printit)
1056 self.menuitem_svg = qw.QAction('Save as SVG|PNG', self.menu)
1057 self.menu.addAction(self.menuitem_svg)
1058 self.menuitem_svg.triggered.connect(
1059 self.savesvg)
1061 self.snuffling_help_menu = qw.QMenu('Help', self.menu)
1062 self.menu.addMenu(self.snuffling_help_menu)
1063 self.menuitem_help = qw.QAction(
1064 'Snuffler Controls', self.snuffling_help_menu)
1065 self.snuffling_help_menu.addAction(self.menuitem_help)
1066 self.menuitem_help.triggered.connect(self.help)
1068 self.snuffling_help_menu.addSeparator()
1070 self.menuitem_about = qw.QAction('About', self.menu)
1071 self.menu.addAction(self.menuitem_about)
1072 self.menuitem_about.triggered.connect(self.about)
1074 self.menuitem_close = qw.QAction('Close', self.menu)
1075 self.menu.addAction(self.menuitem_close)
1076 self.menuitem_close.triggered.connect(self.myclose)
1078 self.menu.addSeparator()
1080 self.menu.triggered.connect(self.update)
1082 self.time_projection = Projection()
1083 self.set_time_range(self.pile.get_tmin(), self.pile.get_tmax())
1084 self.time_projection.set_out_range(0., self.width())
1086 self.gather = None
1088 self.trace_filter = None
1089 self.quick_filter = None
1090 self.quick_filter_patterns = None, None
1091 self.blacklist = []
1093 self.track_to_screen = Projection()
1094 self.track_to_nslc_ids = {}
1096 self.old_vec = None
1097 self.old_processed_traces = None
1099 self.timer = qc.QTimer(self)
1100 self.timer.timeout.connect(self.periodical)
1101 self.timer.setInterval(1000)
1102 self.timer.start()
1103 self.pile.add_listener(self)
1104 self.trace_styles = {}
1105 self.determine_box_styles()
1106 self.setMouseTracking(True)
1108 user_home_dir = os.path.expanduser('~')
1109 self.snuffling_modules = {}
1110 self.snuffling_paths = [os.path.join(user_home_dir, '.snufflings')]
1111 self.default_snufflings = None
1112 self.snufflings = []
1114 self.stations = {}
1116 self.timer_draw = Timer()
1117 self.timer_cutout = Timer()
1118 self.time_spent_painting = 0.0
1119 self.time_last_painted = time.time()
1121 self.interactive_range_change_time = 0.0
1122 self.interactive_range_change_delay_time = 10.0
1123 self.follow_timer = None
1125 self.sortingmode_change_time = 0.0
1126 self.sortingmode_change_delay_time = None
1128 self.old_data_ranges = {}
1130 self.error_messages = {}
1131 self.return_tag = None
1132 self.wheel_pos = 60
1134 self.setAcceptDrops(True)
1135 self._paths_to_load = []
1137 self.tf_cache = {}
1139 self.automatic_updates = True
1141 self.closing = False
1142 self.paint_timer = qc.QTimer(self)
1143 self.paint_timer.timeout.connect(self.reset_updates)
1144 self.paint_timer.setInterval(20)
1145 self.paint_timer.start()
1147 @qc.pyqtSlot()
1148 def reset_updates(self):
1149 if not self.updatesEnabled():
1150 self.setUpdatesEnabled(True)
1152 def fail(self, reason):
1153 box = qw.QMessageBox(self)
1154 box.setText(reason)
1155 box.exec_()
1157 def set_trace_filter(self, filter_func):
1158 self.trace_filter = filter_func
1159 self.sortingmode_change()
1161 def update_trace_filter(self):
1162 if self.blacklist:
1164 def blacklist_func(tr):
1165 return not pyrocko.util.match_nslc(
1166 self.blacklist, tr.nslc_id)
1168 else:
1169 blacklist_func = None
1171 if self.quick_filter is None and blacklist_func is None:
1172 self.set_trace_filter(None)
1173 elif self.quick_filter is None:
1174 self.set_trace_filter(blacklist_func)
1175 elif blacklist_func is None:
1176 self.set_trace_filter(self.quick_filter)
1177 else:
1178 self.set_trace_filter(
1179 lambda tr: blacklist_func(tr) and self.quick_filter(tr))
1181 def set_quick_filter(self, filter_func):
1182 self.quick_filter = filter_func
1183 self.update_trace_filter()
1185 def set_quick_filter_patterns(self, patterns, inputline=None):
1186 if patterns is not None:
1187 self.set_quick_filter(
1188 lambda tr: pyrocko.util.match_nslc(patterns, tr.nslc_id))
1189 else:
1190 self.set_quick_filter(None)
1192 self.quick_filter_patterns = patterns, inputline
1194 def get_quick_filter_patterns(self):
1195 return self.quick_filter_patterns
1197 def add_blacklist_pattern(self, pattern):
1198 if pattern == 'empty':
1199 keys = set(self.pile.nslc_ids)
1200 trs = self.pile.all(
1201 tmin=self.tmin,
1202 tmax=self.tmax,
1203 load_data=False,
1204 degap=False)
1206 for tr in trs:
1207 if tr.nslc_id in keys:
1208 keys.remove(tr.nslc_id)
1210 for key in keys:
1211 xpattern = '.'.join(key)
1212 if xpattern not in self.blacklist:
1213 self.blacklist.append(xpattern)
1215 else:
1216 if pattern in self.blacklist:
1217 self.blacklist.remove(pattern)
1219 self.blacklist.append(pattern)
1221 logger.info('Blacklist is [ %s ]' % ', '.join(self.blacklist))
1222 self.update_trace_filter()
1224 def remove_blacklist_pattern(self, pattern):
1225 if pattern in self.blacklist:
1226 self.blacklist.remove(pattern)
1227 else:
1228 raise PileViewerMainException(
1229 'Pattern not found in blacklist.')
1231 logger.info('Blacklist is [ %s ]' % ', '.join(self.blacklist))
1232 self.update_trace_filter()
1234 def clear_blacklist(self):
1235 self.blacklist = []
1236 self.update_trace_filter()
1238 def ssort(self, tr):
1239 return self._ssort(tr)
1241 def station_key(self, x):
1242 return x.network, x.station
1244 def station_keys(self, x):
1245 return [
1246 (x.network, x.station, x.location),
1247 (x.network, x.station)]
1249 def station_attrib(self, tr, getter, default_getter):
1250 for sk in self.station_keys(tr):
1251 if sk in self.stations:
1252 station = self.stations[sk]
1253 return getter(station)
1255 return default_getter(tr)
1257 def get_station(self, sk):
1258 return self.stations[sk]
1260 def has_station(self, station):
1261 for sk in self.station_keys(station):
1262 if sk in self.stations:
1263 return True
1265 return False
1267 def station_latlon(self, tr, default_getter=lambda tr: (0., 0.)):
1268 return self.station_attrib(
1269 tr, lambda sta: (sta.lat, sta.lon), default_getter)
1271 def set_stations(self, stations):
1272 self.stations = {}
1273 self.add_stations(stations)
1275 def add_stations(self, stations):
1276 for station in stations:
1277 for sk in self.station_keys(station):
1278 self.stations[sk] = station
1280 ev = self.get_active_event()
1281 if ev:
1282 self.set_origin(ev)
1284 def add_event(self, event):
1285 marker = EventMarker(event)
1286 self.add_marker(marker)
1288 def add_events(self, events):
1289 markers = [EventMarker(e) for e in events]
1290 self.add_markers(markers)
1292 def set_event_marker_as_origin(self, ignore=None):
1293 selected = self.selected_markers()
1294 if not selected:
1295 self.fail('An event marker must be selected.')
1296 return
1298 m = selected[0]
1299 if not isinstance(m, EventMarker):
1300 self.fail('Selected marker is not an event.')
1301 return
1303 self.set_active_event_marker(m)
1305 def deactivate_event_marker(self):
1306 if self.active_event_marker:
1307 self.active_event_marker.active = False
1309 self.active_event_marker_changed.emit()
1310 self.active_event_marker = None
1312 def set_active_event_marker(self, event_marker):
1313 if self.active_event_marker:
1314 self.active_event_marker.active = False
1316 self.active_event_marker = event_marker
1317 event_marker.active = True
1318 event = event_marker.get_event()
1319 self.set_origin(event)
1320 self.active_event_marker_changed.emit()
1322 def set_active_event(self, event):
1323 for marker in self.markers:
1324 if isinstance(marker, EventMarker):
1325 if marker.get_event() is event:
1326 self.set_active_event_marker(marker)
1328 def get_active_event_marker(self):
1329 return self.active_event_marker
1331 def get_active_event(self):
1332 m = self.get_active_event_marker()
1333 if m is not None:
1334 return m.get_event()
1335 else:
1336 return None
1338 def get_active_markers(self):
1339 emarker = self.get_active_event_marker()
1340 if emarker is None:
1341 return None, []
1343 else:
1344 ev = emarker.get_event()
1345 pmarkers = [
1346 m for m in self.markers
1347 if isinstance(m, PhaseMarker) and m.get_event() is ev]
1349 return emarker, pmarkers
1351 def set_origin(self, location):
1352 for station in self.stations.values():
1353 station.set_event_relative_data(
1354 location,
1355 distance_3d=self.menuitem_distances_3d.isChecked())
1357 self.sortingmode_change()
1359 def distances_3d_changed(self, ignore):
1360 self.set_event_marker_as_origin(ignore)
1362 def toggletest(self, checked):
1363 if checked:
1364 sp = Integrator()
1366 self.add_shadow_pile(sp)
1367 else:
1368 self.remove_shadow_piles()
1370 def add_shadow_pile(self, shadow_pile):
1371 shadow_pile.set_basepile(self.pile)
1372 shadow_pile.add_listener(self)
1373 self.pile = shadow_pile
1375 def remove_shadow_piles(self):
1376 self.pile = self.pile.get_basepile()
1378 def iter_snuffling_modules(self):
1379 pjoin = os.path.join
1380 for path in self.snuffling_paths:
1382 if not os.path.isdir(path):
1383 os.mkdir(path)
1385 for entry in os.listdir(path):
1386 directory = path
1387 fn = entry
1388 d = pjoin(path, entry)
1389 if os.path.isdir(d):
1390 directory = d
1391 if os.path.isfile(
1392 os.path.join(directory, 'snuffling.py')):
1393 fn = 'snuffling.py'
1395 if not fn.endswith('.py'):
1396 continue
1398 name = fn[:-3]
1400 if (directory, name) not in self.snuffling_modules:
1401 self.snuffling_modules[directory, name] = \
1402 pyrocko.gui.snuffling.SnufflingModule(
1403 directory, name, self)
1405 yield self.snuffling_modules[directory, name]
1407 def setup_snufflings(self):
1408 # user snufflings
1409 for mod in self.iter_snuffling_modules():
1410 try:
1411 mod.load_if_needed()
1412 except pyrocko.gui.snuffling.BrokenSnufflingModule as e:
1413 logger.warning('Snuffling module "%s" is broken' % e)
1415 # load the default snufflings on first run
1416 if self.default_snufflings is None:
1417 self.default_snufflings = pyrocko.gui\
1418 .snufflings.__snufflings__()
1419 for snuffling in self.default_snufflings:
1420 self.add_snuffling(snuffling)
1422 def set_panel_parent(self, panel_parent):
1423 self.panel_parent = panel_parent
1425 def get_panel_parent(self):
1426 return self.panel_parent
1428 def add_snuffling(self, snuffling, reloaded=False):
1429 logger.debug('Adding snuffling %s' % snuffling.get_name())
1430 snuffling.init_gui(
1431 self, self.get_panel_parent(), self, reloaded=reloaded)
1432 self.snufflings.append(snuffling)
1433 self.update()
1435 def remove_snuffling(self, snuffling):
1436 snuffling.delete_gui()
1437 self.update()
1438 self.snufflings.remove(snuffling)
1439 snuffling.pre_destroy()
1441 def add_snuffling_menuitem(self, item):
1442 self.snufflings_menu.addAction(item)
1443 item.setParent(self.snufflings_menu)
1444 sort_actions(self.snufflings_menu)
1446 def remove_snuffling_menuitem(self, item):
1447 self.snufflings_menu.removeAction(item)
1449 def add_snuffling_help_menuitem(self, item):
1450 self.snuffling_help_menu.addAction(item)
1451 item.setParent(self.snuffling_help_menu)
1452 sort_actions(self.snuffling_help_menu)
1454 def remove_snuffling_help_menuitem(self, item):
1455 self.snuffling_help_menu.removeAction(item)
1457 def add_panel_toggler(self, item):
1458 self.toggle_panel_menu.addAction(item)
1459 item.setParent(self.toggle_panel_menu)
1460 sort_actions(self.toggle_panel_menu)
1462 def remove_panel_toggler(self, item):
1463 self.toggle_panel_menu.removeAction(item)
1465 def load(self, paths, regex=None, format='from_extension',
1466 cache_dir=None, force_cache=False):
1468 if cache_dir is None:
1469 cache_dir = pyrocko.config.config().cache_dir
1470 if isinstance(paths, str):
1471 paths = [paths]
1473 fns = pyrocko.util.select_files(
1474 paths, selector=None, regex=regex, show_progress=False)
1476 if not fns:
1477 return
1479 cache = pyrocko.pile.get_cache(cache_dir)
1481 t = [time.time()]
1483 def update_bar(label, value):
1484 pbs = self.parent().get_progressbars()
1485 if label.lower() == 'looking at files':
1486 label = 'Looking at %i files' % len(fns)
1487 else:
1488 label = 'Scanning %i files' % len(fns)
1490 return pbs.set_status(label, value)
1492 def update_progress(label, i, n):
1493 abort = False
1495 qw.qApp.processEvents()
1496 if n != 0:
1497 perc = i*100/n
1498 else:
1499 perc = 100
1500 abort |= update_bar(label, perc)
1501 abort |= self.window().is_closing()
1503 tnow = time.time()
1504 if t[0] + 1. + self.time_spent_painting * 10. < tnow:
1505 self.update()
1506 t[0] = tnow
1508 return abort
1510 self.automatic_updates = False
1512 self.pile.load_files(
1513 sorted(fns),
1514 filename_attributes=regex,
1515 cache=cache,
1516 fileformat=format,
1517 show_progress=False,
1518 update_progress=update_progress)
1520 self.automatic_updates = True
1521 self.update()
1523 def load_queued(self):
1524 if not self._paths_to_load:
1525 return
1526 paths = self._paths_to_load
1527 self._paths_to_load = []
1528 self.load(paths)
1530 def load_soon(self, paths):
1531 self._paths_to_load.extend(paths)
1532 qc.QTimer.singleShot(200, self.load_queued)
1534 def open_waveforms(self):
1535 caption = 'Select one or more files to open'
1537 fns, _ = fnpatch(qw.QFileDialog.getOpenFileNames(
1538 self, caption, options=qfiledialog_options))
1540 if fns:
1541 self.load(list(str(fn) for fn in fns))
1543 def open_waveform_directory(self):
1544 caption = 'Select directory to scan for waveform files'
1546 dn = qw.QFileDialog.getExistingDirectory(
1547 self, caption, options=qfiledialog_options)
1549 if dn:
1550 self.load([str(dn)])
1552 def open_stations(self, fns=None):
1553 caption = 'Select one or more files to open'
1555 if not fns:
1556 fns, _ = fnpatch(qw.QFileDialog.getOpenFileNames(
1557 self, caption, options=qfiledialog_options))
1559 stations = [pyrocko.model.load_stations(str(x)) for x in fns]
1560 for stat in stations:
1561 self.add_stations(stat)
1563 def open_stations_xml(self, fns=None):
1564 from pyrocko.io import stationxml
1566 caption = 'Select one or more StationXML files to open'
1568 if not fns:
1569 fns, _ = fnpatch(qw.QFileDialog.getOpenFileNames(
1570 self, caption, options=qfiledialog_options,
1571 filter='StationXML *.xml (*.xml *.XML);;All files (*)'))
1573 stations = [
1574 stationxml.load_xml(filename=str(x)).get_pyrocko_stations()
1575 for x in fns]
1577 for stat in stations:
1578 self.add_stations(stat)
1580 def add_traces(self, traces):
1581 if traces:
1582 mtf = pyrocko.pile.MemTracesFile(None, traces)
1583 self.pile.add_file(mtf)
1584 ticket = (self.pile, mtf)
1585 return ticket
1586 else:
1587 return (None, None)
1589 def release_data(self, tickets):
1590 for ticket in tickets:
1591 pile, mtf = ticket
1592 if pile is not None:
1593 pile.remove_file(mtf)
1595 def periodical(self):
1596 if self.menuitem_watch.isChecked():
1597 if self.pile.reload_modified():
1598 self.update()
1600 def get_pile(self):
1601 return self.pile
1603 def pile_changed(self, what):
1604 self.pile_has_changed = True
1605 self.pile_has_changed_signal.emit()
1606 if self.automatic_updates:
1607 self.update()
1609 def set_gathering(self, gather=None, order=None, color=None):
1611 if gather is None:
1612 def gather(tr):
1613 return tr.nslc_id
1615 if order is None:
1616 def order(a):
1617 return a
1619 if color is None:
1620 def color(tr):
1621 return tr.location
1623 self.gather = gather
1624 keys = self.pile.gather_keys(gather, self.trace_filter)
1625 self.color_gather = color
1626 self.color_keys = self.pile.gather_keys(color)
1627 previous_ntracks = self.ntracks
1628 self.set_ntracks(len(keys))
1630 if self.shown_tracks_range is None or \
1631 previous_ntracks == 0 or \
1632 self.show_all:
1634 low, high = 0, min(self.ntracks_shown_max, self.ntracks)
1635 key_at_top = None
1636 n = high-low
1638 else:
1639 low, high = self.shown_tracks_range
1640 key_at_top = self.track_keys[low]
1641 n = high-low
1643 self.track_keys = sorted(keys, key=order)
1645 if key_at_top is not None:
1646 try:
1647 ind = self.track_keys.index(key_at_top)
1648 low = ind
1649 high = low+n
1650 except Exception:
1651 pass
1653 self.set_tracks_range((low, high))
1655 self.key_to_row = dict(
1656 [(key, i) for (i, key) in enumerate(self.track_keys)])
1658 def inrange(x, r):
1659 return r[0] <= x and x < r[1]
1661 def trace_selector(trace):
1662 gt = self.gather(trace)
1663 return (
1664 gt in self.key_to_row and
1665 inrange(self.key_to_row[gt], self.shown_tracks_range))
1667 if self.trace_filter is not None:
1668 self.trace_selector = lambda x: \
1669 self.trace_filter(x) and trace_selector(x)
1670 else:
1671 self.trace_selector = trace_selector
1673 if self.tmin == working_system_time_range[0] and \
1674 self.tmax == working_system_time_range[1] or \
1675 self.show_all:
1677 tmin, tmax = self.pile.get_tmin(), self.pile.get_tmax()
1678 if tmin is not None and tmax is not None:
1679 tlen = (tmax - tmin)
1680 tpad = tlen * 5./self.width()
1681 self.set_time_range(tmin-tpad, tmax+tpad)
1683 def set_time_range(self, tmin, tmax):
1684 if tmin is None:
1685 tmin = initial_time_range[0]
1687 if tmax is None:
1688 tmax = initial_time_range[1]
1690 if tmin > tmax:
1691 tmin, tmax = tmax, tmin
1693 if tmin == tmax:
1694 tmin -= 1.
1695 tmax += 1.
1697 tmin = max(working_system_time_range[0], tmin)
1698 tmax = min(working_system_time_range[1], tmax)
1700 min_deltat = self.content_deltat_range()[0]
1701 if (tmax - tmin < min_deltat):
1702 m = (tmin + tmax) / 2.
1703 tmin = m - min_deltat/2.
1704 tmax = m + min_deltat/2.
1706 self.time_projection.set_in_range(tmin, tmax)
1707 self.tmin, self.tmax = tmin, tmax
1709 def get_time_range(self):
1710 return self.tmin, self.tmax
1712 def ypart(self, y):
1713 if y < self.ax_height:
1714 return -1
1715 elif y > self.height()-self.ax_height:
1716 return 1
1717 else:
1718 return 0
1720 def time_fractional_digits(self):
1721 min_deltat = self.content_deltat_range()[0]
1722 return min(9, max(1, int(-math.floor(math.log10(min_deltat)))+2))
1724 def write_markers(self, fn=None):
1725 caption = "Choose a file name to write markers"
1726 if not fn:
1727 fn, _ = fnpatch(qw.QFileDialog.getSaveFileName(
1728 self, caption, options=qfiledialog_options))
1729 if fn:
1730 Marker.save_markers(
1731 self.markers, fn,
1732 fdigits=self.time_fractional_digits())
1734 def write_selected_markers(self, fn=None):
1735 caption = "Choose a file name to write selected markers"
1736 if not fn:
1737 fn, _ = fnpatch(qw.QFileDialog.getSaveFileName(
1738 self, caption, options=qfiledialog_options))
1739 if fn:
1740 Marker.save_markers(
1741 self.iter_selected_markers(), fn,
1742 fdigits=self.time_fractional_digits())
1744 def read_events(self, fn=None):
1745 '''
1746 Open QFileDialog to open, read and add
1747 :py:class:`pyrocko.model.Event` instances and their marker
1748 representation to the pile viewer.
1749 '''
1750 caption = "Selet one or more files to open"
1751 if not fn:
1752 fn, _ = fnpatch(qw.QFileDialog.getOpenFileName(
1753 self, caption, options=qfiledialog_options))
1754 if fn:
1755 self.add_events(pyrocko.model.load_events(fn))
1757 self.associate_phases_to_events()
1759 def read_markers(self, fn=None):
1760 '''
1761 Open QFileDialog to open, read and add markers to the pile viewer.
1762 '''
1763 caption = "Selet one or more files to open"
1764 if not fn:
1765 fn, _ = fnpatch(qw.QFileDialog.getOpenFileName(
1766 self, caption, options=qfiledialog_options))
1767 if fn:
1768 self.add_markers(Marker.load_markers(fn))
1770 self.associate_phases_to_events()
1772 def associate_phases_to_events(self):
1773 associate_phases_to_events(self.markers)
1775 def add_marker(self, marker):
1776 # need index to inform QAbstactTableModel about upcoming change,
1777 # but have to restore current state in order to not cause problems
1778 self.markers.insert(marker)
1779 i = self.markers.remove(marker)
1781 self.begin_markers_add.emit(i, i)
1782 self.markers.insert(marker)
1783 self.end_markers_add.emit()
1784 self.markers_deltat_max = max(
1785 self.markers_deltat_max, marker.tmax - marker.tmin)
1787 def add_markers(self, markers):
1788 if not self.markers:
1789 self.begin_markers_add.emit(0, len(markers) - 1)
1790 self.markers.insert_many(markers)
1791 self.end_markers_add.emit()
1792 self.update_markers_deltat_max()
1793 else:
1794 for marker in markers:
1795 self.add_marker(marker)
1797 def update_markers_deltat_max(self):
1798 if self.markers:
1799 self.markers_deltat_max = max(
1800 marker.tmax - marker.tmin for marker in self.markers)
1802 def remove_marker(self, marker):
1803 '''
1804 Remove a ``marker`` from the :py:class:`PileViewer`.
1806 :param marker: :py:class:`Marker` (or subclass) instance
1807 '''
1809 if marker is self.active_event_marker:
1810 self.deactivate_event_marker()
1812 try:
1813 i = self.markers.index(marker)
1814 self.begin_markers_remove.emit(i, i)
1815 self.markers.remove_at(i)
1816 self.end_markers_remove.emit()
1817 except ValueError:
1818 pass
1820 def remove_markers(self, markers):
1821 '''
1822 Remove a list of ``markers`` from the :py:class:`PileViewer`.
1824 :param markers: list of :py:class:`Marker` (or subclass)
1825 instances
1826 '''
1828 if markers is self.markers:
1829 markers = list(markers)
1831 for marker in markers:
1832 self.remove_marker(marker)
1834 self.update_markers_deltat_max()
1836 def remove_selected_markers(self):
1837 def delete_segment(istart, iend):
1838 self.begin_markers_remove.emit(istart, iend-1)
1839 for _ in range(iend - istart):
1840 self.markers.remove_at(istart)
1842 self.end_markers_remove.emit()
1844 istart = None
1845 ipos = 0
1846 markers = self.markers
1847 nmarkers = len(self.markers)
1848 while ipos < nmarkers:
1849 marker = markers[ipos]
1850 if marker.is_selected():
1851 if marker is self.active_event_marker:
1852 self.deactivate_event_marker()
1854 if istart is None:
1855 istart = ipos
1856 else:
1857 if istart is not None:
1858 delete_segment(istart, ipos)
1859 nmarkers -= ipos - istart
1860 ipos = istart - 1
1861 istart = None
1863 ipos += 1
1865 if istart is not None:
1866 delete_segment(istart, ipos)
1868 self.update_markers_deltat_max()
1870 def selected_markers(self):
1871 return [marker for marker in self.markers if marker.is_selected()]
1873 def iter_selected_markers(self):
1874 for marker in self.markers:
1875 if marker.is_selected():
1876 yield marker
1878 def get_markers(self):
1879 return self.markers
1881 def mousePressEvent(self, mouse_ev):
1882 self.show_all = False
1883 point = self.mapFromGlobal(mouse_ev.globalPos())
1885 if mouse_ev.button() == qc.Qt.LeftButton:
1886 marker = self.marker_under_cursor(point.x(), point.y())
1887 if self.picking:
1888 if self.picking_down is None:
1889 self.picking_down = (
1890 self.time_projection.rev(mouse_ev.x()),
1891 mouse_ev.y())
1893 elif marker is not None:
1894 if not (mouse_ev.modifiers() & qc.Qt.ShiftModifier):
1895 self.deselect_all()
1896 marker.selected = True
1897 self.emit_selected_markers()
1898 self.update()
1899 else:
1900 self.track_start = mouse_ev.x(), mouse_ev.y()
1901 self.track_trange = self.tmin, self.tmax
1903 if mouse_ev.button() == qc.Qt.RightButton:
1904 self.menu.exec_(qg.QCursor.pos())
1905 self.update_status()
1907 def mouseReleaseEvent(self, mouse_ev):
1908 if self.ignore_releases:
1909 self.ignore_releases -= 1
1910 return
1912 if self.picking:
1913 self.stop_picking(mouse_ev.x(), mouse_ev.y())
1914 self.emit_selected_markers()
1916 if self.track_start:
1917 self.update()
1919 self.track_start = None
1920 self.track_trange = None
1921 self.update_status()
1923 def mouseDoubleClickEvent(self, mouse_ev):
1924 self.show_all = False
1925 self.start_picking(None)
1926 self.ignore_releases = 1
1928 def mouseMoveEvent(self, mouse_ev):
1929 self.setUpdatesEnabled(False)
1930 point = self.mapFromGlobal(mouse_ev.globalPos())
1932 if self.picking:
1933 self.update_picking(point.x(), point.y())
1935 elif self.track_start is not None:
1936 x0, y0 = self.track_start
1937 dx = (point.x() - x0)/float(self.width())
1938 dy = (point.y() - y0)/float(self.height())
1939 if self.ypart(y0) == 1:
1940 dy = 0
1942 tmin0, tmax0 = self.track_trange
1944 scale = math.exp(-dy*5.)
1945 dtr = scale*(tmax0-tmin0) - (tmax0-tmin0)
1946 frac = x0/float(self.width())
1947 dt = dx*(tmax0-tmin0)*scale
1949 self.interrupt_following()
1950 self.set_time_range(
1951 tmin0 - dt - dtr*frac,
1952 tmax0 - dt + dtr*(1.-frac))
1954 self.update()
1955 else:
1956 self.hoovering(point.x(), point.y())
1958 self.update_status()
1960 def nslc_ids_under_cursor(self, x, y):
1961 ftrack = self.track_to_screen.rev(y)
1962 nslc_ids = self.get_nslc_ids_for_track(ftrack)
1963 return nslc_ids
1965 def marker_under_cursor(self, x, y):
1966 mouset = self.time_projection.rev(x)
1967 deltat = (self.tmax-self.tmin)*self.click_tolerance/self.width()
1968 relevant_nslc_ids = None
1969 for marker in self.markers:
1970 if marker.kind not in self.visible_marker_kinds:
1971 continue
1973 if (abs(mouset-marker.tmin) < deltat or
1974 abs(mouset-marker.tmax) < deltat):
1976 if relevant_nslc_ids is None:
1977 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y)
1979 marker_nslc_ids = marker.get_nslc_ids()
1980 if not marker_nslc_ids:
1981 return marker
1983 for nslc_id in marker_nslc_ids:
1984 if nslc_id in relevant_nslc_ids:
1985 return marker
1987 def hoovering(self, x, y):
1988 mouset = self.time_projection.rev(x)
1989 deltat = (self.tmax-self.tmin)*self.click_tolerance/self.width()
1990 needupdate = False
1991 haveone = False
1992 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y)
1993 for marker in self.markers:
1994 if marker.kind not in self.visible_marker_kinds:
1995 continue
1997 state = abs(mouset-marker.tmin) < deltat or \
1998 abs(mouset-marker.tmax) < deltat and not haveone
2000 if state:
2001 xstate = False
2003 marker_nslc_ids = marker.get_nslc_ids()
2004 if not marker_nslc_ids:
2005 xstate = True
2007 for nslc in relevant_nslc_ids:
2008 if marker.match_nslc(nslc):
2009 xstate = True
2011 state = xstate
2013 if state:
2014 haveone = True
2015 oldstate = marker.is_alerted()
2016 if oldstate != state:
2017 needupdate = True
2018 marker.set_alerted(state)
2019 if state:
2020 self.message = marker.hoover_message()
2022 if not haveone:
2023 self.message = None
2025 if needupdate:
2026 self.update()
2028 def event(self, event):
2029 if event.type() == qc.QEvent.KeyPress:
2030 self.keyPressEvent(event)
2031 return True
2032 else:
2033 return base.event(self, event)
2035 def keyPressEvent(self, key_event):
2036 self.show_all = False
2037 dt = self.tmax - self.tmin
2038 tmid = (self.tmin + self.tmax) / 2.
2040 try:
2041 keytext = str(key_event.text())
2042 except UnicodeEncodeError:
2043 return
2045 if keytext == '?':
2046 self.help()
2048 elif keytext == ' ':
2049 self.interrupt_following()
2050 self.set_time_range(self.tmin+dt, self.tmax+dt)
2052 elif key_event.key() == qc.Qt.Key_Up:
2053 for m in self.selected_markers():
2054 if isinstance(m, PhaseMarker):
2055 if key_event.modifiers() & qc.Qt.ShiftModifier:
2056 p = 0
2057 else:
2058 p = 1 if m.get_polarity() != 1 else None
2059 m.set_polarity(p)
2061 elif key_event.key() == qc.Qt.Key_Down:
2062 for m in self.selected_markers():
2063 if isinstance(m, PhaseMarker):
2064 if key_event.modifiers() & qc.Qt.ShiftModifier:
2065 p = 0
2066 else:
2067 p = -1 if m.get_polarity() != -1 else None
2068 m.set_polarity(p)
2070 elif keytext == 'b':
2071 dt = self.tmax - self.tmin
2072 self.interrupt_following()
2073 self.set_time_range(self.tmin-dt, self.tmax-dt)
2075 elif key_event.key() in (qc.Qt.Key_Tab, qc.Qt.Key_Backtab):
2076 self.interrupt_following()
2078 tgo = None
2080 class TraceDummy(object):
2081 def __init__(self, marker):
2082 self._marker = marker
2084 @property
2085 def nslc_id(self):
2086 return self._marker.one_nslc()
2088 def marker_to_itrack(marker):
2089 try:
2090 return self.key_to_row.get(
2091 self.gather(TraceDummy(marker)), -1)
2093 except MarkerOneNSLCRequired:
2094 return -1
2096 emarker, pmarkers = self.get_active_markers()
2097 pmarkers = [
2098 m for m in pmarkers if m.kind in self.visible_marker_kinds]
2099 pmarkers.sort(key=lambda m: (
2100 marker_to_itrack(m), (m.tmin + m.tmax) / 2.0))
2102 if key_event.key() == qc.Qt.Key_Backtab:
2103 pmarkers.reverse()
2105 smarkers = self.selected_markers()
2106 iselected = []
2107 for sm in smarkers:
2108 try:
2109 iselected.append(pmarkers.index(sm))
2110 except ValueError:
2111 pass
2113 if iselected:
2114 icurrent = max(iselected) + 1
2115 else:
2116 icurrent = 0
2118 if icurrent < len(pmarkers):
2119 self.deselect_all()
2120 cmarker = pmarkers[icurrent]
2121 cmarker.selected = True
2122 tgo = cmarker.tmin
2123 if not self.tmin < tgo < self.tmax:
2124 self.set_time_range(tgo-dt/2., tgo+dt/2.)
2126 itrack = marker_to_itrack(cmarker)
2127 if itrack != -1:
2128 if itrack < self.shown_tracks_range[0]:
2129 self.scroll_tracks(
2130 - (self.shown_tracks_range[0] - itrack))
2131 elif self.shown_tracks_range[1] <= itrack:
2132 self.scroll_tracks(
2133 itrack - self.shown_tracks_range[1]+1)
2135 if itrack not in self.track_to_nslc_ids:
2136 self.go_to_selection()
2138 elif keytext in ('p', 'n', 'P', 'N'):
2139 smarkers = self.selected_markers()
2140 tgo = None
2141 dir = str(keytext)
2142 if smarkers:
2143 tmid = smarkers[0].tmin
2144 for smarker in smarkers:
2145 if dir == 'n':
2146 tmid = max(smarker.tmin, tmid)
2147 else:
2148 tmid = min(smarker.tmin, tmid)
2150 tgo = tmid
2152 if dir.lower() == 'n':
2153 for marker in sorted(
2154 self.markers,
2155 key=operator.attrgetter('tmin')):
2157 t = marker.tmin
2158 if t > tmid and \
2159 marker.kind in self.visible_marker_kinds and \
2160 (dir == 'n' or
2161 isinstance(marker, EventMarker)):
2163 self.deselect_all()
2164 marker.selected = True
2165 tgo = t
2166 break
2167 else:
2168 for marker in sorted(
2169 self.markers,
2170 key=operator.attrgetter('tmin'),
2171 reverse=True):
2173 t = marker.tmin
2174 if t < tmid and \
2175 marker.kind in self.visible_marker_kinds and \
2176 (dir == 'p' or
2177 isinstance(marker, EventMarker)):
2178 self.deselect_all()
2179 marker.selected = True
2180 tgo = t
2181 break
2183 if tgo is not None:
2184 self.interrupt_following()
2185 self.set_time_range(tgo-dt/2., tgo+dt/2.)
2187 elif keytext == 'q' or keytext == 'x':
2188 self.myclose(keytext)
2190 elif keytext == 'r':
2191 if self.pile.reload_modified():
2192 self.reloaded = True
2194 elif keytext == 'R':
2195 self.setup_snufflings()
2197 elif key_event.key() == qc.Qt.Key_Backspace:
2198 self.remove_selected_markers()
2200 elif keytext == 'a':
2201 for marker in self.markers:
2202 if ((self.tmin <= marker.tmin <= self.tmax or
2203 self.tmin <= marker.tmax <= self.tmax) and
2204 marker.kind in self.visible_marker_kinds):
2205 marker.selected = True
2206 else:
2207 marker.selected = False
2209 elif keytext == 'A':
2210 for marker in self.markers:
2211 if marker.kind in self.visible_marker_kinds:
2212 marker.selected = True
2214 elif keytext == 'd':
2215 self.deselect_all()
2217 elif keytext == 'E':
2218 self.deactivate_event_marker()
2220 elif keytext == 'e':
2221 markers = self.selected_markers()
2222 event_markers_in_spe = [
2223 marker for marker in markers
2224 if not isinstance(marker, PhaseMarker)]
2226 phase_markers = [
2227 marker for marker in markers
2228 if isinstance(marker, PhaseMarker)]
2230 if len(event_markers_in_spe) == 1:
2231 event_marker = event_markers_in_spe[0]
2232 if not isinstance(event_marker, EventMarker):
2233 nslcs = list(event_marker.nslc_ids)
2234 lat, lon = 0.0, 0.0
2235 old = self.get_active_event()
2236 if len(nslcs) == 1:
2237 lat, lon = self.station_latlon(NSLC(*nslcs[0]))
2238 elif old is not None:
2239 lat, lon = old.lat, old.lon
2241 event_marker.convert_to_event_marker(lat, lon)
2243 self.set_active_event_marker(event_marker)
2244 event = event_marker.get_event()
2245 for marker in phase_markers:
2246 marker.set_event(event)
2248 else:
2249 for marker in event_markers_in_spe:
2250 marker.convert_to_event_marker()
2252 elif keytext in ('0', '1', '2', '3', '4', '5'):
2253 for marker in self.selected_markers():
2254 marker.set_kind(int(keytext))
2255 self.emit_selected_markers()
2257 elif key_event.key() in fkey_map:
2258 self.handle_fkeys(key_event.key())
2260 elif key_event.key() == qc.Qt.Key_Escape:
2261 if self.picking:
2262 self.stop_picking(0, 0, abort=True)
2264 elif key_event.key() == qc.Qt.Key_PageDown:
2265 self.scroll_tracks(
2266 self.shown_tracks_range[1]-self.shown_tracks_range[0])
2268 elif key_event.key() == qc.Qt.Key_PageUp:
2269 self.scroll_tracks(
2270 self.shown_tracks_range[0]-self.shown_tracks_range[1])
2272 elif keytext == '+':
2273 self.zoom_tracks(0., 1.)
2275 elif keytext == '-':
2276 self.zoom_tracks(0., -1.)
2278 elif keytext == '=':
2279 ntracks_shown = self.shown_tracks_range[1] - \
2280 self.shown_tracks_range[0]
2281 dtracks = self.initial_ntracks_shown_max - ntracks_shown
2282 self.zoom_tracks(0., dtracks)
2284 elif keytext == ':':
2285 self.want_input.emit()
2287 elif keytext == 'f':
2288 if self.window().windowState() & qc.Qt.WindowFullScreen or \
2289 self.window().windowState() & qc.Qt.WindowMaximized:
2291 self.window().showNormal()
2292 else:
2293 if macosx:
2294 self.window().showMaximized()
2295 else:
2296 self.window().showFullScreen()
2298 elif keytext == 'g':
2299 self.go_to_selection()
2301 elif keytext == 'G':
2302 self.go_to_selection(tight=True)
2304 elif keytext == 'm':
2305 self.toggle_marker_editor()
2307 elif keytext == 'c':
2308 self.toggle_main_controls()
2310 elif key_event.key() in (qc.Qt.Key_Left, qc.Qt.Key_Right):
2311 dir = 1
2312 amount = 1
2313 if key_event.key() == qc.Qt.Key_Left:
2314 dir = -1
2315 if key_event.modifiers() & qc.Qt.ShiftModifier:
2316 amount = 10
2317 self.nudge_selected_markers(dir*amount)
2319 if keytext != '' and keytext in 'degaApPnN':
2320 self.emit_selected_markers()
2322 self.update()
2323 self.update_status()
2325 def handle_fkeys(self, key):
2326 self.set_phase_kind(
2327 self.selected_markers(),
2328 fkey_map[key] + 1)
2329 self.emit_selected_markers()
2331 def emit_selected_markers(self):
2332 ibounds = []
2333 last_selected = False
2334 for imarker, marker in enumerate(self.markers):
2335 this_selected = marker.is_selected()
2336 if this_selected != last_selected:
2337 ibounds.append(imarker)
2339 last_selected = this_selected
2341 if last_selected:
2342 ibounds.append(len(self.markers))
2344 chunks = list(zip(ibounds[::2], ibounds[1::2]))
2345 self.n_selected_markers = sum(
2346 chunk[1] - chunk[0] for chunk in chunks)
2347 self.marker_selection_changed.emit(chunks)
2349 def toggle_marker_editor(self):
2350 self.panel_parent.toggle_marker_editor()
2352 def toggle_main_controls(self):
2353 self.panel_parent.toggle_main_controls()
2355 def nudge_selected_markers(self, npixels):
2356 a, b = self.time_projection.ur
2357 c, d = self.time_projection.xr
2358 for marker in self.selected_markers():
2359 if not isinstance(marker, EventMarker):
2360 marker.tmin += npixels * (d-c)/b
2361 marker.tmax += npixels * (d-c)/b
2363 def about(self):
2364 fn = pyrocko.util.data_file('snuffler.png')
2365 with open(pyrocko.util.data_file('snuffler_about.html')) as f:
2366 txt = f.read()
2367 label = qw.QLabel(txt % {'logo': fn})
2368 label.setAlignment(qc.Qt.AlignVCenter | qc.Qt.AlignHCenter)
2369 self.show_doc('About', [label], target='tab')
2371 def help(self):
2372 class MyScrollArea(qw.QScrollArea):
2374 def sizeHint(self):
2375 s = qc.QSize()
2376 s.setWidth(self.widget().sizeHint().width())
2377 s.setHeight(self.widget().sizeHint().height())
2378 return s
2380 with open(pyrocko.util.data_file(
2381 'snuffler_help.html')) as f:
2382 hcheat = qw.QLabel(f.read())
2384 with open(pyrocko.util.data_file(
2385 'snuffler_help_epilog.html')) as f:
2386 hepilog = qw.QLabel(f.read())
2388 for h in [hcheat, hepilog]:
2389 h.setAlignment(qc.Qt.AlignTop | qc.Qt.AlignHCenter)
2390 h.setWordWrap(True)
2392 self.show_doc('Help', [hcheat, hepilog], target='panel')
2394 def show_doc(self, name, labels, target='panel'):
2395 scroller = qw.QScrollArea()
2396 frame = qw.QFrame(scroller)
2397 frame.setLineWidth(0)
2398 layout = qw.QVBoxLayout()
2399 layout.setContentsMargins(0, 0, 0, 0)
2400 layout.setSpacing(0)
2401 frame.setLayout(layout)
2402 scroller.setWidget(frame)
2403 scroller.setWidgetResizable(True)
2404 frame.setBackgroundRole(qg.QPalette.Base)
2405 for h in labels:
2406 h.setParent(frame)
2407 h.setMargin(3)
2408 h.setTextInteractionFlags(
2409 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse)
2410 h.setBackgroundRole(qg.QPalette.Base)
2411 layout.addWidget(h)
2412 h.linkActivated.connect(
2413 self.open_link)
2415 if self.panel_parent is not None:
2416 if target == 'panel':
2417 self.panel_parent.add_panel(
2418 name, scroller, True, volatile=False)
2419 else:
2420 self.panel_parent.add_tab(name, scroller)
2422 def open_link(self, link):
2423 qg.QDesktopServices.openUrl(qc.QUrl(link))
2425 def wheelEvent(self, wheel_event):
2426 if use_pyqt5:
2427 self.wheel_pos += wheel_event.angleDelta().y()
2428 else:
2429 self.wheel_pos += wheel_event.delta()
2431 n = self.wheel_pos // 120
2432 self.wheel_pos = self.wheel_pos % 120
2433 if n == 0:
2434 return
2436 amount = max(
2437 1.,
2438 abs(self.shown_tracks_range[0]-self.shown_tracks_range[1])/5.)
2439 wdelta = amount * n
2441 trmin, trmax = self.track_to_screen.get_in_range()
2442 anchor = (self.track_to_screen.rev(wheel_event.y())-trmin) \
2443 / (trmax-trmin)
2445 if wheel_event.modifiers() & qc.Qt.ControlModifier:
2446 self.zoom_tracks(anchor, wdelta)
2447 else:
2448 self.scroll_tracks(-wdelta)
2450 def dragEnterEvent(self, event):
2451 if event.mimeData().hasUrls():
2452 if any(url.toLocalFile() for url in event.mimeData().urls()):
2453 event.setDropAction(qc.Qt.LinkAction)
2454 event.accept()
2456 def dropEvent(self, event):
2457 if event.mimeData().hasUrls():
2458 paths = list(
2459 str(url.toLocalFile()) for url in event.mimeData().urls())
2460 event.acceptProposedAction()
2461 self.load(paths)
2463 def get_phase_name(self, kind):
2464 return self.config.get_phase_name(kind)
2466 def set_phase_kind(self, markers, kind):
2467 phasename = self.get_phase_name(kind)
2469 for marker in markers:
2470 if isinstance(marker, PhaseMarker):
2471 if kind == 10:
2472 marker.convert_to_marker()
2473 else:
2474 marker.set_phasename(phasename)
2475 marker.set_event(self.get_active_event())
2477 elif isinstance(marker, EventMarker):
2478 pass
2480 else:
2481 if kind != 10:
2482 event = self.get_active_event()
2483 marker.convert_to_phase_marker(
2484 event, phasename, None, False)
2486 def set_ntracks(self, ntracks):
2487 if self.ntracks != ntracks:
2488 self.ntracks = ntracks
2489 if self.shown_tracks_range is not None:
2490 l, h = self.shown_tracks_range
2491 else:
2492 l, h = 0, self.ntracks
2494 self.tracks_range_changed.emit(self.ntracks, l, h)
2496 def set_tracks_range(self, range, start=None):
2498 low, high = range
2499 low = min(self.ntracks-1, low)
2500 high = min(self.ntracks, high)
2501 low = max(0, low)
2502 high = max(1, high)
2504 if start is None:
2505 start = float(low)
2507 if self.shown_tracks_range != (low, high):
2508 self.shown_tracks_range = low, high
2509 self.shown_tracks_start = start
2511 self.tracks_range_changed.emit(self.ntracks, low, high)
2513 def scroll_tracks(self, shift):
2514 shown = self.shown_tracks_range
2515 shiftmin = -shown[0]
2516 shiftmax = self.ntracks-shown[1]
2517 shift = max(shiftmin, shift)
2518 shift = min(shiftmax, shift)
2519 shown = shown[0] + shift, shown[1] + shift
2521 self.set_tracks_range((int(shown[0]), int(shown[1])))
2523 self.update()
2525 def zoom_tracks(self, anchor, delta):
2526 ntracks_shown = self.shown_tracks_range[1] \
2527 - self.shown_tracks_range[0]
2529 if (ntracks_shown == 1 and delta <= 0) or \
2530 (ntracks_shown == self.ntracks and delta >= 0):
2531 return
2533 ntracks_shown += int(round(delta))
2534 ntracks_shown = min(max(1, ntracks_shown), self.ntracks)
2536 u = self.shown_tracks_start
2537 nu = max(0., u-anchor*delta)
2538 nv = nu + ntracks_shown
2539 if nv > self.ntracks:
2540 nu -= nv - self.ntracks
2541 nv -= nv - self.ntracks
2543 self.set_tracks_range((int(round(nu)), int(round(nv))), nu)
2545 self.ntracks_shown_max = self.shown_tracks_range[1] \
2546 - self.shown_tracks_range[0]
2548 self.update()
2550 def content_time_range(self):
2551 pile = self.get_pile()
2552 tmin, tmax = pile.get_tmin(), pile.get_tmax()
2553 if tmin is None:
2554 tmin = initial_time_range[0]
2555 if tmax is None:
2556 tmax = initial_time_range[1]
2558 return tmin, tmax
2560 def content_deltat_range(self):
2561 pile = self.get_pile()
2563 deltatmin, deltatmax = pile.get_deltatmin(), pile.get_deltatmax()
2565 if deltatmin is None:
2566 deltatmin = 0.001
2568 if deltatmax is None:
2569 deltatmax = 1000.0
2571 return deltatmin, deltatmax
2573 def make_good_looking_time_range(self, tmin, tmax, tight=False):
2574 if tmax < tmin:
2575 tmin, tmax = tmax, tmin
2577 deltatmin = self.content_deltat_range()[0]
2578 dt = deltatmin * self.visible_length * 0.95
2580 if dt == 0.0:
2581 dt = 1.0
2583 if tight:
2584 if tmax != tmin:
2585 dtm = tmax - tmin
2586 tmin -= dtm*0.1
2587 tmax += dtm*0.1
2588 return tmin, tmax
2589 else:
2590 tcenter = (tmin + tmax) / 2.
2591 tmin = tcenter - 0.5*dt
2592 tmax = tcenter + 0.5*dt
2593 return tmin, tmax
2595 if tmax-tmin < dt:
2596 vmin, vmax = self.get_time_range()
2597 dt = min(vmax - vmin, dt)
2599 tcenter = (tmin+tmax)/2.
2600 etmin, etmax = tmin, tmax
2601 tmin = min(etmin, tcenter - 0.5*dt)
2602 tmax = max(etmax, tcenter + 0.5*dt)
2603 dtm = tmax-tmin
2604 if etmin == tmin:
2605 tmin -= dtm*0.1
2606 if etmax == tmax:
2607 tmax += dtm*0.1
2609 else:
2610 dtm = tmax-tmin
2611 tmin -= dtm*0.1
2612 tmax += dtm*0.1
2614 return tmin, tmax
2616 def go_to_selection(self, tight=False):
2617 markers = self.selected_markers()
2618 if markers:
2619 tmax, tmin = self.content_time_range()
2620 for marker in markers:
2621 tmin = min(tmin, marker.tmin)
2622 tmax = max(tmax, marker.tmax)
2624 else:
2625 if tight:
2626 vmin, vmax = self.get_time_range()
2627 tmin = tmax = (vmin + vmax) / 2.
2628 else:
2629 tmin, tmax = self.content_time_range()
2631 tmin, tmax = self.make_good_looking_time_range(
2632 tmin, tmax, tight=tight)
2634 self.interrupt_following()
2635 self.set_time_range(tmin, tmax)
2636 self.update()
2638 def go_to_time(self, t, tlen=None):
2639 tmax = t
2640 if tlen is not None:
2641 tmax = t+tlen
2642 tmin, tmax = self.make_good_looking_time_range(t, tmax)
2643 self.interrupt_following()
2644 self.set_time_range(tmin, tmax)
2645 self.update()
2647 def go_to_event_by_name(self, name):
2648 for marker in self.markers:
2649 if isinstance(marker, EventMarker):
2650 event = marker.get_event()
2651 if event.name and event.name.lower() == name.lower():
2652 tmin, tmax = self.make_good_looking_time_range(
2653 event.time, event.time)
2655 self.interrupt_following()
2656 self.set_time_range(tmin, tmax)
2658 def printit(self):
2659 from .qt_compat import qprint
2660 printer = qprint.QPrinter()
2661 printer.setOrientation(qprint.QPrinter.Landscape)
2663 dialog = qprint.QPrintDialog(printer, self)
2664 dialog.setWindowTitle('Print')
2666 if dialog.exec_() != qw.QDialog.Accepted:
2667 return
2669 painter = qg.QPainter()
2670 painter.begin(printer)
2671 page = printer.pageRect()
2672 self.drawit(
2673 painter, printmode=False, w=page.width(), h=page.height())
2675 painter.end()
2677 def savesvg(self, fn=None):
2679 if not fn:
2680 fn, _ = fnpatch(qw.QFileDialog.getSaveFileName(
2681 self,
2682 'Save as SVG|PNG',
2683 os.path.expanduser(os.path.join('~', 'untitled.svg')),
2684 'SVG|PNG (*.svg *.png)',
2685 options=qfiledialog_options))
2687 if fn == '':
2688 return
2690 if str(fn).endswith('.svg'):
2691 w, h = 842, 595
2692 margin = 0.025
2693 m = max(w, h)*margin
2695 generator = qsvg.QSvgGenerator()
2696 generator.setFileName(fn)
2697 generator.setSize(qc.QSize(w, h))
2698 generator.setViewBox(qc.QRectF(-m, -m, w+2*m, h+2*m))
2700 painter = qg.QPainter()
2701 painter.begin(generator)
2702 self.drawit(painter, printmode=False, w=w, h=h)
2703 painter.end()
2705 elif str(fn).endswith('.png'):
2706 if use_pyqt5:
2707 pixmap = self.grab()
2708 else:
2709 pixmap = qg.QPixmap().grabWidget(self)
2711 pixmap.save(fn)
2713 else:
2714 logger.warning('unsupported file type')
2716 def paintEvent(self, paint_ev):
2717 '''
2718 Called by QT whenever widget needs to be painted.
2719 '''
2720 painter = qg.QPainter(self)
2722 if self.menuitem_antialias.isChecked():
2723 painter.setRenderHint(qg.QPainter.Antialiasing)
2725 self.drawit(painter)
2727 logger.debug(
2728 'Time spent drawing: %.3f %.3f %.3f %.3f %.3f' %
2729 (self.timer_draw - self.timer_cutout))
2731 logger.debug(
2732 'Time spent processing: %.3f %.3f %.3f %.3f %.3f' %
2733 self.timer_cutout.get())
2735 self.time_spent_painting = self.timer_draw.get()[-1]
2736 self.time_last_painted = time.time()
2738 def determine_box_styles(self):
2740 traces = list(self.pile.iter_traces())
2741 traces.sort(key=operator.attrgetter('full_id'))
2742 istyle = 0
2743 trace_styles = {}
2744 for itr, tr in enumerate(traces):
2745 if itr > 0:
2746 other = traces[itr-1]
2747 if not (
2748 other.nslc_id == tr.nslc_id
2749 and other.deltat == tr.deltat
2750 and abs(other.tmax - tr.tmin)
2751 < gap_lap_tolerance*tr.deltat):
2753 istyle += 1
2755 trace_styles[tr.full_id, tr.deltat] = istyle
2757 self.trace_styles = trace_styles
2759 def draw_trace_boxes(self, p, time_projection, track_projections):
2761 for v_projection in track_projections.values():
2762 v_projection.set_in_range(0., 1.)
2764 def selector(x):
2765 return x.overlaps(*time_projection.get_in_range())
2767 if self.trace_filter is not None:
2768 def tselector(x):
2769 return selector(x) and self.trace_filter(x)
2771 else:
2772 tselector = selector
2774 traces = list(self.pile.iter_traces(
2775 group_selector=selector, trace_selector=tselector))
2777 traces.sort(key=operator.attrgetter('full_id'))
2779 def drawbox(itrack, istyle, traces):
2780 v_projection = track_projections[itrack]
2781 dvmin = v_projection(0.)
2782 dvmax = v_projection(1.)
2783 dtmin = time_projection.clipped(traces[0].tmin)
2784 dtmax = time_projection.clipped(traces[-1].tmax)
2786 style = box_styles[istyle % len(box_styles)]
2787 rect = qc.QRectF(dtmin, dvmin, float(dtmax-dtmin), dvmax-dvmin)
2788 p.fillRect(rect, style.fill_brush)
2789 p.setPen(style.frame_pen)
2790 p.drawRect(rect)
2792 traces_by_style = {}
2793 for itr, tr in enumerate(traces):
2794 gt = self.gather(tr)
2795 if gt not in self.key_to_row:
2796 continue
2798 itrack = self.key_to_row[gt]
2799 if itrack not in track_projections:
2800 continue
2802 istyle = self.trace_styles.get((tr.full_id, tr.deltat), 0)
2804 if len(traces) < 500:
2805 drawbox(itrack, istyle, [tr])
2806 else:
2807 if (itrack, istyle) not in traces_by_style:
2808 traces_by_style[itrack, istyle] = []
2809 traces_by_style[itrack, istyle].append(tr)
2811 for (itrack, istyle), traces in traces_by_style.items():
2812 drawbox(itrack, istyle, traces)
2814 def draw_visible_markers(
2815 self, p, vcenter_projection, primary_pen):
2817 try:
2818 markers = self.markers.with_key_in_limited(
2819 self.tmin - self.markers_deltat_max, self.tmax, 2000)
2821 except pyrocko.pile.TooMany:
2822 tmin = self.markers[0].tmin
2823 tmax = self.markers[-1].tmax
2824 umin_view, umax_view = self.time_projection.get_out_range()
2825 umin = max(umin_view, self.time_projection(tmin))
2826 umax = min(umax_view, self.time_projection(tmax))
2827 v0, _ = vcenter_projection.get_out_range()
2828 label_bg = qg.QBrush(qg.QColor(255, 255, 255))
2830 p.save()
2832 pen = qg.QPen(primary_pen)
2833 pen.setWidth(2)
2834 pen.setStyle(qc.Qt.DotLine)
2835 # pat = [5., 3.]
2836 # pen.setDashPattern(pat)
2837 p.setPen(pen)
2839 if self.n_selected_markers == len(self.markers):
2840 s_selected = ' (all selected)'
2841 elif self.n_selected_markers > 0:
2842 s_selected = ' (%i selected)' % self.n_selected_markers
2843 else:
2844 s_selected = ''
2846 draw_label(
2847 p, umin+10., v0-10.,
2848 '%i Markers' % len(self.markers) + s_selected,
2849 label_bg, 'LB')
2851 line = qc.QLineF(umin, v0, umax, v0)
2852 p.drawLine(line)
2853 p.restore()
2855 return
2857 for marker in markers:
2858 if marker.tmin < self.tmax and self.tmin < marker.tmax \
2859 and marker.kind in self.visible_marker_kinds:
2861 marker.draw(
2862 p, self.time_projection, vcenter_projection,
2863 with_label=True)
2865 def drawit(self, p, printmode=False, w=None, h=None):
2866 '''
2867 This performs the actual drawing.
2868 '''
2870 self.timer_draw.start()
2872 if self.gather is None:
2873 self.set_gathering()
2875 if self.pile_has_changed:
2877 if not self.sortingmode_change_delayed():
2878 self.sortingmode_change()
2880 if self.menuitem_showboxes.isChecked():
2881 self.determine_box_styles()
2883 self.pile_has_changed = False
2885 if h is None:
2886 h = float(self.height())
2887 if w is None:
2888 w = float(self.width())
2890 if printmode:
2891 primary_color = (0, 0, 0)
2892 else:
2893 primary_color = pyrocko.plot.tango_colors['aluminium5']
2895 primary_pen = qg.QPen(qg.QColor(*primary_color))
2897 ax_h = float(self.ax_height)
2899 vbottom_ax_projection = Projection()
2900 vtop_ax_projection = Projection()
2901 vcenter_projection = Projection()
2903 self.time_projection.set_out_range(0., w)
2904 vbottom_ax_projection.set_out_range(h-ax_h, h)
2905 vtop_ax_projection.set_out_range(0., ax_h)
2906 vcenter_projection.set_out_range(ax_h, h-ax_h)
2907 vcenter_projection.set_in_range(0., 1.)
2908 self.track_to_screen.set_out_range(ax_h, h-ax_h)
2910 self.track_to_screen.set_in_range(*self.shown_tracks_range)
2911 track_projections = {}
2912 for i in range(*self.shown_tracks_range):
2913 proj = Projection()
2914 proj.set_out_range(
2915 self.track_to_screen(i+0.05),
2916 self.track_to_screen(i+1.-0.05))
2918 track_projections[i] = proj
2920 if self.tmin < self.tmax:
2921 self.time_projection.set_in_range(self.tmin, self.tmax)
2922 vbottom_ax_projection.set_in_range(0, ax_h)
2924 self.tax.drawit(p, self.time_projection, vbottom_ax_projection)
2926 yscaler = pyrocko.plot.AutoScaler()
2927 if not printmode and self.menuitem_showboxes.isChecked():
2928 self.draw_trace_boxes(
2929 p, self.time_projection, track_projections)
2931 if self.floating_marker:
2932 self.floating_marker.draw(
2933 p, self.time_projection, vcenter_projection)
2935 self.draw_visible_markers(
2936 p, vcenter_projection, primary_pen)
2938 p.setPen(primary_pen)
2940 font = qg.QFont()
2941 font.setBold(True)
2943 axannotfont = qg.QFont()
2944 axannotfont.setBold(True)
2945 axannotfont.setPointSize(8)
2947 p.setFont(font)
2948 label_bg = qg.QBrush(qg.QColor(255, 255, 255, 100))
2950 processed_traces = self.prepare_cutout2(
2951 self.tmin, self.tmax,
2952 trace_selector=self.trace_selector,
2953 degap=self.menuitem_degap.isChecked(),
2954 demean=self.menuitem_demean.isChecked())
2956 color_lookup = dict(
2957 [(k, i) for (i, k) in enumerate(self.color_keys)])
2959 self.track_to_nslc_ids = {}
2960 nticks = 0
2961 annot_labels = []
2962 if processed_traces:
2963 show_scales = self.menuitem_showscalerange.isChecked() \
2964 or self.menuitem_showscaleaxis.isChecked()
2966 fm = qg.QFontMetrics(axannotfont, p.device())
2967 trackheight = self.track_to_screen(1.-0.05) \
2968 - self.track_to_screen(0.05)
2970 nlinesavail = trackheight/float(fm.lineSpacing())
2971 if self.menuitem_showscaleaxis.isChecked():
2972 nticks = max(3, min(nlinesavail * 0.5, 15))
2973 else:
2974 nticks = 15
2976 yscaler = pyrocko.plot.AutoScaler(
2977 no_exp_interval=(-3, 2), approx_ticks=nticks,
2978 snap=show_scales
2979 and not self.menuitem_showscaleaxis.isChecked())
2981 data_ranges = pyrocko.trace.minmax(
2982 processed_traces,
2983 key=self.scaling_key,
2984 mode=self.scaling_base)
2986 if not self.menuitem_fixscalerange.isChecked():
2987 self.old_data_ranges = data_ranges
2988 else:
2989 data_ranges.update(self.old_data_ranges)
2991 self.apply_scaling_hooks(data_ranges)
2993 trace_to_itrack = {}
2994 track_scaling_keys = {}
2995 track_scaling_colors = {}
2996 for trace in processed_traces:
2997 gt = self.gather(trace)
2998 if gt not in self.key_to_row:
2999 continue
3001 itrack = self.key_to_row[gt]
3002 if itrack not in track_projections:
3003 continue
3005 trace_to_itrack[trace] = itrack
3007 if itrack not in self.track_to_nslc_ids:
3008 self.track_to_nslc_ids[itrack] = set()
3010 self.track_to_nslc_ids[itrack].add(trace.nslc_id)
3012 if itrack not in track_scaling_keys:
3013 track_scaling_keys[itrack] = set()
3015 scaling_key = self.scaling_key(trace)
3016 track_scaling_keys[itrack].add(scaling_key)
3018 color = pyrocko.plot.color(
3019 color_lookup[self.color_gather(trace)])
3021 k = itrack, scaling_key
3022 if k not in track_scaling_colors \
3023 and self.menuitem_colortraces.isChecked():
3024 track_scaling_colors[k] = color
3025 else:
3026 track_scaling_colors[k] = primary_color
3028 # y axes, zero lines
3030 trace_projections = {}
3031 for itrack in list(track_projections.keys()):
3032 if itrack not in track_scaling_keys:
3033 continue
3034 uoff = 0
3035 for scaling_key in track_scaling_keys[itrack]:
3036 data_range = data_ranges[scaling_key]
3037 dymin, dymax = data_range
3038 ymin, ymax, yinc = yscaler.make_scale(
3039 (dymin/self.gain, dymax/self.gain))
3040 iexp = yscaler.make_exp(yinc)
3041 factor = 10**iexp
3042 trace_projection = track_projections[itrack].copy()
3043 trace_projection.set_in_range(ymax, ymin)
3044 trace_projections[itrack, scaling_key] = \
3045 trace_projection
3046 umin, umax = self.time_projection.get_out_range()
3047 vmin, vmax = trace_projection.get_out_range()
3048 umax_zeroline = umax
3049 uoffnext = uoff
3051 if show_scales:
3052 pen = qg.QPen(primary_pen)
3053 k = itrack, scaling_key
3054 if k in track_scaling_colors:
3055 c = qg.QColor(*track_scaling_colors[
3056 itrack, scaling_key])
3058 pen.setColor(c)
3060 p.setPen(pen)
3061 if nlinesavail > 3:
3062 if self.menuitem_showscaleaxis.isChecked():
3063 ymin_annot = math.ceil(ymin/yinc)*yinc
3064 ny_annot = int(
3065 math.floor(ymax/yinc)
3066 - math.ceil(ymin/yinc)) + 1
3068 for iy_annot in range(ny_annot):
3069 y = ymin_annot + iy_annot*yinc
3070 v = trace_projection(y)
3071 line = qc.QLineF(
3072 umax-10-uoff, v, umax-uoff, v)
3074 p.drawLine(line)
3075 if iy_annot == ny_annot - 1 \
3076 and iexp != 0:
3078 sexp = ' × ' \
3079 '10<sup>%i</sup>' % iexp
3080 else:
3081 sexp = ''
3083 snum = num_to_html(y/factor)
3084 lab = Label(
3085 p,
3086 umax-20-uoff,
3087 v, '%s%s' % (snum, sexp),
3088 label_bg=None,
3089 anchor='MR',
3090 font=axannotfont,
3091 color=c)
3093 uoffnext = max(
3094 lab.rect.width()+30.,
3095 uoffnext)
3097 annot_labels.append(lab)
3098 if y == 0.:
3099 umax_zeroline = \
3100 umax - 20 \
3101 - lab.rect.width() - 10 \
3102 - uoff
3103 else:
3104 if not self.menuitem_showboxes\
3105 .isChecked():
3106 qpoints = make_QPolygonF(
3107 [umax-20-uoff,
3108 umax-10-uoff,
3109 umax-10-uoff,
3110 umax-20-uoff],
3111 [vmax, vmax, vmin, vmin])
3112 p.drawPolyline(qpoints)
3114 snum = num_to_html(ymin)
3115 labmin = Label(
3116 p, umax-15-uoff, vmax, snum,
3117 label_bg=None,
3118 anchor='BR',
3119 font=axannotfont,
3120 color=c)
3122 annot_labels.append(labmin)
3123 snum = num_to_html(ymax)
3124 labmax = Label(
3125 p, umax-15-uoff, vmin, snum,
3126 label_bg=None,
3127 anchor='TR',
3128 font=axannotfont,
3129 color=c)
3131 annot_labels.append(labmax)
3133 for lab in (labmin, labmax):
3134 uoffnext = max(
3135 lab.rect.width()+10., uoffnext)
3137 if self.menuitem_showzeroline.isChecked():
3138 v = trace_projection(0.)
3139 if vmin <= v <= vmax:
3140 line = qc.QLineF(umin, v, umax_zeroline, v)
3141 p.drawLine(line)
3143 uoff = uoffnext
3145 p.setFont(font)
3146 p.setPen(primary_pen)
3147 for trace in processed_traces:
3148 if trace not in trace_to_itrack:
3149 continue
3151 itrack = trace_to_itrack[trace]
3152 scaling_key = self.scaling_key(trace)
3153 trace_projection = trace_projections[
3154 itrack, scaling_key]
3156 vdata = trace_projection(trace.get_ydata())
3158 udata_min = float(self.time_projection(trace.tmin))
3159 udata_max = float(self.time_projection(
3160 trace.tmin+trace.deltat*(vdata.size-1)))
3161 udata = num.linspace(udata_min, udata_max, vdata.size)
3163 qpoints = make_QPolygonF(udata, vdata)
3165 umin, umax = self.time_projection.get_out_range()
3166 vmin, vmax = trace_projection.get_out_range()
3168 trackrect = qc.QRectF(umin, vmin, umax-umin, vmax-vmin)
3170 if self.menuitem_cliptraces.isChecked():
3171 p.setClipRect(trackrect)
3173 if self.menuitem_colortraces.isChecked():
3174 color = pyrocko.plot.color(
3175 color_lookup[self.color_gather(trace)])
3176 pen = qg.QPen(qg.QColor(*color), 1)
3177 p.setPen(pen)
3179 p.drawPolyline(qpoints)
3181 if self.floating_marker:
3182 self.floating_marker.draw_trace(
3183 self, p, trace,
3184 self.time_projection, trace_projection, 1.0)
3186 for marker in self.markers.with_key_in(
3187 self.tmin - self.markers_deltat_max,
3188 self.tmax):
3190 if marker.tmin < self.tmax \
3191 and self.tmin < marker.tmax \
3192 and marker.kind \
3193 in self.visible_marker_kinds:
3194 marker.draw_trace(
3195 self, p, trace, self.time_projection,
3196 trace_projection, 1.0)
3198 p.setPen(primary_pen)
3200 if self.menuitem_cliptraces.isChecked():
3201 p.setClipRect(0, 0, w, h)
3203 p.setPen(primary_pen)
3205 while font.pointSize() > 2:
3206 fm = qg.QFontMetrics(font, p.device())
3207 trackheight = self.track_to_screen(1.-0.05) \
3208 - self.track_to_screen(0.05)
3209 nlinesavail = trackheight/float(fm.lineSpacing())
3210 if nlinesavail > 1:
3211 break
3213 font.setPointSize(font.pointSize()-1)
3215 p.setFont(font)
3216 for key in self.track_keys:
3217 itrack = self.key_to_row[key]
3218 if itrack in track_projections:
3219 plabel = ' '.join(
3220 [str(x) for x in key if x is not None])
3221 lx = 10
3222 ly = self.track_to_screen(itrack+0.5)
3223 draw_label(p, lx, ly, plabel, label_bg, 'ML')
3225 for lab in annot_labels:
3226 lab.draw()
3228 self.timer_draw.stop()
3230 def see_data_params(self):
3232 min_deltat = self.content_deltat_range()[0]
3234 # determine padding and downampling requirements
3235 if self.lowpass is not None:
3236 deltat_target = 1./self.lowpass * 0.25
3237 ndecimate = min(
3238 50,
3239 max(1, int(round(deltat_target / min_deltat))))
3240 tpad = 1./self.lowpass * 2.
3241 else:
3242 ndecimate = 1
3243 tpad = min_deltat*5.
3245 if self.highpass is not None:
3246 tpad = max(1./self.highpass * 2., tpad)
3248 nsee_points_per_trace = 5000*10
3249 tsee = ndecimate*nsee_points_per_trace*min_deltat
3251 return ndecimate, tpad, tsee
3253 def clean_update(self):
3254 self.old_processed_traces = None
3255 self.update()
3257 def get_adequate_tpad(self):
3258 tpad = 0.
3259 for f in [self.highpass, self.lowpass]:
3260 if f is not None:
3261 tpad = max(tpad, 1.0/f)
3263 for snuffling in self.snufflings:
3264 if snuffling._post_process_hook_enabled \
3265 or snuffling._pre_process_hook_enabled:
3267 tpad = max(tpad, snuffling.get_tpad())
3269 return tpad
3271 def prepare_cutout2(
3272 self, tmin, tmax, trace_selector=None, degap=True,
3273 demean=True, nmax=6000):
3275 if self.pile.is_empty():
3276 return []
3278 nmax = self.visible_length
3280 self.timer_cutout.start()
3282 tsee = tmax-tmin
3283 min_deltat_wo_decimate = tsee/nmax
3284 min_deltat_w_decimate = min_deltat_wo_decimate / 32.
3286 min_deltat_allow = min_deltat_wo_decimate
3287 if self.lowpass is not None:
3288 target_deltat_lp = 0.25/self.lowpass
3289 if target_deltat_lp > min_deltat_wo_decimate:
3290 min_deltat_allow = min_deltat_w_decimate
3292 min_deltat_allow = math.exp(
3293 int(math.floor(math.log(min_deltat_allow))))
3295 tmin_ = tmin
3296 tmax_ = tmax
3298 # fetch more than needed?
3299 if self.menuitem_liberal_fetch.isChecked():
3300 tlen = pyrocko.trace.nextpow2((tmax-tmin)*1.5)
3301 tmin = math.floor(tmin/tlen) * tlen
3302 tmax = math.ceil(tmax/tlen) * tlen
3304 fft_filtering = self.menuitem_fft_filtering.isChecked()
3305 lphp = self.menuitem_lphp.isChecked()
3306 ads = self.menuitem_allowdownsampling.isChecked()
3308 tpad = self.get_adequate_tpad()
3309 tpad = max(tpad, tsee)
3311 # state vector to decide if cached traces can be used
3312 vec = (
3313 tmin, tmax, tpad, trace_selector, degap, demean, self.lowpass,
3314 self.highpass, fft_filtering, lphp,
3315 min_deltat_allow, self.rotate, self.shown_tracks_range,
3316 ads, self.pile.get_update_count())
3318 if (self.old_vec
3319 and self.old_vec[0] <= vec[0]
3320 and vec[1] <= self.old_vec[1]
3321 and vec[2:] == self.old_vec[2:]
3322 and not (self.reloaded or self.menuitem_watch.isChecked())
3323 and self.old_processed_traces is not None):
3325 logger.debug('Using cached traces')
3326 processed_traces = self.old_processed_traces
3328 else:
3329 self.old_vec = vec
3331 processed_traces = []
3333 if self.pile.deltatmax >= min_deltat_allow:
3335 def group_selector(gr):
3336 return gr.deltatmax >= min_deltat_allow
3338 if trace_selector is not None:
3339 def trace_selectorx(tr):
3340 return tr.deltat >= min_deltat_allow \
3341 and trace_selector(tr)
3342 else:
3343 def trace_selectorx(tr):
3344 return tr.deltat >= min_deltat_allow
3346 for traces in self.pile.chopper(
3347 tmin=tmin, tmax=tmax, tpad=tpad,
3348 want_incomplete=True,
3349 degap=degap,
3350 maxgap=gap_lap_tolerance,
3351 maxlap=gap_lap_tolerance,
3352 keep_current_files_open=True,
3353 group_selector=group_selector,
3354 trace_selector=trace_selectorx,
3355 accessor_id=id(self),
3356 snap=(math.floor, math.ceil),
3357 include_last=True):
3359 if demean:
3360 for tr in traces:
3361 if (tr.meta and tr.meta.get('tabu', False)):
3362 continue
3363 y = tr.get_ydata()
3364 tr.set_ydata(y - num.mean(y))
3366 traces = self.pre_process_hooks(traces)
3368 for trace in traces:
3370 if not (trace.meta
3371 and trace.meta.get('tabu', False)):
3373 if fft_filtering:
3374 but = pyrocko.trace.ButterworthResponse
3375 multres = pyrocko.trace.MultiplyResponse
3376 if self.lowpass is not None \
3377 or self.highpass is not None:
3379 it = num.arange(
3380 trace.data_len(), dtype=float)
3381 detr_data, m, b = detrend(
3382 it, trace.get_ydata())
3384 trace.set_ydata(detr_data)
3386 freqs, fdata = trace.spectrum(
3387 pad_to_pow2=True, tfade=None)
3389 nfreqs = fdata.size
3391 key = (trace.deltat, nfreqs)
3393 if key not in self.tf_cache:
3394 resps = []
3395 if self.lowpass is not None:
3396 resps.append(but(
3397 order=4,
3398 corner=self.lowpass,
3399 type='low'))
3401 if self.highpass is not None:
3402 resps.append(but(
3403 order=4,
3404 corner=self.highpass,
3405 type='high'))
3407 resp = multres(resps)
3408 self.tf_cache[key] = \
3409 resp.evaluate(freqs)
3411 filtered_data = num.fft.irfft(
3412 fdata*self.tf_cache[key]
3413 )[:trace.data_len()]
3415 retrended_data = retrend(
3416 it, filtered_data, m, b)
3418 trace.set_ydata(retrended_data)
3420 else:
3422 if ads and self.lowpass is not None:
3423 while trace.deltat \
3424 < min_deltat_wo_decimate:
3426 trace.downsample(2, demean=False)
3428 fmax = 0.5/trace.deltat
3429 if not lphp and (
3430 self.lowpass is not None
3431 and self.highpass is not None
3432 and self.lowpass < fmax
3433 and self.highpass < fmax
3434 and self.highpass < self.lowpass):
3436 trace.bandpass(
3437 2, self.highpass, self.lowpass)
3438 else:
3439 if self.lowpass is not None:
3440 if self.lowpass < 0.5/trace.deltat:
3441 trace.lowpass(
3442 4, self.lowpass,
3443 demean=False)
3445 if self.highpass is not None:
3446 if self.lowpass is None \
3447 or self.highpass \
3448 < self.lowpass:
3450 if self.highpass < \
3451 0.5/trace.deltat:
3452 trace.highpass(
3453 4, self.highpass,
3454 demean=False)
3456 processed_traces.append(trace)
3458 if self.rotate != 0.0:
3459 phi = self.rotate/180.*math.pi
3460 cphi = math.cos(phi)
3461 sphi = math.sin(phi)
3462 for a in processed_traces:
3463 for b in processed_traces:
3464 if (a.network == b.network
3465 and a.station == b.station
3466 and a.location == b.location
3467 and ((a.channel.lower().endswith('n')
3468 and b.channel.lower().endswith('e'))
3469 or (a.channel.endswith('1')
3470 and b.channel.endswith('2')))
3471 and abs(a.deltat-b.deltat) < a.deltat*0.001
3472 and abs(a.tmin-b.tmin) < a.deltat*0.01 and
3473 len(a.get_ydata()) == len(b.get_ydata())):
3475 aydata = a.get_ydata()*cphi+b.get_ydata()*sphi
3476 bydata = -a.get_ydata()*sphi+b.get_ydata()*cphi
3477 a.set_ydata(aydata)
3478 b.set_ydata(bydata)
3480 processed_traces = self.post_process_hooks(processed_traces)
3482 self.old_processed_traces = processed_traces
3484 chopped_traces = []
3485 for trace in processed_traces:
3486 try:
3487 ctrace = trace.chop(
3488 tmin_-trace.deltat*4., tmax_+trace.deltat*4.,
3489 inplace=False)
3491 except pyrocko.trace.NoData:
3492 continue
3494 if ctrace.data_len() < 2:
3495 continue
3497 chopped_traces.append(ctrace)
3499 self.timer_cutout.stop()
3500 return chopped_traces
3502 def pre_process_hooks(self, traces):
3503 for snuffling in self.snufflings:
3504 if snuffling._pre_process_hook_enabled:
3505 traces = snuffling.pre_process_hook(traces)
3507 return traces
3509 def post_process_hooks(self, traces):
3510 for snuffling in self.snufflings:
3511 if snuffling._post_process_hook_enabled:
3512 traces = snuffling.post_process_hook(traces)
3514 return traces
3516 def visible_length_change(self, ignore=None):
3517 for menuitem, vlen in self.menuitems_visible_length:
3518 if menuitem.isChecked():
3519 self.visible_length = vlen
3521 def scaling_base_change(self, ignore=None):
3522 for menuitem, scaling_base in self.menuitems_scaling_base:
3523 if menuitem.isChecked():
3524 self.scaling_base = scaling_base
3526 def scalingmode_change(self, ignore=None):
3527 for menuitem, scaling_key in self.menuitems_scaling:
3528 if menuitem.isChecked():
3529 self.scaling_key = scaling_key
3531 def apply_scaling_hooks(self, data_ranges):
3532 for k in sorted(self.scaling_hooks.keys()):
3533 hook = self.scaling_hooks[k]
3534 hook(data_ranges)
3536 def set_scaling_hook(self, k, hook):
3537 self.scaling_hooks[k] = hook
3539 def remove_scaling_hook(self, k):
3540 del self.scaling_hooks[k]
3542 def remove_scaling_hooks(self):
3543 self.scaling_hooks = {}
3545 def s_sortingmode_change(self, ignore=None):
3546 for menuitem, valfunc in self.menuitems_ssorting:
3547 if menuitem.isChecked():
3548 self._ssort = valfunc
3550 self.sortingmode_change()
3552 def sortingmode_change(self, ignore=None):
3553 for menuitem, (gather, order, color) in self.menuitems_sorting:
3554 if menuitem.isChecked():
3555 self.set_gathering(gather, order, color)
3557 self.sortingmode_change_time = time.time()
3559 def lowpass_change(self, value, ignore=None):
3560 self.lowpass = value
3561 self.passband_check()
3562 self.tf_cache = {}
3563 self.update()
3565 def highpass_change(self, value, ignore=None):
3566 self.highpass = value
3567 self.passband_check()
3568 self.tf_cache = {}
3569 self.update()
3571 def passband_check(self):
3572 if self.highpass and self.lowpass \
3573 and self.highpass >= self.lowpass:
3575 self.message = 'Corner frequency of highpass larger than ' \
3576 'corner frequency of lowpass! I will now ' \
3577 'deactivate the highpass.'
3579 self.update_status()
3580 else:
3581 oldmess = self.message
3582 self.message = None
3583 if oldmess is not None:
3584 self.update_status()
3586 def gain_change(self, value, ignore):
3587 self.gain = value
3588 self.update()
3590 def rot_change(self, value, ignore):
3591 self.rotate = value
3592 self.update()
3594 def set_selected_markers(self, markers):
3595 '''
3596 Set a list of markers selected
3598 :param markers: list of markers
3599 '''
3600 self.deselect_all()
3601 for m in markers:
3602 m.selected = True
3604 self.update()
3606 def deselect_all(self):
3607 for marker in self.markers:
3608 marker.selected = False
3610 def animate_picking(self):
3611 point = self.mapFromGlobal(qg.QCursor.pos())
3612 self.update_picking(point.x(), point.y(), doshift=True)
3614 def get_nslc_ids_for_track(self, ftrack):
3615 itrack = int(ftrack)
3616 return self.track_to_nslc_ids.get(itrack, [])
3618 def stop_picking(self, x, y, abort=False):
3619 if self.picking:
3620 self.update_picking(x, y, doshift=False)
3621 self.picking = None
3622 self.picking_down = None
3623 self.picking_timer.stop()
3624 self.picking_timer = None
3625 if not abort:
3626 self.add_marker(self.floating_marker)
3627 self.floating_marker.selected = True
3628 self.emit_selected_markers()
3630 self.floating_marker = None
3632 def start_picking(self, ignore):
3634 if not self.picking:
3635 self.deselect_all()
3636 self.picking = qw.QRubberBand(qw.QRubberBand.Rectangle)
3637 point = self.mapFromGlobal(qg.QCursor.pos())
3639 gpoint = self.mapToGlobal(qc.QPoint(point.x(), 0))
3640 self.picking.setGeometry(
3641 gpoint.x(), gpoint.y(), 1, self.height())
3642 t = self.time_projection.rev(point.x())
3644 ftrack = self.track_to_screen.rev(point.y())
3645 nslc_ids = self.get_nslc_ids_for_track(ftrack)
3646 self.floating_marker = Marker(nslc_ids, t, t)
3647 self.floating_marker.selected = True
3649 self.picking_timer = qc.QTimer()
3650 self.picking_timer.timeout.connect(
3651 self.animate_picking)
3653 self.picking_timer.setInterval(50)
3654 self.picking_timer.start()
3656 def update_picking(self, x, y, doshift=False):
3657 if self.picking:
3658 mouset = self.time_projection.rev(x)
3659 dt = 0.0
3660 if mouset < self.tmin or mouset > self.tmax:
3661 if mouset < self.tmin:
3662 dt = -(self.tmin - mouset)
3663 else:
3664 dt = mouset - self.tmax
3665 ddt = self.tmax-self.tmin
3666 dt = max(dt, -ddt/10.)
3667 dt = min(dt, ddt/10.)
3669 x0 = x
3670 if self.picking_down is not None:
3671 x0 = self.time_projection(self.picking_down[0])
3673 w = abs(x-x0)
3674 x0 = min(x0, x)
3676 tmin, tmax = (
3677 self.time_projection.rev(x0),
3678 self.time_projection.rev(x0+w))
3680 tmin, tmax = (
3681 max(working_system_time_range[0], tmin),
3682 min(working_system_time_range[1], tmax))
3684 p1 = self.mapToGlobal(qc.QPoint(x0, 0))
3686 self.picking.setGeometry(
3687 p1.x(), p1.y(), max(w, 1), self.height())
3689 ftrack = self.track_to_screen.rev(y)
3690 nslc_ids = self.get_nslc_ids_for_track(ftrack)
3691 self.floating_marker.set(nslc_ids, tmin, tmax)
3693 if dt != 0.0 and doshift:
3694 self.interrupt_following()
3695 self.set_time_range(self.tmin+dt, self.tmax+dt)
3697 self.update()
3699 def update_status(self):
3701 if self.message is None:
3702 point = self.mapFromGlobal(qg.QCursor.pos())
3704 mouse_t = self.time_projection.rev(point.x())
3705 if not is_working_time(mouse_t):
3706 return
3708 if self.floating_marker:
3709 tmi, tma = (
3710 self.floating_marker.tmin,
3711 self.floating_marker.tmax)
3713 tt, ms = gmtime_x(tmi)
3715 if tmi == tma:
3716 message = mystrftime(
3717 fmt='Pick: %Y-%m-%d %H:%M:%S .%r',
3718 tt=tt, milliseconds=ms)
3719 else:
3720 srange = '%g s' % (tma-tmi)
3721 message = mystrftime(
3722 fmt='Start: %Y-%m-%d %H:%M:%S .%r Length: '+srange,
3723 tt=tt, milliseconds=ms)
3724 else:
3725 tt, ms = gmtime_x(mouse_t)
3727 message = mystrftime(fmt=None, tt=tt, milliseconds=ms)
3728 else:
3729 message = self.message
3731 sb = self.window().statusBar()
3732 sb.clearMessage()
3733 sb.showMessage(message)
3735 def set_sortingmode_change_delay_time(self, dt):
3736 self.sortingmode_change_delay_time = dt
3738 def sortingmode_change_delayed(self):
3739 now = time.time()
3740 return (
3741 self.sortingmode_change_delay_time is not None
3742 and now - self.sortingmode_change_time
3743 < self.sortingmode_change_delay_time)
3745 def set_visible_marker_kinds(self, kinds):
3746 self.deselect_all()
3747 self.visible_marker_kinds = tuple(kinds)
3748 self.emit_selected_markers()
3750 def following(self):
3751 return self.follow_timer is not None \
3752 and not self.following_interrupted()
3754 def interrupt_following(self):
3755 self.interactive_range_change_time = time.time()
3757 def following_interrupted(self, now=None):
3758 if now is None:
3759 now = time.time()
3760 return now - self.interactive_range_change_time \
3761 < self.interactive_range_change_delay_time
3763 def follow(self, tlen, interval=50, lapse=None, tmax_start=None):
3764 if tmax_start is None:
3765 tmax_start = time.time()
3766 self.show_all = False
3767 self.follow_time = tlen
3768 self.follow_timer = qc.QTimer(self)
3769 self.follow_timer.timeout.connect(
3770 self.follow_update)
3771 self.follow_timer.setInterval(interval)
3772 self.follow_timer.start()
3773 self.follow_started = time.time()
3774 self.follow_lapse = lapse
3775 self.follow_tshift = self.follow_started - tmax_start
3776 self.interactive_range_change_time = 0.0
3778 def unfollow(self):
3779 if self.follow_timer is not None:
3780 self.follow_timer.stop()
3781 self.follow_timer = None
3782 self.interactive_range_change_time = 0.0
3784 def follow_update(self):
3785 rnow = time.time()
3786 if self.follow_lapse is None:
3787 now = rnow
3788 else:
3789 now = self.follow_started + (rnow - self.follow_started) \
3790 * self.follow_lapse
3792 if self.following_interrupted(rnow):
3793 return
3794 self.set_time_range(
3795 now-self.follow_time-self.follow_tshift,
3796 now-self.follow_tshift)
3798 self.update()
3800 def myclose(self, return_tag=''):
3801 self.return_tag = return_tag
3802 self.window().close()
3804 def cleanup(self):
3805 self.about_to_close.emit()
3806 self.timer.stop()
3807 if self.follow_timer is not None:
3808 self.follow_timer.stop()
3810 for snuffling in list(self.snufflings):
3811 self.remove_snuffling(snuffling)
3813 def set_error_message(self, key, value):
3814 if value is None:
3815 if key in self.error_messages:
3816 del self.error_messages[key]
3817 else:
3818 self.error_messages[key] = value
3820 def inputline_changed(self, text):
3821 pass
3823 def inputline_finished(self, text):
3824 line = str(text)
3826 toks = line.split()
3827 clearit, hideit, error = False, True, None
3828 if len(toks) >= 1:
3829 command = toks[0].lower()
3831 try:
3832 quick_filter_commands = {
3833 'n': '%s.*.*.*',
3834 's': '*.%s.*.*',
3835 'l': '*.*.%s.*',
3836 'c': '*.*.*.%s'}
3838 if command in quick_filter_commands:
3839 if len(toks) >= 2:
3840 patterns = [
3841 quick_filter_commands[toks[0]] % pat
3842 for pat in toks[1:]]
3843 self.set_quick_filter_patterns(patterns, line)
3844 else:
3845 self.set_quick_filter_patterns(None)
3847 self.update()
3849 elif command in ('hide', 'unhide'):
3850 if len(toks) >= 2:
3851 patterns = []
3852 if len(toks) == 2:
3853 patterns = [toks[1]]
3854 elif len(toks) >= 3:
3855 x = {
3856 'n': '%s.*.*.*',
3857 's': '*.%s.*.*',
3858 'l': '*.*.%s.*',
3859 'c': '*.*.*.%s'}
3861 if toks[1] in x:
3862 patterns.extend(
3863 x[toks[1]] % tok for tok in toks[2:])
3865 for pattern in patterns:
3866 if command == 'hide':
3867 self.add_blacklist_pattern(pattern)
3868 else:
3869 self.remove_blacklist_pattern(pattern)
3871 elif command == 'unhide' and len(toks) == 1:
3872 self.clear_blacklist()
3874 clearit = True
3876 self.update()
3878 elif command == 'markers':
3879 if len(toks) == 2:
3880 if toks[1] == 'all':
3881 kinds = self.all_marker_kinds
3882 else:
3883 kinds = []
3884 for x in toks[1]:
3885 try:
3886 kinds.append(int(x))
3887 except Exception:
3888 pass
3890 self.set_visible_marker_kinds(kinds)
3892 elif len(toks) == 1:
3893 self.set_visible_marker_kinds(())
3895 self.update()
3897 elif command == 'scaling':
3898 if len(toks) == 2:
3899 hideit = False
3900 error = 'wrong number of arguments'
3902 if len(toks) >= 3:
3903 vmin, vmax = [
3904 pyrocko.model.float_or_none(x)
3905 for x in toks[-2:]]
3907 def upd(d, k, vmin, vmax):
3908 if k in d:
3909 if vmin is not None:
3910 d[k] = vmin, d[k][1]
3911 if vmax is not None:
3912 d[k] = d[k][0], vmax
3914 if len(toks) == 1:
3915 self.remove_scaling_hooks()
3917 elif len(toks) == 3:
3918 def hook(data_ranges):
3919 for k in data_ranges:
3920 upd(data_ranges, k, vmin, vmax)
3922 self.set_scaling_hook('_', hook)
3924 elif len(toks) == 4:
3925 pattern = toks[1]
3927 def hook(data_ranges):
3928 for k in pyrocko.util.match_nslcs(
3929 pattern, list(data_ranges.keys())):
3931 upd(data_ranges, k, vmin, vmax)
3933 self.set_scaling_hook(pattern, hook)
3935 elif command == 'goto':
3936 toks2 = line.split(None, 1)
3937 if len(toks2) == 2:
3938 arg = toks2[1]
3939 m = re.match(
3940 r'^\d\d\d\d(-\d\d(-\d\d( \d\d(:\d\d'
3941 r'(:\d\d(\.\d+)?)?)?)?)?)?$', arg)
3942 if m:
3943 tlen = None
3944 if not m.group(1):
3945 tlen = 12*32*24*60*60
3946 elif not m.group(2):
3947 tlen = 32*24*60*60
3948 elif not m.group(3):
3949 tlen = 24*60*60
3950 elif not m.group(4):
3951 tlen = 60*60
3952 elif not m.group(5):
3953 tlen = 60
3955 supl = '1970-01-01 00:00:00'
3956 if len(supl) > len(arg):
3957 arg = arg + supl[-(len(supl)-len(arg)):]
3958 t = pyrocko.util.str_to_time(arg)
3959 self.go_to_time(t, tlen=tlen)
3961 elif re.match(r'^\d\d:\d\d(:\d\d(\.\d+)?)?$', arg):
3962 supl = '00:00:00'
3963 if len(supl) > len(arg):
3964 arg = arg + supl[-(len(supl)-len(arg)):]
3965 tmin, tmax = self.get_time_range()
3966 sdate = pyrocko.util.time_to_str(
3967 tmin/2.+tmax/2., format='%Y-%m-%d')
3968 t = pyrocko.util.str_to_time(sdate + ' ' + arg)
3969 self.go_to_time(t)
3971 else:
3972 self.go_to_event_by_name(arg)
3974 else:
3975 raise PileViewerMainException(
3976 'No such command: %s' % command)
3978 except PileViewerMainException as e:
3979 error = str(e)
3980 hideit = False
3982 return clearit, hideit, error
3984 return PileViewerMain
3987PileViewerMain = MakePileViewerMainClass(qw.QWidget)
3988GLPileViewerMain = MakePileViewerMainClass(qgl.QGLWidget)
3991class LineEditWithAbort(qw.QLineEdit):
3993 aborted = qc.pyqtSignal()
3994 history_down = qc.pyqtSignal()
3995 history_up = qc.pyqtSignal()
3997 def keyPressEvent(self, key_event):
3998 if key_event.key() == qc.Qt.Key_Escape:
3999 self.aborted.emit()
4000 elif key_event.key() == qc.Qt.Key_Down:
4001 self.history_down.emit()
4002 elif key_event.key() == qc.Qt.Key_Up:
4003 self.history_up.emit()
4004 else:
4005 return qw.QLineEdit.keyPressEvent(self, key_event)
4008class PileViewer(qw.QFrame):
4009 '''
4010 PileViewerMain + Controls + Inputline
4011 '''
4013 def __init__(
4014 self, pile,
4015 ntracks_shown_max=20,
4016 marker_editor_sortable=True,
4017 use_opengl=False,
4018 panel_parent=None,
4019 *args):
4021 qw.QFrame.__init__(self, *args)
4023 if use_opengl:
4024 self.viewer = GLPileViewerMain(
4025 pile,
4026 ntracks_shown_max=ntracks_shown_max,
4027 panel_parent=panel_parent)
4028 else:
4029 self.viewer = PileViewerMain(
4030 pile,
4031 ntracks_shown_max=ntracks_shown_max,
4032 panel_parent=panel_parent)
4034 self.marker_editor_sortable = marker_editor_sortable
4036 layout = qw.QGridLayout()
4037 self.setLayout(layout)
4038 layout.setContentsMargins(0, 0, 0, 0)
4039 layout.setSpacing(0)
4041 self.setFrameShape(qw.QFrame.StyledPanel)
4042 self.setFrameShadow(qw.QFrame.Sunken)
4044 self.input_area = qw.QFrame(self)
4045 ia_layout = qw.QGridLayout()
4046 ia_layout.setContentsMargins(11, 11, 11, 11)
4047 self.input_area.setLayout(ia_layout)
4049 self.inputline = LineEditWithAbort(self.input_area)
4050 self.inputline.returnPressed.connect(
4051 self.inputline_returnpressed)
4052 self.inputline.editingFinished.connect(
4053 self.inputline_finished)
4054 self.inputline.aborted.connect(
4055 self.inputline_aborted)
4057 self.inputline.history_down.connect(
4058 lambda: self.step_through_history(1))
4059 self.inputline.history_up.connect(
4060 lambda: self.step_through_history(-1))
4062 self.inputline.textEdited.connect(
4063 self.inputline_changed)
4065 self.inputline.setFocusPolicy(qc.Qt.ClickFocus)
4066 self.input_area.hide()
4067 self.history = None
4069 self.inputline_error_str = None
4071 self.inputline_error = qw.QLabel()
4072 self.inputline_error.hide()
4074 ia_layout.addWidget(self.inputline, 0, 0)
4075 ia_layout.addWidget(self.inputline_error, 1, 0)
4076 layout.addWidget(self.input_area, 0, 0, 1, 2)
4077 layout.addWidget(self.viewer, 1, 0)
4079 pb = Progressbars(self)
4080 layout.addWidget(pb, 2, 0, 1, 2)
4081 self.progressbars = pb
4083 scrollbar = qw.QScrollBar(qc.Qt.Vertical)
4084 self.scrollbar = scrollbar
4085 layout.addWidget(scrollbar, 1, 1)
4086 self.scrollbar.valueChanged.connect(
4087 self.scrollbar_changed)
4089 self.block_scrollbar_changes = False
4091 self.viewer.want_input.connect(
4092 self.inputline_show)
4093 self.viewer.tracks_range_changed.connect(
4094 self.tracks_range_changed)
4095 self.viewer.pile_has_changed_signal.connect(
4096 self.adjust_controls)
4097 self.viewer.about_to_close.connect(
4098 self.save_inputline_history)
4100 def cleanup(self):
4101 self.viewer.cleanup()
4103 def get_progressbars(self):
4104 return self.progressbars
4106 def inputline_show(self):
4107 if not self.history:
4108 self.load_inputline_history()
4110 self.input_area.show()
4111 self.inputline.setFocus(qc.Qt.OtherFocusReason)
4112 self.inputline.selectAll()
4114 def inputline_set_error(self, string):
4115 self.inputline_error_str = string
4116 self.inputline.setPalette(pyrocko.gui.util.get_err_palette())
4117 self.inputline.selectAll()
4118 self.inputline_error.setText(string)
4119 self.input_area.show()
4120 self.inputline_error.show()
4122 def inputline_clear_error(self):
4123 if self.inputline_error_str:
4124 self.inputline.setPalette(qw.QApplication.palette())
4125 self.inputline_error_str = None
4126 self.inputline_error.clear()
4127 self.inputline_error.hide()
4129 def inputline_changed(self, line):
4130 self.viewer.inputline_changed(str(line))
4131 self.inputline_clear_error()
4133 def inputline_returnpressed(self):
4134 line = str(self.inputline.text())
4135 clearit, hideit, error = self.viewer.inputline_finished(line)
4137 if error:
4138 self.inputline_set_error(error)
4140 line = line.strip()
4142 if line != '' and not error:
4143 if not (len(self.history) >= 1 and line == self.history[-1]):
4144 self.history.append(line)
4146 if clearit:
4148 self.inputline.blockSignals(True)
4149 qpat, qinp = self.viewer.get_quick_filter_patterns()
4150 if qpat is None:
4151 self.inputline.clear()
4152 else:
4153 self.inputline.setText(qinp)
4154 self.inputline.blockSignals(False)
4156 if hideit and not error:
4157 self.viewer.setFocus(qc.Qt.OtherFocusReason)
4158 self.input_area.hide()
4160 self.hist_ind = len(self.history)
4162 def inputline_aborted(self):
4163 '''
4164 Hide the input line.
4165 '''
4166 self.viewer.setFocus(qc.Qt.OtherFocusReason)
4167 self.hist_ind = len(self.history)
4168 self.input_area.hide()
4170 def save_inputline_history(self):
4171 '''
4172 Save input line history to "$HOME/.pyrocko/.snuffler_history.pf"
4173 '''
4174 if not self.history:
4175 return
4177 conf = pyrocko.config
4178 fn_hist = conf.expand(conf.make_conf_path_tmpl('.snuffler_history'))
4179 with open(fn_hist, 'w') as f:
4180 i = min(100, len(self.history))
4181 for c in self.history[-i:]:
4182 f.write('%s\n' % c)
4184 def load_inputline_history(self):
4185 '''
4186 Load input line history from "$HOME/.pyrocko/.snuffler_history.pf"
4187 '''
4188 conf = pyrocko.config
4189 fn_hist = conf.expand(conf.make_conf_path_tmpl('.snuffler_history'))
4190 if not os.path.exists(fn_hist):
4191 with open(fn_hist, 'w+') as f:
4192 f.write('\n')
4194 with open(fn_hist, 'r') as f:
4195 self.history = [line.strip() for line in f.readlines()]
4197 self.hist_ind = len(self.history)
4199 def step_through_history(self, ud=1):
4200 '''
4201 Step through input line history and set the input line text.
4202 '''
4203 n = len(self.history)
4204 self.hist_ind += ud
4205 self.hist_ind %= (n + 1)
4206 if len(self.history) != 0 and self.hist_ind != n:
4207 self.inputline.setText(self.history[self.hist_ind])
4208 else:
4209 self.inputline.setText('')
4211 def inputline_finished(self):
4212 pass
4214 def tracks_range_changed(self, ntracks, ilo, ihi):
4215 if self.block_scrollbar_changes:
4216 return
4218 self.scrollbar.blockSignals(True)
4219 self.scrollbar.setPageStep(ihi-ilo)
4220 vmax = max(0, ntracks-(ihi-ilo))
4221 self.scrollbar.setRange(0, vmax)
4222 self.scrollbar.setValue(ilo)
4223 self.scrollbar.setHidden(vmax == 0)
4224 self.scrollbar.blockSignals(False)
4226 def scrollbar_changed(self, value):
4227 self.block_scrollbar_changes = True
4228 ilo = value
4229 ihi = ilo + self.scrollbar.pageStep()
4230 self.viewer.set_tracks_range((ilo, ihi))
4231 self.block_scrollbar_changes = False
4232 self.update_contents()
4234 def controls(self):
4235 frame = qw.QFrame(self)
4236 layout = qw.QGridLayout()
4237 frame.setLayout(layout)
4239 minfreq = 0.001
4240 maxfreq = 1000.0
4241 self.lowpass_control = ValControl(high_is_none=True)
4242 self.lowpass_control.setup(
4243 'Lowpass [Hz]:', minfreq, maxfreq, maxfreq, 0)
4244 self.highpass_control = ValControl(low_is_none=True)
4245 self.highpass_control.setup(
4246 'Highpass [Hz]:', minfreq, maxfreq, minfreq, 1)
4247 self.gain_control = ValControl()
4248 self.gain_control.setup('Gain:', 0.001, 1000., 1., 2)
4249 self.rot_control = LinValControl()
4250 self.rot_control.setup('Rotate [deg]:', -180., 180., 0., 3)
4252 self.lowpass_control.valchange.connect(
4253 self.viewer.lowpass_change)
4254 self.highpass_control.valchange.connect(
4255 self.viewer.highpass_change)
4256 self.gain_control.valchange.connect(
4257 self.viewer.gain_change)
4258 self.rot_control.valchange.connect(
4259 self.viewer.rot_change)
4261 for icontrol, control in enumerate((
4262 self.highpass_control,
4263 self.lowpass_control,
4264 self.gain_control,
4265 self.rot_control)):
4267 for iwidget, widget in enumerate(control.widgets()):
4268 layout.addWidget(widget, icontrol, iwidget)
4270 spacer = qw.QSpacerItem(
4271 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding)
4272 layout.addItem(spacer, 4, 0, 1, 3)
4274 self.adjust_controls()
4275 return frame
4277 def marker_editor(self):
4278 editor = pyrocko.gui.marker_editor.MarkerEditor(
4279 self, sortable=self.marker_editor_sortable)
4281 editor.set_viewer(self.get_view())
4282 editor.get_marker_model().dataChanged.connect(
4283 self.update_contents)
4284 return editor
4286 def adjust_controls(self):
4287 dtmin, dtmax = self.viewer.content_deltat_range()
4288 maxfreq = 0.5/dtmin
4289 minfreq = (0.5/dtmax)*0.001
4290 self.lowpass_control.set_range(minfreq, maxfreq)
4291 self.highpass_control.set_range(minfreq, maxfreq)
4293 def setup_snufflings(self):
4294 self.viewer.setup_snufflings()
4296 def get_view(self):
4297 return self.viewer
4299 def update_contents(self):
4300 self.viewer.update()
4302 def get_pile(self):
4303 return self.viewer.get_pile()