1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5from __future__ import absolute_import 

6 

7import math 

8import copy 

9import logging 

10import sys 

11 

12import numpy as num 

13 

14from pyrocko import util, plot, model, trace 

15from pyrocko.util import TableWriter, TableReader, gmtime_x, mystrftime 

16 

17 

18logger = logging.getLogger('pyrocko.gui.marker') 

19 

20 

21if sys.version_info[0] >= 3: 

22 polarity_symbols = {1: u'\u2191', -1: u'\u2193', None: u'', 0: u'\u2195'} 

23else: 

24 polarity_symbols = {1: '+', -1: '-', None: '', 0: '0'} 

25 

26 

27def str_to_float_or_none(s): 

28 if s == 'None': 

29 return None 

30 return float(s) 

31 

32 

33def str_to_str_or_none(s): 

34 if s == 'None': 

35 return None 

36 return s 

37 

38 

39def str_to_int_or_none(s): 

40 if s == 'None': 

41 return None 

42 return int(s) 

43 

44 

45def str_to_bool(s): 

46 return s.lower() in ('true', 't', '1') 

47 

48 

49def myctime(timestamp): 

50 tt, ms = gmtime_x(timestamp) 

51 return mystrftime(None, tt, ms) 

52 

53 

54g_color_b = [plot.color(x) for x in ( 

55 'scarletred1', 'scarletred2', 'scarletred3', 

56 'chameleon1', 'chameleon2', 'chameleon3', 

57 'skyblue1', 'skyblue2', 'skyblue3', 

58 'orange1', 'orange2', 'orange3', 

59 'plum1', 'plum2', 'plum3', 

60 'chocolate1', 'chocolate2', 'chocolate3', 

61 'butter1', 'butter2', 'butter3', 

62 'aluminium3', 'aluminium4', 'aluminium5')] 

63 

64 

65class MarkerParseError(Exception): 

66 pass 

67 

68 

69class MarkerOneNSLCRequired(Exception): 

70 pass 

71 

72 

73class Marker(object): 

74 ''' 

75 General purpose marker GUI element and base class for 

76 :py:class:`EventMarker` and :py:class:`PhaseMarker`. 

77 

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 ''' 

85 

86 @staticmethod 

87 def save_markers(markers, fn, fdigits=3): 

88 ''' 

89 Static method to write marker objects to file. 

90 

91 :param markers: list of :py:class:`Marker` objects 

92 :param fn: filename as string 

93 :param fdigits: number of decimal digits to use for sub-second time 

94 strings (default 3) 

95 ''' 

96 f = open(fn, 'w') 

97 f.write('# Snuffler Markers File Version 0.2\n') 

98 writer = TableWriter(f) 

99 for marker in markers: 

100 a = marker.get_attributes(fdigits=fdigits) 

101 w = marker.get_attribute_widths(fdigits=fdigits) 

102 row = [] 

103 for x in a: 

104 if x is None or x == '': 

105 row.append('None') 

106 else: 

107 row.append(x) 

108 

109 writer.writerow(row, w) 

110 

111 f.close() 

112 

113 @staticmethod 

114 def load_markers(fn): 

115 ''' 

116 Static method to load markers from file. 

117 

118 :param filename: filename as string 

119 :returns: list of :py:class:`Marker`, :py:class:`EventMarker` or 

120 :py:class:`PhaseMarker` objects 

121 ''' 

122 markers = [] 

123 with open(fn, 'r') as f: 

124 line = f.readline() 

125 if not line.startswith('# Snuffler Markers File Version'): 

126 raise MarkerParseError('Not a marker file') 

127 

128 elif line.startswith('# Snuffler Markers File Version 0.2'): 

129 reader = TableReader(f) 

130 while not reader.eof: 

131 row = reader.readrow() 

132 if not row: 

133 continue 

134 if row[0] == 'event:': 

135 marker = EventMarker.from_attributes(row) 

136 elif row[0] == 'phase:': 

137 marker = PhaseMarker.from_attributes(row) 

138 else: 

139 marker = Marker.from_attributes(row) 

140 

141 markers.append(marker) 

142 else: 

143 logger.warning('Unsupported Markers File Version') 

144 

145 return markers 

146 

147 def __init__(self, nslc_ids, tmin, tmax, kind=0): 

148 self.set(nslc_ids, tmin, tmax) 

149 self.alerted = False 

150 self.selected = False 

151 self.kind = kind 

152 self.active = False 

153 

154 def set(self, nslc_ids, tmin, tmax): 

155 ''' 

156 Set ``nslc_ids``, start time and end time of :py:class:`Marker`. 

157 

158 :param nslc_ids: list or set of (network, station, location, channel) 

159 tuples 

160 :param tmin: start time 

161 :param tmax: end time 

162 ''' 

163 self.nslc_ids = nslc_ids 

164 self.tmin = util.to_time_float(tmin) 

165 self.tmax = util.to_time_float(tmax) 

166 

167 def set_kind(self, kind): 

168 ''' 

169 Set kind of :py:class:`Marker`. 

170 

171 :param kind: (optional) integer to distinguish groups of markers 

172 (color-coded) 

173 ''' 

174 self.kind = kind 

175 

176 def get_tmin(self): 

177 ''' 

178 Get *start time* of :py:class:`Marker`. 

179 ''' 

180 return self.tmin 

181 

182 def get_tmax(self): 

183 ''' 

184 Get *end time* of :py:class:`Marker`. 

185 ''' 

186 return self.tmax 

187 

188 def get_nslc_ids(self): 

189 ''' 

190 Get marker's network-station-location-channel pattern. 

191 

192 :returns: list or set of (network, station, location, channel) tuples 

193 

194 The network, station, location, or channel strings may contain wildcard 

195 expressions. 

196 ''' 

197 return self.nslc_ids 

198 

199 def is_alerted(self): 

200 return self.alerted 

201 

202 def is_selected(self): 

203 return self.selected 

204 

205 def set_alerted(self, state): 

206 self.alerted = state 

207 

208 def match_nsl(self, nsl): 

209 ''' 

210 See documentation of :py:func:`pyrocko.util.match_nslc`. 

211 ''' 

212 patterns = ['.'.join(x[:3]) for x in self.nslc_ids] 

213 return util.match_nslc(patterns, nsl) 

214 

215 def match_nslc(self, nslc): 

216 ''' 

217 See documentation of :py:func:`pyrocko.util.match_nslc`. 

218 ''' 

219 patterns = ['.'.join(x) for x in self.nslc_ids] 

220 return util.match_nslc(patterns, nslc) 

221 

222 def one_nslc(self): 

223 ''' 

224 If one *nslc_id* defines this marker return this id. 

225 If more than one *nslc_id* is defined in the :py:class:`Marker`s 

226 *nslc_ids* raise :py:exc:`MarkerOneNSLCRequired`. 

227 ''' 

228 if len(self.nslc_ids) != 1: 

229 raise MarkerOneNSLCRequired() 

230 

231 return list(self.nslc_ids)[0] 

232 

233 def hoover_message(self): 

234 return '' 

235 

236 def copy(self): 

237 ''' 

238 Get a copy of this marker. 

239 ''' 

240 return copy.deepcopy(self) 

241 

242 def __str__(self): 

243 traces = ','.join(['.'.join(nslc_id) for nslc_id in self.nslc_ids]) 

244 st = myctime 

245 if self.tmin == self.tmax: 

246 return '%s %i %s' % (st(self.tmin), self.kind, traces) 

247 else: 

248 return '%s %s %g %i %s' % ( 

249 st(self.tmin), st(self.tmax), self.tmax-self.tmin, self.kind, 

250 traces) 

251 

252 def get_attributes(self, fdigits=3): 

253 traces = ','.join(['.'.join(nslc_id) for nslc_id in self.nslc_ids]) 

254 

255 def st(t): 

256 return util.time_to_str( 

257 t, format='%Y-%m-%d %H:%M:%S.'+'%iFRAC' % fdigits) 

258 

259 vals = [] 

260 vals.extend(st(self.tmin).split()) 

261 if self.tmin != self.tmax: 

262 vals.extend(st(self.tmax).split()) 

263 vals.append(self.tmax-self.tmin) 

264 

265 vals.append(self.kind) 

266 vals.append(traces) 

267 return vals 

268 

269 def get_attribute_widths(self, fdigits=3): 

270 ws = [10, 9+fdigits] 

271 if self.tmin != self.tmax: 

272 ws.extend([10, 9+fdigits, 12]) 

273 ws.extend([2, 15]) 

274 return ws 

275 

276 @staticmethod 

277 def parse_attributes(vals): 

278 tmin = util.str_to_time(vals[0] + ' ' + vals[1]) 

279 i = 2 

280 tmax = tmin 

281 if len(vals) == 7: 

282 tmax = util.str_to_time(vals[2] + ' ' + vals[3]) 

283 i = 5 

284 

285 kind = int(vals[i]) 

286 traces = vals[i+1] 

287 if traces == 'None': 

288 nslc_ids = [] 

289 else: 

290 nslc_ids = tuple( 

291 [tuple(nslc_id.split('.')) for nslc_id in traces.split(',')]) 

292 

293 return nslc_ids, tmin, tmax, kind 

294 

295 @staticmethod 

296 def from_attributes(vals): 

297 return Marker(*Marker.parse_attributes(vals)) 

298 

299 def select_color(self, colorlist): 

300 

301 def cl(x): 

302 return colorlist[(self.kind*3+x) % len(colorlist)] 

303 

304 if self.selected: 

305 return cl(1) 

306 

307 if self.alerted: 

308 return cl(1) 

309 

310 return cl(2) 

311 

312 def draw( 

313 self, p, time_projection, y_projection, 

314 draw_line=True, 

315 draw_triangle=False, 

316 **kwargs): 

317 

318 from .qt_compat import qc, qg 

319 from . import util as gui_util 

320 

321 color = self.select_color(g_color_b) 

322 pen = qg.QPen(qg.QColor(*color)) 

323 pen.setWidth(2) 

324 p.setPen(pen) 

325 

326 umin = time_projection(self.tmin) 

327 umax = time_projection(self.tmax) 

328 v0, v1 = y_projection.get_out_range() 

329 line = qc.QLineF(umin-1, v0, umax+1, v0) 

330 p.drawLine(line) 

331 

332 if self.selected or self.alerted or not self.nslc_ids: 

333 linepen = qg.QPen(pen) 

334 if self.selected or self.alerted: 

335 linepen.setStyle(qc.Qt.CustomDashLine) 

336 pat = [5., 3.] 

337 linepen.setDashPattern(pat) 

338 if self.alerted and not self.selected: 

339 linepen.setColor(qg.QColor(150, 150, 150)) 

340 

341 s = 9. 

342 utriangle = gui_util.make_QPolygonF( 

343 [-0.577*s, 0., 0.577*s], [0., 1.*s, 0.]) 

344 ltriangle = gui_util.make_QPolygonF( 

345 [-0.577*s, 0., 0.577*s], [0., -1.*s, 0.]) 

346 

347 def drawline(t): 

348 u = time_projection(t) 

349 line = qc.QLineF(u, v0, u, v1) 

350 p.drawLine(line) 

351 

352 def drawtriangles(t): 

353 u = time_projection(t) 

354 t = qg.QPolygonF(utriangle) 

355 t.translate(u, v0) 

356 p.drawConvexPolygon(t) 

357 t = qg.QPolygonF(ltriangle) 

358 t.translate(u, v1) 

359 p.drawConvexPolygon(t) 

360 

361 if draw_line or self.selected or self.alerted: 

362 p.setPen(linepen) 

363 drawline(self.tmin) 

364 drawline(self.tmax) 

365 

366 if draw_triangle: 

367 pen.setStyle(qc.Qt.SolidLine) 

368 pen.setJoinStyle(qc.Qt.MiterJoin) 

369 pen.setWidth(2) 

370 p.setPen(pen) 

371 p.setBrush(qg.QColor(*color)) 

372 drawtriangles(self.tmin) 

373 

374 def draw_trace( 

375 self, viewer, p, tr, time_projection, track_projection, gain, 

376 outline_label=False): 

377 

378 from .qt_compat import qc, qg 

379 from . import util as gui_util 

380 

381 if self.nslc_ids and not self.match_nslc(tr.nslc_id): 

382 return 

383 

384 color = self.select_color(g_color_b) 

385 pen = qg.QPen(qg.QColor(*color)) 

386 pen.setWidth(2) 

387 p.setPen(pen) 

388 p.setBrush(qc.Qt.NoBrush) 

389 

390 def drawpoint(t, y): 

391 u = time_projection(t) 

392 v = track_projection(y) 

393 rect = qc.QRectF(u-2, v-2, 4, 4) 

394 p.drawRect(rect) 

395 

396 def drawline(t): 

397 u = time_projection(t) 

398 v0, v1 = track_projection.get_out_range() 

399 line = qc.QLineF(u, v0, u, v1) 

400 p.drawLine(line) 

401 

402 try: 

403 snippet = tr.chop( 

404 self.tmin, self.tmax, 

405 inplace=False, 

406 include_last=True, 

407 snap=(math.ceil, math.floor)) 

408 

409 vdata = track_projection(gain*snippet.get_ydata()) 

410 udata_min = float( 

411 time_projection(snippet.tmin)) 

412 udata_max = float( 

413 time_projection(snippet.tmin+snippet.deltat*(vdata.size-1))) 

414 udata = num.linspace(udata_min, udata_max, vdata.size) 

415 qpoints = gui_util.make_QPolygonF(udata, vdata) 

416 pen.setWidth(1) 

417 p.setPen(pen) 

418 p.drawPolyline(qpoints) 

419 pen.setWidth(2) 

420 p.setPen(pen) 

421 drawpoint(*tr(self.tmin, clip=True, snap=math.ceil)) 

422 drawpoint(*tr(self.tmax, clip=True, snap=math.floor)) 

423 

424 except trace.NoData: 

425 pass 

426 

427 color = self.select_color(g_color_b) 

428 pen = qg.QPen(qg.QColor(*color)) 

429 pen.setWidth(2) 

430 p.setPen(pen) 

431 

432 drawline(self.tmin) 

433 drawline(self.tmax) 

434 

435 label = self.get_label() 

436 if label: 

437 label_bg = qg.QBrush(qg.QColor(255, 255, 255)) 

438 

439 u = time_projection(self.tmin) 

440 v0, v1 = track_projection.get_out_range() 

441 if outline_label: 

442 du = -7 

443 else: 

444 du = -5 

445 gui_util.draw_label( 

446 p, u+du, v0, label, label_bg, 'TR', 

447 outline=outline_label) 

448 

449 if self.tmin == self.tmax: 

450 try: 

451 drawpoint(self.tmin, tr.interpolate(self.tmin)) 

452 

453 except IndexError: 

454 pass 

455 

456 def get_label(self): 

457 return None 

458 

459 def convert_to_phase_marker( 

460 self, 

461 event=None, 

462 phasename=None, 

463 polarity=None, 

464 automatic=None, 

465 incidence_angle=None, 

466 takeoff_angle=None): 

467 

468 if isinstance(self, PhaseMarker): 

469 return 

470 

471 self.__class__ = PhaseMarker 

472 self._event = event 

473 self._phasename = phasename 

474 self._polarity = polarity 

475 self._automatic = automatic 

476 self._incidence_angle = incidence_angle 

477 self._takeoff_angle = takeoff_angle 

478 if self._event: 

479 self._event_hash = event.get_hash() 

480 self._event_time = event.time 

481 else: 

482 self._event_hash = None 

483 self._event_time = None 

484 self.active = False 

485 

486 def convert_to_event_marker(self, lat=0., lon=0.): 

487 if isinstance(self, EventMarker): 

488 return 

489 

490 if isinstance(self, PhaseMarker): 

491 self.convert_to_marker() 

492 

493 self.__class__ = EventMarker 

494 self._event = model.Event(lat, lon, time=self.tmin, name='Event') 

495 self._event_hash = self._event.get_hash() 

496 self.active = False 

497 self.tmax = self.tmin 

498 self.nslc_ids = [] 

499 

500 

501class EventMarker(Marker): 

502 ''' 

503 GUI element representing a seismological event. 

504 

505 :param event: A :py:class:`pyrocko.model.Event` object containing meta 

506 information of a seismological event 

507 :param kind: (optional) integer to distinguish groups of markers 

508 :param event_hash: (optional) hash code of event (see: 

509 :py:meth:`pyrocko.model.Event.get_hash`) 

510 ''' 

511 

512 def __init__(self, event, kind=0, event_hash=None): 

513 Marker.__init__(self, [], event.time, event.time, kind) 

514 self._event = event 

515 self.active = False 

516 self._event_hash = event_hash 

517 

518 def get_event_hash(self): 

519 if self._event_hash is not None: 

520 return self._event_hash 

521 else: 

522 return self._event.get_hash() 

523 

524 def label(self): 

525 t = [] 

526 mag = self._event.magnitude 

527 if mag is not None: 

528 t.append('M%3.1f' % mag) 

529 

530 reg = self._event.region 

531 if reg is not None: 

532 t.append(reg) 

533 

534 nam = self._event.name 

535 if nam is not None: 

536 t.append(nam) 

537 

538 s = ' '.join(t) 

539 if not s: 

540 s = '(Event)' 

541 return s 

542 

543 def draw(self, p, time_projection, y_projection, with_label=False): 

544 Marker.draw( 

545 self, p, time_projection, y_projection, 

546 draw_line=False, 

547 draw_triangle=True) 

548 

549 if with_label: 

550 self.draw_label(p, time_projection, y_projection) 

551 

552 def draw_label(self, p, time_projection, y_projection): 

553 from .qt_compat import qg 

554 from . import util as gui_util 

555 

556 u = time_projection(self.tmin) 

557 v0, v1 = y_projection.get_out_range() 

558 label_bg = qg.QBrush(qg.QColor(255, 255, 255)) 

559 gui_util.draw_label( 

560 p, u, v0-10., self.label(), label_bg, 'CB', 

561 outline=self.active) 

562 

563 def get_event(self): 

564 ''' 

565 Return an instance of the :py:class:`pyrocko.model.Event` associated 

566 to this :py:class:`EventMarker` 

567 ''' 

568 return self._event 

569 

570 def draw_trace(self, viewer, p, tr, time_projection, track_projection, 

571 gain): 

572 pass 

573 

574 def hoover_message(self): 

575 ev = self.get_event() 

576 evs = [] 

577 for k in 'magnitude lat lon depth name region catalog'.split(): 

578 if ev.__dict__[k] is not None and ev.__dict__[k] != '': 

579 if k == 'depth': 

580 sv = '%g km' % (ev.depth * 0.001) 

581 else: 

582 sv = '%s' % ev.__dict__[k] 

583 evs.append('%s = %s' % (k, sv)) 

584 

585 return ', '.join(evs) 

586 

587 def get_attributes(self, fdigits=3): 

588 attributes = ['event:'] 

589 attributes.extend(Marker.get_attributes(self, fdigits=fdigits)) 

590 del attributes[-1] 

591 e = self._event 

592 attributes.extend([ 

593 e.get_hash(), e.lat, e.lon, e.depth, e.magnitude, e.catalog, 

594 e.name, e.region]) 

595 

596 return attributes 

597 

598 def get_attribute_widths(self, fdigits=3): 

599 ws = [6] 

600 ws.extend(Marker.get_attribute_widths(self, fdigits=fdigits)) 

601 del ws[-1] 

602 ws.extend([14, 12, 12, 12, 4, 5, 0, 0]) 

603 return ws 

604 

605 @staticmethod 

606 def from_attributes(vals): 

607 

608 nslc_ids, tmin, tmax, kind = Marker.parse_attributes( 

609 vals[1:] + ['None']) 

610 lat, lon, depth, magnitude = [ 

611 str_to_float_or_none(x) for x in vals[5:9]] 

612 catalog, name, region = [ 

613 str_to_str_or_none(x) for x in vals[9:]] 

614 e = model.Event( 

615 lat, lon, time=tmin, name=name, depth=depth, magnitude=magnitude, 

616 region=region, catalog=catalog) 

617 marker = EventMarker( 

618 e, kind, event_hash=str_to_str_or_none(vals[4])) 

619 return marker 

620 

621 

622class PhaseMarker(Marker): 

623 ''' 

624 A PhaseMarker is a GUI-element representing a seismological phase arrival 

625 

626 :param nslc_ids: list of (network, station, location, channel) tuples (may 

627 contain wildcards) 

628 :param tmin: start time 

629 :param tmax: end time 

630 :param kind: (optional) integer to distinguish groups of markers 

631 (color-coded) 

632 :param event: a :py:class:`pyrocko.model.Event` object containing meta 

633 information of a seismological event 

634 :param event_hash: (optional) hash code of event (see: 

635 :py:meth:`pyrocko.model.Event.get_hash`) 

636 :param event_time: (optional) time of the associated event 

637 :param phasename: (optional) name of the phase associated with the marker 

638 :param polarity: (optional) polarity of arriving phase 

639 :param automatic: (optional) 

640 :param incident_angle: (optional) incident angle of phase 

641 :param takeoff_angle: (optional) take off angle of phase 

642 ''' 

643 def __init__( 

644 self, nslc_ids, tmin, tmax, 

645 kind=0, 

646 event=None, 

647 event_hash=None, 

648 event_time=None, 

649 phasename=None, 

650 polarity=None, 

651 automatic=None, 

652 incidence_angle=None, 

653 takeoff_angle=None): 

654 

655 Marker.__init__(self, nslc_ids, tmin, tmax, kind) 

656 self._event = event 

657 self._event_hash = event_hash 

658 self._event_time = event_time 

659 self._phasename = phasename 

660 self._automatic = automatic 

661 self._incidence_angle = incidence_angle 

662 self._takeoff_angle = takeoff_angle 

663 

664 self.set_polarity(polarity) 

665 

666 def draw_trace(self, viewer, p, tr, time_projection, track_projection, 

667 gain): 

668 

669 Marker.draw_trace( 

670 self, viewer, p, tr, time_projection, track_projection, gain, 

671 outline_label=( 

672 self._event is not None and 

673 self._event == viewer.get_active_event())) 

674 

675 def get_label(self): 

676 t = [] 

677 if self._phasename is not None: 

678 t.append(self._phasename) 

679 if self._polarity is not None: 

680 t.append(self.get_polarity_symbol()) 

681 

682 if self._automatic: 

683 t.append('@') 

684 

685 return ''.join(t) 

686 

687 def get_event(self): 

688 ''' 

689 Return an instance of the :py:class:`pyrocko.model.Event` associated 

690 to this :py:class:`EventMarker` 

691 ''' 

692 return self._event 

693 

694 def get_event_hash(self): 

695 if self._event_hash is not None: 

696 return self._event_hash 

697 else: 

698 if self._event is None: 

699 return None 

700 else: 

701 return self._event.get_hash() 

702 

703 def get_event_time(self): 

704 if self._event is not None: 

705 return self._event.time 

706 else: 

707 return self._event_time 

708 

709 def set_event_hash(self, event_hash): 

710 self._event_hash = event_hash 

711 

712 def set_event(self, event): 

713 self._event = event 

714 if event is not None: 

715 self.set_event_hash(event.get_hash()) 

716 

717 def get_phasename(self): 

718 return self._phasename 

719 

720 def set_phasename(self, phasename): 

721 self._phasename = phasename 

722 

723 def set_polarity(self, polarity): 

724 if polarity not in [1, -1, 0, None]: 

725 raise ValueError('polarity has to be 1, -1, 0 or None') 

726 self._polarity = polarity 

727 

728 def get_polarity_symbol(self): 

729 return polarity_symbols.get(self._polarity, '') 

730 

731 def get_polarity(self): 

732 return self._polarity 

733 

734 def convert_to_marker(self): 

735 del self._event 

736 del self._event_hash 

737 del self._phasename 

738 del self._polarity 

739 del self._automatic 

740 del self._incidence_angle 

741 del self._takeoff_angle 

742 self.active = False 

743 self.__class__ = Marker 

744 

745 def hoover_message(self): 

746 toks = [] 

747 for k in 'incidence_angle takeoff_angle polarity'.split(): 

748 v = getattr(self, '_' + k) 

749 if v is not None: 

750 toks.append('%s = %s' % (k, v)) 

751 

752 return ', '.join(toks) 

753 

754 def get_attributes(self, fdigits=3): 

755 attributes = ['phase:'] 

756 attributes.extend(Marker.get_attributes(self, fdigits=fdigits)) 

757 

758 et = None, None 

759 if self._event: 

760 et = self._st(self._event.time, fdigits).split() 

761 elif self._event_time: 

762 et = self._st(self._event_time, fdigits).split() 

763 

764 attributes.extend([ 

765 self.get_event_hash(), et[0], et[1], self._phasename, 

766 self._polarity, self._automatic]) 

767 

768 return attributes 

769 

770 def _st(self, t, fdigits): 

771 return util.time_to_str( 

772 t, format='%Y-%m-%d %H:%M:%S.'+'%iFRAC' % fdigits) 

773 

774 def get_attribute_widths(self, fdigits=3): 

775 ws = [6] 

776 ws.extend(Marker.get_attribute_widths(self, fdigits=fdigits)) 

777 ws.extend([14, 12, 12, 8, 4, 5]) 

778 return ws 

779 

780 @staticmethod 

781 def from_attributes(vals): 

782 if len(vals) == 14: 

783 nbasicvals = 7 

784 else: 

785 nbasicvals = 4 

786 nslc_ids, tmin, tmax, kind = Marker.parse_attributes( 

787 vals[1:1+nbasicvals]) 

788 

789 i = 8 

790 if len(vals) == 14: 

791 i = 11 

792 

793 event_hash = str_to_str_or_none(vals[i-3]) 

794 event_sdate = str_to_str_or_none(vals[i-2]) 

795 event_stime = str_to_str_or_none(vals[i-1]) 

796 

797 if event_sdate is not None and event_stime is not None: 

798 event_time = util.str_to_time(event_sdate + ' ' + event_stime) 

799 else: 

800 event_time = None 

801 

802 phasename = str_to_str_or_none(vals[i]) 

803 polarity = str_to_int_or_none(vals[i+1]) 

804 automatic = str_to_bool(vals[i+2]) 

805 marker = PhaseMarker(nslc_ids, tmin, tmax, kind, event=None, 

806 event_hash=event_hash, event_time=event_time, 

807 phasename=phasename, polarity=polarity, 

808 automatic=automatic) 

809 return marker 

810 

811 

812def load_markers(filename): 

813 ''' 

814 Load markers from file. 

815 

816 :param filename: filename as string 

817 :returns: list of :py:class:`Marker` Objects 

818 ''' 

819 

820 return Marker.load_markers(filename) 

821 

822 

823def save_markers(markers, filename, fdigits=3): 

824 ''' 

825 Save markers to file. 

826 

827 :param markers: list of :py:class:`Marker` Objects 

828 :param filename: filename as string 

829 :param fdigits: number of decimal digits to use for sub-second time strings 

830 ''' 

831 

832 return Marker.save_markers(markers, filename, fdigits=fdigits) 

833 

834 

835def associate_phases_to_events(markers): 

836 ''' 

837 Reassociate phases to events after import from markers file. 

838 ''' 

839 

840 hash_to_events = {} 

841 time_to_events = {} 

842 for marker in markers: 

843 if isinstance(marker, EventMarker): 

844 ev = marker.get_event() 

845 hash_to_events[marker.get_event_hash()] = ev 

846 time_to_events[ev.time] = ev 

847 

848 for marker in markers: 

849 if isinstance(marker, PhaseMarker): 

850 h = marker.get_event_hash() 

851 t = marker.get_event_time() 

852 if marker.get_event() is None: 

853 if h is not None and h in hash_to_events: 

854 marker.set_event(hash_to_events[h]) 

855 marker.set_event_hash(None) 

856 elif t is not None and t in time_to_events: 

857 marker.set_event(time_to_events[t]) 

858 marker.set_event_hash(None)