1515import getpass
1616import glob
1717import os
18+ import ast
1819import shutil
1920import tarfile
2021import tempfile
2122import logging
2223
2324import numpy as np
24- from obspy import Catalog , Stream , read , read_events
25+ from obspy import Catalog , Stream , UTCDateTime , read , read_events
2526from obspy .core .event import Comment , CreationInfo
27+ from obspy .core .util .attribdict import AttribDict
2628
2729from eqcorrscan .core .match_filter .template import Template , group_templates
2830from 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
0 commit comments