Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/gui/snuffler/marker.py: 84%

479 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-10-04 09:52 +0000

1# https://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.snuffler.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 ''' 

70 Raised when a marker with exactly one NSLC entry is required but there are 

71 zero or more than one. 

72 ''' 

73 pass 

74 

75 

76class Marker(object): 

77 ''' 

78 General purpose marker GUI element and base class for 

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

80 

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

82 (may contain wildcards) 

83 :param tmin: start time 

84 :param tmax: end time 

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

86 (color-coded) 

87 ''' 

88 

89 @staticmethod 

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

91 ''' 

92 Static method to write marker objects to file. 

93 

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

95 :param fn: filename as string 

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

97 strings (default 3) 

98 ''' 

99 f = open(fn, 'w') 

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

101 writer = TableWriter(f) 

102 for marker in markers: 

103 a = marker.get_attributes(fdigits=fdigits) 

104 w = marker.get_attribute_widths(fdigits=fdigits) 

105 row = [] 

106 for x in a: 

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

108 row.append('None') 

109 else: 

110 row.append(x) 

111 

112 writer.writerow(row, w) 

113 

114 f.close() 

115 

116 @staticmethod 

117 def load_markers(fn): 

118 ''' 

119 Static method to load markers from file. 

120 

121 :param filename: filename as string 

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

123 :py:class:`PhaseMarker` objects 

124 ''' 

125 markers = [] 

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

127 line = f.readline() 

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

129 raise MarkerParseError('Not a marker file') 

130 

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

132 reader = TableReader(f) 

133 while not reader.eof: 

134 row = reader.readrow() 

135 if not row: 

136 continue 

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

138 marker = EventMarker.from_attributes(row) 

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

140 marker = PhaseMarker.from_attributes(row) 

141 else: 

142 marker = Marker.from_attributes(row) 

143 

144 markers.append(marker) 

145 else: 

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

147 

148 return markers 

149 

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

151 self.set(nslc_ids, tmin, tmax) 

152 self.alerted = False 

153 self.selected = False 

154 self.kind = kind 

155 self.active = False 

156 

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

158 ''' 

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

160 

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

162 tuples 

163 :param tmin: start time 

164 :param tmax: end time 

165 ''' 

166 self.nslc_ids = nslc_ids 

167 self.tmin = util.to_time_float(tmin) 

168 self.tmax = util.to_time_float(tmax) 

169 

170 def set_kind(self, kind): 

171 ''' 

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

173 

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

175 (color-coded) 

176 ''' 

177 self.kind = kind 

178 

179 def get_tmin(self): 

180 ''' 

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

182 ''' 

183 return self.tmin 

184 

185 def get_tmax(self): 

186 ''' 

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

188 ''' 

189 return self.tmax 

190 

191 def get_nslc_ids(self): 

192 ''' 

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

194 

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

196 

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

198 expressions. 

199 ''' 

200 return self.nslc_ids 

201 

202 def is_alerted(self): 

203 return self.alerted 

204 

205 def is_selected(self): 

206 return self.selected 

207 

208 def set_alerted(self, state): 

209 self.alerted = state 

210 

211 def match_nsl(self, nsl): 

212 ''' 

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

214 ''' 

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

216 return util.match_nslc(patterns, nsl) 

217 

218 def match_nslc(self, nslc): 

219 ''' 

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

221 ''' 

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

223 return util.match_nslc(patterns, nslc) 

224 

225 def one_nslc(self): 

226 ''' 

227 Get single NSLC pattern or raise an exception if there is not exactly 

228 one. 

229 

230 If *nslc_ids* contains a single entry, return it. If more than one is 

231 available, raise :py:exc:`MarkerOneNSLCRequired`. 

232 ''' 

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

234 raise MarkerOneNSLCRequired() 

235 

236 return list(self.nslc_ids)[0] 

237 

238 def hoover_message(self): 

239 return '' 

240 

241 def copy(self): 

242 ''' 

243 Get a copy of this marker. 

244 ''' 

245 return copy.deepcopy(self) 

246 

247 def __str__(self): 

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

249 st = myctime 

250 if self.tmin == self.tmax: 

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

252 else: 

253 return '%s %s %g %i %s' % ( 

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

255 traces) 

256 

257 def get_attributes(self, fdigits=3): 

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

259 

260 def st(t): 

261 return util.time_to_str( 

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

263 

264 vals = [] 

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

266 if self.tmin != self.tmax: 

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

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

269 

270 vals.append(self.kind) 

271 vals.append(traces) 

272 return vals 

273 

274 def get_attribute_widths(self, fdigits=3): 

275 ws = [10, 9+fdigits] 

276 if self.tmin != self.tmax: 

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

278 ws.extend([2, 15]) 

279 return ws 

280 

281 @staticmethod 

282 def parse_attributes(vals): 

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

284 i = 2 

285 tmax = tmin 

286 if len(vals) == 7: 

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

288 i = 5 

289 

290 kind = int(vals[i]) 

291 traces = vals[i+1] 

292 if traces == 'None': 

293 nslc_ids = [] 

294 else: 

295 nslc_ids = tuple( 

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

297 

298 return nslc_ids, tmin, tmax, kind 

299 

300 @staticmethod 

301 def from_attributes(vals): 

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

303 

304 def select_color(self, colorlist): 

305 

306 def cl(x): 

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

308 

309 if self.selected: 

310 return cl(1) 

311 

312 if self.alerted: 

313 return cl(1) 

314 

315 return cl(2) 

316 

317 def draw( 

318 self, p, time_projection, y_projection, 

319 draw_line=True, 

320 draw_triangle=False, 

321 **kwargs): 

322 

323 from ..qt_compat import qc, qg 

324 from .. import util as gui_util 

325 

326 color = self.select_color(g_color_b) 

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

328 pen.setWidth(2) 

329 p.setPen(pen) 

330 

331 umin = time_projection(self.tmin) 

332 umax = time_projection(self.tmax) 

333 v0, v1 = y_projection.get_out_range() 

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

335 p.drawLine(line) 

336 

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

338 linepen = qg.QPen(pen) 

339 if self.selected or self.alerted: 

340 linepen.setStyle(qc.Qt.CustomDashLine) 

341 pat = [5., 3.] 

342 linepen.setDashPattern(pat) 

343 if self.alerted and not self.selected: 

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

345 

346 s = 9. 

347 utriangle = gui_util.make_QPolygonF( 

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

349 ltriangle = gui_util.make_QPolygonF( 

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

351 

352 def drawline(t): 

353 u = time_projection(t) 

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

355 p.drawLine(line) 

356 

357 def drawtriangles(t): 

358 u = time_projection(t) 

359 t = qg.QPolygonF(utriangle) 

360 t.translate(u, v0) 

361 p.drawConvexPolygon(t) 

362 t = qg.QPolygonF(ltriangle) 

363 t.translate(u, v1) 

364 p.drawConvexPolygon(t) 

365 

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

367 p.setPen(linepen) 

368 drawline(self.tmin) 

369 drawline(self.tmax) 

370 

371 if draw_triangle: 

372 pen.setStyle(qc.Qt.SolidLine) 

373 pen.setJoinStyle(qc.Qt.MiterJoin) 

374 pen.setWidth(2) 

375 p.setPen(pen) 

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

377 drawtriangles(self.tmin) 

378 

379 def draw_trace( 

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

381 outline_label=False): 

382 

383 from ..qt_compat import qc, qg 

384 from .. import util as gui_util 

385 

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

387 return 

388 

389 color = self.select_color(g_color_b) 

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

391 pen.setWidth(2) 

392 p.setPen(pen) 

393 p.setBrush(qc.Qt.NoBrush) 

394 

395 def drawpoint(t, y): 

396 u = time_projection(t) 

397 v = track_projection(y) 

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

399 p.drawRect(rect) 

400 

401 def drawline(t): 

402 u = time_projection(t) 

403 v0, v1 = track_projection.get_out_range() 

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

405 p.drawLine(line) 

406 

407 try: 

408 snippet = tr.chop( 

409 self.tmin, self.tmax, 

410 inplace=False, 

411 include_last=True, 

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

413 

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

415 udata_min = float( 

416 time_projection(snippet.tmin)) 

417 udata_max = float( 

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

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

420 qpoints = gui_util.make_QPolygonF(udata, vdata) 

421 pen.setWidth(1) 

422 p.setPen(pen) 

423 p.drawPolyline(qpoints) 

424 pen.setWidth(2) 

425 p.setPen(pen) 

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

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

428 

429 except trace.NoData: 

430 pass 

431 

432 color = self.select_color(g_color_b) 

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

434 pen.setWidth(2) 

435 p.setPen(pen) 

436 

437 drawline(self.tmin) 

438 drawline(self.tmax) 

439 

440 label = self.get_label() 

441 if label: 

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

443 

444 u = time_projection(self.tmin) 

445 v0, v1 = track_projection.get_out_range() 

446 if outline_label: 

447 du = -7 

448 else: 

449 du = -5 

450 gui_util.draw_label( 

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

452 outline=outline_label) 

453 

454 if self.tmin == self.tmax: 

455 try: 

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

457 

458 except IndexError: 

459 pass 

460 

461 def get_label(self): 

462 return None 

463 

464 def convert_to_phase_marker( 

465 self, 

466 event=None, 

467 phasename=None, 

468 polarity=None, 

469 automatic=None, 

470 incidence_angle=None, 

471 takeoff_angle=None): 

472 

473 if isinstance(self, PhaseMarker): 

474 return 

475 

476 self.__class__ = PhaseMarker 

477 self._event = event 

478 self._phasename = phasename 

479 self._polarity = polarity 

480 self._automatic = automatic 

481 self._incidence_angle = incidence_angle 

482 self._takeoff_angle = takeoff_angle 

483 if self._event: 

484 self._event_hash = event.get_hash() 

485 self._event_time = event.time 

486 else: 

487 self._event_hash = None 

488 self._event_time = None 

489 self.active = False 

490 

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

492 if isinstance(self, EventMarker): 

493 return 

494 

495 if isinstance(self, PhaseMarker): 

496 self.convert_to_marker() 

497 

498 self.__class__ = EventMarker 

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

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

501 self.active = False 

502 self.tmax = self.tmin 

503 self.nslc_ids = [] 

504 

505 

506class EventMarker(Marker): 

507 ''' 

508 GUI element representing a seismological event. 

509 

510 :param event: A :py:class:`~pyrocko.model.event.Event` object containing 

511 meta information of a seismological event 

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

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

514 :py:meth:`~pyrocko.model.event.Event.get_hash`) 

515 ''' 

516 

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

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

519 self._event = event 

520 self.active = False 

521 self._event_hash = event_hash 

522 

523 def get_event_hash(self): 

524 if self._event_hash is not None: 

525 return self._event_hash 

526 else: 

527 return self._event.get_hash() 

528 

529 def label(self): 

530 t = [] 

531 mag = self._event.magnitude 

532 if mag is not None: 

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

534 

535 reg = self._event.region 

536 if reg is not None: 

537 t.append(reg) 

538 

539 nam = self._event.name 

540 if nam is not None: 

541 t.append(nam) 

542 

543 s = ' '.join(t) 

544 if not s: 

545 s = '(Event)' 

546 return s 

547 

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

549 Marker.draw( 

550 self, p, time_projection, y_projection, 

551 draw_line=False, 

552 draw_triangle=True) 

553 

554 if with_label: 

555 self.draw_label(p, time_projection, y_projection) 

556 

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

558 from ..qt_compat import qg 

559 from .. import util as gui_util 

560 

561 u = time_projection(self.tmin) 

562 v0, v1 = y_projection.get_out_range() 

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

564 gui_util.draw_label( 

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

566 outline=self.active) 

567 

568 def get_event(self): 

569 ''' 

570 Return an instance of the :py:class:`~pyrocko.model.event.Event` 

571 associated to this :py:class:`EventMarker` 

572 ''' 

573 return self._event 

574 

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

576 gain): 

577 pass 

578 

579 def hoover_message(self): 

580 ev = self.get_event() 

581 evs = [] 

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

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

584 if k == 'depth': 

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

586 else: 

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

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

589 

590 return ', '.join(evs) 

591 

592 def get_attributes(self, fdigits=3): 

593 attributes = ['event:'] 

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

595 del attributes[-1] 

596 e = self._event 

597 attributes.extend([ 

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

599 e.name, e.region]) 

600 

601 return attributes 

602 

603 def get_attribute_widths(self, fdigits=3): 

604 ws = [6] 

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

606 del ws[-1] 

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

608 return ws 

609 

610 @staticmethod 

611 def from_attributes(vals): 

612 

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

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

615 lat, lon, depth, magnitude = [ 

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

617 catalog, name, region = [ 

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

619 e = model.Event( 

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

621 region=region, catalog=catalog) 

622 marker = EventMarker( 

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

624 return marker 

625 

626 

627class PhaseMarker(Marker): 

628 ''' 

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

630 

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

632 contain wildcards) 

633 :param tmin: start time 

634 :param tmax: end time 

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

636 (color-coded) 

637 :param event: a :py:class:`~pyrocko.model.event.Event` object containing 

638 meta information of a seismological event 

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

640 :py:meth:`~pyrocko.model.event.Event.get_hash`) 

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

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

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

644 :param automatic: (optional) 

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

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

647 ''' 

648 def __init__( 

649 self, nslc_ids, tmin, tmax, 

650 kind=0, 

651 event=None, 

652 event_hash=None, 

653 event_time=None, 

654 phasename=None, 

655 polarity=None, 

656 automatic=None, 

657 incidence_angle=None, 

658 takeoff_angle=None): 

659 

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

661 self._event = event 

662 self._event_hash = event_hash 

663 self._event_time = event_time 

664 self._phasename = phasename 

665 self._automatic = automatic 

666 self._incidence_angle = incidence_angle 

667 self._takeoff_angle = takeoff_angle 

668 

669 self.set_polarity(polarity) 

670 

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

672 gain): 

673 

674 Marker.draw_trace( 

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

676 outline_label=( 

677 self._event is not None and 

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

679 

680 def get_label(self): 

681 t = [] 

682 if self._phasename is not None: 

683 t.append(self._phasename) 

684 if self._polarity is not None: 

685 t.append(self.get_polarity_symbol()) 

686 

687 if self._automatic: 

688 t.append('@') 

689 

690 return ''.join(t) 

691 

692 def get_event(self): 

693 ''' 

694 Return an instance of the :py:class:`~pyrocko.model.event.Event` 

695 associated to this :py:class:`EventMarker` 

696 ''' 

697 return self._event 

698 

699 def get_event_hash(self): 

700 if self._event_hash is not None: 

701 return self._event_hash 

702 else: 

703 if self._event is None: 

704 return None 

705 else: 

706 return self._event.get_hash() 

707 

708 def get_event_time(self): 

709 if self._event is not None: 

710 return self._event.time 

711 else: 

712 return self._event_time 

713 

714 def set_event_hash(self, event_hash): 

715 self._event_hash = event_hash 

716 

717 def set_event(self, event): 

718 self._event = event 

719 if event is not None: 

720 self.set_event_hash(event.get_hash()) 

721 

722 def get_phasename(self): 

723 return self._phasename 

724 

725 def set_phasename(self, phasename): 

726 self._phasename = phasename 

727 

728 def set_polarity(self, polarity): 

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

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

731 self._polarity = polarity 

732 

733 def get_polarity_symbol(self): 

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

735 

736 def get_polarity(self): 

737 return self._polarity 

738 

739 def convert_to_marker(self): 

740 del self._event 

741 del self._event_hash 

742 del self._phasename 

743 del self._polarity 

744 del self._automatic 

745 del self._incidence_angle 

746 del self._takeoff_angle 

747 self.active = False 

748 self.__class__ = Marker 

749 

750 def hoover_message(self): 

751 toks = [] 

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

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

754 if v is not None: 

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

756 

757 return ', '.join(toks) 

758 

759 def get_attributes(self, fdigits=3): 

760 attributes = ['phase:'] 

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

762 

763 et = None, None 

764 if self._event: 

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

766 elif self._event_time: 

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

768 

769 attributes.extend([ 

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

771 self._polarity, self._automatic]) 

772 

773 return attributes 

774 

775 def _st(self, t, fdigits): 

776 return util.time_to_str( 

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

778 

779 def get_attribute_widths(self, fdigits=3): 

780 ws = [6] 

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

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

783 return ws 

784 

785 @staticmethod 

786 def from_attributes(vals): 

787 if len(vals) == 14: 

788 nbasicvals = 7 

789 else: 

790 nbasicvals = 4 

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

792 vals[1:1+nbasicvals]) 

793 

794 i = 8 

795 if len(vals) == 14: 

796 i = 11 

797 

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

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

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

801 

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

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

804 else: 

805 event_time = None 

806 

807 phasename = str_to_str_or_none(vals[i]) 

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

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

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

811 event_hash=event_hash, event_time=event_time, 

812 phasename=phasename, polarity=polarity, 

813 automatic=automatic) 

814 return marker 

815 

816 

817def load_markers(filename): 

818 ''' 

819 Load markers from file. 

820 

821 :param filename: filename as string 

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

823 ''' 

824 

825 return Marker.load_markers(filename) 

826 

827 

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

829 ''' 

830 Save markers to file. 

831 

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

833 :param filename: filename as string 

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

835 ''' 

836 

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

838 

839 

840def associate_phases_to_events(markers): 

841 ''' 

842 Reassociate phases to events after import from markers file. 

843 ''' 

844 

845 hash_to_events = {} 

846 time_to_events = {} 

847 for marker in markers: 

848 if isinstance(marker, EventMarker): 

849 ev = marker.get_event() 

850 hash_to_events[marker.get_event_hash()] = ev 

851 time_to_events[ev.time] = ev 

852 

853 for marker in markers: 

854 if isinstance(marker, PhaseMarker): 

855 h = marker.get_event_hash() 

856 t = marker.get_event_time() 

857 if marker.get_event() is None: 

858 if h is not None and h in hash_to_events: 

859 marker.set_event(hash_to_events[h]) 

860 marker.set_event_hash(None) 

861 elif t is not None and t in time_to_events: 

862 marker.set_event(time_to_events[t]) 

863 marker.set_event_hash(None)