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 

16import pyrocko.plot.colors 

17 

18 

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

20 

21 

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

26 

27 

28def str_to_float_or_none(s): 

29 if s == 'None': 

30 return None 

31 return float(s) 

32 

33 

34def str_to_str_or_none(s): 

35 if s == 'None': 

36 return None 

37 return s 

38 

39 

40def str_to_int_or_none(s): 

41 if s == 'None': 

42 return None 

43 return int(s) 

44 

45 

46def str_to_bool(s): 

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

48 

49 

50def myctime(timestamp): 

51 tt, ms = gmtime_x(timestamp) 

52 return mystrftime(None, tt, ms) 

53 

54 

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

69 

70 

71class MarkerParseError(Exception): 

72 pass 

73 

74 

75class MarkerOneNSLCRequired(Exception): 

76 pass 

77 

78 

79class Marker(object): 

80 ''' 

81 General purpose marker GUI element and base class for 

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

83 

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

91 

92 @staticmethod 

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

94 ''' 

95 Static method to write marker objects to file. 

96 

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) 

114 

115 writer.writerow(row, w) 

116 

117 f.close() 

118 

119 @staticmethod 

120 def load_markers(fn): 

121 ''' 

122 Static method to load markers from file. 

123 

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

133 

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) 

146 

147 markers.append(marker) 

148 else: 

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

150 

151 return markers 

152 

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 

159 

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

161 ''' 

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

163 

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) 

172 

173 def set_kind(self, kind): 

174 ''' 

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

176 

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

178 (color-coded) 

179 ''' 

180 self.kind = kind 

181 

182 def get_tmin(self): 

183 ''' 

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

185 ''' 

186 return self.tmin 

187 

188 def get_tmax(self): 

189 ''' 

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

191 ''' 

192 return self.tmax 

193 

194 def get_nslc_ids(self): 

195 ''' 

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

197 

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

199 

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

201 expressions. 

202 ''' 

203 return self.nslc_ids 

204 

205 def is_alerted(self): 

206 return self.alerted 

207 

208 def is_selected(self): 

209 return self.selected 

210 

211 def set_alerted(self, state): 

212 self.alerted = state 

213 

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) 

220 

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) 

227 

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() 

236 

237 return list(self.nslc_ids)[0] 

238 

239 def hoover_message(self): 

240 return '' 

241 

242 def copy(self): 

243 ''' 

244 Get a copy of this marker. 

245 ''' 

246 return copy.deepcopy(self) 

247 

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) 

257 

258 def get_attributes(self, fdigits=3): 

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

260 

261 def st(t): 

262 return util.time_to_str( 

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

264 

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) 

270 

271 vals.append(self.kind) 

272 vals.append(traces) 

273 return vals 

274 

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 

281 

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 

290 

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(',')]) 

298 

299 return nslc_ids, tmin, tmax, kind 

300 

301 @staticmethod 

302 def from_attributes(vals): 

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

304 

305 def select_color(self, colorlist): 

306 

307 def cl(x): 

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

309 

310 if self.selected: 

311 return cl(1) 

312 

313 if self.alerted: 

314 return cl(1) 

315 

316 return cl(2) 

317 

318 def draw( 

319 self, p, time_projection, y_projection, 

320 draw_line=True, 

321 draw_triangle=False, 

322 **kwargs): 

323 

324 from .qt_compat import qc, qg 

325 from . import util as gui_util 

326 

327 color = self.select_color(g_color_b) 

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

329 pen.setWidth(2) 

330 p.setPen(pen) 

331 

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) 

337 

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

346 

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.]) 

352 

353 def drawline(t): 

354 u = time_projection(t) 

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

356 p.drawLine(line) 

357 

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) 

366 

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

368 p.setPen(linepen) 

369 drawline(self.tmin) 

370 drawline(self.tmax) 

371 

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) 

379 

380 def draw_trace( 

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

382 outline_label=False): 

383 

384 from .qt_compat import qc, qg 

385 from . import util as gui_util 

386 

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

388 return 

389 

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) 

395 

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) 

401 

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) 

407 

408 try: 

409 snippet = tr.chop( 

410 self.tmin, self.tmax, 

411 inplace=False, 

412 include_last=True, 

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

414 

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

429 

430 except trace.NoData: 

431 pass 

432 

433 color = self.select_color(g_color_b) 

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

435 pen.setWidth(2) 

436 p.setPen(pen) 

437 

438 drawline(self.tmin) 

439 drawline(self.tmax) 

440 

441 label = self.get_label() 

442 if label: 

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

444 

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) 

454 

455 if self.tmin == self.tmax: 

456 try: 

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

458 

459 except IndexError: 

460 pass 

461 

462 def get_label(self): 

463 return None 

464 

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): 

473 

474 if isinstance(self, PhaseMarker): 

475 return 

476 

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 

491 

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

493 if isinstance(self, EventMarker): 

494 return 

495 

496 if isinstance(self, PhaseMarker): 

497 self.convert_to_marker() 

498 

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 = [] 

505 

506 

507class EventMarker(Marker): 

508 ''' 

509 GUI element representing a seismological event. 

510 

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

517 

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 

523 

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() 

529 

530 def label(self): 

531 t = [] 

532 mag = self._event.magnitude 

533 if mag is not None: 

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

535 

536 reg = self._event.region 

537 if reg is not None: 

538 t.append(reg) 

539 

540 nam = self._event.name 

541 if nam is not None: 

542 t.append(nam) 

543 

544 s = ' '.join(t) 

545 if not s: 

546 s = '(Event)' 

547 return s 

548 

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) 

554 

555 if with_label: 

556 self.draw_label(p, time_projection, y_projection) 

557 

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

559 from .qt_compat import qg 

560 from . import util as gui_util 

561 

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) 

568 

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 

575 

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

577 gain): 

578 pass 

579 

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

590 

591 return ', '.join(evs) 

592 

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]) 

601 

602 return attributes 

603 

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 

610 

611 @staticmethod 

612 def from_attributes(vals): 

613 

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 

626 

627 

628class PhaseMarker(Marker): 

629 ''' 

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

631 

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): 

660 

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 

669 

670 self.set_polarity(polarity) 

671 

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

673 gain): 

674 

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())) 

680 

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()) 

687 

688 if self._automatic: 

689 t.append('@') 

690 

691 return ''.join(t) 

692 

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 

699 

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() 

708 

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 

714 

715 def set_event_hash(self, event_hash): 

716 self._event_hash = event_hash 

717 

718 def set_event(self, event): 

719 self._event = event 

720 if event is not None: 

721 self.set_event_hash(event.get_hash()) 

722 

723 def get_phasename(self): 

724 return self._phasename 

725 

726 def set_phasename(self, phasename): 

727 self._phasename = phasename 

728 

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 

733 

734 def get_polarity_symbol(self): 

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

736 

737 def get_polarity(self): 

738 return self._polarity 

739 

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 

750 

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

757 

758 return ', '.join(toks) 

759 

760 def get_attributes(self, fdigits=3): 

761 attributes = ['phase:'] 

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

763 

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() 

769 

770 attributes.extend([ 

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

772 self._polarity, self._automatic]) 

773 

774 return attributes 

775 

776 def _st(self, t, fdigits): 

777 return util.time_to_str( 

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

779 

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 

785 

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]) 

794 

795 i = 8 

796 if len(vals) == 14: 

797 i = 11 

798 

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]) 

802 

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 

807 

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 

816 

817 

818def load_markers(filename): 

819 ''' 

820 Load markers from file. 

821 

822 :param filename: filename as string 

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

824 ''' 

825 

826 return Marker.load_markers(filename) 

827 

828 

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

830 ''' 

831 Save markers to file. 

832 

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

837 

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

839 

840 

841def associate_phases_to_events(markers): 

842 ''' 

843 Reassociate phases to events after import from markers file. 

844 ''' 

845 

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 

853 

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)