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 try:
1560 stations = [pyrocko.model.load_stations(str(x)) for x in fns]
1561 for stat in stations:
1562 self.add_stations(stat)
1564 except Exception as e:
1565 self.fail('Failed to read station file: %s' % str(e))
1567 def open_stations_xml(self, fns=None):
1568 from pyrocko.io import stationxml
1570 caption = 'Select one or more StationXML files to open'
1572 if not fns:
1573 fns, _ = fnpatch(qw.QFileDialog.getOpenFileNames(
1574 self, caption, options=qfiledialog_options,
1575 filter='StationXML *.xml (*.xml *.XML);;All files (*)'))
1577 try:
1578 stations = [
1579 stationxml.load_xml(filename=str(x)).get_pyrocko_stations()
1580 for x in fns]
1582 for stat in stations:
1583 self.add_stations(stat)
1585 except Exception as e:
1586 self.fail('Failed to read StationXML file: %s' % str(e))
1588 def add_traces(self, traces):
1589 if traces:
1590 mtf = pyrocko.pile.MemTracesFile(None, traces)
1591 self.pile.add_file(mtf)
1592 ticket = (self.pile, mtf)
1593 return ticket
1594 else:
1595 return (None, None)
1597 def release_data(self, tickets):
1598 for ticket in tickets:
1599 pile, mtf = ticket
1600 if pile is not None:
1601 pile.remove_file(mtf)
1603 def periodical(self):
1604 if self.menuitem_watch.isChecked():
1605 if self.pile.reload_modified():
1606 self.update()
1608 def get_pile(self):
1609 return self.pile
1611 def pile_changed(self, what):
1612 self.pile_has_changed = True
1613 self.pile_has_changed_signal.emit()
1614 if self.automatic_updates:
1615 self.update()
1617 def set_gathering(self, gather=None, order=None, color=None):
1619 if gather is None:
1620 def gather(tr):
1621 return tr.nslc_id
1623 if order is None:
1624 def order(a):
1625 return a
1627 if color is None:
1628 def color(tr):
1629 return tr.location
1631 self.gather = gather
1632 keys = self.pile.gather_keys(gather, self.trace_filter)
1633 self.color_gather = color
1634 self.color_keys = self.pile.gather_keys(color)
1635 previous_ntracks = self.ntracks
1636 self.set_ntracks(len(keys))
1638 if self.shown_tracks_range is None or \
1639 previous_ntracks == 0 or \
1640 self.show_all:
1642 low, high = 0, min(self.ntracks_shown_max, self.ntracks)
1643 key_at_top = None
1644 n = high-low
1646 else:
1647 low, high = self.shown_tracks_range
1648 key_at_top = self.track_keys[low]
1649 n = high-low
1651 self.track_keys = sorted(keys, key=order)
1653 if key_at_top is not None:
1654 try:
1655 ind = self.track_keys.index(key_at_top)
1656 low = ind
1657 high = low+n
1658 except Exception:
1659 pass
1661 self.set_tracks_range((low, high))
1663 self.key_to_row = dict(
1664 [(key, i) for (i, key) in enumerate(self.track_keys)])
1666 def inrange(x, r):
1667 return r[0] <= x and x < r[1]
1669 def trace_selector(trace):
1670 gt = self.gather(trace)
1671 return (
1672 gt in self.key_to_row and
1673 inrange(self.key_to_row[gt], self.shown_tracks_range))
1675 if self.trace_filter is not None:
1676 self.trace_selector = lambda x: \
1677 self.trace_filter(x) and trace_selector(x)
1678 else:
1679 self.trace_selector = trace_selector
1681 if self.tmin == working_system_time_range[0] and \
1682 self.tmax == working_system_time_range[1] or \
1683 self.show_all:
1685 tmin, tmax = self.pile.get_tmin(), self.pile.get_tmax()
1686 if tmin is not None and tmax is not None:
1687 tlen = (tmax - tmin)
1688 tpad = tlen * 5./self.width()
1689 self.set_time_range(tmin-tpad, tmax+tpad)
1691 def set_time_range(self, tmin, tmax):
1692 if tmin is None:
1693 tmin = initial_time_range[0]
1695 if tmax is None:
1696 tmax = initial_time_range[1]
1698 if tmin > tmax:
1699 tmin, tmax = tmax, tmin
1701 if tmin == tmax:
1702 tmin -= 1.
1703 tmax += 1.
1705 tmin = max(working_system_time_range[0], tmin)
1706 tmax = min(working_system_time_range[1], tmax)
1708 min_deltat = self.content_deltat_range()[0]
1709 if (tmax - tmin < min_deltat):
1710 m = (tmin + tmax) / 2.
1711 tmin = m - min_deltat/2.
1712 tmax = m + min_deltat/2.
1714 self.time_projection.set_in_range(tmin, tmax)
1715 self.tmin, self.tmax = tmin, tmax
1717 def get_time_range(self):
1718 return self.tmin, self.tmax
1720 def ypart(self, y):
1721 if y < self.ax_height:
1722 return -1
1723 elif y > self.height()-self.ax_height:
1724 return 1
1725 else:
1726 return 0
1728 def time_fractional_digits(self):
1729 min_deltat = self.content_deltat_range()[0]
1730 return min(9, max(1, int(-math.floor(math.log10(min_deltat)))+2))
1732 def write_markers(self, fn=None):
1733 caption = "Choose a file name to write markers"
1734 if not fn:
1735 fn, _ = fnpatch(qw.QFileDialog.getSaveFileName(
1736 self, caption, options=qfiledialog_options))
1737 if fn:
1738 try:
1739 Marker.save_markers(
1740 self.markers, fn,
1741 fdigits=self.time_fractional_digits())
1743 except Exception as e:
1744 self.fail('Failed to write marker file: %s' % str(e))
1746 def write_selected_markers(self, fn=None):
1747 caption = "Choose a file name to write selected markers"
1748 if not fn:
1749 fn, _ = fnpatch(qw.QFileDialog.getSaveFileName(
1750 self, caption, options=qfiledialog_options))
1751 if fn:
1752 try:
1753 Marker.save_markers(
1754 self.iter_selected_markers(),
1755 fn,
1756 fdigits=self.time_fractional_digits())
1758 except Exception as e:
1759 self.fail('Failed to write marker file: %s' % str(e))
1761 def read_events(self, fn=None):
1762 '''
1763 Open QFileDialog to open, read and add
1764 :py:class:`pyrocko.model.Event` instances and their marker
1765 representation to the pile viewer.
1766 '''
1767 caption = "Selet one or more files to open"
1768 if not fn:
1769 fn, _ = fnpatch(qw.QFileDialog.getOpenFileName(
1770 self, caption, options=qfiledialog_options))
1771 if fn:
1772 try:
1773 self.add_events(pyrocko.model.load_events(fn))
1774 self.associate_phases_to_events()
1776 except Exception as e:
1777 self.fail('Failed to read event file: %s' % str(e))
1779 def read_markers(self, fn=None):
1780 '''
1781 Open QFileDialog to open, read and add markers to the pile viewer.
1782 '''
1783 caption = "Selet one or more files to open"
1784 if not fn:
1785 fn, _ = fnpatch(qw.QFileDialog.getOpenFileName(
1786 self, caption, options=qfiledialog_options))
1787 if fn:
1788 try:
1789 self.add_markers(Marker.load_markers(fn))
1790 self.associate_phases_to_events()
1792 except Exception as e:
1793 self.fail('Failed to read marker file: %s' % str(e))
1795 def associate_phases_to_events(self):
1796 associate_phases_to_events(self.markers)
1798 def add_marker(self, marker):
1799 # need index to inform QAbstactTableModel about upcoming change,
1800 # but have to restore current state in order to not cause problems
1801 self.markers.insert(marker)
1802 i = self.markers.remove(marker)
1804 self.begin_markers_add.emit(i, i)
1805 self.markers.insert(marker)
1806 self.end_markers_add.emit()
1807 self.markers_deltat_max = max(
1808 self.markers_deltat_max, marker.tmax - marker.tmin)
1810 def add_markers(self, markers):
1811 if not self.markers:
1812 self.begin_markers_add.emit(0, len(markers) - 1)
1813 self.markers.insert_many(markers)
1814 self.end_markers_add.emit()
1815 self.update_markers_deltat_max()
1816 else:
1817 for marker in markers:
1818 self.add_marker(marker)
1820 def update_markers_deltat_max(self):
1821 if self.markers:
1822 self.markers_deltat_max = max(
1823 marker.tmax - marker.tmin for marker in self.markers)
1825 def remove_marker(self, marker):
1826 '''
1827 Remove a ``marker`` from the :py:class:`PileViewer`.
1829 :param marker: :py:class:`Marker` (or subclass) instance
1830 '''
1832 if marker is self.active_event_marker:
1833 self.deactivate_event_marker()
1835 try:
1836 i = self.markers.index(marker)
1837 self.begin_markers_remove.emit(i, i)
1838 self.markers.remove_at(i)
1839 self.end_markers_remove.emit()
1840 except ValueError:
1841 pass
1843 def remove_markers(self, markers):
1844 '''
1845 Remove a list of ``markers`` from the :py:class:`PileViewer`.
1847 :param markers: list of :py:class:`Marker` (or subclass)
1848 instances
1849 '''
1851 if markers is self.markers:
1852 markers = list(markers)
1854 for marker in markers:
1855 self.remove_marker(marker)
1857 self.update_markers_deltat_max()
1859 def remove_selected_markers(self):
1860 def delete_segment(istart, iend):
1861 self.begin_markers_remove.emit(istart, iend-1)
1862 for _ in range(iend - istart):
1863 self.markers.remove_at(istart)
1865 self.end_markers_remove.emit()
1867 istart = None
1868 ipos = 0
1869 markers = self.markers
1870 nmarkers = len(self.markers)
1871 while ipos < nmarkers:
1872 marker = markers[ipos]
1873 if marker.is_selected():
1874 if marker is self.active_event_marker:
1875 self.deactivate_event_marker()
1877 if istart is None:
1878 istart = ipos
1879 else:
1880 if istart is not None:
1881 delete_segment(istart, ipos)
1882 nmarkers -= ipos - istart
1883 ipos = istart - 1
1884 istart = None
1886 ipos += 1
1888 if istart is not None:
1889 delete_segment(istart, ipos)
1891 self.update_markers_deltat_max()
1893 def selected_markers(self):
1894 return [marker for marker in self.markers if marker.is_selected()]
1896 def iter_selected_markers(self):
1897 for marker in self.markers:
1898 if marker.is_selected():
1899 yield marker
1901 def get_markers(self):
1902 return self.markers
1904 def mousePressEvent(self, mouse_ev):
1905 self.show_all = False
1906 point = self.mapFromGlobal(mouse_ev.globalPos())
1908 if mouse_ev.button() == qc.Qt.LeftButton:
1909 marker = self.marker_under_cursor(point.x(), point.y())
1910 if self.picking:
1911 if self.picking_down is None:
1912 self.picking_down = (
1913 self.time_projection.rev(mouse_ev.x()),
1914 mouse_ev.y())
1916 elif marker is not None:
1917 if not (mouse_ev.modifiers() & qc.Qt.ShiftModifier):
1918 self.deselect_all()
1919 marker.selected = True
1920 self.emit_selected_markers()
1921 self.update()
1922 else:
1923 self.track_start = mouse_ev.x(), mouse_ev.y()
1924 self.track_trange = self.tmin, self.tmax
1926 if mouse_ev.button() == qc.Qt.RightButton:
1927 self.menu.exec_(qg.QCursor.pos())
1928 self.update_status()
1930 def mouseReleaseEvent(self, mouse_ev):
1931 if self.ignore_releases:
1932 self.ignore_releases -= 1
1933 return
1935 if self.picking:
1936 self.stop_picking(mouse_ev.x(), mouse_ev.y())
1937 self.emit_selected_markers()
1939 if self.track_start:
1940 self.update()
1942 self.track_start = None
1943 self.track_trange = None
1944 self.update_status()
1946 def mouseDoubleClickEvent(self, mouse_ev):
1947 self.show_all = False
1948 self.start_picking(None)
1949 self.ignore_releases = 1
1951 def mouseMoveEvent(self, mouse_ev):
1952 self.setUpdatesEnabled(False)
1953 point = self.mapFromGlobal(mouse_ev.globalPos())
1955 if self.picking:
1956 self.update_picking(point.x(), point.y())
1958 elif self.track_start is not None:
1959 x0, y0 = self.track_start
1960 dx = (point.x() - x0)/float(self.width())
1961 dy = (point.y() - y0)/float(self.height())
1962 if self.ypart(y0) == 1:
1963 dy = 0
1965 tmin0, tmax0 = self.track_trange
1967 scale = math.exp(-dy*5.)
1968 dtr = scale*(tmax0-tmin0) - (tmax0-tmin0)
1969 frac = x0/float(self.width())
1970 dt = dx*(tmax0-tmin0)*scale
1972 self.interrupt_following()
1973 self.set_time_range(
1974 tmin0 - dt - dtr*frac,
1975 tmax0 - dt + dtr*(1.-frac))
1977 self.update()
1978 else:
1979 self.hoovering(point.x(), point.y())
1981 self.update_status()
1983 def nslc_ids_under_cursor(self, x, y):
1984 ftrack = self.track_to_screen.rev(y)
1985 nslc_ids = self.get_nslc_ids_for_track(ftrack)
1986 return nslc_ids
1988 def marker_under_cursor(self, x, y):
1989 mouset = self.time_projection.rev(x)
1990 deltat = (self.tmax-self.tmin)*self.click_tolerance/self.width()
1991 relevant_nslc_ids = None
1992 for marker in self.markers:
1993 if marker.kind not in self.visible_marker_kinds:
1994 continue
1996 if (abs(mouset-marker.tmin) < deltat or
1997 abs(mouset-marker.tmax) < deltat):
1999 if relevant_nslc_ids is None:
2000 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y)
2002 marker_nslc_ids = marker.get_nslc_ids()
2003 if not marker_nslc_ids:
2004 return marker
2006 for nslc_id in marker_nslc_ids:
2007 if nslc_id in relevant_nslc_ids:
2008 return marker
2010 def hoovering(self, x, y):
2011 mouset = self.time_projection.rev(x)
2012 deltat = (self.tmax-self.tmin)*self.click_tolerance/self.width()
2013 needupdate = False
2014 haveone = False
2015 relevant_nslc_ids = self.nslc_ids_under_cursor(x, y)
2016 for marker in self.markers:
2017 if marker.kind not in self.visible_marker_kinds:
2018 continue
2020 state = abs(mouset-marker.tmin) < deltat or \
2021 abs(mouset-marker.tmax) < deltat and not haveone
2023 if state:
2024 xstate = False
2026 marker_nslc_ids = marker.get_nslc_ids()
2027 if not marker_nslc_ids:
2028 xstate = True
2030 for nslc in relevant_nslc_ids:
2031 if marker.match_nslc(nslc):
2032 xstate = True
2034 state = xstate
2036 if state:
2037 haveone = True
2038 oldstate = marker.is_alerted()
2039 if oldstate != state:
2040 needupdate = True
2041 marker.set_alerted(state)
2042 if state:
2043 self.message = marker.hoover_message()
2045 if not haveone:
2046 self.message = None
2048 if needupdate:
2049 self.update()
2051 def event(self, event):
2052 if event.type() == qc.QEvent.KeyPress:
2053 self.keyPressEvent(event)
2054 return True
2055 else:
2056 return base.event(self, event)
2058 def keyPressEvent(self, key_event):
2059 self.show_all = False
2060 dt = self.tmax - self.tmin
2061 tmid = (self.tmin + self.tmax) / 2.
2063 try:
2064 keytext = str(key_event.text())
2065 except UnicodeEncodeError:
2066 return
2068 if keytext == '?':
2069 self.help()
2071 elif keytext == ' ':
2072 self.interrupt_following()
2073 self.set_time_range(self.tmin+dt, self.tmax+dt)
2075 elif key_event.key() == qc.Qt.Key_Up:
2076 for m in self.selected_markers():
2077 if isinstance(m, PhaseMarker):
2078 if key_event.modifiers() & qc.Qt.ShiftModifier:
2079 p = 0
2080 else:
2081 p = 1 if m.get_polarity() != 1 else None
2082 m.set_polarity(p)
2084 elif key_event.key() == qc.Qt.Key_Down:
2085 for m in self.selected_markers():
2086 if isinstance(m, PhaseMarker):
2087 if key_event.modifiers() & qc.Qt.ShiftModifier:
2088 p = 0
2089 else:
2090 p = -1 if m.get_polarity() != -1 else None
2091 m.set_polarity(p)
2093 elif keytext == 'b':
2094 dt = self.tmax - self.tmin
2095 self.interrupt_following()
2096 self.set_time_range(self.tmin-dt, self.tmax-dt)
2098 elif key_event.key() in (qc.Qt.Key_Tab, qc.Qt.Key_Backtab):
2099 self.interrupt_following()
2101 tgo = None
2103 class TraceDummy(object):
2104 def __init__(self, marker):
2105 self._marker = marker
2107 @property
2108 def nslc_id(self):
2109 return self._marker.one_nslc()
2111 def marker_to_itrack(marker):
2112 try:
2113 return self.key_to_row.get(
2114 self.gather(TraceDummy(marker)), -1)
2116 except MarkerOneNSLCRequired:
2117 return -1
2119 emarker, pmarkers = self.get_active_markers()
2120 pmarkers = [
2121 m for m in pmarkers if m.kind in self.visible_marker_kinds]
2122 pmarkers.sort(key=lambda m: (
2123 marker_to_itrack(m), (m.tmin + m.tmax) / 2.0))
2125 if key_event.key() == qc.Qt.Key_Backtab:
2126 pmarkers.reverse()
2128 smarkers = self.selected_markers()
2129 iselected = []
2130 for sm in smarkers:
2131 try:
2132 iselected.append(pmarkers.index(sm))
2133 except ValueError:
2134 pass
2136 if iselected:
2137 icurrent = max(iselected) + 1
2138 else:
2139 icurrent = 0
2141 if icurrent < len(pmarkers):
2142 self.deselect_all()
2143 cmarker = pmarkers[icurrent]
2144 cmarker.selected = True
2145 tgo = cmarker.tmin
2146 if not self.tmin < tgo < self.tmax:
2147 self.set_time_range(tgo-dt/2., tgo+dt/2.)
2149 itrack = marker_to_itrack(cmarker)
2150 if itrack != -1:
2151 if itrack < self.shown_tracks_range[0]:
2152 self.scroll_tracks(
2153 - (self.shown_tracks_range[0] - itrack))
2154 elif self.shown_tracks_range[1] <= itrack:
2155 self.scroll_tracks(
2156 itrack - self.shown_tracks_range[1]+1)
2158 if itrack not in self.track_to_nslc_ids:
2159 self.go_to_selection()
2161 elif keytext in ('p', 'n', 'P', 'N'):
2162 smarkers = self.selected_markers()
2163 tgo = None
2164 dir = str(keytext)
2165 if smarkers:
2166 tmid = smarkers[0].tmin
2167 for smarker in smarkers:
2168 if dir == 'n':
2169 tmid = max(smarker.tmin, tmid)
2170 else:
2171 tmid = min(smarker.tmin, tmid)
2173 tgo = tmid
2175 if dir.lower() == 'n':
2176 for marker in sorted(
2177 self.markers,
2178 key=operator.attrgetter('tmin')):
2180 t = marker.tmin
2181 if t > tmid and \
2182 marker.kind in self.visible_marker_kinds and \
2183 (dir == 'n' or
2184 isinstance(marker, EventMarker)):
2186 self.deselect_all()
2187 marker.selected = True
2188 tgo = t
2189 break
2190 else:
2191 for marker in sorted(
2192 self.markers,
2193 key=operator.attrgetter('tmin'),
2194 reverse=True):
2196 t = marker.tmin
2197 if t < tmid and \
2198 marker.kind in self.visible_marker_kinds and \
2199 (dir == 'p' or
2200 isinstance(marker, EventMarker)):
2201 self.deselect_all()
2202 marker.selected = True
2203 tgo = t
2204 break
2206 if tgo is not None:
2207 self.interrupt_following()
2208 self.set_time_range(tgo-dt/2., tgo+dt/2.)
2210 elif keytext == 'q' or keytext == 'x':
2211 self.myclose(keytext)
2213 elif keytext == 'r':
2214 if self.pile.reload_modified():
2215 self.reloaded = True
2217 elif keytext == 'R':
2218 self.setup_snufflings()
2220 elif key_event.key() == qc.Qt.Key_Backspace:
2221 self.remove_selected_markers()
2223 elif keytext == 'a':
2224 for marker in self.markers:
2225 if ((self.tmin <= marker.tmin <= self.tmax or
2226 self.tmin <= marker.tmax <= self.tmax) and
2227 marker.kind in self.visible_marker_kinds):
2228 marker.selected = True
2229 else:
2230 marker.selected = False
2232 elif keytext == 'A':
2233 for marker in self.markers:
2234 if marker.kind in self.visible_marker_kinds:
2235 marker.selected = True
2237 elif keytext == 'd':
2238 self.deselect_all()
2240 elif keytext == 'E':
2241 self.deactivate_event_marker()
2243 elif keytext == 'e':
2244 markers = self.selected_markers()
2245 event_markers_in_spe = [
2246 marker for marker in markers
2247 if not isinstance(marker, PhaseMarker)]
2249 phase_markers = [
2250 marker for marker in markers
2251 if isinstance(marker, PhaseMarker)]
2253 if len(event_markers_in_spe) == 1:
2254 event_marker = event_markers_in_spe[0]
2255 if not isinstance(event_marker, EventMarker):
2256 nslcs = list(event_marker.nslc_ids)
2257 lat, lon = 0.0, 0.0
2258 old = self.get_active_event()
2259 if len(nslcs) == 1:
2260 lat, lon = self.station_latlon(NSLC(*nslcs[0]))
2261 elif old is not None:
2262 lat, lon = old.lat, old.lon
2264 event_marker.convert_to_event_marker(lat, lon)
2266 self.set_active_event_marker(event_marker)
2267 event = event_marker.get_event()
2268 for marker in phase_markers:
2269 marker.set_event(event)
2271 else:
2272 for marker in event_markers_in_spe:
2273 marker.convert_to_event_marker()
2275 elif keytext in ('0', '1', '2', '3', '4', '5'):
2276 for marker in self.selected_markers():
2277 marker.set_kind(int(keytext))
2278 self.emit_selected_markers()
2280 elif key_event.key() in fkey_map:
2281 self.handle_fkeys(key_event.key())
2283 elif key_event.key() == qc.Qt.Key_Escape:
2284 if self.picking:
2285 self.stop_picking(0, 0, abort=True)
2287 elif key_event.key() == qc.Qt.Key_PageDown:
2288 self.scroll_tracks(
2289 self.shown_tracks_range[1]-self.shown_tracks_range[0])
2291 elif key_event.key() == qc.Qt.Key_PageUp:
2292 self.scroll_tracks(
2293 self.shown_tracks_range[0]-self.shown_tracks_range[1])
2295 elif keytext == '+':
2296 self.zoom_tracks(0., 1.)
2298 elif keytext == '-':
2299 self.zoom_tracks(0., -1.)
2301 elif keytext == '=':
2302 ntracks_shown = self.shown_tracks_range[1] - \
2303 self.shown_tracks_range[0]
2304 dtracks = self.initial_ntracks_shown_max - ntracks_shown
2305 self.zoom_tracks(0., dtracks)
2307 elif keytext == ':':
2308 self.want_input.emit()
2310 elif keytext == 'f':
2311 if self.window().windowState() & qc.Qt.WindowFullScreen or \
2312 self.window().windowState() & qc.Qt.WindowMaximized:
2314 self.window().showNormal()
2315 else:
2316 if macosx:
2317 self.window().showMaximized()
2318 else:
2319 self.window().showFullScreen()
2321 elif keytext == 'g':
2322 self.go_to_selection()
2324 elif keytext == 'G':
2325 self.go_to_selection(tight=True)
2327 elif keytext == 'm':
2328 self.toggle_marker_editor()
2330 elif keytext == 'c':
2331 self.toggle_main_controls()
2333 elif key_event.key() in (qc.Qt.Key_Left, qc.Qt.Key_Right):
2334 dir = 1
2335 amount = 1
2336 if key_event.key() == qc.Qt.Key_Left:
2337 dir = -1
2338 if key_event.modifiers() & qc.Qt.ShiftModifier:
2339 amount = 10
2340 self.nudge_selected_markers(dir*amount)
2342 if keytext != '' and keytext in 'degaApPnN':
2343 self.emit_selected_markers()
2345 self.update()
2346 self.update_status()
2348 def handle_fkeys(self, key):
2349 self.set_phase_kind(
2350 self.selected_markers(),
2351 fkey_map[key] + 1)
2352 self.emit_selected_markers()
2354 def emit_selected_markers(self):
2355 ibounds = []
2356 last_selected = False
2357 for imarker, marker in enumerate(self.markers):
2358 this_selected = marker.is_selected()
2359 if this_selected != last_selected:
2360 ibounds.append(imarker)
2362 last_selected = this_selected
2364 if last_selected:
2365 ibounds.append(len(self.markers))
2367 chunks = list(zip(ibounds[::2], ibounds[1::2]))
2368 self.n_selected_markers = sum(
2369 chunk[1] - chunk[0] for chunk in chunks)
2370 self.marker_selection_changed.emit(chunks)
2372 def toggle_marker_editor(self):
2373 self.panel_parent.toggle_marker_editor()
2375 def toggle_main_controls(self):
2376 self.panel_parent.toggle_main_controls()
2378 def nudge_selected_markers(self, npixels):
2379 a, b = self.time_projection.ur
2380 c, d = self.time_projection.xr
2381 for marker in self.selected_markers():
2382 if not isinstance(marker, EventMarker):
2383 marker.tmin += npixels * (d-c)/b
2384 marker.tmax += npixels * (d-c)/b
2386 def about(self):
2387 fn = pyrocko.util.data_file('snuffler.png')
2388 with open(pyrocko.util.data_file('snuffler_about.html')) as f:
2389 txt = f.read()
2390 label = qw.QLabel(txt % {'logo': fn})
2391 label.setAlignment(qc.Qt.AlignVCenter | qc.Qt.AlignHCenter)
2392 self.show_doc('About', [label], target='tab')
2394 def help(self):
2395 class MyScrollArea(qw.QScrollArea):
2397 def sizeHint(self):
2398 s = qc.QSize()
2399 s.setWidth(self.widget().sizeHint().width())
2400 s.setHeight(self.widget().sizeHint().height())
2401 return s
2403 with open(pyrocko.util.data_file(
2404 'snuffler_help.html')) as f:
2405 hcheat = qw.QLabel(f.read())
2407 with open(pyrocko.util.data_file(
2408 'snuffler_help_epilog.html')) as f:
2409 hepilog = qw.QLabel(f.read())
2411 for h in [hcheat, hepilog]:
2412 h.setAlignment(qc.Qt.AlignTop | qc.Qt.AlignHCenter)
2413 h.setWordWrap(True)
2415 self.show_doc('Help', [hcheat, hepilog], target='panel')
2417 def show_doc(self, name, labels, target='panel'):
2418 scroller = qw.QScrollArea()
2419 frame = qw.QFrame(scroller)
2420 frame.setLineWidth(0)
2421 layout = qw.QVBoxLayout()
2422 layout.setContentsMargins(0, 0, 0, 0)
2423 layout.setSpacing(0)
2424 frame.setLayout(layout)
2425 scroller.setWidget(frame)
2426 scroller.setWidgetResizable(True)
2427 frame.setBackgroundRole(qg.QPalette.Base)
2428 for h in labels:
2429 h.setParent(frame)
2430 h.setMargin(3)
2431 h.setTextInteractionFlags(
2432 qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse)
2433 h.setBackgroundRole(qg.QPalette.Base)
2434 layout.addWidget(h)
2435 h.linkActivated.connect(
2436 self.open_link)
2438 if self.panel_parent is not None:
2439 if target == 'panel':
2440 self.panel_parent.add_panel(
2441 name, scroller, True, volatile=False)
2442 else:
2443 self.panel_parent.add_tab(name, scroller)
2445 def open_link(self, link):
2446 qg.QDesktopServices.openUrl(qc.QUrl(link))
2448 def wheelEvent(self, wheel_event):
2449 if use_pyqt5:
2450 self.wheel_pos += wheel_event.angleDelta().y()
2451 else:
2452 self.wheel_pos += wheel_event.delta()
2454 n = self.wheel_pos // 120
2455 self.wheel_pos = self.wheel_pos % 120
2456 if n == 0:
2457 return
2459 amount = max(
2460 1.,
2461 abs(self.shown_tracks_range[0]-self.shown_tracks_range[1])/5.)
2462 wdelta = amount * n
2464 trmin, trmax = self.track_to_screen.get_in_range()
2465 anchor = (self.track_to_screen.rev(wheel_event.y())-trmin) \
2466 / (trmax-trmin)
2468 if wheel_event.modifiers() & qc.Qt.ControlModifier:
2469 self.zoom_tracks(anchor, wdelta)
2470 else:
2471 self.scroll_tracks(-wdelta)
2473 def dragEnterEvent(self, event):
2474 if event.mimeData().hasUrls():
2475 if any(url.toLocalFile() for url in event.mimeData().urls()):
2476 event.setDropAction(qc.Qt.LinkAction)
2477 event.accept()
2479 def dropEvent(self, event):
2480 if event.mimeData().hasUrls():
2481 paths = list(
2482 str(url.toLocalFile()) for url in event.mimeData().urls())
2483 event.acceptProposedAction()
2484 self.load(paths)
2486 def get_phase_name(self, kind):
2487 return self.config.get_phase_name(kind)
2489 def set_phase_kind(self, markers, kind):
2490 phasename = self.get_phase_name(kind)
2492 for marker in markers:
2493 if isinstance(marker, PhaseMarker):
2494 if kind == 10:
2495 marker.convert_to_marker()
2496 else:
2497 marker.set_phasename(phasename)
2498 marker.set_event(self.get_active_event())
2500 elif isinstance(marker, EventMarker):
2501 pass
2503 else:
2504 if kind != 10:
2505 event = self.get_active_event()
2506 marker.convert_to_phase_marker(
2507 event, phasename, None, False)
2509 def set_ntracks(self, ntracks):
2510 if self.ntracks != ntracks:
2511 self.ntracks = ntracks
2512 if self.shown_tracks_range is not None:
2513 l, h = self.shown_tracks_range
2514 else:
2515 l, h = 0, self.ntracks
2517 self.tracks_range_changed.emit(self.ntracks, l, h)
2519 def set_tracks_range(self, range, start=None):
2521 low, high = range
2522 low = min(self.ntracks-1, low)
2523 high = min(self.ntracks, high)
2524 low = max(0, low)
2525 high = max(1, high)
2527 if start is None:
2528 start = float(low)
2530 if self.shown_tracks_range != (low, high):
2531 self.shown_tracks_range = low, high
2532 self.shown_tracks_start = start
2534 self.tracks_range_changed.emit(self.ntracks, low, high)
2536 def scroll_tracks(self, shift):
2537 shown = self.shown_tracks_range
2538 shiftmin = -shown[0]
2539 shiftmax = self.ntracks-shown[1]
2540 shift = max(shiftmin, shift)
2541 shift = min(shiftmax, shift)
2542 shown = shown[0] + shift, shown[1] + shift
2544 self.set_tracks_range((int(shown[0]), int(shown[1])))
2546 self.update()
2548 def zoom_tracks(self, anchor, delta):
2549 ntracks_shown = self.shown_tracks_range[1] \
2550 - self.shown_tracks_range[0]
2552 if (ntracks_shown == 1 and delta <= 0) or \
2553 (ntracks_shown == self.ntracks and delta >= 0):
2554 return
2556 ntracks_shown += int(round(delta))
2557 ntracks_shown = min(max(1, ntracks_shown), self.ntracks)
2559 u = self.shown_tracks_start
2560 nu = max(0., u-anchor*delta)
2561 nv = nu + ntracks_shown
2562 if nv > self.ntracks:
2563 nu -= nv - self.ntracks
2564 nv -= nv - self.ntracks
2566 self.set_tracks_range((int(round(nu)), int(round(nv))), nu)
2568 self.ntracks_shown_max = self.shown_tracks_range[1] \
2569 - self.shown_tracks_range[0]
2571 self.update()
2573 def content_time_range(self):
2574 pile = self.get_pile()
2575 tmin, tmax = pile.get_tmin(), pile.get_tmax()
2576 if tmin is None:
2577 tmin = initial_time_range[0]
2578 if tmax is None:
2579 tmax = initial_time_range[1]
2581 return tmin, tmax
2583 def content_deltat_range(self):
2584 pile = self.get_pile()
2586 deltatmin, deltatmax = pile.get_deltatmin(), pile.get_deltatmax()
2588 if deltatmin is None:
2589 deltatmin = 0.001
2591 if deltatmax is None:
2592 deltatmax = 1000.0
2594 return deltatmin, deltatmax
2596 def make_good_looking_time_range(self, tmin, tmax, tight=False):
2597 if tmax < tmin:
2598 tmin, tmax = tmax, tmin
2600 deltatmin = self.content_deltat_range()[0]
2601 dt = deltatmin * self.visible_length * 0.95
2603 if dt == 0.0:
2604 dt = 1.0
2606 if tight:
2607 if tmax != tmin:
2608 dtm = tmax - tmin
2609 tmin -= dtm*0.1
2610 tmax += dtm*0.1
2611 return tmin, tmax
2612 else:
2613 tcenter = (tmin + tmax) / 2.
2614 tmin = tcenter - 0.5*dt
2615 tmax = tcenter + 0.5*dt
2616 return tmin, tmax
2618 if tmax-tmin < dt:
2619 vmin, vmax = self.get_time_range()
2620 dt = min(vmax - vmin, dt)
2622 tcenter = (tmin+tmax)/2.
2623 etmin, etmax = tmin, tmax
2624 tmin = min(etmin, tcenter - 0.5*dt)
2625 tmax = max(etmax, tcenter + 0.5*dt)
2626 dtm = tmax-tmin
2627 if etmin == tmin:
2628 tmin -= dtm*0.1
2629 if etmax == tmax:
2630 tmax += dtm*0.1
2632 else:
2633 dtm = tmax-tmin
2634 tmin -= dtm*0.1
2635 tmax += dtm*0.1
2637 return tmin, tmax
2639 def go_to_selection(self, tight=False):
2640 markers = self.selected_markers()
2641 if markers:
2642 tmax, tmin = self.content_time_range()
2643 for marker in markers:
2644 tmin = min(tmin, marker.tmin)
2645 tmax = max(tmax, marker.tmax)
2647 else:
2648 if tight:
2649 vmin, vmax = self.get_time_range()
2650 tmin = tmax = (vmin + vmax) / 2.
2651 else:
2652 tmin, tmax = self.content_time_range()
2654 tmin, tmax = self.make_good_looking_time_range(
2655 tmin, tmax, tight=tight)
2657 self.interrupt_following()
2658 self.set_time_range(tmin, tmax)
2659 self.update()
2661 def go_to_time(self, t, tlen=None):
2662 tmax = t
2663 if tlen is not None:
2664 tmax = t+tlen
2665 tmin, tmax = self.make_good_looking_time_range(t, tmax)
2666 self.interrupt_following()
2667 self.set_time_range(tmin, tmax)
2668 self.update()
2670 def go_to_event_by_name(self, name):
2671 for marker in self.markers:
2672 if isinstance(marker, EventMarker):
2673 event = marker.get_event()
2674 if event.name and event.name.lower() == name.lower():
2675 tmin, tmax = self.make_good_looking_time_range(
2676 event.time, event.time)
2678 self.interrupt_following()
2679 self.set_time_range(tmin, tmax)
2681 def printit(self):
2682 from .qt_compat import qprint
2683 printer = qprint.QPrinter()
2684 printer.setOrientation(qprint.QPrinter.Landscape)
2686 dialog = qprint.QPrintDialog(printer, self)
2687 dialog.setWindowTitle('Print')
2689 if dialog.exec_() != qw.QDialog.Accepted:
2690 return
2692 painter = qg.QPainter()
2693 painter.begin(printer)
2694 page = printer.pageRect()
2695 self.drawit(
2696 painter, printmode=False, w=page.width(), h=page.height())
2698 painter.end()
2700 def savesvg(self, fn=None):
2702 if not fn:
2703 fn, _ = fnpatch(qw.QFileDialog.getSaveFileName(
2704 self,
2705 'Save as SVG|PNG',
2706 os.path.expanduser(os.path.join('~', 'untitled.svg')),
2707 'SVG|PNG (*.svg *.png)',
2708 options=qfiledialog_options))
2710 if fn == '':
2711 return
2713 fn = str(fn)
2715 if fn.lower().endswith('.svg'):
2716 try:
2717 w, h = 842, 595
2718 margin = 0.025
2719 m = max(w, h)*margin
2721 generator = qsvg.QSvgGenerator()
2722 generator.setFileName(fn)
2723 generator.setSize(qc.QSize(w, h))
2724 generator.setViewBox(qc.QRectF(-m, -m, w+2*m, h+2*m))
2726 painter = qg.QPainter()
2727 painter.begin(generator)
2728 self.drawit(painter, printmode=False, w=w, h=h)
2729 painter.end()
2731 except Exception as e:
2732 self.fail('Failed to write SVG file: %s' % str(e))
2734 elif fn.lower().endswith('.png'):
2735 if use_pyqt5:
2736 pixmap = self.grab()
2737 else:
2738 pixmap = qg.QPixmap().grabWidget(self)
2740 try:
2741 pixmap.save(fn)
2743 except Exception as e:
2744 self.fail('Failed to write PNG file: %s' % str(e))
2746 else:
2747 self.fail(
2748 'Unsupported file type: filename must end with ".svg" or '
2749 '".png".')
2751 def paintEvent(self, paint_ev):
2752 '''
2753 Called by QT whenever widget needs to be painted.
2754 '''
2755 painter = qg.QPainter(self)
2757 if self.menuitem_antialias.isChecked():
2758 painter.setRenderHint(qg.QPainter.Antialiasing)
2760 self.drawit(painter)
2762 logger.debug(
2763 'Time spent drawing: %.3f %.3f %.3f %.3f %.3f' %
2764 (self.timer_draw - self.timer_cutout))
2766 logger.debug(
2767 'Time spent processing: %.3f %.3f %.3f %.3f %.3f' %
2768 self.timer_cutout.get())
2770 self.time_spent_painting = self.timer_draw.get()[-1]
2771 self.time_last_painted = time.time()
2773 def determine_box_styles(self):
2775 traces = list(self.pile.iter_traces())
2776 traces.sort(key=operator.attrgetter('full_id'))
2777 istyle = 0
2778 trace_styles = {}
2779 for itr, tr in enumerate(traces):
2780 if itr > 0:
2781 other = traces[itr-1]
2782 if not (
2783 other.nslc_id == tr.nslc_id
2784 and other.deltat == tr.deltat
2785 and abs(other.tmax - tr.tmin)
2786 < gap_lap_tolerance*tr.deltat):
2788 istyle += 1
2790 trace_styles[tr.full_id, tr.deltat] = istyle
2792 self.trace_styles = trace_styles
2794 def draw_trace_boxes(self, p, time_projection, track_projections):
2796 for v_projection in track_projections.values():
2797 v_projection.set_in_range(0., 1.)
2799 def selector(x):
2800 return x.overlaps(*time_projection.get_in_range())
2802 if self.trace_filter is not None:
2803 def tselector(x):
2804 return selector(x) and self.trace_filter(x)
2806 else:
2807 tselector = selector
2809 traces = list(self.pile.iter_traces(
2810 group_selector=selector, trace_selector=tselector))
2812 traces.sort(key=operator.attrgetter('full_id'))
2814 def drawbox(itrack, istyle, traces):
2815 v_projection = track_projections[itrack]
2816 dvmin = v_projection(0.)
2817 dvmax = v_projection(1.)
2818 dtmin = time_projection.clipped(traces[0].tmin)
2819 dtmax = time_projection.clipped(traces[-1].tmax)
2821 style = box_styles[istyle % len(box_styles)]
2822 rect = qc.QRectF(dtmin, dvmin, float(dtmax-dtmin), dvmax-dvmin)
2823 p.fillRect(rect, style.fill_brush)
2824 p.setPen(style.frame_pen)
2825 p.drawRect(rect)
2827 traces_by_style = {}
2828 for itr, tr in enumerate(traces):
2829 gt = self.gather(tr)
2830 if gt not in self.key_to_row:
2831 continue
2833 itrack = self.key_to_row[gt]
2834 if itrack not in track_projections:
2835 continue
2837 istyle = self.trace_styles.get((tr.full_id, tr.deltat), 0)
2839 if len(traces) < 500:
2840 drawbox(itrack, istyle, [tr])
2841 else:
2842 if (itrack, istyle) not in traces_by_style:
2843 traces_by_style[itrack, istyle] = []
2844 traces_by_style[itrack, istyle].append(tr)
2846 for (itrack, istyle), traces in traces_by_style.items():
2847 drawbox(itrack, istyle, traces)
2849 def draw_visible_markers(
2850 self, p, vcenter_projection, primary_pen):
2852 try:
2853 markers = self.markers.with_key_in_limited(
2854 self.tmin - self.markers_deltat_max, self.tmax, 2000)
2856 except pyrocko.pile.TooMany:
2857 tmin = self.markers[0].tmin
2858 tmax = self.markers[-1].tmax
2859 umin_view, umax_view = self.time_projection.get_out_range()
2860 umin = max(umin_view, self.time_projection(tmin))
2861 umax = min(umax_view, self.time_projection(tmax))
2862 v0, _ = vcenter_projection.get_out_range()
2863 label_bg = qg.QBrush(qg.QColor(255, 255, 255))
2865 p.save()
2867 pen = qg.QPen(primary_pen)
2868 pen.setWidth(2)
2869 pen.setStyle(qc.Qt.DotLine)
2870 # pat = [5., 3.]
2871 # pen.setDashPattern(pat)
2872 p.setPen(pen)
2874 if self.n_selected_markers == len(self.markers):
2875 s_selected = ' (all selected)'
2876 elif self.n_selected_markers > 0:
2877 s_selected = ' (%i selected)' % self.n_selected_markers
2878 else:
2879 s_selected = ''
2881 draw_label(
2882 p, umin+10., v0-10.,
2883 '%i Markers' % len(self.markers) + s_selected,
2884 label_bg, 'LB')
2886 line = qc.QLineF(umin, v0, umax, v0)
2887 p.drawLine(line)
2888 p.restore()
2890 return
2892 for marker in markers:
2893 if marker.tmin < self.tmax and self.tmin < marker.tmax \
2894 and marker.kind in self.visible_marker_kinds:
2896 marker.draw(
2897 p, self.time_projection, vcenter_projection,
2898 with_label=True)
2900 def drawit(self, p, printmode=False, w=None, h=None):
2901 '''
2902 This performs the actual drawing.
2903 '''
2905 self.timer_draw.start()
2907 if self.gather is None:
2908 self.set_gathering()
2910 if self.pile_has_changed:
2912 if not self.sortingmode_change_delayed():
2913 self.sortingmode_change()
2915 if self.menuitem_showboxes.isChecked():
2916 self.determine_box_styles()
2918 self.pile_has_changed = False
2920 if h is None:
2921 h = float(self.height())
2922 if w is None:
2923 w = float(self.width())
2925 if printmode:
2926 primary_color = (0, 0, 0)
2927 else:
2928 primary_color = pyrocko.plot.tango_colors['aluminium5']
2930 primary_pen = qg.QPen(qg.QColor(*primary_color))
2932 ax_h = float(self.ax_height)
2934 vbottom_ax_projection = Projection()
2935 vtop_ax_projection = Projection()
2936 vcenter_projection = Projection()
2938 self.time_projection.set_out_range(0., w)
2939 vbottom_ax_projection.set_out_range(h-ax_h, h)
2940 vtop_ax_projection.set_out_range(0., ax_h)
2941 vcenter_projection.set_out_range(ax_h, h-ax_h)
2942 vcenter_projection.set_in_range(0., 1.)
2943 self.track_to_screen.set_out_range(ax_h, h-ax_h)
2945 self.track_to_screen.set_in_range(*self.shown_tracks_range)
2946 track_projections = {}
2947 for i in range(*self.shown_tracks_range):
2948 proj = Projection()
2949 proj.set_out_range(
2950 self.track_to_screen(i+0.05),
2951 self.track_to_screen(i+1.-0.05))
2953 track_projections[i] = proj
2955 if self.tmin < self.tmax:
2956 self.time_projection.set_in_range(self.tmin, self.tmax)
2957 vbottom_ax_projection.set_in_range(0, ax_h)
2959 self.tax.drawit(p, self.time_projection, vbottom_ax_projection)
2961 yscaler = pyrocko.plot.AutoScaler()
2962 if not printmode and self.menuitem_showboxes.isChecked():
2963 self.draw_trace_boxes(
2964 p, self.time_projection, track_projections)
2966 if self.floating_marker:
2967 self.floating_marker.draw(
2968 p, self.time_projection, vcenter_projection)
2970 self.draw_visible_markers(
2971 p, vcenter_projection, primary_pen)
2973 p.setPen(primary_pen)
2975 font = qg.QFont()
2976 font.setBold(True)
2978 axannotfont = qg.QFont()
2979 axannotfont.setBold(True)
2980 axannotfont.setPointSize(8)
2982 p.setFont(font)
2983 label_bg = qg.QBrush(qg.QColor(255, 255, 255, 100))
2985 processed_traces = self.prepare_cutout2(
2986 self.tmin, self.tmax,
2987 trace_selector=self.trace_selector,
2988 degap=self.menuitem_degap.isChecked(),
2989 demean=self.menuitem_demean.isChecked())
2991 color_lookup = dict(
2992 [(k, i) for (i, k) in enumerate(self.color_keys)])
2994 self.track_to_nslc_ids = {}
2995 nticks = 0
2996 annot_labels = []
2997 if processed_traces:
2998 show_scales = self.menuitem_showscalerange.isChecked() \
2999 or self.menuitem_showscaleaxis.isChecked()
3001 fm = qg.QFontMetrics(axannotfont, p.device())
3002 trackheight = self.track_to_screen(1.-0.05) \
3003 - self.track_to_screen(0.05)
3005 nlinesavail = trackheight/float(fm.lineSpacing())
3006 if self.menuitem_showscaleaxis.isChecked():
3007 nticks = max(3, min(nlinesavail * 0.5, 15))
3008 else:
3009 nticks = 15
3011 yscaler = pyrocko.plot.AutoScaler(
3012 no_exp_interval=(-3, 2), approx_ticks=nticks,
3013 snap=show_scales
3014 and not self.menuitem_showscaleaxis.isChecked())
3016 data_ranges = pyrocko.trace.minmax(
3017 processed_traces,
3018 key=self.scaling_key,
3019 mode=self.scaling_base)
3021 if not self.menuitem_fixscalerange.isChecked():
3022 self.old_data_ranges = data_ranges
3023 else:
3024 data_ranges.update(self.old_data_ranges)
3026 self.apply_scaling_hooks(data_ranges)
3028 trace_to_itrack = {}
3029 track_scaling_keys = {}
3030 track_scaling_colors = {}
3031 for trace in processed_traces:
3032 gt = self.gather(trace)
3033 if gt not in self.key_to_row:
3034 continue
3036 itrack = self.key_to_row[gt]
3037 if itrack not in track_projections:
3038 continue
3040 trace_to_itrack[trace] = itrack
3042 if itrack not in self.track_to_nslc_ids:
3043 self.track_to_nslc_ids[itrack] = set()
3045 self.track_to_nslc_ids[itrack].add(trace.nslc_id)
3047 if itrack not in track_scaling_keys:
3048 track_scaling_keys[itrack] = set()
3050 scaling_key = self.scaling_key(trace)
3051 track_scaling_keys[itrack].add(scaling_key)
3053 color = pyrocko.plot.color(
3054 color_lookup[self.color_gather(trace)])
3056 k = itrack, scaling_key
3057 if k not in track_scaling_colors \
3058 and self.menuitem_colortraces.isChecked():
3059 track_scaling_colors[k] = color
3060 else:
3061 track_scaling_colors[k] = primary_color
3063 # y axes, zero lines
3065 trace_projections = {}
3066 for itrack in list(track_projections.keys()):
3067 if itrack not in track_scaling_keys:
3068 continue
3069 uoff = 0
3070 for scaling_key in track_scaling_keys[itrack]:
3071 data_range = data_ranges[scaling_key]
3072 dymin, dymax = data_range
3073 ymin, ymax, yinc = yscaler.make_scale(
3074 (dymin/self.gain, dymax/self.gain))
3075 iexp = yscaler.make_exp(yinc)
3076 factor = 10**iexp
3077 trace_projection = track_projections[itrack].copy()
3078 trace_projection.set_in_range(ymax, ymin)
3079 trace_projections[itrack, scaling_key] = \
3080 trace_projection
3081 umin, umax = self.time_projection.get_out_range()
3082 vmin, vmax = trace_projection.get_out_range()
3083 umax_zeroline = umax
3084 uoffnext = uoff
3086 if show_scales:
3087 pen = qg.QPen(primary_pen)
3088 k = itrack, scaling_key
3089 if k in track_scaling_colors:
3090 c = qg.QColor(*track_scaling_colors[
3091 itrack, scaling_key])
3093 pen.setColor(c)
3095 p.setPen(pen)
3096 if nlinesavail > 3:
3097 if self.menuitem_showscaleaxis.isChecked():
3098 ymin_annot = math.ceil(ymin/yinc)*yinc
3099 ny_annot = int(
3100 math.floor(ymax/yinc)
3101 - math.ceil(ymin/yinc)) + 1
3103 for iy_annot in range(ny_annot):
3104 y = ymin_annot + iy_annot*yinc
3105 v = trace_projection(y)
3106 line = qc.QLineF(
3107 umax-10-uoff, v, umax-uoff, v)
3109 p.drawLine(line)
3110 if iy_annot == ny_annot - 1 \
3111 and iexp != 0:
3113 sexp = ' × ' \
3114 '10<sup>%i</sup>' % iexp
3115 else:
3116 sexp = ''
3118 snum = num_to_html(y/factor)
3119 lab = Label(
3120 p,
3121 umax-20-uoff,
3122 v, '%s%s' % (snum, sexp),
3123 label_bg=None,
3124 anchor='MR',
3125 font=axannotfont,
3126 color=c)
3128 uoffnext = max(
3129 lab.rect.width()+30.,
3130 uoffnext)
3132 annot_labels.append(lab)
3133 if y == 0.:
3134 umax_zeroline = \
3135 umax - 20 \
3136 - lab.rect.width() - 10 \
3137 - uoff
3138 else:
3139 if not self.menuitem_showboxes\
3140 .isChecked():
3141 qpoints = make_QPolygonF(
3142 [umax-20-uoff,
3143 umax-10-uoff,
3144 umax-10-uoff,
3145 umax-20-uoff],
3146 [vmax, vmax, vmin, vmin])
3147 p.drawPolyline(qpoints)
3149 snum = num_to_html(ymin)
3150 labmin = Label(
3151 p, umax-15-uoff, vmax, snum,
3152 label_bg=None,
3153 anchor='BR',
3154 font=axannotfont,
3155 color=c)
3157 annot_labels.append(labmin)
3158 snum = num_to_html(ymax)
3159 labmax = Label(
3160 p, umax-15-uoff, vmin, snum,
3161 label_bg=None,
3162 anchor='TR',
3163 font=axannotfont,
3164 color=c)
3166 annot_labels.append(labmax)
3168 for lab in (labmin, labmax):
3169 uoffnext = max(
3170 lab.rect.width()+10., uoffnext)
3172 if self.menuitem_showzeroline.isChecked():
3173 v = trace_projection(0.)
3174 if vmin <= v <= vmax:
3175 line = qc.QLineF(umin, v, umax_zeroline, v)
3176 p.drawLine(line)
3178 uoff = uoffnext
3180 p.setFont(font)
3181 p.setPen(primary_pen)
3182 for trace in processed_traces:
3183 if trace not in trace_to_itrack:
3184 continue
3186 itrack = trace_to_itrack[trace]
3187 scaling_key = self.scaling_key(trace)
3188 trace_projection = trace_projections[
3189 itrack, scaling_key]
3191 vdata = trace_projection(trace.get_ydata())
3193 udata_min = float(self.time_projection(trace.tmin))
3194 udata_max = float(self.time_projection(
3195 trace.tmin+trace.deltat*(vdata.size-1)))
3196 udata = num.linspace(udata_min, udata_max, vdata.size)
3198 qpoints = make_QPolygonF(udata, vdata)
3200 umin, umax = self.time_projection.get_out_range()
3201 vmin, vmax = trace_projection.get_out_range()
3203 trackrect = qc.QRectF(umin, vmin, umax-umin, vmax-vmin)
3205 if self.menuitem_cliptraces.isChecked():
3206 p.setClipRect(trackrect)
3208 if self.menuitem_colortraces.isChecked():
3209 color = pyrocko.plot.color(
3210 color_lookup[self.color_gather(trace)])
3211 pen = qg.QPen(qg.QColor(*color), 1)
3212 p.setPen(pen)
3214 p.drawPolyline(qpoints)
3216 if self.floating_marker:
3217 self.floating_marker.draw_trace(
3218 self, p, trace,
3219 self.time_projection, trace_projection, 1.0)
3221 for marker in self.markers.with_key_in(
3222 self.tmin - self.markers_deltat_max,
3223 self.tmax):
3225 if marker.tmin < self.tmax \
3226 and self.tmin < marker.tmax \
3227 and marker.kind \
3228 in self.visible_marker_kinds:
3229 marker.draw_trace(
3230 self, p, trace, self.time_projection,
3231 trace_projection, 1.0)
3233 p.setPen(primary_pen)
3235 if self.menuitem_cliptraces.isChecked():
3236 p.setClipRect(0, 0, w, h)
3238 p.setPen(primary_pen)
3240 while font.pointSize() > 2:
3241 fm = qg.QFontMetrics(font, p.device())
3242 trackheight = self.track_to_screen(1.-0.05) \
3243 - self.track_to_screen(0.05)
3244 nlinesavail = trackheight/float(fm.lineSpacing())
3245 if nlinesavail > 1:
3246 break
3248 font.setPointSize(font.pointSize()-1)
3250 p.setFont(font)
3251 for key in self.track_keys:
3252 itrack = self.key_to_row[key]
3253 if itrack in track_projections:
3254 plabel = ' '.join(
3255 [str(x) for x in key if x is not None])
3256 lx = 10
3257 ly = self.track_to_screen(itrack+0.5)
3258 draw_label(p, lx, ly, plabel, label_bg, 'ML')
3260 for lab in annot_labels:
3261 lab.draw()
3263 self.timer_draw.stop()
3265 def see_data_params(self):
3267 min_deltat = self.content_deltat_range()[0]
3269 # determine padding and downampling requirements
3270 if self.lowpass is not None:
3271 deltat_target = 1./self.lowpass * 0.25
3272 ndecimate = min(
3273 50,
3274 max(1, int(round(deltat_target / min_deltat))))
3275 tpad = 1./self.lowpass * 2.
3276 else:
3277 ndecimate = 1
3278 tpad = min_deltat*5.
3280 if self.highpass is not None:
3281 tpad = max(1./self.highpass * 2., tpad)
3283 nsee_points_per_trace = 5000*10
3284 tsee = ndecimate*nsee_points_per_trace*min_deltat
3286 return ndecimate, tpad, tsee
3288 def clean_update(self):
3289 self.old_processed_traces = None
3290 self.update()
3292 def get_adequate_tpad(self):
3293 tpad = 0.
3294 for f in [self.highpass, self.lowpass]:
3295 if f is not None:
3296 tpad = max(tpad, 1.0/f)
3298 for snuffling in self.snufflings:
3299 if snuffling._post_process_hook_enabled \
3300 or snuffling._pre_process_hook_enabled:
3302 tpad = max(tpad, snuffling.get_tpad())
3304 return tpad
3306 def prepare_cutout2(
3307 self, tmin, tmax, trace_selector=None, degap=True,
3308 demean=True, nmax=6000):
3310 if self.pile.is_empty():
3311 return []
3313 nmax = self.visible_length
3315 self.timer_cutout.start()
3317 tsee = tmax-tmin
3318 min_deltat_wo_decimate = tsee/nmax
3319 min_deltat_w_decimate = min_deltat_wo_decimate / 32.
3321 min_deltat_allow = min_deltat_wo_decimate
3322 if self.lowpass is not None:
3323 target_deltat_lp = 0.25/self.lowpass
3324 if target_deltat_lp > min_deltat_wo_decimate:
3325 min_deltat_allow = min_deltat_w_decimate
3327 min_deltat_allow = math.exp(
3328 int(math.floor(math.log(min_deltat_allow))))
3330 tmin_ = tmin
3331 tmax_ = tmax
3333 # fetch more than needed?
3334 if self.menuitem_liberal_fetch.isChecked():
3335 tlen = pyrocko.trace.nextpow2((tmax-tmin)*1.5)
3336 tmin = math.floor(tmin/tlen) * tlen
3337 tmax = math.ceil(tmax/tlen) * tlen
3339 fft_filtering = self.menuitem_fft_filtering.isChecked()
3340 lphp = self.menuitem_lphp.isChecked()
3341 ads = self.menuitem_allowdownsampling.isChecked()
3343 tpad = self.get_adequate_tpad()
3344 tpad = max(tpad, tsee)
3346 # state vector to decide if cached traces can be used
3347 vec = (
3348 tmin, tmax, tpad, trace_selector, degap, demean, self.lowpass,
3349 self.highpass, fft_filtering, lphp,
3350 min_deltat_allow, self.rotate, self.shown_tracks_range,
3351 ads, self.pile.get_update_count())
3353 if (self.old_vec
3354 and self.old_vec[0] <= vec[0]
3355 and vec[1] <= self.old_vec[1]
3356 and vec[2:] == self.old_vec[2:]
3357 and not (self.reloaded or self.menuitem_watch.isChecked())
3358 and self.old_processed_traces is not None):
3360 logger.debug('Using cached traces')
3361 processed_traces = self.old_processed_traces
3363 else:
3364 self.old_vec = vec
3366 processed_traces = []
3368 if self.pile.deltatmax >= min_deltat_allow:
3370 def group_selector(gr):
3371 return gr.deltatmax >= min_deltat_allow
3373 if trace_selector is not None:
3374 def trace_selectorx(tr):
3375 return tr.deltat >= min_deltat_allow \
3376 and trace_selector(tr)
3377 else:
3378 def trace_selectorx(tr):
3379 return tr.deltat >= min_deltat_allow
3381 for traces in self.pile.chopper(
3382 tmin=tmin, tmax=tmax, tpad=tpad,
3383 want_incomplete=True,
3384 degap=degap,
3385 maxgap=gap_lap_tolerance,
3386 maxlap=gap_lap_tolerance,
3387 keep_current_files_open=True,
3388 group_selector=group_selector,
3389 trace_selector=trace_selectorx,
3390 accessor_id=id(self),
3391 snap=(math.floor, math.ceil),
3392 include_last=True):
3394 if demean:
3395 for tr in traces:
3396 if (tr.meta and tr.meta.get('tabu', False)):
3397 continue
3398 y = tr.get_ydata()
3399 tr.set_ydata(y - num.mean(y))
3401 traces = self.pre_process_hooks(traces)
3403 for trace in traces:
3405 if not (trace.meta
3406 and trace.meta.get('tabu', False)):
3408 if fft_filtering:
3409 but = pyrocko.trace.ButterworthResponse
3410 multres = pyrocko.trace.MultiplyResponse
3411 if self.lowpass is not None \
3412 or self.highpass is not None:
3414 it = num.arange(
3415 trace.data_len(), dtype=float)
3416 detr_data, m, b = detrend(
3417 it, trace.get_ydata())
3419 trace.set_ydata(detr_data)
3421 freqs, fdata = trace.spectrum(
3422 pad_to_pow2=True, tfade=None)
3424 nfreqs = fdata.size
3426 key = (trace.deltat, nfreqs)
3428 if key not in self.tf_cache:
3429 resps = []
3430 if self.lowpass is not None:
3431 resps.append(but(
3432 order=4,
3433 corner=self.lowpass,
3434 type='low'))
3436 if self.highpass is not None:
3437 resps.append(but(
3438 order=4,
3439 corner=self.highpass,
3440 type='high'))
3442 resp = multres(resps)
3443 self.tf_cache[key] = \
3444 resp.evaluate(freqs)
3446 filtered_data = num.fft.irfft(
3447 fdata*self.tf_cache[key]
3448 )[:trace.data_len()]
3450 retrended_data = retrend(
3451 it, filtered_data, m, b)
3453 trace.set_ydata(retrended_data)
3455 else:
3457 if ads and self.lowpass is not None:
3458 while trace.deltat \
3459 < min_deltat_wo_decimate:
3461 trace.downsample(2, demean=False)
3463 fmax = 0.5/trace.deltat
3464 if not lphp and (
3465 self.lowpass is not None
3466 and self.highpass is not None
3467 and self.lowpass < fmax
3468 and self.highpass < fmax
3469 and self.highpass < self.lowpass):
3471 trace.bandpass(
3472 2, self.highpass, self.lowpass)
3473 else:
3474 if self.lowpass is not None:
3475 if self.lowpass < 0.5/trace.deltat:
3476 trace.lowpass(
3477 4, self.lowpass,
3478 demean=False)
3480 if self.highpass is not None:
3481 if self.lowpass is None \
3482 or self.highpass \
3483 < self.lowpass:
3485 if self.highpass < \
3486 0.5/trace.deltat:
3487 trace.highpass(
3488 4, self.highpass,
3489 demean=False)
3491 processed_traces.append(trace)
3493 if self.rotate != 0.0:
3494 phi = self.rotate/180.*math.pi
3495 cphi = math.cos(phi)
3496 sphi = math.sin(phi)
3497 for a in processed_traces:
3498 for b in processed_traces:
3499 if (a.network == b.network
3500 and a.station == b.station
3501 and a.location == b.location
3502 and ((a.channel.lower().endswith('n')
3503 and b.channel.lower().endswith('e'))
3504 or (a.channel.endswith('1')
3505 and b.channel.endswith('2')))
3506 and abs(a.deltat-b.deltat) < a.deltat*0.001
3507 and abs(a.tmin-b.tmin) < a.deltat*0.01 and
3508 len(a.get_ydata()) == len(b.get_ydata())):
3510 aydata = a.get_ydata()*cphi+b.get_ydata()*sphi
3511 bydata = -a.get_ydata()*sphi+b.get_ydata()*cphi
3512 a.set_ydata(aydata)
3513 b.set_ydata(bydata)
3515 processed_traces = self.post_process_hooks(processed_traces)
3517 self.old_processed_traces = processed_traces
3519 chopped_traces = []
3520 for trace in processed_traces:
3521 try:
3522 ctrace = trace.chop(
3523 tmin_-trace.deltat*4., tmax_+trace.deltat*4.,
3524 inplace=False)
3526 except pyrocko.trace.NoData:
3527 continue
3529 if ctrace.data_len() < 2:
3530 continue
3532 chopped_traces.append(ctrace)
3534 self.timer_cutout.stop()
3535 return chopped_traces
3537 def pre_process_hooks(self, traces):
3538 for snuffling in self.snufflings:
3539 if snuffling._pre_process_hook_enabled:
3540 traces = snuffling.pre_process_hook(traces)
3542 return traces
3544 def post_process_hooks(self, traces):
3545 for snuffling in self.snufflings:
3546 if snuffling._post_process_hook_enabled:
3547 traces = snuffling.post_process_hook(traces)
3549 return traces
3551 def visible_length_change(self, ignore=None):
3552 for menuitem, vlen in self.menuitems_visible_length:
3553 if menuitem.isChecked():
3554 self.visible_length = vlen
3556 def scaling_base_change(self, ignore=None):
3557 for menuitem, scaling_base in self.menuitems_scaling_base:
3558 if menuitem.isChecked():
3559 self.scaling_base = scaling_base
3561 def scalingmode_change(self, ignore=None):
3562 for menuitem, scaling_key in self.menuitems_scaling:
3563 if menuitem.isChecked():
3564 self.scaling_key = scaling_key
3566 def apply_scaling_hooks(self, data_ranges):
3567 for k in sorted(self.scaling_hooks.keys()):
3568 hook = self.scaling_hooks[k]
3569 hook(data_ranges)
3571 def set_scaling_hook(self, k, hook):
3572 self.scaling_hooks[k] = hook
3574 def remove_scaling_hook(self, k):
3575 del self.scaling_hooks[k]
3577 def remove_scaling_hooks(self):
3578 self.scaling_hooks = {}
3580 def s_sortingmode_change(self, ignore=None):
3581 for menuitem, valfunc in self.menuitems_ssorting:
3582 if menuitem.isChecked():
3583 self._ssort = valfunc
3585 self.sortingmode_change()
3587 def sortingmode_change(self, ignore=None):
3588 for menuitem, (gather, order, color) in self.menuitems_sorting:
3589 if menuitem.isChecked():
3590 self.set_gathering(gather, order, color)
3592 self.sortingmode_change_time = time.time()
3594 def lowpass_change(self, value, ignore=None):
3595 self.lowpass = value
3596 self.passband_check()
3597 self.tf_cache = {}
3598 self.update()
3600 def highpass_change(self, value, ignore=None):
3601 self.highpass = value
3602 self.passband_check()
3603 self.tf_cache = {}
3604 self.update()
3606 def passband_check(self):
3607 if self.highpass and self.lowpass \
3608 and self.highpass >= self.lowpass:
3610 self.message = 'Corner frequency of highpass larger than ' \
3611 'corner frequency of lowpass! I will now ' \
3612 'deactivate the highpass.'
3614 self.update_status()
3615 else:
3616 oldmess = self.message
3617 self.message = None
3618 if oldmess is not None:
3619 self.update_status()
3621 def gain_change(self, value, ignore):
3622 self.gain = value
3623 self.update()
3625 def rot_change(self, value, ignore):
3626 self.rotate = value
3627 self.update()
3629 def set_selected_markers(self, markers):
3630 '''
3631 Set a list of markers selected
3633 :param markers: list of markers
3634 '''
3635 self.deselect_all()
3636 for m in markers:
3637 m.selected = True
3639 self.update()
3641 def deselect_all(self):
3642 for marker in self.markers:
3643 marker.selected = False
3645 def animate_picking(self):
3646 point = self.mapFromGlobal(qg.QCursor.pos())
3647 self.update_picking(point.x(), point.y(), doshift=True)
3649 def get_nslc_ids_for_track(self, ftrack):
3650 itrack = int(ftrack)
3651 return self.track_to_nslc_ids.get(itrack, [])
3653 def stop_picking(self, x, y, abort=False):
3654 if self.picking:
3655 self.update_picking(x, y, doshift=False)
3656 self.picking = None
3657 self.picking_down = None
3658 self.picking_timer.stop()
3659 self.picking_timer = None
3660 if not abort:
3661 self.add_marker(self.floating_marker)
3662 self.floating_marker.selected = True
3663 self.emit_selected_markers()
3665 self.floating_marker = None
3667 def start_picking(self, ignore):
3669 if not self.picking:
3670 self.deselect_all()
3671 self.picking = qw.QRubberBand(qw.QRubberBand.Rectangle)
3672 point = self.mapFromGlobal(qg.QCursor.pos())
3674 gpoint = self.mapToGlobal(qc.QPoint(point.x(), 0))
3675 self.picking.setGeometry(
3676 gpoint.x(), gpoint.y(), 1, self.height())
3677 t = self.time_projection.rev(point.x())
3679 ftrack = self.track_to_screen.rev(point.y())
3680 nslc_ids = self.get_nslc_ids_for_track(ftrack)
3681 self.floating_marker = Marker(nslc_ids, t, t)
3682 self.floating_marker.selected = True
3684 self.picking_timer = qc.QTimer()
3685 self.picking_timer.timeout.connect(
3686 self.animate_picking)
3688 self.picking_timer.setInterval(50)
3689 self.picking_timer.start()
3691 def update_picking(self, x, y, doshift=False):
3692 if self.picking:
3693 mouset = self.time_projection.rev(x)
3694 dt = 0.0
3695 if mouset < self.tmin or mouset > self.tmax:
3696 if mouset < self.tmin:
3697 dt = -(self.tmin - mouset)
3698 else:
3699 dt = mouset - self.tmax
3700 ddt = self.tmax-self.tmin
3701 dt = max(dt, -ddt/10.)
3702 dt = min(dt, ddt/10.)
3704 x0 = x
3705 if self.picking_down is not None:
3706 x0 = self.time_projection(self.picking_down[0])
3708 w = abs(x-x0)
3709 x0 = min(x0, x)
3711 tmin, tmax = (
3712 self.time_projection.rev(x0),
3713 self.time_projection.rev(x0+w))
3715 tmin, tmax = (
3716 max(working_system_time_range[0], tmin),
3717 min(working_system_time_range[1], tmax))
3719 p1 = self.mapToGlobal(qc.QPoint(x0, 0))
3721 self.picking.setGeometry(
3722 p1.x(), p1.y(), max(w, 1), self.height())
3724 ftrack = self.track_to_screen.rev(y)
3725 nslc_ids = self.get_nslc_ids_for_track(ftrack)
3726 self.floating_marker.set(nslc_ids, tmin, tmax)
3728 if dt != 0.0 and doshift:
3729 self.interrupt_following()
3730 self.set_time_range(self.tmin+dt, self.tmax+dt)
3732 self.update()
3734 def update_status(self):
3736 if self.message is None:
3737 point = self.mapFromGlobal(qg.QCursor.pos())
3739 mouse_t = self.time_projection.rev(point.x())
3740 if not is_working_time(mouse_t):
3741 return
3743 if self.floating_marker:
3744 tmi, tma = (
3745 self.floating_marker.tmin,
3746 self.floating_marker.tmax)
3748 tt, ms = gmtime_x(tmi)
3750 if tmi == tma:
3751 message = mystrftime(
3752 fmt='Pick: %Y-%m-%d %H:%M:%S .%r',
3753 tt=tt, milliseconds=ms)
3754 else:
3755 srange = '%g s' % (tma-tmi)
3756 message = mystrftime(
3757 fmt='Start: %Y-%m-%d %H:%M:%S .%r Length: '+srange,
3758 tt=tt, milliseconds=ms)
3759 else:
3760 tt, ms = gmtime_x(mouse_t)
3762 message = mystrftime(fmt=None, tt=tt, milliseconds=ms)
3763 else:
3764 message = self.message
3766 sb = self.window().statusBar()
3767 sb.clearMessage()
3768 sb.showMessage(message)
3770 def set_sortingmode_change_delay_time(self, dt):
3771 self.sortingmode_change_delay_time = dt
3773 def sortingmode_change_delayed(self):
3774 now = time.time()
3775 return (
3776 self.sortingmode_change_delay_time is not None
3777 and now - self.sortingmode_change_time
3778 < self.sortingmode_change_delay_time)
3780 def set_visible_marker_kinds(self, kinds):
3781 self.deselect_all()
3782 self.visible_marker_kinds = tuple(kinds)
3783 self.emit_selected_markers()
3785 def following(self):
3786 return self.follow_timer is not None \
3787 and not self.following_interrupted()
3789 def interrupt_following(self):
3790 self.interactive_range_change_time = time.time()
3792 def following_interrupted(self, now=None):
3793 if now is None:
3794 now = time.time()
3795 return now - self.interactive_range_change_time \
3796 < self.interactive_range_change_delay_time
3798 def follow(self, tlen, interval=50, lapse=None, tmax_start=None):
3799 if tmax_start is None:
3800 tmax_start = time.time()
3801 self.show_all = False
3802 self.follow_time = tlen
3803 self.follow_timer = qc.QTimer(self)
3804 self.follow_timer.timeout.connect(
3805 self.follow_update)
3806 self.follow_timer.setInterval(interval)
3807 self.follow_timer.start()
3808 self.follow_started = time.time()
3809 self.follow_lapse = lapse
3810 self.follow_tshift = self.follow_started - tmax_start
3811 self.interactive_range_change_time = 0.0
3813 def unfollow(self):
3814 if self.follow_timer is not None:
3815 self.follow_timer.stop()
3816 self.follow_timer = None
3817 self.interactive_range_change_time = 0.0
3819 def follow_update(self):
3820 rnow = time.time()
3821 if self.follow_lapse is None:
3822 now = rnow
3823 else:
3824 now = self.follow_started + (rnow - self.follow_started) \
3825 * self.follow_lapse
3827 if self.following_interrupted(rnow):
3828 return
3829 self.set_time_range(
3830 now-self.follow_time-self.follow_tshift,
3831 now-self.follow_tshift)
3833 self.update()
3835 def myclose(self, return_tag=''):
3836 self.return_tag = return_tag
3837 self.window().close()
3839 def cleanup(self):
3840 self.about_to_close.emit()
3841 self.timer.stop()
3842 if self.follow_timer is not None:
3843 self.follow_timer.stop()
3845 for snuffling in list(self.snufflings):
3846 self.remove_snuffling(snuffling)
3848 def set_error_message(self, key, value):
3849 if value is None:
3850 if key in self.error_messages:
3851 del self.error_messages[key]
3852 else:
3853 self.error_messages[key] = value
3855 def inputline_changed(self, text):
3856 pass
3858 def inputline_finished(self, text):
3859 line = str(text)
3861 toks = line.split()
3862 clearit, hideit, error = False, True, None
3863 if len(toks) >= 1:
3864 command = toks[0].lower()
3866 try:
3867 quick_filter_commands = {
3868 'n': '%s.*.*.*',
3869 's': '*.%s.*.*',
3870 'l': '*.*.%s.*',
3871 'c': '*.*.*.%s'}
3873 if command in quick_filter_commands:
3874 if len(toks) >= 2:
3875 patterns = [
3876 quick_filter_commands[toks[0]] % pat
3877 for pat in toks[1:]]
3878 self.set_quick_filter_patterns(patterns, line)
3879 else:
3880 self.set_quick_filter_patterns(None)
3882 self.update()
3884 elif command in ('hide', 'unhide'):
3885 if len(toks) >= 2:
3886 patterns = []
3887 if len(toks) == 2:
3888 patterns = [toks[1]]
3889 elif len(toks) >= 3:
3890 x = {
3891 'n': '%s.*.*.*',
3892 's': '*.%s.*.*',
3893 'l': '*.*.%s.*',
3894 'c': '*.*.*.%s'}
3896 if toks[1] in x:
3897 patterns.extend(
3898 x[toks[1]] % tok for tok in toks[2:])
3900 for pattern in patterns:
3901 if command == 'hide':
3902 self.add_blacklist_pattern(pattern)
3903 else:
3904 self.remove_blacklist_pattern(pattern)
3906 elif command == 'unhide' and len(toks) == 1:
3907 self.clear_blacklist()
3909 clearit = True
3911 self.update()
3913 elif command == 'markers':
3914 if len(toks) == 2:
3915 if toks[1] == 'all':
3916 kinds = self.all_marker_kinds
3917 else:
3918 kinds = []
3919 for x in toks[1]:
3920 try:
3921 kinds.append(int(x))
3922 except Exception:
3923 pass
3925 self.set_visible_marker_kinds(kinds)
3927 elif len(toks) == 1:
3928 self.set_visible_marker_kinds(())
3930 self.update()
3932 elif command == 'scaling':
3933 if len(toks) == 2:
3934 hideit = False
3935 error = 'wrong number of arguments'
3937 if len(toks) >= 3:
3938 vmin, vmax = [
3939 pyrocko.model.float_or_none(x)
3940 for x in toks[-2:]]
3942 def upd(d, k, vmin, vmax):
3943 if k in d:
3944 if vmin is not None:
3945 d[k] = vmin, d[k][1]
3946 if vmax is not None:
3947 d[k] = d[k][0], vmax
3949 if len(toks) == 1:
3950 self.remove_scaling_hooks()
3952 elif len(toks) == 3:
3953 def hook(data_ranges):
3954 for k in data_ranges:
3955 upd(data_ranges, k, vmin, vmax)
3957 self.set_scaling_hook('_', hook)
3959 elif len(toks) == 4:
3960 pattern = toks[1]
3962 def hook(data_ranges):
3963 for k in pyrocko.util.match_nslcs(
3964 pattern, list(data_ranges.keys())):
3966 upd(data_ranges, k, vmin, vmax)
3968 self.set_scaling_hook(pattern, hook)
3970 elif command == 'goto':
3971 toks2 = line.split(None, 1)
3972 if len(toks2) == 2:
3973 arg = toks2[1]
3974 m = re.match(
3975 r'^\d\d\d\d(-\d\d(-\d\d( \d\d(:\d\d'
3976 r'(:\d\d(\.\d+)?)?)?)?)?)?$', arg)
3977 if m:
3978 tlen = None
3979 if not m.group(1):
3980 tlen = 12*32*24*60*60
3981 elif not m.group(2):
3982 tlen = 32*24*60*60
3983 elif not m.group(3):
3984 tlen = 24*60*60
3985 elif not m.group(4):
3986 tlen = 60*60
3987 elif not m.group(5):
3988 tlen = 60
3990 supl = '1970-01-01 00:00:00'
3991 if len(supl) > len(arg):
3992 arg = arg + supl[-(len(supl)-len(arg)):]
3993 t = pyrocko.util.str_to_time(arg)
3994 self.go_to_time(t, tlen=tlen)
3996 elif re.match(r'^\d\d:\d\d(:\d\d(\.\d+)?)?$', arg):
3997 supl = '00:00:00'
3998 if len(supl) > len(arg):
3999 arg = arg + supl[-(len(supl)-len(arg)):]
4000 tmin, tmax = self.get_time_range()
4001 sdate = pyrocko.util.time_to_str(
4002 tmin/2.+tmax/2., format='%Y-%m-%d')
4003 t = pyrocko.util.str_to_time(sdate + ' ' + arg)
4004 self.go_to_time(t)
4006 else:
4007 self.go_to_event_by_name(arg)
4009 else:
4010 raise PileViewerMainException(
4011 'No such command: %s' % command)
4013 except PileViewerMainException as e:
4014 error = str(e)
4015 hideit = False
4017 return clearit, hideit, error
4019 return PileViewerMain
4022PileViewerMain = MakePileViewerMainClass(qw.QWidget)
4023GLPileViewerMain = MakePileViewerMainClass(qgl.QGLWidget)
4026class LineEditWithAbort(qw.QLineEdit):
4028 aborted = qc.pyqtSignal()
4029 history_down = qc.pyqtSignal()
4030 history_up = qc.pyqtSignal()
4032 def keyPressEvent(self, key_event):
4033 if key_event.key() == qc.Qt.Key_Escape:
4034 self.aborted.emit()
4035 elif key_event.key() == qc.Qt.Key_Down:
4036 self.history_down.emit()
4037 elif key_event.key() == qc.Qt.Key_Up:
4038 self.history_up.emit()
4039 else:
4040 return qw.QLineEdit.keyPressEvent(self, key_event)
4043class PileViewer(qw.QFrame):
4044 '''
4045 PileViewerMain + Controls + Inputline
4046 '''
4048 def __init__(
4049 self, pile,
4050 ntracks_shown_max=20,
4051 marker_editor_sortable=True,
4052 use_opengl=False,
4053 panel_parent=None,
4054 *args):
4056 qw.QFrame.__init__(self, *args)
4058 if use_opengl:
4059 self.viewer = GLPileViewerMain(
4060 pile,
4061 ntracks_shown_max=ntracks_shown_max,
4062 panel_parent=panel_parent)
4063 else:
4064 self.viewer = PileViewerMain(
4065 pile,
4066 ntracks_shown_max=ntracks_shown_max,
4067 panel_parent=panel_parent)
4069 self.marker_editor_sortable = marker_editor_sortable
4071 layout = qw.QGridLayout()
4072 self.setLayout(layout)
4073 layout.setContentsMargins(0, 0, 0, 0)
4074 layout.setSpacing(0)
4076 self.setFrameShape(qw.QFrame.StyledPanel)
4077 self.setFrameShadow(qw.QFrame.Sunken)
4079 self.input_area = qw.QFrame(self)
4080 ia_layout = qw.QGridLayout()
4081 ia_layout.setContentsMargins(11, 11, 11, 11)
4082 self.input_area.setLayout(ia_layout)
4084 self.inputline = LineEditWithAbort(self.input_area)
4085 self.inputline.returnPressed.connect(
4086 self.inputline_returnpressed)
4087 self.inputline.editingFinished.connect(
4088 self.inputline_finished)
4089 self.inputline.aborted.connect(
4090 self.inputline_aborted)
4092 self.inputline.history_down.connect(
4093 lambda: self.step_through_history(1))
4094 self.inputline.history_up.connect(
4095 lambda: self.step_through_history(-1))
4097 self.inputline.textEdited.connect(
4098 self.inputline_changed)
4100 self.inputline.setFocusPolicy(qc.Qt.ClickFocus)
4101 self.input_area.hide()
4102 self.history = None
4104 self.inputline_error_str = None
4106 self.inputline_error = qw.QLabel()
4107 self.inputline_error.hide()
4109 ia_layout.addWidget(self.inputline, 0, 0)
4110 ia_layout.addWidget(self.inputline_error, 1, 0)
4111 layout.addWidget(self.input_area, 0, 0, 1, 2)
4112 layout.addWidget(self.viewer, 1, 0)
4114 pb = Progressbars(self)
4115 layout.addWidget(pb, 2, 0, 1, 2)
4116 self.progressbars = pb
4118 scrollbar = qw.QScrollBar(qc.Qt.Vertical)
4119 self.scrollbar = scrollbar
4120 layout.addWidget(scrollbar, 1, 1)
4121 self.scrollbar.valueChanged.connect(
4122 self.scrollbar_changed)
4124 self.block_scrollbar_changes = False
4126 self.viewer.want_input.connect(
4127 self.inputline_show)
4128 self.viewer.tracks_range_changed.connect(
4129 self.tracks_range_changed)
4130 self.viewer.pile_has_changed_signal.connect(
4131 self.adjust_controls)
4132 self.viewer.about_to_close.connect(
4133 self.save_inputline_history)
4135 def cleanup(self):
4136 self.viewer.cleanup()
4138 def get_progressbars(self):
4139 return self.progressbars
4141 def inputline_show(self):
4142 if not self.history:
4143 self.load_inputline_history()
4145 self.input_area.show()
4146 self.inputline.setFocus(qc.Qt.OtherFocusReason)
4147 self.inputline.selectAll()
4149 def inputline_set_error(self, string):
4150 self.inputline_error_str = string
4151 self.inputline.setPalette(pyrocko.gui.util.get_err_palette())
4152 self.inputline.selectAll()
4153 self.inputline_error.setText(string)
4154 self.input_area.show()
4155 self.inputline_error.show()
4157 def inputline_clear_error(self):
4158 if self.inputline_error_str:
4159 self.inputline.setPalette(qw.QApplication.palette())
4160 self.inputline_error_str = None
4161 self.inputline_error.clear()
4162 self.inputline_error.hide()
4164 def inputline_changed(self, line):
4165 self.viewer.inputline_changed(str(line))
4166 self.inputline_clear_error()
4168 def inputline_returnpressed(self):
4169 line = str(self.inputline.text())
4170 clearit, hideit, error = self.viewer.inputline_finished(line)
4172 if error:
4173 self.inputline_set_error(error)
4175 line = line.strip()
4177 if line != '' and not error:
4178 if not (len(self.history) >= 1 and line == self.history[-1]):
4179 self.history.append(line)
4181 if clearit:
4183 self.inputline.blockSignals(True)
4184 qpat, qinp = self.viewer.get_quick_filter_patterns()
4185 if qpat is None:
4186 self.inputline.clear()
4187 else:
4188 self.inputline.setText(qinp)
4189 self.inputline.blockSignals(False)
4191 if hideit and not error:
4192 self.viewer.setFocus(qc.Qt.OtherFocusReason)
4193 self.input_area.hide()
4195 self.hist_ind = len(self.history)
4197 def inputline_aborted(self):
4198 '''
4199 Hide the input line.
4200 '''
4201 self.viewer.setFocus(qc.Qt.OtherFocusReason)
4202 self.hist_ind = len(self.history)
4203 self.input_area.hide()
4205 def save_inputline_history(self):
4206 '''
4207 Save input line history to "$HOME/.pyrocko/.snuffler_history.pf"
4208 '''
4209 if not self.history:
4210 return
4212 conf = pyrocko.config
4213 fn_hist = conf.expand(conf.make_conf_path_tmpl('.snuffler_history'))
4214 with open(fn_hist, 'w') as f:
4215 i = min(100, len(self.history))
4216 for c in self.history[-i:]:
4217 f.write('%s\n' % c)
4219 def load_inputline_history(self):
4220 '''
4221 Load input line history from "$HOME/.pyrocko/.snuffler_history.pf"
4222 '''
4223 conf = pyrocko.config
4224 fn_hist = conf.expand(conf.make_conf_path_tmpl('.snuffler_history'))
4225 if not os.path.exists(fn_hist):
4226 with open(fn_hist, 'w+') as f:
4227 f.write('\n')
4229 with open(fn_hist, 'r') as f:
4230 self.history = [line.strip() for line in f.readlines()]
4232 self.hist_ind = len(self.history)
4234 def step_through_history(self, ud=1):
4235 '''
4236 Step through input line history and set the input line text.
4237 '''
4238 n = len(self.history)
4239 self.hist_ind += ud
4240 self.hist_ind %= (n + 1)
4241 if len(self.history) != 0 and self.hist_ind != n:
4242 self.inputline.setText(self.history[self.hist_ind])
4243 else:
4244 self.inputline.setText('')
4246 def inputline_finished(self):
4247 pass
4249 def tracks_range_changed(self, ntracks, ilo, ihi):
4250 if self.block_scrollbar_changes:
4251 return
4253 self.scrollbar.blockSignals(True)
4254 self.scrollbar.setPageStep(ihi-ilo)
4255 vmax = max(0, ntracks-(ihi-ilo))
4256 self.scrollbar.setRange(0, vmax)
4257 self.scrollbar.setValue(ilo)
4258 self.scrollbar.setHidden(vmax == 0)
4259 self.scrollbar.blockSignals(False)
4261 def scrollbar_changed(self, value):
4262 self.block_scrollbar_changes = True
4263 ilo = value
4264 ihi = ilo + self.scrollbar.pageStep()
4265 self.viewer.set_tracks_range((ilo, ihi))
4266 self.block_scrollbar_changes = False
4267 self.update_contents()
4269 def controls(self):
4270 frame = qw.QFrame(self)
4271 layout = qw.QGridLayout()
4272 frame.setLayout(layout)
4274 minfreq = 0.001
4275 maxfreq = 1000.0
4276 self.lowpass_control = ValControl(high_is_none=True)
4277 self.lowpass_control.setup(
4278 'Lowpass [Hz]:', minfreq, maxfreq, maxfreq, 0)
4279 self.highpass_control = ValControl(low_is_none=True)
4280 self.highpass_control.setup(
4281 'Highpass [Hz]:', minfreq, maxfreq, minfreq, 1)
4282 self.gain_control = ValControl()
4283 self.gain_control.setup('Gain:', 0.001, 1000., 1., 2)
4284 self.rot_control = LinValControl()
4285 self.rot_control.setup('Rotate [deg]:', -180., 180., 0., 3)
4287 self.lowpass_control.valchange.connect(
4288 self.viewer.lowpass_change)
4289 self.highpass_control.valchange.connect(
4290 self.viewer.highpass_change)
4291 self.gain_control.valchange.connect(
4292 self.viewer.gain_change)
4293 self.rot_control.valchange.connect(
4294 self.viewer.rot_change)
4296 for icontrol, control in enumerate((
4297 self.highpass_control,
4298 self.lowpass_control,
4299 self.gain_control,
4300 self.rot_control)):
4302 for iwidget, widget in enumerate(control.widgets()):
4303 layout.addWidget(widget, icontrol, iwidget)
4305 spacer = qw.QSpacerItem(
4306 0, 0, qw.QSizePolicy.Expanding, qw.QSizePolicy.Expanding)
4307 layout.addItem(spacer, 4, 0, 1, 3)
4309 self.adjust_controls()
4310 return frame
4312 def marker_editor(self):
4313 editor = pyrocko.gui.marker_editor.MarkerEditor(
4314 self, sortable=self.marker_editor_sortable)
4316 editor.set_viewer(self.get_view())
4317 editor.get_marker_model().dataChanged.connect(
4318 self.update_contents)
4319 return editor
4321 def adjust_controls(self):
4322 dtmin, dtmax = self.viewer.content_deltat_range()
4323 maxfreq = 0.5/dtmin
4324 minfreq = (0.5/dtmax)*0.001
4325 self.lowpass_control.set_range(minfreq, maxfreq)
4326 self.highpass_control.set_range(minfreq, maxfreq)
4328 def setup_snufflings(self):
4329 self.viewer.setup_snufflings()
4331 def get_view(self):
4332 return self.viewer
4334 def update_contents(self):
4335 self.viewer.update()
4337 def get_pile(self):
4338 return self.viewer.get_pile()