1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
5from __future__ import absolute_import
7import math
8import copy
9import logging
10import sys
12import numpy as num
14from pyrocko import util, plot, model, trace
15from pyrocko.util import TableWriter, TableReader, gmtime_x, mystrftime
16import pyrocko.plot.colors
19logger = logging.getLogger('pyrocko.gui.marker')
22if sys.version_info[0] >= 3:
23 polarity_symbols = {1: u'\u2191', -1: u'\u2193', None: u'', 0: u'\u2195'}
24else:
25 polarity_symbols = {1: '+', -1: '-', None: '', 0: '0'}
28def str_to_float_or_none(s):
29 if s == 'None':
30 return None
31 return float(s)
34def str_to_str_or_none(s):
35 if s == 'None':
36 return None
37 return s
40def str_to_int_or_none(s):
41 if s == 'None':
42 return None
43 return int(s)
46def str_to_bool(s):
47 return s.lower() in ('true', 't', '1')
50def myctime(timestamp):
51 tt, ms = gmtime_x(timestamp)
52 return mystrftime(None, tt, ms)
55g_color_b = [
56 pyrocko.plot.colors.g_nat_colors[x]
57 if x in pyrocko.plot.colors.g_nat_colors
58 else plot.color(x) for x in (
59 'nat_acc_gray', 'nat_acc_gray', 'nat_acc_gray',
60 'nat_acc_purple', 'nat_acc_purple', 'nat_acc_purple',
61 'nat_acc_blue', 'nat_acc_blue', 'nat_acc_blue',
62 'nat_acc_orange', 'nat_acc_orange', 'nat_acc_orange',
63 'skyblue1', 'skyblue2', 'skyblue3',
64 'orange1', 'orange2', 'orange3',
65 'plum1', 'plum2', 'plum3',
66 'chocolate1', 'chocolate2', 'chocolate3',
67 'butter1', 'butter2', 'butter3',
68 'aluminium3', 'aluminium4', 'aluminium5')]
71class MarkerParseError(Exception):
72 pass
75class MarkerOneNSLCRequired(Exception):
76 pass
79class Marker(object):
80 '''
81 General purpose marker GUI element and base class for
82 :py:class:`EventMarker` and :py:class:`PhaseMarker`.
84 :param nslc_ids: list of (network, station, location, channel) tuples
85 (may contain wildcards)
86 :param tmin: start time
87 :param tmax: end time
88 :param kind: (optional) integer to distinguish groups of markers
89 (color-coded)
90 '''
92 @staticmethod
93 def save_markers(markers, fn, fdigits=3):
94 '''
95 Static method to write marker objects to file.
97 :param markers: list of :py:class:`Marker` objects
98 :param fn: filename as string
99 :param fdigits: number of decimal digits to use for sub-second time
100 strings (default 3)
101 '''
102 f = open(fn, 'w')
103 f.write('# Snuffler Markers File Version 0.2\n')
104 writer = TableWriter(f)
105 for marker in markers:
106 a = marker.get_attributes(fdigits=fdigits)
107 w = marker.get_attribute_widths(fdigits=fdigits)
108 row = []
109 for x in a:
110 if x is None or x == '':
111 row.append('None')
112 else:
113 row.append(x)
115 writer.writerow(row, w)
117 f.close()
119 @staticmethod
120 def load_markers(fn):
121 '''
122 Static method to load markers from file.
124 :param filename: filename as string
125 :returns: list of :py:class:`Marker`, :py:class:`EventMarker` or
126 :py:class:`PhaseMarker` objects
127 '''
128 markers = []
129 with open(fn, 'r') as f:
130 line = f.readline()
131 if not line.startswith('# Snuffler Markers File Version'):
132 raise MarkerParseError('Not a marker file')
134 elif line.startswith('# Snuffler Markers File Version 0.2'):
135 reader = TableReader(f)
136 while not reader.eof:
137 row = reader.readrow()
138 if not row:
139 continue
140 if row[0] == 'event:':
141 marker = EventMarker.from_attributes(row)
142 elif row[0] == 'phase:':
143 marker = PhaseMarker.from_attributes(row)
144 else:
145 marker = Marker.from_attributes(row)
147 markers.append(marker)
148 else:
149 logger.warning('Unsupported Markers File Version')
151 return markers
153 def __init__(self, nslc_ids, tmin, tmax, kind=0):
154 self.set(nslc_ids, tmin, tmax)
155 self.alerted = False
156 self.selected = False
157 self.kind = kind
158 self.active = False
160 def set(self, nslc_ids, tmin, tmax):
161 '''
162 Set ``nslc_ids``, start time and end time of :py:class:`Marker`.
164 :param nslc_ids: list or set of (network, station, location, channel)
165 tuples
166 :param tmin: start time
167 :param tmax: end time
168 '''
169 self.nslc_ids = nslc_ids
170 self.tmin = util.to_time_float(tmin)
171 self.tmax = util.to_time_float(tmax)
173 def set_kind(self, kind):
174 '''
175 Set kind of :py:class:`Marker`.
177 :param kind: (optional) integer to distinguish groups of markers
178 (color-coded)
179 '''
180 self.kind = kind
182 def get_tmin(self):
183 '''
184 Get *start time* of :py:class:`Marker`.
185 '''
186 return self.tmin
188 def get_tmax(self):
189 '''
190 Get *end time* of :py:class:`Marker`.
191 '''
192 return self.tmax
194 def get_nslc_ids(self):
195 '''
196 Get marker's network-station-location-channel pattern.
198 :returns: list or set of (network, station, location, channel) tuples
200 The network, station, location, or channel strings may contain wildcard
201 expressions.
202 '''
203 return self.nslc_ids
205 def is_alerted(self):
206 return self.alerted
208 def is_selected(self):
209 return self.selected
211 def set_alerted(self, state):
212 self.alerted = state
214 def match_nsl(self, nsl):
215 '''
216 See documentation of :py:func:`pyrocko.util.match_nslc`.
217 '''
218 patterns = ['.'.join(x[:3]) for x in self.nslc_ids]
219 return util.match_nslc(patterns, nsl)
221 def match_nslc(self, nslc):
222 '''
223 See documentation of :py:func:`pyrocko.util.match_nslc`.
224 '''
225 patterns = ['.'.join(x) for x in self.nslc_ids]
226 return util.match_nslc(patterns, nslc)
228 def one_nslc(self):
229 '''
230 If one *nslc_id* defines this marker return this id.
231 If more than one *nslc_id* is defined in the :py:class:`Marker`s
232 *nslc_ids* raise :py:exc:`MarkerOneNSLCRequired`.
233 '''
234 if len(self.nslc_ids) != 1:
235 raise MarkerOneNSLCRequired()
237 return list(self.nslc_ids)[0]
239 def hoover_message(self):
240 return ''
242 def copy(self):
243 '''
244 Get a copy of this marker.
245 '''
246 return copy.deepcopy(self)
248 def __str__(self):
249 traces = ','.join(['.'.join(nslc_id) for nslc_id in self.nslc_ids])
250 st = myctime
251 if self.tmin == self.tmax:
252 return '%s %i %s' % (st(self.tmin), self.kind, traces)
253 else:
254 return '%s %s %g %i %s' % (
255 st(self.tmin), st(self.tmax), self.tmax-self.tmin, self.kind,
256 traces)
258 def get_attributes(self, fdigits=3):
259 traces = ','.join(['.'.join(nslc_id) for nslc_id in self.nslc_ids])
261 def st(t):
262 return util.time_to_str(
263 t, format='%Y-%m-%d %H:%M:%S.'+'%iFRAC' % fdigits)
265 vals = []
266 vals.extend(st(self.tmin).split())
267 if self.tmin != self.tmax:
268 vals.extend(st(self.tmax).split())
269 vals.append(self.tmax-self.tmin)
271 vals.append(self.kind)
272 vals.append(traces)
273 return vals
275 def get_attribute_widths(self, fdigits=3):
276 ws = [10, 9+fdigits]
277 if self.tmin != self.tmax:
278 ws.extend([10, 9+fdigits, 12])
279 ws.extend([2, 15])
280 return ws
282 @staticmethod
283 def parse_attributes(vals):
284 tmin = util.str_to_time(vals[0] + ' ' + vals[1])
285 i = 2
286 tmax = tmin
287 if len(vals) == 7:
288 tmax = util.str_to_time(vals[2] + ' ' + vals[3])
289 i = 5
291 kind = int(vals[i])
292 traces = vals[i+1]
293 if traces == 'None':
294 nslc_ids = []
295 else:
296 nslc_ids = tuple(
297 [tuple(nslc_id.split('.')) for nslc_id in traces.split(',')])
299 return nslc_ids, tmin, tmax, kind
301 @staticmethod
302 def from_attributes(vals):
303 return Marker(*Marker.parse_attributes(vals))
305 def select_color(self, colorlist):
307 def cl(x):
308 return colorlist[(self.kind*3+x) % len(colorlist)]
310 if self.selected:
311 return cl(1)
313 if self.alerted:
314 return cl(1)
316 return cl(2)
318 def draw(
319 self, p, time_projection, y_projection,
320 draw_line=True,
321 draw_triangle=False,
322 **kwargs):
324 from .qt_compat import qc, qg
325 from . import util as gui_util
327 color = self.select_color(g_color_b)
328 pen = qg.QPen(qg.QColor(*color))
329 pen.setWidth(2)
330 p.setPen(pen)
332 umin = time_projection(self.tmin)
333 umax = time_projection(self.tmax)
334 v0, v1 = y_projection.get_out_range()
335 line = qc.QLineF(umin-1, v0, umax+1, v0)
336 p.drawLine(line)
338 if self.selected or self.alerted or not self.nslc_ids:
339 linepen = qg.QPen(pen)
340 if self.selected or self.alerted:
341 linepen.setStyle(qc.Qt.CustomDashLine)
342 pat = [5., 3.]
343 linepen.setDashPattern(pat)
344 if self.alerted and not self.selected:
345 linepen.setColor(qg.QColor(150, 150, 150))
347 s = 9.
348 utriangle = gui_util.make_QPolygonF(
349 [-0.577*s, 0., 0.577*s], [0., 1.*s, 0.])
350 ltriangle = gui_util.make_QPolygonF(
351 [-0.577*s, 0., 0.577*s], [0., -1.*s, 0.])
353 def drawline(t):
354 u = time_projection(t)
355 line = qc.QLineF(u, v0, u, v1)
356 p.drawLine(line)
358 def drawtriangles(t):
359 u = time_projection(t)
360 t = qg.QPolygonF(utriangle)
361 t.translate(u, v0)
362 p.drawConvexPolygon(t)
363 t = qg.QPolygonF(ltriangle)
364 t.translate(u, v1)
365 p.drawConvexPolygon(t)
367 if draw_line or self.selected or self.alerted:
368 p.setPen(linepen)
369 drawline(self.tmin)
370 drawline(self.tmax)
372 if draw_triangle:
373 pen.setStyle(qc.Qt.SolidLine)
374 pen.setJoinStyle(qc.Qt.MiterJoin)
375 pen.setWidth(2)
376 p.setPen(pen)
377 p.setBrush(qg.QColor(*color))
378 drawtriangles(self.tmin)
380 def draw_trace(
381 self, viewer, p, tr, time_projection, track_projection, gain,
382 outline_label=False):
384 from .qt_compat import qc, qg
385 from . import util as gui_util
387 if self.nslc_ids and not self.match_nslc(tr.nslc_id):
388 return
390 color = self.select_color(g_color_b)
391 pen = qg.QPen(qg.QColor(*color))
392 pen.setWidth(2)
393 p.setPen(pen)
394 p.setBrush(qc.Qt.NoBrush)
396 def drawpoint(t, y):
397 u = time_projection(t)
398 v = track_projection(y)
399 rect = qc.QRectF(u-2, v-2, 4, 4)
400 p.drawRect(rect)
402 def drawline(t):
403 u = time_projection(t)
404 v0, v1 = track_projection.get_out_range()
405 line = qc.QLineF(u, v0, u, v1)
406 p.drawLine(line)
408 try:
409 snippet = tr.chop(
410 self.tmin, self.tmax,
411 inplace=False,
412 include_last=True,
413 snap=(math.ceil, math.floor))
415 vdata = track_projection(gain*snippet.get_ydata())
416 udata_min = float(
417 time_projection(snippet.tmin))
418 udata_max = float(
419 time_projection(snippet.tmin+snippet.deltat*(vdata.size-1)))
420 udata = num.linspace(udata_min, udata_max, vdata.size)
421 qpoints = gui_util.make_QPolygonF(udata, vdata)
422 pen.setWidth(1)
423 p.setPen(pen)
424 p.drawPolyline(qpoints)
425 pen.setWidth(2)
426 p.setPen(pen)
427 drawpoint(*tr(self.tmin, clip=True, snap=math.ceil))
428 drawpoint(*tr(self.tmax, clip=True, snap=math.floor))
430 except trace.NoData:
431 pass
433 color = self.select_color(g_color_b)
434 pen = qg.QPen(qg.QColor(*color))
435 pen.setWidth(2)
436 p.setPen(pen)
438 drawline(self.tmin)
439 drawline(self.tmax)
441 label = self.get_label()
442 if label:
443 label_bg = qg.QBrush(qg.QColor(255, 255, 255))
445 u = time_projection(self.tmin)
446 v0, v1 = track_projection.get_out_range()
447 if outline_label:
448 du = -7
449 else:
450 du = -5
451 gui_util.draw_label(
452 p, u+du, v0, label, label_bg, 'TR',
453 outline=outline_label)
455 if self.tmin == self.tmax:
456 try:
457 drawpoint(self.tmin, tr.interpolate(self.tmin))
459 except IndexError:
460 pass
462 def get_label(self):
463 return None
465 def convert_to_phase_marker(
466 self,
467 event=None,
468 phasename=None,
469 polarity=None,
470 automatic=None,
471 incidence_angle=None,
472 takeoff_angle=None):
474 if isinstance(self, PhaseMarker):
475 return
477 self.__class__ = PhaseMarker
478 self._event = event
479 self._phasename = phasename
480 self._polarity = polarity
481 self._automatic = automatic
482 self._incidence_angle = incidence_angle
483 self._takeoff_angle = takeoff_angle
484 if self._event:
485 self._event_hash = event.get_hash()
486 self._event_time = event.time
487 else:
488 self._event_hash = None
489 self._event_time = None
490 self.active = False
492 def convert_to_event_marker(self, lat=0., lon=0.):
493 if isinstance(self, EventMarker):
494 return
496 if isinstance(self, PhaseMarker):
497 self.convert_to_marker()
499 self.__class__ = EventMarker
500 self._event = model.Event(lat, lon, time=self.tmin, name='Event')
501 self._event_hash = self._event.get_hash()
502 self.active = False
503 self.tmax = self.tmin
504 self.nslc_ids = []
507class EventMarker(Marker):
508 '''
509 GUI element representing a seismological event.
511 :param event: A :py:class:`pyrocko.model.Event` object containing meta
512 information of a seismological event
513 :param kind: (optional) integer to distinguish groups of markers
514 :param event_hash: (optional) hash code of event (see:
515 :py:meth:`pyrocko.model.Event.get_hash`)
516 '''
518 def __init__(self, event, kind=0, event_hash=None):
519 Marker.__init__(self, [], event.time, event.time, kind)
520 self._event = event
521 self.active = False
522 self._event_hash = event_hash
524 def get_event_hash(self):
525 if self._event_hash is not None:
526 return self._event_hash
527 else:
528 return self._event.get_hash()
530 def label(self):
531 t = []
532 mag = self._event.magnitude
533 if mag is not None:
534 t.append('M%3.1f' % mag)
536 reg = self._event.region
537 if reg is not None:
538 t.append(reg)
540 nam = self._event.name
541 if nam is not None:
542 t.append(nam)
544 s = ' '.join(t)
545 if not s:
546 s = '(Event)'
547 return s
549 def draw(self, p, time_projection, y_projection, with_label=False):
550 Marker.draw(
551 self, p, time_projection, y_projection,
552 draw_line=False,
553 draw_triangle=True)
555 if with_label:
556 self.draw_label(p, time_projection, y_projection)
558 def draw_label(self, p, time_projection, y_projection):
559 from .qt_compat import qg
560 from . import util as gui_util
562 u = time_projection(self.tmin)
563 v0, v1 = y_projection.get_out_range()
564 label_bg = qg.QBrush(qg.QColor(255, 255, 255))
565 gui_util.draw_label(
566 p, u, v0-10., self.label(), label_bg, 'CB',
567 outline=self.active)
569 def get_event(self):
570 '''
571 Return an instance of the :py:class:`pyrocko.model.Event` associated
572 to this :py:class:`EventMarker`
573 '''
574 return self._event
576 def draw_trace(self, viewer, p, tr, time_projection, track_projection,
577 gain):
578 pass
580 def hoover_message(self):
581 ev = self.get_event()
582 evs = []
583 for k in 'magnitude lat lon depth name region catalog'.split():
584 if ev.__dict__[k] is not None and ev.__dict__[k] != '':
585 if k == 'depth':
586 sv = '%g km' % (ev.depth * 0.001)
587 else:
588 sv = '%s' % ev.__dict__[k]
589 evs.append('%s = %s' % (k, sv))
591 return ', '.join(evs)
593 def get_attributes(self, fdigits=3):
594 attributes = ['event:']
595 attributes.extend(Marker.get_attributes(self, fdigits=fdigits))
596 del attributes[-1]
597 e = self._event
598 attributes.extend([
599 e.get_hash(), e.lat, e.lon, e.depth, e.magnitude, e.catalog,
600 e.name, e.region])
602 return attributes
604 def get_attribute_widths(self, fdigits=3):
605 ws = [6]
606 ws.extend(Marker.get_attribute_widths(self, fdigits=fdigits))
607 del ws[-1]
608 ws.extend([14, 12, 12, 12, 4, 5, 0, 0])
609 return ws
611 @staticmethod
612 def from_attributes(vals):
614 nslc_ids, tmin, tmax, kind = Marker.parse_attributes(
615 vals[1:] + ['None'])
616 lat, lon, depth, magnitude = [
617 str_to_float_or_none(x) for x in vals[5:9]]
618 catalog, name, region = [
619 str_to_str_or_none(x) for x in vals[9:]]
620 e = model.Event(
621 lat, lon, time=tmin, name=name, depth=depth, magnitude=magnitude,
622 region=region, catalog=catalog)
623 marker = EventMarker(
624 e, kind, event_hash=str_to_str_or_none(vals[4]))
625 return marker
628class PhaseMarker(Marker):
629 '''
630 A PhaseMarker is a GUI-element representing a seismological phase arrival
632 :param nslc_ids: list of (network, station, location, channel) tuples (may
633 contain wildcards)
634 :param tmin: start time
635 :param tmax: end time
636 :param kind: (optional) integer to distinguish groups of markers
637 (color-coded)
638 :param event: a :py:class:`pyrocko.model.Event` object containing meta
639 information of a seismological event
640 :param event_hash: (optional) hash code of event (see:
641 :py:meth:`pyrocko.model.Event.get_hash`)
642 :param event_time: (optional) time of the associated event
643 :param phasename: (optional) name of the phase associated with the marker
644 :param polarity: (optional) polarity of arriving phase
645 :param automatic: (optional)
646 :param incident_angle: (optional) incident angle of phase
647 :param takeoff_angle: (optional) take off angle of phase
648 '''
649 def __init__(
650 self, nslc_ids, tmin, tmax,
651 kind=0,
652 event=None,
653 event_hash=None,
654 event_time=None,
655 phasename=None,
656 polarity=None,
657 automatic=None,
658 incidence_angle=None,
659 takeoff_angle=None):
661 Marker.__init__(self, nslc_ids, tmin, tmax, kind)
662 self._event = event
663 self._event_hash = event_hash
664 self._event_time = event_time
665 self._phasename = phasename
666 self._automatic = automatic
667 self._incidence_angle = incidence_angle
668 self._takeoff_angle = takeoff_angle
670 self.set_polarity(polarity)
672 def draw_trace(self, viewer, p, tr, time_projection, track_projection,
673 gain):
675 Marker.draw_trace(
676 self, viewer, p, tr, time_projection, track_projection, gain,
677 outline_label=(
678 self._event is not None and
679 self._event == viewer.get_active_event()))
681 def get_label(self):
682 t = []
683 if self._phasename is not None:
684 t.append(self._phasename)
685 if self._polarity is not None:
686 t.append(self.get_polarity_symbol())
688 if self._automatic:
689 t.append('@')
691 return ''.join(t)
693 def get_event(self):
694 '''
695 Return an instance of the :py:class:`pyrocko.model.Event` associated
696 to this :py:class:`EventMarker`
697 '''
698 return self._event
700 def get_event_hash(self):
701 if self._event_hash is not None:
702 return self._event_hash
703 else:
704 if self._event is None:
705 return None
706 else:
707 return self._event.get_hash()
709 def get_event_time(self):
710 if self._event is not None:
711 return self._event.time
712 else:
713 return self._event_time
715 def set_event_hash(self, event_hash):
716 self._event_hash = event_hash
718 def set_event(self, event):
719 self._event = event
720 if event is not None:
721 self.set_event_hash(event.get_hash())
723 def get_phasename(self):
724 return self._phasename
726 def set_phasename(self, phasename):
727 self._phasename = phasename
729 def set_polarity(self, polarity):
730 if polarity not in [1, -1, 0, None]:
731 raise ValueError('polarity has to be 1, -1, 0 or None')
732 self._polarity = polarity
734 def get_polarity_symbol(self):
735 return polarity_symbols.get(self._polarity, '')
737 def get_polarity(self):
738 return self._polarity
740 def convert_to_marker(self):
741 del self._event
742 del self._event_hash
743 del self._phasename
744 del self._polarity
745 del self._automatic
746 del self._incidence_angle
747 del self._takeoff_angle
748 self.active = False
749 self.__class__ = Marker
751 def hoover_message(self):
752 toks = []
753 for k in 'incidence_angle takeoff_angle polarity'.split():
754 v = getattr(self, '_' + k)
755 if v is not None:
756 toks.append('%s = %s' % (k, v))
758 return ', '.join(toks)
760 def get_attributes(self, fdigits=3):
761 attributes = ['phase:']
762 attributes.extend(Marker.get_attributes(self, fdigits=fdigits))
764 et = None, None
765 if self._event:
766 et = self._st(self._event.time, fdigits).split()
767 elif self._event_time:
768 et = self._st(self._event_time, fdigits).split()
770 attributes.extend([
771 self.get_event_hash(), et[0], et[1], self._phasename,
772 self._polarity, self._automatic])
774 return attributes
776 def _st(self, t, fdigits):
777 return util.time_to_str(
778 t, format='%Y-%m-%d %H:%M:%S.'+'%iFRAC' % fdigits)
780 def get_attribute_widths(self, fdigits=3):
781 ws = [6]
782 ws.extend(Marker.get_attribute_widths(self, fdigits=fdigits))
783 ws.extend([14, 12, 12, 8, 4, 5])
784 return ws
786 @staticmethod
787 def from_attributes(vals):
788 if len(vals) == 14:
789 nbasicvals = 7
790 else:
791 nbasicvals = 4
792 nslc_ids, tmin, tmax, kind = Marker.parse_attributes(
793 vals[1:1+nbasicvals])
795 i = 8
796 if len(vals) == 14:
797 i = 11
799 event_hash = str_to_str_or_none(vals[i-3])
800 event_sdate = str_to_str_or_none(vals[i-2])
801 event_stime = str_to_str_or_none(vals[i-1])
803 if event_sdate is not None and event_stime is not None:
804 event_time = util.str_to_time(event_sdate + ' ' + event_stime)
805 else:
806 event_time = None
808 phasename = str_to_str_or_none(vals[i])
809 polarity = str_to_int_or_none(vals[i+1])
810 automatic = str_to_bool(vals[i+2])
811 marker = PhaseMarker(nslc_ids, tmin, tmax, kind, event=None,
812 event_hash=event_hash, event_time=event_time,
813 phasename=phasename, polarity=polarity,
814 automatic=automatic)
815 return marker
818def load_markers(filename):
819 '''
820 Load markers from file.
822 :param filename: filename as string
823 :returns: list of :py:class:`Marker` Objects
824 '''
826 return Marker.load_markers(filename)
829def save_markers(markers, filename, fdigits=3):
830 '''
831 Save markers to file.
833 :param markers: list of :py:class:`Marker` Objects
834 :param filename: filename as string
835 :param fdigits: number of decimal digits to use for sub-second time strings
836 '''
838 return Marker.save_markers(markers, filename, fdigits=fdigits)
841def associate_phases_to_events(markers):
842 '''
843 Reassociate phases to events after import from markers file.
844 '''
846 hash_to_events = {}
847 time_to_events = {}
848 for marker in markers:
849 if isinstance(marker, EventMarker):
850 ev = marker.get_event()
851 hash_to_events[marker.get_event_hash()] = ev
852 time_to_events[ev.time] = ev
854 for marker in markers:
855 if isinstance(marker, PhaseMarker):
856 h = marker.get_event_hash()
857 t = marker.get_event_time()
858 if marker.get_event() is None:
859 if h is not None and h in hash_to_events:
860 marker.set_event(hash_to_events[h])
861 marker.set_event_hash(None)
862 elif t is not None and t in time_to_events:
863 marker.set_event(time_to_events[t])
864 marker.set_event_hash(None)