Skip to content

Commit ee8a94c

Browse files
committed
more general read/write trace metadata; only set when available for all traces
1 parent 9c50776 commit ee8a94c

2 files changed

Lines changed: 73 additions & 46 deletions

File tree

eqcorrscan/core/match_filter/tribe.py

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
import getpass
1616
import glob
1717
import os
18+
import ast
1819
import shutil
1920
import tarfile
2021
import tempfile
2122
import logging
2223

2324
import numpy as np
24-
from obspy import Catalog, Stream, read, read_events
25+
from obspy import Catalog, Stream, UTCDateTime, read, read_events
2526
from obspy.core.event import Comment, CreationInfo
27+
from obspy.core.util.attribdict import AttribDict
2628

2729
from eqcorrscan.core.match_filter.template import Template, group_templates
2830
from eqcorrscan.core.match_filter.party import Party
@@ -298,27 +300,40 @@ def write(self, filename, compress=True, catalog_format="QUAKEML"):
298300
if comment.text and comment.text.startswith(
299301
"eqcorrscan_template_"):
300302
comment.text = "eqcorrscan_template_{0}".format(t.name)
301-
# TODO: add extra info for each trace to event object, write
302-
# into quakeml, and read back from quakeml into trace
303-
# stats
304-
trace_ids = [tr.id for tr in t.st]
303+
namespace = 'EQcorrscan'
304+
unique_keys = []
305305
try:
306+
unique_keys = list(set([key for tr in t.st
307+
for key in tr.stats.extra.keys()]))
308+
trace_ids = [tr.id for tr in t.st]
306309
if not hasattr(t.event, 'extra'):
307-
t.event.extra = {}
308-
namespace = 'EQcorrscan'
310+
t.event.extra = AttribDict()
309311
t.event.extra.update(
310-
{'trace_ids': { 'value': trace_ids,
311-
'namespace': namespace}})
312-
for key in t.st[0].extra.keys():
313-
trace_extra_parameters = [
314-
tr.stats.extra.get(key) for tr in t.st]
315-
event_key = 'trace_' + key
316-
t.event.extra.update(
317-
{event_key: { 'value': trace_extra_parameters,
318-
'namespace': namespace}})
312+
{'trace_ids': {'value': trace_ids,
313+
'namespace': namespace}})
319314
except AttributeError:
320315
Logger.warning(
321316
'Template %s has no extended trace-metadata', t.name)
317+
for key in unique_keys:
318+
try:
319+
trace_extra_parameters = [
320+
tr.stats.extra.get(key) for tr in t.st]
321+
# Check if metadata are time-values - need to be stored
322+
# as strings in event / Quakeml.
323+
#if 'time' in key:
324+
if all([isinstance(val, UTCDateTime)
325+
for val in trace_extra_parameters]):
326+
trace_extra_parameters = [
327+
str(time_val)
328+
for time_val in trace_extra_parameters]
329+
except AttributeError:
330+
Logger.warning('Traces for template %s are missing '
331+
'entries for key %s', t.name, key)
332+
continue
333+
event_key = 'trace_' + key
334+
t.event.extra.update(
335+
{event_key: {'value': trace_extra_parameters,
336+
'namespace': namespace}})
322337
tribe_cat.append(t.event)
323338
if len(tribe_cat) > 0:
324339
tribe_cat.write(
@@ -401,7 +416,6 @@ def _read_from_folder(self, dirname):
401416
for comment in event.comments:
402417
if comment.text == 'eqcorrscan_template_' + template.name:
403418
template.event = event
404-
self._assign_trace_metadata(template, event)
405419
t_file = [t for t in t_files
406420
if t.split(os.sep)[-1] == template.name + '.ms']
407421
if len(t_file) == 0:
@@ -411,16 +425,25 @@ def _read_from_folder(self, dirname):
411425
elif len(t_file) > 1:
412426
Logger.warning('Multiple waveforms found, using: ' + t_file[0])
413427
template.st = read(t_file[0])
428+
self._assign_trace_metadata(template, event)
414429
self.templates.extend(templates)
415430
return
416431

417432
def _assign_trace_metadata(self, template, event):
418-
# TODO: put Template trace metadata back into
419-
# trace.extra
433+
"""
434+
Internal function to put template trace metadata back into
435+
trace.stats.extra.
436+
"""
420437
try:
421438
if template.st is None:
422439
return
423440
n_traces = len(template.st)
441+
# List of strings stored in QuakeML file is read back in as just
442+
# one long string; so convert string-representation of list back to
443+
# an actual list of strings:
444+
if isinstance(event.extra.trace_ids.value, str):
445+
event.extra.trace_ids.value = ast.literal_eval(
446+
event.extra.trace_ids.value)
424447
n_traces_metadata = len(event.extra.trace_ids.value)
425448
# First check that stream has the right number of traces -
426449
# otherwise, we'll need to split the traces according to the
@@ -456,22 +479,36 @@ def _assign_trace_metadata(self, template, event):
456479
n_traces = len(template.st)
457480
namespace = 'EQcorrscan'
458481
for key, value in template.event.extra.items():
482+
# Only handle metadata intended for EQcorrscan
483+
if not value.namespace == namespace:
484+
continue
459485
if not key.startswith('trace_'):
460486
# extra metadata not intended for template stream
461487
continue
462-
if not len(value) == n_traces:
488+
# Check if need to convert string-representation of list back
489+
# to list:
490+
if isinstance(value.value, str):
491+
value.value = ast.literal_eval(value.value)
492+
# Convert time-strings back to UTCDateTime:
493+
if 'time' in key:
494+
value.value = [
495+
UTCDateTime(time_str) for time_str in value.value]
496+
if len(value.value) != n_traces:
463497
Logger.warning(
464498
'Not enough values in extra event metadata for key %s '
465499
'to assign to all traces for template %s.', key,
466500
template.name)
501+
continue
467502
trace_key = key.removeprefix('trace_')
468-
for tr, tr_metadata_value in zip(template.st, value):
469-
tr.stats.extra[trace_key].update({
470-
value: tr_metadata_value,
471-
'namespace': namespace})
472-
except (KeyError, AttributeError):
503+
for tr, tr_metadata_value in zip(template.st, value.value):
504+
if not hasattr(tr.stats, 'extra'):
505+
tr.stats.extra = AttribDict()
506+
tr.stats.extra.update(
507+
{trace_key: tr_metadata_value})
508+
except (KeyError, AttributeError) as e:
473509
# TODO decide whether to support tribes without
474510
# extended metadata
511+
Logger.warning(e)
475512
pass
476513
return
477514

eqcorrscan/core/template_gen.py

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from obspy import Stream, read, Trace, UTCDateTime, read_events
2525
from obspy.core.event import Catalog
2626
from obspy.clients.fdsn import Client as FDSNClient
27+
from obspy.core.util.attribdict import AttribDict
2728

2829
from eqcorrscan.utils.sac_util import sactoevent
2930
from eqcorrscan.utils import pre_processing
@@ -822,30 +823,19 @@ def _template_gen(picks, st, length, swin='all', prepick=0.05,
822823
weight = 1
823824
namespace = 'EQcorrscan'
824825
if not hasattr(tr_cut.stats, 'extra'):
825-
tr_cut.stats.extra = {}
826+
tr_cut.stats.extra = AttribDict()
826827
tr_cut.stats.extra.update(
827-
{'length_npts': {'value': tr_cut.stats.npts,
828-
'namespace': namespace}})
828+
{'length_npts': tr_cut.stats.npts})
829829
tr_cut.stats.extra.update(
830-
{'starttime': {
831-
'value': tr_cut.stats.starttime,
832-
'namespace': namespace}})
830+
{'starttime': tr_cut.stats.starttime})
831+
tr_cut.stats.extra.update({
832+
'endtime': tr_cut.stats.endtime})
833+
tr_cut.stats.extra.update({
834+
'peak_snr': peak_snr})
835+
tr_cut.stats.extra.update({
836+
'rms_snr': rms_snr})
833837
tr_cut.stats.extra.update(
834-
{'endtime': {
835-
'value': tr_cut.stats.endtime,
836-
'namespace': namespace}})
837-
tr_cut.stats.extra.update(
838-
{'peak_snr': {
839-
'value': peak_snr,
840-
'namespace': namespace}})
841-
tr_cut.stats.extra.update(
842-
{'rms_snr': {
843-
'value': rms_snr,
844-
'namespace': namespace}})
845-
tr_cut.stats.extra.update(
846-
{'weight': {
847-
'value': weight,
848-
'namespace': namespace}})
838+
{'weight': weight})
849839
st1 += tr_cut
850840
used_tr = True
851841
if not used_tr:

0 commit comments

Comments
 (0)