1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

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

5 

6import math 

7import copy 

8import logging 

9import sys 

10 

11import numpy as num 

12 

13from pyrocko import util, plot, model, trace 

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

15 

16 

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

18 

19 

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

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

22else: 

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

24 

25 

26def str_to_float_or_none(s): 

27 if s == 'None': 

28 return None 

29 return float(s) 

30 

31 

32def str_to_str_or_none(s): 

33 if s == 'None': 

34 return None 

35 return s 

36 

37 

38def str_to_int_or_none(s): 

39 if s == 'None': 

40 return None 

41 return int(s) 

42 

43 

44def str_to_bool(s): 

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

46 

47 

48def myctime(timestamp): 

49 tt, ms = gmtime_x(timestamp) 

50 return mystrftime(None, tt, ms) 

51 

52 

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

54 'scarletred1', 'scarletred2', 'scarletred3', 

55 'chameleon1', 'chameleon2', 'chameleon3', 

56 'skyblue1', 'skyblue2', 'skyblue3', 

57 'orange1', 'orange2', 'orange3', 

58 'plum1', 'plum2', 'plum3', 

59 'chocolate1', 'chocolate2', 'chocolate3', 

60 'butter1', 'butter2', 'butter3', 

61 'aluminium3', 'aluminium4', 'aluminium5')] 

62 

63 

64class MarkerParseError(Exception): 

65 pass 

66 

67 

68class MarkerOneNSLCRequired(Exception): 

69 pass 

70 

71 

72class Marker(object): 

73 ''' 

74 General purpose marker GUI element and base class for 

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

76 

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

78 (may contain wildcards) 

79 :param tmin: start time 

80 :param tmax: end time 

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

82 (color-coded) 

83 ''' 

84 

85 @staticmethod 

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

87 ''' 

88 Static method to write marker objects to file. 

89 

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

91 :param fn: filename as string 

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

93 strings (default 3) 

94 ''' 

95 f = open(fn, 'w') 

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

97 writer = TableWriter(f) 

98 for marker in markers: 

99 a = marker.get_attributes(fdigits=fdigits) 

100 w = marker.get_attribute_widths(fdigits=fdigits) 

101 row = [] 

102 for x in a: 

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

104 row.append('None') 

105 else: 

106 row.append(x) 

107 

108 writer.writerow(row, w) 

109 

110 f.close() 

111 

112 @staticmethod 

113 def load_markers(fn): 

114 ''' 

115 Static method to load markers from file. 

116 

117 :param filename: filename as string 

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

119 :py:class:`PhaseMarker` objects 

120 ''' 

121 markers = [] 

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

123 line = f.readline() 

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

125 raise MarkerParseError('Not a marker file') 

126 

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

128 reader = TableReader(f) 

129 while not reader.eof: 

130 row = reader.readrow() 

131 if not row: 

132 continue 

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

134 marker = EventMarker.from_attributes(row) 

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

136 marker = PhaseMarker.from_attributes(row) 

137 else: 

138 marker = Marker.from_attributes(row) 

139 

140 markers.append(marker) 

141 else: 

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

143 

144 return markers 

145 

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

147 self.set(nslc_ids, tmin, tmax) 

148 self.alerted = False 

149 self.selected = False 

150 self.kind = kind 

151 self.active = False 

152 

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

154 ''' 

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

156 

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

158 tuples 

159 :param tmin: start time 

160 :param tmax: end time 

161 ''' 

162 self.nslc_ids = nslc_ids 

163 self.tmin = util.to_time_float(tmin) 

164 self.tmax = util.to_time_float(tmax) 

165 

166 def set_kind(self, kind): 

167 ''' 

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

169 

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

171 (color-coded) 

172 ''' 

173 self.kind = kind 

174 

175 def get_tmin(self): 

176 ''' 

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

178 ''' 

179 return self.tmin 

180 

181 def get_tmax(self): 

182 ''' 

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

184 ''' 

185 return self.tmax 

186 

187 def get_nslc_ids(self): 

188 ''' 

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

190 

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

192 

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

194 expressions. 

195 ''' 

196 return self.nslc_ids 

197 

198 def is_alerted(self): 

199 return self.alerted 

200 

201 def is_selected(self): 

202 return self.selected 

203 

204 def set_alerted(self, state): 

205 self.alerted = state 

206 

207 def match_nsl(self, nsl): 

208 ''' 

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

210 ''' 

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

212 return util.match_nslc(patterns, nsl) 

213 

214 def match_nslc(self, nslc): 

215 ''' 

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

217 ''' 

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

219 return util.match_nslc(patterns, nslc) 

220 

221 def one_nslc(self): 

222 ''' 

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

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

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

226 ''' 

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

228 raise MarkerOneNSLCRequired() 

229 

230 return list(self.nslc_ids)[0] 

231 

232 def hoover_message(self): 

233 return '' 

234 

235 def copy(self): 

236 ''' 

237 Get a copy of this marker. 

238 ''' 

239 return copy.deepcopy(self) 

240 

241 def __str__(self): 

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

243 st = myctime 

244 if self.tmin == self.tmax: 

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

246 else: 

247 return '%s %s %g %i %s' % ( 

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

249 traces) 

250 

251 def get_attributes(self, fdigits=3): 

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

253 

254 def st(t): 

255 return util.time_to_str( 

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

257 

258 vals = [] 

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

260 if self.tmin != self.tmax: 

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

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

263 

264 vals.append(self.kind) 

265 vals.append(traces) 

266 return vals 

267 

268 def get_attribute_widths(self, fdigits=3): 

269 ws = [10, 9+fdigits] 

270 if self.tmin != self.tmax: 

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

272 ws.extend([2, 15]) 

273 return ws 

274 

275 @staticmethod 

276 def parse_attributes(vals): 

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

278 i = 2 

279 tmax = tmin 

280 if len(vals) == 7: 

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

282 i = 5 

283 

284 kind = int(vals[i]) 

285 traces = vals[i+1] 

286 if traces == 'None': 

287 nslc_ids = [] 

288 else: 

289 nslc_ids = tuple( 

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

291 

292 return nslc_ids, tmin, tmax, kind 

293 

294 @staticmethod 

295 def from_attributes(vals): 

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

297 

298 def select_color(self, colorlist): 

299 

300 def cl(x): 

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

302 

303 if self.selected: 

304 return cl(1) 

305 

306 if self.alerted: 

307 return cl(1) 

308 

309 return cl(2) 

310 

311 def draw( 

312 self, p, time_projection, y_projection, 

313 draw_line=True, 

314 draw_triangle=False, 

315 **kwargs): 

316 

317 from .qt_compat import qc, qg 

318 from . import util as gui_util 

319 

320 color = self.select_color(g_color_b) 

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

322 pen.setWidth(2) 

323 p.setPen(pen) 

324 

325 umin = time_projection(self.tmin) 

326 umax = time_projection(self.tmax) 

327 v0, v1 = y_projection.get_out_range() 

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

329 p.drawLine(line) 

330 

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

332 linepen = qg.QPen(pen) 

333 if self.selected or self.alerted: 

334 linepen.setStyle(qc.Qt.CustomDashLine) 

335 pat = [5., 3.] 

336 linepen.setDashPattern(pat) 

337 if self.alerted and not self.selected: 

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

339 

340 s = 9. 

341 utriangle = gui_util.make_QPolygonF( 

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

343 ltriangle = gui_util.make_QPolygonF( 

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

345 

346 def drawline(t): 

347 u = time_projection(t) 

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

349 p.drawLine(line) 

350 

351 def drawtriangles(t): 

352 u = time_projection(t) 

353 t = qg.QPolygonF(utriangle) 

354 t.translate(u, v0) 

355 p.drawConvexPolygon(t) 

356 t = qg.QPolygonF(ltriangle) 

357 t.translate(u, v1) 

358 p.drawConvexPolygon(t) 

359 

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

361 p.setPen(linepen) 

362 drawline(self.tmin) 

363 drawline(self.tmax) 

364 

365 if draw_triangle: 

366 pen.setStyle(qc.Qt.SolidLine) 

367 pen.setJoinStyle(qc.Qt.MiterJoin) 

368 pen.setWidth(2) 

369 p.setPen(pen) 

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

371 drawtriangles(self.tmin) 

372 

373 def draw_trace( 

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

375 outline_label=False): 

376 

377 from .qt_compat import qc, qg 

378 from . import util as gui_util 

379 

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

381 return 

382 

383 color = self.select_color(g_color_b) 

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

385 pen.setWidth(2) 

386 p.setPen(pen) 

387 p.setBrush(qc.Qt.NoBrush) 

388 

389 def drawpoint(t, y): 

390 u = time_projection(t) 

391 v = track_projection(y) 

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

393 p.drawRect(rect) 

394 

395 def drawline(t): 

396 u = time_projection(t) 

397 v0, v1 = track_projection.get_out_range() 

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

399 p.drawLine(line) 

400 

401 try: 

402 snippet = tr.chop( 

403 self.tmin, self.tmax, 

404 inplace=False, 

405 include_last=True, 

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

407 

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

409 udata_min = float( 

410 time_projection(snippet.tmin)) 

411 udata_max = float( 

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

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

414 qpoints = gui_util.make_QPolygonF(udata, vdata) 

415 pen.setWidth(1) 

416 p.setPen(pen) 

417 p.drawPolyline(qpoints) 

418 pen.setWidth(2) 

419 p.setPen(pen) 

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

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

422 

423 except trace.NoData: 

424 pass 

425 

426 color = self.select_color(g_color_b) 

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

428 pen.setWidth(2) 

429 p.setPen(pen) 

430 

431 drawline(self.tmin) 

432 drawline(self.tmax) 

433 

434 label = self.get_label() 

435 if label: 

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

437 

438 u = time_projection(self.tmin) 

439 v0, v1 = track_projection.get_out_range() 

440 if outline_label: 

441 du = -7 

442 else: 

443 du = -5 

444 gui_util.draw_label( 

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

446 outline=outline_label) 

447 

448 if self.tmin == self.tmax: 

449 try: 

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

451 

452 except IndexError: 

453 pass 

454 

455 def get_label(self): 

456 return None 

457 

458 def convert_to_phase_marker( 

459 self, 

460 event=None, 

461 phasename=None, 

462 polarity=None, 

463 automatic=None, 

464 incidence_angle=None, 

465 takeoff_angle=None): 

466 

467 if isinstance(self, PhaseMarker): 

468 return 

469 

470 self.__class__ = PhaseMarker 

471 self._event = event 

472 self._phasename = phasename 

473 self._polarity = polarity 

474 self._automatic = automatic 

475 self._incidence_angle = incidence_angle 

476 self._takeoff_angle = takeoff_angle 

477 if self._event: 

478 self._event_hash = event.get_hash() 

479 self._event_time = event.time 

480 else: 

481 self._event_hash = None 

482 self._event_time = None 

483 self.active = False 

484 

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

486 if isinstance(self, EventMarker): 

487 return 

488 

489 if isinstance(self, PhaseMarker): 

490 self.convert_to_marker() 

491 

492 self.__class__ = EventMarker 

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

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

495 self.active = False 

496 self.tmax = self.tmin 

497 self.nslc_ids = [] 

498 

499 

500class EventMarker(Marker): 

501 ''' 

502 GUI element representing a seismological event. 

503 

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

505 information of a seismological event 

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

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

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

509 ''' 

510 

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

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

513 self._event = event 

514 self.active = False 

515 self._event_hash = event_hash 

516 

517 def get_event_hash(self): 

518 if self._event_hash is not None: 

519 return self._event_hash 

520 else: 

521 return self._event.get_hash() 

522 

523 def label(self): 

524 t = [] 

525 mag = self._event.magnitude 

526 if mag is not None: 

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

528 

529 reg = self._event.region 

530 if reg is not None: 

531 t.append(reg) 

532 

533 nam = self._event.name 

534 if nam is not None: 

535 t.append(nam) 

536 

537 s = ' '.join(t) 

538 if not s: 

539 s = '(Event)' 

540 return s 

541 

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

543 Marker.draw( 

544 self, p, time_projection, y_projection, 

545 draw_line=False, 

546 draw_triangle=True) 

547 

548 if with_label: 

549 self.draw_label(p, time_projection, y_projection) 

550 

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

552 from .qt_compat import qg 

553 from . import util as gui_util 

554 

555 u = time_projection(self.tmin) 

556 v0, v1 = y_projection.get_out_range() 

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

558 gui_util.draw_label( 

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

560 outline=self.active) 

561 

562 def get_event(self): 

563 ''' 

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

565 to this :py:class:`EventMarker` 

566 ''' 

567 return self._event 

568 

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

570 gain): 

571 pass 

572 

573 def hoover_message(self): 

574 ev = self.get_event() 

575 evs = [] 

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

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

578 if k == 'depth': 

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

580 else: 

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

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

583 

584 return ', '.join(evs) 

585 

586 def get_attributes(self, fdigits=3): 

587 attributes = ['event:'] 

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

589 del attributes[-1] 

590 e = self._event 

591 attributes.extend([ 

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

593 e.name, e.region]) 

594 

595 return attributes 

596 

597 def get_attribute_widths(self, fdigits=3): 

598 ws = [6] 

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

600 del ws[-1] 

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

602 return ws 

603 

604 @staticmethod 

605 def from_attributes(vals): 

606 

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

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

609 lat, lon, depth, magnitude = [ 

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

611 catalog, name, region = [ 

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

613 e = model.Event( 

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

615 region=region, catalog=catalog) 

616 marker = EventMarker( 

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

618 return marker 

619 

620 

621class PhaseMarker(Marker): 

622 ''' 

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

624 

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

626 contain wildcards) 

627 :param tmin: start time 

628 :param tmax: end time 

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

630 (color-coded) 

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

632 information of a seismological event 

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

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

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

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

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

638 :param automatic: (optional) 

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

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

641 ''' 

642 def __init__( 

643 self, nslc_ids, tmin, tmax, 

644 kind=0, 

645 event=None, 

646 event_hash=None, 

647 event_time=None, 

648 phasename=None, 

649 polarity=None, 

650 automatic=None, 

651 incidence_angle=None, 

652 takeoff_angle=None): 

653 

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

655 self._event = event 

656 self._event_hash = event_hash 

657 self._event_time = event_time 

658 self._phasename = phasename 

659 self._automatic = automatic 

660 self._incidence_angle = incidence_angle 

661 self._takeoff_angle = takeoff_angle 

662 

663 self.set_polarity(polarity) 

664 

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

666 gain): 

667 

668 Marker.draw_trace( 

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

670 outline_label=( 

671 self._event is not None and 

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

673 

674 def get_label(self): 

675 t = [] 

676 if self._phasename is not None: 

677 t.append(self._phasename) 

678 if self._polarity is not None: 

679 t.append(self.get_polarity_symbol()) 

680 

681 if self._automatic: 

682 t.append('@') 

683 

684 return ''.join(t) 

685 

686 def get_event(self): 

687 ''' 

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

689 to this :py:class:`EventMarker` 

690 ''' 

691 return self._event 

692 

693 def get_event_hash(self): 

694 if self._event_hash is not None: 

695 return self._event_hash 

696 else: 

697 if self._event is None: 

698 return None 

699 else: 

700 return self._event.get_hash() 

701 

702 def get_event_time(self): 

703 if self._event is not None: 

704 return self._event.time 

705 else: 

706 return self._event_time 

707 

708 def set_event_hash(self, event_hash): 

709 self._event_hash = event_hash 

710 

711 def set_event(self, event): 

712 self._event = event 

713 if event is not None: 

714 self.set_event_hash(event.get_hash()) 

715 

716 def get_phasename(self): 

717 return self._phasename 

718 

719 def set_phasename(self, phasename): 

720 self._phasename = phasename 

721 

722 def set_polarity(self, polarity): 

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

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

725 self._polarity = polarity 

726 

727 def get_polarity_symbol(self): 

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

729 

730 def get_polarity(self): 

731 return self._polarity 

732 

733 def convert_to_marker(self): 

734 del self._event 

735 del self._event_hash 

736 del self._phasename 

737 del self._polarity 

738 del self._automatic 

739 del self._incidence_angle 

740 del self._takeoff_angle 

741 self.active = False 

742 self.__class__ = Marker 

743 

744 def hoover_message(self): 

745 toks = [] 

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

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

748 if v is not None: 

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

750 

751 return ', '.join(toks) 

752 

753 def get_attributes(self, fdigits=3): 

754 attributes = ['phase:'] 

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

756 

757 et = None, None 

758 if self._event: 

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

760 elif self._event_time: 

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

762 

763 attributes.extend([ 

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

765 self._polarity, self._automatic]) 

766 

767 return attributes 

768 

769 def _st(self, t, fdigits): 

770 return util.time_to_str( 

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

772 

773 def get_attribute_widths(self, fdigits=3): 

774 ws = [6] 

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

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

777 return ws 

778 

779 @staticmethod 

780 def from_attributes(vals): 

781 if len(vals) == 14: 

782 nbasicvals = 7 

783 else: 

784 nbasicvals = 4 

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

786 vals[1:1+nbasicvals]) 

787 

788 i = 8 

789 if len(vals) == 14: 

790 i = 11 

791 

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

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

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

795 

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

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

798 else: 

799 event_time = None 

800 

801 phasename = str_to_str_or_none(vals[i]) 

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

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

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

805 event_hash=event_hash, event_time=event_time, 

806 phasename=phasename, polarity=polarity, 

807 automatic=automatic) 

808 return marker 

809 

810 

811def load_markers(filename): 

812 ''' 

813 Load markers from file. 

814 

815 :param filename: filename as string 

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

817 ''' 

818 

819 return Marker.load_markers(filename) 

820 

821 

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

823 ''' 

824 Save markers to file. 

825 

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

827 :param filename: filename as string 

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

829 ''' 

830 

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

832 

833 

834def associate_phases_to_events(markers): 

835 ''' 

836 Reassociate phases to events after import from markers file. 

837 ''' 

838 

839 hash_to_events = {} 

840 time_to_events = {} 

841 for marker in markers: 

842 if isinstance(marker, EventMarker): 

843 ev = marker.get_event() 

844 hash_to_events[marker.get_event_hash()] = ev 

845 time_to_events[ev.time] = ev 

846 

847 for marker in markers: 

848 if isinstance(marker, PhaseMarker): 

849 h = marker.get_event_hash() 

850 t = marker.get_event_time() 

851 if marker.get_event() is None: 

852 if h is not None and h in hash_to_events: 

853 marker.set_event(hash_to_events[h]) 

854 marker.set_event_hash(None) 

855 elif t is not None and t in time_to_events: 

856 marker.set_event(time_to_events[t]) 

857 marker.set_event_hash(None)