1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
5from __future__ import absolute_import
7import calendar
8import math
9import time
10import copy
11import logging
12import sys
14import numpy as num
16from pyrocko import util, plot, model, trace
17from pyrocko.util import TableWriter, TableReader, gmtime_x, mystrftime
20logger = logging.getLogger('pyrocko.gui.marker')
23if sys.version_info[0] >= 3:
24 polarity_symbols = {1: u'\u2191', -1: u'\u2193', None: u'', 0: u'\u2195'}
25else:
26 polarity_symbols = {1: '+', -1: '-', None: '', 0: '0'}
29def str_to_float_or_none(s):
30 if s == 'None':
31 return None
32 return float(s)
35def str_to_str_or_none(s):
36 if s == 'None':
37 return None
38 return s
41def str_to_int_or_none(s):
42 if s == 'None':
43 return None
44 return int(s)
47def str_to_bool(s):
48 return s.lower() in ('true', 't', '1')
51def myctime(timestamp):
52 tt, ms = gmtime_x(timestamp)
53 return mystrftime(None, tt, ms)
56g_color_b = [plot.color(x) for x in (
57 'scarletred1', 'scarletred2', 'scarletred3',
58 'chameleon1', 'chameleon2', 'chameleon3',
59 'skyblue1', 'skyblue2', 'skyblue3',
60 'orange1', 'orange2', 'orange3',
61 'plum1', 'plum2', 'plum3',
62 'chocolate1', 'chocolate2', 'chocolate3')]
65class MarkerParseError(Exception):
66 pass
69class MarkerOneNSLCRequired(Exception):
70 pass
73class Marker(object):
74 '''
75 General purpose marker GUI element and base class for
76 :py:class:`EventMarker` and :py:class:`PhaseMarker`.
78 :param nslc_ids: list of (network, station, location, channel) tuples
79 (may contain wildcards)
80 :param tmin: start time
81 :param tmax: end time
82 :param kind: (optional) integer to distinguish groups of markers
83 (color-coded)
84 '''
86 @staticmethod
87 def from_string(line):
89 def fail():
90 raise MarkerParseError(
91 'Unable to create marker from string: "%s"' % line)
93 def parsedate(ymd, hms, sfs):
94 return util.to_time_str(calendar.timegm(
95 time.strptime(ymd+' '+hms, '%Y-%m-%d %H:%M:%S'))) + float(sfs)
97 try:
98 toks = line.split()
99 if len(toks) in (4, 5):
100 tmin = parsedate(*toks[:3])
101 tmax = tmin
103 elif len(toks) in (8, 9):
104 tmin = parsedate(*toks[:3])
105 tmax = parsedate(*toks[3:6])
107 else:
108 fail()
110 if len(toks) in (5, 9):
111 kind = int(toks[-2])
112 else:
113 kind = int(toks[-1])
115 nslc_ids = []
116 if len(toks) in (5, 9):
117 for snslc in toks[-1].split(','):
118 nslc = snslc.split('.')
119 if len(nslc) != 4:
120 fail()
122 nslc_ids.append(tuple(nslc))
124 except MarkerParseError:
125 fail()
127 return Marker(nslc_ids, tmin, tmax, kind=kind)
129 @staticmethod
130 def save_markers(markers, fn, fdigits=3):
131 '''
132 Static method to write marker objects to file.
134 :param markers: list of :py:class:`Marker` objects
135 :param fn: filename as string
136 :param fdigits: number of decimal digits to use for sub-second time
137 strings (default 3)
138 '''
139 f = open(fn, 'w')
140 f.write('# Snuffler Markers File Version 0.2\n')
141 writer = TableWriter(f)
142 for marker in markers:
143 a = marker.get_attributes(fdigits=fdigits)
144 w = marker.get_attribute_widths(fdigits=fdigits)
145 row = []
146 for x in a:
147 if x is None or x == '':
148 row.append('None')
149 else:
150 row.append(x)
152 writer.writerow(row, w)
154 f.close()
156 @staticmethod
157 def load_markers(fn):
158 '''
159 Static method to load markers from file.
161 :param filename: filename as string
162 :returns: list of :py:class:`Marker`, :py:class:`EventMarker` or
163 :py:class:`PhaseMarker` objects
164 '''
165 markers = []
166 with open(fn, 'r') as f:
167 line = f.readline()
168 if not line.startswith('# Snuffler Markers File Version'):
169 f.seek(0)
170 for iline, line in enumerate(f):
171 line = str(line.decode('ascii'))
172 sline = line.strip()
173 if not sline or sline.startswith('#'):
174 continue
175 try:
176 m = Marker.from_string(sline)
177 markers.append(m)
179 except MarkerParseError:
180 logger.warning(
181 'Invalid marker definition in line %i of file "%s"'
182 % (iline+1, fn))
184 f.close()
186 elif line.startswith('# Snuffler Markers File Version 0.2'):
187 reader = TableReader(f)
188 while not reader.eof:
189 row = reader.readrow()
190 if not row:
191 continue
192 if row[0] == 'event:':
193 marker = EventMarker.from_attributes(row)
194 elif row[0] == 'phase:':
195 marker = PhaseMarker.from_attributes(row)
196 else:
197 marker = Marker.from_attributes(row)
199 markers.append(marker)
200 else:
201 logger.warning('Unsupported Markers File Version')
203 return markers
205 def __init__(self, nslc_ids, tmin, tmax, kind=0):
206 self.set(nslc_ids, tmin, tmax)
207 self.alerted = False
208 self.selected = False
209 self.kind = kind
210 self.active = False
212 def set(self, nslc_ids, tmin, tmax):
213 '''
214 Set ``nslc_ids``, start time and end time of :py:class:`Marker`.
216 :param nslc_ids: list or set of (network, station, location, channel)
217 tuples
218 :param tmin: start time
219 :param tmax: end time
220 '''
221 self.nslc_ids = nslc_ids
222 self.tmin = util.to_time_float(tmin)
223 self.tmax = util.to_time_float(tmax)
225 def set_kind(self, kind):
226 '''
227 Set kind of :py:class:`Marker`.
229 :param kind: (optional) integer to distinguish groups of markers
230 (color-coded)
231 '''
232 self.kind = kind
234 def get_tmin(self):
235 '''
236 Get *start time* of :py:class:`Marker`.
237 '''
238 return self.tmin
240 def get_tmax(self):
241 '''
242 Get *end time* of :py:class:`Marker`.
243 '''
244 return self.tmax
246 def get_nslc_ids(self):
247 '''
248 Get marker's network-station-location-channel pattern.
250 :returns: list or set of (network, station, location, channel) tuples
252 The network, station, location, or channel strings may contain wildcard
253 expressions.
254 '''
255 return self.nslc_ids
257 def is_alerted(self):
258 return self.alerted
260 def is_selected(self):
261 return self.selected
263 def set_alerted(self, state):
264 self.alerted = state
266 def match_nsl(self, nsl):
267 '''
268 See documentation of :py:func:`pyrocko.util.match_nslc`.
269 '''
270 patterns = ['.'.join(x[:3]) for x in self.nslc_ids]
271 return util.match_nslc(patterns, nsl)
273 def match_nslc(self, nslc):
274 '''
275 See documentation of :py:func:`pyrocko.util.match_nslc`.
276 '''
277 patterns = ['.'.join(x) for x in self.nslc_ids]
278 return util.match_nslc(patterns, nslc)
280 def one_nslc(self):
281 '''
282 If one *nslc_id* defines this marker return this id.
283 If more than one *nslc_id* is defined in the :py:class:`Marker`s
284 *nslc_ids* raise :py:exc:`MarkerOneNSLCRequired`.
285 '''
286 if len(self.nslc_ids) != 1:
287 raise MarkerOneNSLCRequired()
289 return list(self.nslc_ids)[0]
291 def hoover_message(self):
292 return ''
294 def copy(self):
295 '''
296 Get a copy of this marker.
297 '''
298 return copy.deepcopy(self)
300 def __str__(self):
301 traces = ','.join(['.'.join(nslc_id) for nslc_id in self.nslc_ids])
302 st = myctime
303 if self.tmin == self.tmax:
304 return '%s %i %s' % (st(self.tmin), self.kind, traces)
305 else:
306 return '%s %s %g %i %s' % (
307 st(self.tmin), st(self.tmax), self.tmax-self.tmin, self.kind,
308 traces)
310 def get_attributes(self, fdigits=3):
311 traces = ','.join(['.'.join(nslc_id) for nslc_id in self.nslc_ids])
313 def st(t):
314 return util.time_to_str(
315 t, format='%Y-%m-%d %H:%M:%S.'+'%iFRAC' % fdigits)
317 vals = []
318 vals.extend(st(self.tmin).split())
319 if self.tmin != self.tmax:
320 vals.extend(st(self.tmax).split())
321 vals.append(self.tmax-self.tmin)
323 vals.append(self.kind)
324 vals.append(traces)
325 return vals
327 def get_attribute_widths(self, fdigits=3):
328 ws = [10, 9+fdigits]
329 if self.tmin != self.tmax:
330 ws.extend([10, 9+fdigits, 12])
331 ws.extend([2, 15])
332 return ws
334 @staticmethod
335 def parse_attributes(vals):
336 tmin = util.str_to_time(vals[0] + ' ' + vals[1])
337 i = 2
338 tmax = tmin
339 if len(vals) == 7:
340 tmax = util.str_to_time(vals[2] + ' ' + vals[3])
341 i = 5
343 kind = int(vals[i])
344 traces = vals[i+1]
345 if traces == 'None':
346 nslc_ids = []
347 else:
348 nslc_ids = tuple(
349 [tuple(nslc_id.split('.')) for nslc_id in traces.split(',')])
351 return nslc_ids, tmin, tmax, kind
353 @staticmethod
354 def from_attributes(vals):
355 return Marker(*Marker.parse_attributes(vals))
357 def select_color(self, colorlist):
359 def cl(x):
360 return colorlist[(self.kind*3+x) % len(colorlist)]
362 if self.selected:
363 return cl(1)
365 if self.alerted:
366 return cl(1)
368 return cl(2)
370 def draw(
371 self, p, time_projection, y_projection,
372 draw_line=True,
373 draw_triangle=False,
374 **kwargs):
376 from .qt_compat import qc, qg
377 from . import util as gui_util
379 color = self.select_color(g_color_b)
380 pen = qg.QPen(qg.QColor(*color))
381 pen.setWidth(2)
382 p.setPen(pen)
384 umin = time_projection(self.tmin)
385 umax = time_projection(self.tmax)
386 v0, v1 = y_projection.get_out_range()
387 line = qc.QLineF(umin-1, v0, umax+1, v0)
388 p.drawLine(line)
390 if self.selected or self.alerted or not self.nslc_ids:
391 linepen = qg.QPen(pen)
392 if self.selected or self.alerted:
393 linepen.setStyle(qc.Qt.CustomDashLine)
394 pat = [5., 3.]
395 linepen.setDashPattern(pat)
396 if self.alerted and not self.selected:
397 linepen.setColor(qg.QColor(150, 150, 150))
399 s = 9.
400 utriangle = gui_util.make_QPolygonF(
401 [-0.577*s, 0., 0.577*s], [0., 1.*s, 0.])
402 ltriangle = gui_util.make_QPolygonF(
403 [-0.577*s, 0., 0.577*s], [0., -1.*s, 0.])
405 def drawline(t):
406 u = time_projection(t)
407 line = qc.QLineF(u, v0, u, v1)
408 p.drawLine(line)
410 def drawtriangles(t):
411 u = time_projection(t)
412 t = qg.QPolygonF(utriangle)
413 t.translate(u, v0)
414 p.drawConvexPolygon(t)
415 t = qg.QPolygonF(ltriangle)
416 t.translate(u, v1)
417 p.drawConvexPolygon(t)
419 if draw_line or self.selected or self.alerted:
420 p.setPen(linepen)
421 drawline(self.tmin)
422 drawline(self.tmax)
424 if draw_triangle:
425 pen.setStyle(qc.Qt.SolidLine)
426 pen.setJoinStyle(qc.Qt.MiterJoin)
427 pen.setWidth(2)
428 p.setPen(pen)
429 p.setBrush(qg.QColor(*color))
430 drawtriangles(self.tmin)
432 def draw_trace(
433 self, viewer, p, tr, time_projection, track_projection, gain,
434 outline_label=False):
436 from .qt_compat import qc, qg
437 from . import util as gui_util
439 if self.nslc_ids and not self.match_nslc(tr.nslc_id):
440 return
442 color = self.select_color(g_color_b)
443 pen = qg.QPen(qg.QColor(*color))
444 pen.setWidth(2)
445 p.setPen(pen)
446 p.setBrush(qc.Qt.NoBrush)
448 def drawpoint(t, y):
449 u = time_projection(t)
450 v = track_projection(y)
451 rect = qc.QRectF(u-2, v-2, 4, 4)
452 p.drawRect(rect)
454 def drawline(t):
455 u = time_projection(t)
456 v0, v1 = track_projection.get_out_range()
457 line = qc.QLineF(u, v0, u, v1)
458 p.drawLine(line)
460 try:
461 snippet = tr.chop(
462 self.tmin, self.tmax,
463 inplace=False,
464 include_last=True,
465 snap=(math.ceil, math.floor))
467 vdata = track_projection(gain*snippet.get_ydata())
468 udata_min = float(
469 time_projection(snippet.tmin))
470 udata_max = float(
471 time_projection(snippet.tmin+snippet.deltat*(vdata.size-1)))
472 udata = num.linspace(udata_min, udata_max, vdata.size)
473 qpoints = gui_util.make_QPolygonF(udata, vdata)
474 pen.setWidth(1)
475 p.setPen(pen)
476 p.drawPolyline(qpoints)
477 pen.setWidth(2)
478 p.setPen(pen)
479 drawpoint(*tr(self.tmin, clip=True, snap=math.ceil))
480 drawpoint(*tr(self.tmax, clip=True, snap=math.floor))
482 except trace.NoData:
483 pass
485 color = self.select_color(g_color_b)
486 pen = qg.QPen(qg.QColor(*color))
487 pen.setWidth(2)
488 p.setPen(pen)
490 drawline(self.tmin)
491 drawline(self.tmax)
493 label = self.get_label()
494 if label:
495 label_bg = qg.QBrush(qg.QColor(255, 255, 255))
497 u = time_projection(self.tmin)
498 v0, v1 = track_projection.get_out_range()
499 if outline_label:
500 du = -7
501 else:
502 du = -5
503 gui_util.draw_label(
504 p, u+du, v0, label, label_bg, 'TR',
505 outline=outline_label)
507 if self.tmin == self.tmax:
508 try:
509 drawpoint(self.tmin, tr.interpolate(self.tmin))
511 except IndexError:
512 pass
514 def get_label(self):
515 return None
517 def convert_to_phase_marker(
518 self,
519 event=None,
520 phasename=None,
521 polarity=None,
522 automatic=None,
523 incidence_angle=None,
524 takeoff_angle=None):
526 if isinstance(self, PhaseMarker):
527 return
529 self.__class__ = PhaseMarker
530 self._event = event
531 self._phasename = phasename
532 self._polarity = polarity
533 self._automatic = automatic
534 self._incidence_angle = incidence_angle
535 self._takeoff_angle = takeoff_angle
536 if self._event:
537 self._event_hash = event.get_hash()
538 self._event_time = event.time
539 else:
540 self._event_hash = None
541 self._event_time = None
542 self.active = False
544 def convert_to_event_marker(self, lat=0., lon=0.):
545 if isinstance(self, EventMarker):
546 return
548 if isinstance(self, PhaseMarker):
549 self.convert_to_marker()
551 self.__class__ = EventMarker
552 self._event = model.Event(lat, lon, time=self.tmin, name='Event')
553 self._event_hash = self._event.get_hash()
554 self.active = False
555 self.tmax = self.tmin
556 self.nslc_ids = []
559class EventMarker(Marker):
560 '''
561 GUI element representing a seismological event.
563 :param event: A :py:class:`pyrocko.model.Event` object containing meta
564 information of a seismological event
565 :param kind: (optional) integer to distinguish groups of markers
566 :param event_hash: (optional) hash code of event (see:
567 :py:meth:`pyrocko.model.Event.get_hash`)
568 '''
570 def __init__(self, event, kind=0, event_hash=None):
571 Marker.__init__(self, [], event.time, event.time, kind)
572 self._event = event
573 self.active = False
574 self._event_hash = event_hash
576 def get_event_hash(self):
577 if self._event_hash is not None:
578 return self._event_hash
579 else:
580 return self._event.get_hash()
582 def label(self):
583 t = []
584 mag = self._event.magnitude
585 if mag is not None:
586 t.append('M%3.1f' % mag)
588 reg = self._event.region
589 if reg is not None:
590 t.append(reg)
592 nam = self._event.name
593 if nam is not None:
594 t.append(nam)
596 s = ' '.join(t)
597 if not s:
598 s = '(Event)'
599 return s
601 def draw(self, p, time_projection, y_projection, with_label=False):
602 Marker.draw(
603 self, p, time_projection, y_projection,
604 draw_line=False,
605 draw_triangle=True)
607 if with_label:
608 self.draw_label(p, time_projection, y_projection)
610 def draw_label(self, p, time_projection, y_projection):
611 from .qt_compat import qg
612 from . import util as gui_util
614 u = time_projection(self.tmin)
615 v0, v1 = y_projection.get_out_range()
616 label_bg = qg.QBrush(qg.QColor(255, 255, 255))
617 gui_util.draw_label(
618 p, u, v0-10., self.label(), label_bg, 'CB',
619 outline=self.active)
621 def get_event(self):
622 '''
623 Return an instance of the :py:class:`pyrocko.model.Event` associated
624 to this :py:class:`EventMarker`
625 '''
626 return self._event
628 def draw_trace(self, viewer, p, tr, time_projection, track_projection,
629 gain):
630 pass
632 def hoover_message(self):
633 ev = self.get_event()
634 evs = []
635 for k in 'magnitude lat lon depth name region catalog'.split():
636 if ev.__dict__[k] is not None and ev.__dict__[k] != '':
637 if k == 'depth':
638 sv = '%g km' % (ev.depth * 0.001)
639 else:
640 sv = '%s' % ev.__dict__[k]
641 evs.append('%s = %s' % (k, sv))
643 return ', '.join(evs)
645 def get_attributes(self, fdigits=3):
646 attributes = ['event:']
647 attributes.extend(Marker.get_attributes(self, fdigits=fdigits))
648 del attributes[-1]
649 e = self._event
650 attributes.extend([
651 e.get_hash(), e.lat, e.lon, e.depth, e.magnitude, e.catalog,
652 e.name, e.region])
654 return attributes
656 def get_attribute_widths(self, fdigits=3):
657 ws = [6]
658 ws.extend(Marker.get_attribute_widths(self, fdigits=fdigits))
659 del ws[-1]
660 ws.extend([14, 12, 12, 12, 4, 5, 0, 0])
661 return ws
663 @staticmethod
664 def from_attributes(vals):
666 nslc_ids, tmin, tmax, kind = Marker.parse_attributes(
667 vals[1:] + ['None'])
668 lat, lon, depth, magnitude = [
669 str_to_float_or_none(x) for x in vals[5:9]]
670 catalog, name, region = [
671 str_to_str_or_none(x) for x in vals[9:]]
672 e = model.Event(
673 lat, lon, time=tmin, name=name, depth=depth, magnitude=magnitude,
674 region=region, catalog=catalog)
675 marker = EventMarker(
676 e, kind, event_hash=str_to_str_or_none(vals[4]))
677 return marker
680class PhaseMarker(Marker):
681 '''
682 A PhaseMarker is a GUI-element representing a seismological phase arrival
684 :param nslc_ids: list of (network, station, location, channel) tuples (may
685 contain wildcards)
686 :param tmin: start time
687 :param tmax: end time
688 :param kind: (optional) integer to distinguish groups of markers
689 (color-coded)
690 :param event: a :py:class:`pyrocko.model.Event` object containing meta
691 information of a seismological event
692 :param event_hash: (optional) hash code of event (see:
693 :py:meth:`pyrocko.model.Event.get_hash`)
694 :param event_time: (optional) time of the associated event
695 :param phasename: (optional) name of the phase associated with the marker
696 :param polarity: (optional) polarity of arriving phase
697 :param automatic: (optional)
698 :param incident_angle: (optional) incident angle of phase
699 :param takeoff_angle: (optional) take off angle of phase
700 '''
701 def __init__(
702 self, nslc_ids, tmin, tmax,
703 kind=0,
704 event=None,
705 event_hash=None,
706 event_time=None,
707 phasename=None,
708 polarity=None,
709 automatic=None,
710 incidence_angle=None,
711 takeoff_angle=None):
713 Marker.__init__(self, nslc_ids, tmin, tmax, kind)
714 self._event = event
715 self._event_hash = event_hash
716 self._event_time = event_time
717 self._phasename = phasename
718 self._automatic = automatic
719 self._incidence_angle = incidence_angle
720 self._takeoff_angle = takeoff_angle
722 self.set_polarity(polarity)
724 def draw_trace(self, viewer, p, tr, time_projection, track_projection,
725 gain):
727 Marker.draw_trace(
728 self, viewer, p, tr, time_projection, track_projection, gain,
729 outline_label=(
730 self._event is not None and
731 self._event == viewer.get_active_event()))
733 def get_label(self):
734 t = []
735 if self._phasename is not None:
736 t.append(self._phasename)
737 if self._polarity is not None:
738 t.append(self.get_polarity_symbol())
740 if self._automatic:
741 t.append('@')
743 return ''.join(t)
745 def get_event(self):
746 '''
747 Return an instance of the :py:class:`pyrocko.model.Event` associated
748 to this :py:class:`EventMarker`
749 '''
750 return self._event
752 def get_event_hash(self):
753 if self._event_hash is not None:
754 return self._event_hash
755 else:
756 if self._event is None:
757 return None
758 else:
759 return self._event.get_hash()
761 def get_event_time(self):
762 if self._event is not None:
763 return self._event.time
764 else:
765 return self._event_time
767 def set_event_hash(self, event_hash):
768 self._event_hash = event_hash
770 def set_event(self, event):
771 self._event = event
772 if event is not None:
773 self.set_event_hash(event.get_hash())
775 def get_phasename(self):
776 return self._phasename
778 def set_phasename(self, phasename):
779 self._phasename = phasename
781 def set_polarity(self, polarity):
782 if polarity not in [1, -1, 0, None]:
783 raise ValueError('polarity has to be 1, -1, 0 or None')
784 self._polarity = polarity
786 def get_polarity_symbol(self):
787 return polarity_symbols.get(self._polarity, '')
789 def get_polarity(self):
790 return self._polarity
792 def convert_to_marker(self):
793 del self._event
794 del self._event_hash
795 del self._phasename
796 del self._polarity
797 del self._automatic
798 del self._incidence_angle
799 del self._takeoff_angle
800 self.active = False
801 self.__class__ = Marker
803 def hoover_message(self):
804 toks = []
805 for k in 'incidence_angle takeoff_angle polarity'.split():
806 v = getattr(self, '_' + k)
807 if v is not None:
808 toks.append('%s = %s' % (k, v))
810 return ', '.join(toks)
812 def get_attributes(self, fdigits=3):
813 attributes = ['phase:']
814 attributes.extend(Marker.get_attributes(self, fdigits=fdigits))
816 et = None, None
817 if self._event:
818 et = self._st(self._event.time, fdigits).split()
819 elif self._event_time:
820 et = self._st(self._event_time, fdigits).split()
822 attributes.extend([
823 self.get_event_hash(), et[0], et[1], self._phasename,
824 self._polarity, self._automatic])
826 return attributes
828 def _st(self, t, fdigits):
829 return util.time_to_str(
830 t, format='%Y-%m-%d %H:%M:%S.'+'%iFRAC' % fdigits)
832 def get_attribute_widths(self, fdigits=3):
833 ws = [6]
834 ws.extend(Marker.get_attribute_widths(self, fdigits=fdigits))
835 ws.extend([14, 12, 12, 8, 4, 5])
836 return ws
838 @staticmethod
839 def from_attributes(vals):
840 if len(vals) == 14:
841 nbasicvals = 7
842 else:
843 nbasicvals = 4
844 nslc_ids, tmin, tmax, kind = Marker.parse_attributes(
845 vals[1:1+nbasicvals])
847 i = 8
848 if len(vals) == 14:
849 i = 11
851 event_hash = str_to_str_or_none(vals[i-3])
852 event_sdate = str_to_str_or_none(vals[i-2])
853 event_stime = str_to_str_or_none(vals[i-1])
855 if event_sdate is not None and event_stime is not None:
856 event_time = util.str_to_time(event_sdate + ' ' + event_stime)
857 else:
858 event_time = None
860 phasename = str_to_str_or_none(vals[i])
861 polarity = str_to_int_or_none(vals[i+1])
862 automatic = str_to_bool(vals[i+2])
863 marker = PhaseMarker(nslc_ids, tmin, tmax, kind, event=None,
864 event_hash=event_hash, event_time=event_time,
865 phasename=phasename, polarity=polarity,
866 automatic=automatic)
867 return marker
870def load_markers(filename):
871 '''
872 Load markers from file.
874 :param filename: filename as string
875 :returns: list of :py:class:`Marker` Objects
876 '''
878 return Marker.load_markers(filename)
881def save_markers(markers, filename, fdigits=3):
882 '''
883 Save markers to file.
885 :param markers: list of :py:class:`Marker` Objects
886 :param filename: filename as string
887 :param fdigits: number of decimal digits to use for sub-second time strings
888 '''
890 return Marker.save_markers(markers, filename, fdigits=fdigits)
893def associate_phases_to_events(markers):
894 '''
895 Reassociate phases to events after import from markers file.
896 '''
898 hash_to_events = {}
899 time_to_events = {}
900 for marker in markers:
901 if isinstance(marker, EventMarker):
902 ev = marker.get_event()
903 hash_to_events[marker.get_event_hash()] = ev
904 time_to_events[ev.time] = ev
906 for marker in markers:
907 if isinstance(marker, PhaseMarker):
908 h = marker.get_event_hash()
909 t = marker.get_event_time()
910 if marker.get_event() is None:
911 if h is not None and h in hash_to_events:
912 marker.set_event(hash_to_events[h])
913 marker.set_event_hash(None)
914 elif t is not None and t in time_to_events:
915 marker.set_event(time_to_events[t])
916 marker.set_event_hash(None)