Source code for pisces.io.sac

# Converted to python 3.5.2 on 02/07/17
# By: Jeremy Webster

"""
Conversions between SAC header variables and CSS or KB Core fields.


Converts a SAC header dictionary into a list of table dictionaries and vice-versa.

"""
# XXX: currently not working
# TODO:
#   - remove functions already in pisces.io.trace
#   - make everything just translate dictionaries, not classes and make
#     everything less obspy.Trace-centric
#   - change db.flatfile.KBTABLEDICT to use pisces.schema.util.get_infovals
#   - use pysac (hopefully obspy.io.sac, eventually)
#   - make a bunch of column/header translation functions, like "kcmpnm_to_chan"
#     and "chan_to_kcmpnm", and use them in higher-level functions, like
#     "sachdr_to_wfdisc" and "wfdisc_to_sachdr".

import sys
import os
from collections import OrderedDict
import functools

from obspy.core import UTCDateTime, AttribDict
import obspy.geodetics as geod

import pisces.tables.kbcore as kb
from pisces.io.readwaveform import read_waveform
from pisces.io.util import _map_header, _buildhdr

# ObsPy default values
OBSPYDEFAULT = {'network': '',
                'station': '',
                'location': '',
                'channel': ''}

# TODO: much of this will be superseded by obspy.io.sac
# ------------------ CONVERT SAC HEADER DICTIONARY TO TABLES ------------#
# SAC default values
IDEFAULT = -12345
FDEFAULT = -12345.0
SDEFAULT = '-12345  '
SLDEFAULT = '-12345          '
SACDEFAULT = {'a': FDEFAULT, 'az': FDEFAULT, 'b': FDEFAULT, 'baz': FDEFAULT,
              'cmpaz': FDEFAULT, 'cmpinc': FDEFAULT, 'delta': FDEFAULT,
              'depmax': FDEFAULT, 'depmen': FDEFAULT, 'depmin': FDEFAULT,
              'dist': FDEFAULT, 'e': FDEFAULT, 'evdp': FDEFAULT, 'evla': FDEFAULT,
              'evlo': FDEFAULT, 'f': FDEFAULT, 'gcarc': FDEFAULT, 'idep': IDEFAULT,
              'ievreg': IDEFAULT, 'ievtype': IDEFAULT, 'iftype': IDEFAULT,
              'iinst': IDEFAULT, 'imagsrc': IDEFAULT, 'imagtyp': IDEFAULT,
              'int1': FDEFAULT, 'iqual': IDEFAULT, 'istreg': IDEFAULT,
              'isynth': IDEFAULT, 'iztype': IDEFAULT, 'ka': SDEFAULT,
              'kcmpnm': SDEFAULT, 'kdatrd': SDEFAULT, 'kevnm': SLDEFAULT,
              'kf': SDEFAULT, 'khole': SDEFAULT, 'kinst': SDEFAULT,
              'knetwk': SDEFAULT, 'ko': SDEFAULT, 'kstnm': SDEFAULT, 'kt0': SDEFAULT,
              'kt1': SDEFAULT, 'kt2': SDEFAULT, 'kt3': SDEFAULT, 'kt4': SDEFAULT,
              'kt5': SDEFAULT, 'kt6': SDEFAULT, 'kt7': SDEFAULT, 'kt8': SDEFAULT,
              'kt9': SDEFAULT, 'kuser0': SDEFAULT, 'kuser1': SDEFAULT,
              'kuser2': SDEFAULT, 'lcalda': IDEFAULT, 'leven': IDEFAULT,
              'lovrok': IDEFAULT, 'lpspol': IDEFAULT, 'mag': FDEFAULT,
              'nevid': IDEFAULT, 'norid': IDEFAULT, 'npts': IDEFAULT,
              'nvhdr': IDEFAULT, 'nwfid': IDEFAULT, 'nzhour': IDEFAULT,
              'nzjday': IDEFAULT, 'nzmin': IDEFAULT, 'nzmsec': IDEFAULT,
              'nzsec': IDEFAULT, 'nzyear': IDEFAULT, 'o': FDEFAULT,
              'odelta': FDEFAULT, 'scale': FDEFAULT, 'stdp': FDEFAULT,
              'stel': FDEFAULT, 'stla': FDEFAULT, 'stlo': FDEFAULT, 't0': FDEFAULT,
              't1': FDEFAULT, 't2': FDEFAULT, 't3': FDEFAULT, 't4': FDEFAULT,
              't5': FDEFAULT, 't6': FDEFAULT, 't7': FDEFAULT, 't8': FDEFAULT,
              't9': FDEFAULT, 'unused10': FDEFAULT, 'unused11': FDEFAULT,
              'unused12': FDEFAULT, 'unused6': FDEFAULT, 'unused7': FDEFAULT,
              'unused8': FDEFAULT, 'unused9': FDEFAULT, 'user0': FDEFAULT,
              'user1': FDEFAULT, 'user2': FDEFAULT, 'user3': FDEFAULT,
              'user4': FDEFAULT, 'user5': FDEFAULT, 'user6': FDEFAULT,
              'user7': FDEFAULT, 'user8': FDEFAULT, 'user9': FDEFAULT,
              'xmaximum': FDEFAULT, 'xminimum': FDEFAULT, 'ymaximum': FDEFAULT,
              'yminimum': FDEFAULT}

############################# DECORATORS ######################################
# Decorator functions allow readable, reusable handling of header values.


[docs]def cast_to_int(original_func): """ Cast a function's argument to int before the function operates on it, like: Examples -------- >>> @cast_to_int ... def nwfid_to_wfid(nwfid): ... return nwfid >>> nwfid_to_wfid(12.3) 12 """ @functools.wraps(original_func) def converter(hdr): return original_func(int(hdr)) return converter
[docs]def cast_to_float(original_func): """ Cast a function's argument to float before the function operates on it, like: Examples -------- >>> @cast_to_float ... def stla_to_lat(stla): ... return stla >>> lat = stla_to_lat(numpy.float32(12.3)) >>> type(lat) float # not numpy.float32 """ @functools.wraps(original_func) def converter(hdr): return original_func(float(hdr)) return converter
[docs]def strip_string(original_func): """ Strip white space from a function's argument to float before the function operates on it, like: Examples -------- >>> @strip_string ... def kcmpnm_to_chan(kcmpnm): ... return kcmpnm[:6] >>> kclmpnm_to_chan(' my_too_long_component ') 'my_too' """ @functools.wraps(original_func) def converter(hdr): return original_func(int(hdr)) return converter
[docs]def truncate_string(N): def make_func(original_func): @functools.wraps(original_func) def func_wrapper(val): return val[:N] return func_wrapper
# decorator for when you find default/null SAC header values # if a value is detected, function returns return_value instead of being executed # XXX: not clear how this could be used with other decorators
[docs]def swap_if_value(detected_value, return_value): def check_value(original_func): def func_wrapper(arg): if arg == detected_value: out = return_value else: out = arg return out return func_wrapper
# ############################## STRING HEADER CONVERSIONS #################### # SAC -> CSS
[docs]def kcmpnm_to_chan(kcmpnm): return kcmpnm.strip()[:8]
[docs]def kevnm_to_evname(kevnm): return kevnm.strip()
[docs]def kinst_to_insname(kinst): return kinst.strip()
[docs]def knetwk_to_net(knetwk): return knetwk.strip()[:8]
[docs]def kstnm_to_sta(kstnm): return kstnm.strip()[:6]
# CSS -> SAC
[docs]def chan_to_kcmpnm(chan): return chan
[docs]def evname_to_kevnm(evname): return evname
[docs]def insname_to_kinst(insname): return insname
[docs]def net_to_knetwk(net): return net
[docs]def sta_to_kstnm(sta): return kstnm
# ############################## INT HEADER CONVERSIONS ####################### # DATA # ENUM_NAMES = {1: 'itime', 2: 'irlim', 3: 'iamph', 4: 'ixy', 5: 'iunkn', 6: 'idisp', 7: 'ivel', 8: 'iacc', 9: 'ib', 10: 'iday', 11: 'io', 12: 'ia', 13: 'it0', 14: 'it1', 15: 'it2', 16: 'it3', 17: 'it4', 18: 'it5', 19: 'it6', 20: 'it7', 21: 'it8', 22: 'it9', 23: 'iradnv', 24: 'itannv', 25: 'iradev', 26: 'itanev', 27: 'inorth', 28: 'ieast', 29: 'ihorza', 30: 'idown', 31: 'iup', 32: 'illlbb', 33: 'iwwsn1', 34: 'iwwsn2', 35: 'ihglp', 36: 'isro', 37: 'inucl', 38: 'ipren', 39: 'ipostn', 40: 'iquake', 41: 'ipreq', 42: 'ipostq', 43: 'ichem', 44: 'iother', 45: 'igood', 46: 'iglch', 47: 'idrop', 48: 'ilowsn', 49: 'irldta', 50: 'ivolts', 52: 'imb', 53: 'ims', 54: 'iml', 55: 'imw', 56: 'imd', 57: 'imx', 58: 'ineic', 59: 'ipdeq', 60: 'ipdew', 61: 'ipde', 62: 'iisc', 63: 'ireb', 64: 'iusgs', 65: 'ibrk', 66: 'icaltech', 67: 'illnl', 68: 'ievloc', 69: 'ijsop', 70: 'iuser', 71: 'iunknown', 72: 'iqb', 73: 'iqb1', 74: 'iqb2', 75: 'iqbx', 76: 'iqmt', 77: 'ieq', 78: 'ieq1', 79: 'ieq2', 80: 'ime', 81: 'iex', 82: 'inu', 83: 'inc', 84: 'io_', 85: 'il', 86: 'ir', 87: 'it', 88: 'iu', 89: 'ieq3', 90: 'ieq0', 91: 'iex0', 92: 'iqc', 93: 'iqb0', 94: 'igey', 95: 'ilit', 96: 'imet', 97: 'iodor', 103: 'ios'} # ievtyp -> etype ETYPEDICT = {37: 'en', 38: 'ex', 39: 'ex', 40: 'qt', 41: 'qt', 42: 'qt', 43: 'ec', 72: 'me', 73: 'me', 74: 'me', 75: 'me', 76: 'mb', 77: 'qt', 78: 'qt', 79: 'qt', 80: 'ex', 81: 'ex', 82: 'en', 83: 'mc'} # evtype -> ievtyp IEVTYPDICT = dict((_val, _key) for _key, _val in ETYPEDICT.items()) # # imagsrc -> auth AUTHDICT = {58: 'ISC:NEIC', 61: 'PDE', 62: 'ISC', 63: 'REB-ICD', 64: 'IUSGS', 65: 'ISC:BERK', 66: 'ICALTECH', 67: 'ILLNL', 68: 'IEVLOC', 69: 'IJSOP', 70: 'IUSER', 71: 'IUNKNOWN'} # # auth -> imagsrc IMAGSRCDICT = dict((_val, _key) for _key, _val in AUTHDICT.items()) # SAC -> CSS
[docs]def ievreg_to_grn(ievreg): return int(ievreg)
[docs]def ievtyp_to_etype(ievtyp): """Provide the ievtyp(e) integer, get the etype string.""" return ETYPEDICT[ievtyp]
# the following functions accept a SAC header dictionary, and return respective # kbcore table instances, assumes default SAC header values set to None
[docs]def get_sac_reftime(header): """ Get SAC header reference time as a UTCDateTime instance from a SAC header dictionary. If using ObsPy to read the SAC file, use debug_headers=True to get the full header, including nz time headers. """ # "nz" fields are not kept when ObsPy reads a SAC file. They're used # to make "starttime", then discarded. # t0 = nzyear + nzjday + nzhour + nzminute + nzsecond + nzmsec*1000 # tr.stats.starttime = t0 + b # therefore: t0 = starttime - b # TODO: let null nz values be 0? try: yr = header['nzyear'] if 0 <= yr <= 99: yr += 1900 nzjday = header['nzjday'] nzhour = header['nzhour'] nzmin = header['nzmin'] nzsec = header['nzsec'] nzmsec = header['nzmsec'] except KeyError as e: msg = "Not enough time information: {}".format(e.message) raise KeyError(msg) try: reftime = UTCDateTime(year=yr, julday=nzjday, hour=nzhour, minute=nzmin, second=nzsec, microsecond=nzmsec * 1000) # reftime = datetime.datetime(yr, 1, 1, nzhour, nzmin, nzsec, nzmsec * 1000) + \ # datetime.timedelta(int(nzjday-1)) # NOTE: epoch seconds can be got by: # (reftime - datetime.datetime(1970,1,1)).total_seconds() except ValueError: # may contain -12345 null values? msg = "Invalid or missing time headers." raise ValueError(msg) return reftime
def _cast_int(d, keys): for key in keys: try: d[key] = int(d[key]) except (TypeError, KeyError): # no key, or int(None) pass return d def _cast_float(d, keys): for key in keys: try: d[key] = float(d[key]) except (TypeError, KeyError): # no key, or int(None) pass return d def _clean_str(d, keys): for key in keys: try: d[key] = d[key].strip() except (KeyError, AttributeError): # no key, None.strip() pass return d
[docs]def sachdr2site(header): """ Provide a SAC header dictionary, get a site table dictionary. """ sac_site = [('kstnm', 'sta'), ('stla', 'lat'), ('stlo', 'lon'), ('stel', 'elev')] sitedict = {} for hdr, col in sac_site: val = header.get(hdr, None) sitedict[col] = val if val != SACDEFAULT[hdr] else None # clean up try: sitedict['elev'] /= 1000.0 except (TypeError, KeyError): # no 'elev' pass sitedict = _cast_float(sitedict, ['lat', 'lon', 'elev']) sitedict = _clean_str(sitedict, ['sta']) sitedict['sta'] = sitedict['sta'].strip()[:6] return [sitedict] or []
[docs]def sachdr2arraysite(header): """ Provide a SAC header dictionary, get a site table dictionary. This is a slight modification of sachdr2site that handles deast & dnorth being in SAC's user7 & user8 header fields (a poor but documented standard that is the de facto appoach - see SAC's BBFK or ARRAYMAP or BRTT's db2sac in Antelope). Note that user7 & user8 are expected to be in km (consistent with SAC expectations). """ sac_site = [('kstnm', 'sta'), ('stla', 'lat'), ('stlo', 'lon'), ('stel', 'elev'), ('user7','deast'), ('user8','dnorth')] sitedict = {} for hdr, col in sac_site: val = header.get(hdr, None) sitedict[col] = val if val != SACDEFAULT[hdr] else None # clean up try: sitedict['elev'] /= 1000.0 except (TypeError, KeyError): #no 'elev' pass sitedict = _cast_float(sitedict, ['lat', 'lon', 'elev', 'deast', 'dnorth']) sitedict = _clean_str(sitedict, ['sta']) sitedict['sta'] = sitedict['sta'].strip()[:6] return [sitedict] or []
[docs]def sachdr2sitechan(header): """ Provide a sac header dictionary, get a sitechan table dictionary. """ sac_sitechan = [('kstnm', 'sta'), ('kcmpnm', 'chan'), ('cmpaz', 'hang'), ('cmpinc', 'vang'), ('stdp', 'edepth')] sitechandict = AttribDict() for hdr, col in sac_sitechan: val = header.get(hdr, None) sitechandict[col] = val if val != SACDEFAULT[hdr] else None try: sitechandict['edepth'] /= 1000.0 except (TypeError, KeyError): # edepth is None or missing pass sitechandict = _cast_float(sitechandict, ['hang', 'vang', 'edepth']) sitechandict = _clean_str(sitechandict, ['sta', 'chan']) sitechandict['sta'] = sitechandict['sta'].strip()[:6] return [sitechandict] or []
[docs]def sachdr2affiliation(header): sac_affil = [('knetwk', 'net'), ('kstnm', 'sta')] affildict = AttribDict() for hdr, col in sac_affil: val = header.get(hdr, None) affildict[col] = val if val != SACDEFAULT[hdr] else None affildict = _clean_str(affildict, ['net', 'sta']) affildict['sta'] = affildict['sta'].strip()[:6] return [affildict] or []
[docs]def sachdr2instrument(header): # TODO: investigate hdr['resp0-9'] values sac_instr = [('kinst', 'insname'), ('iinst', 'instype'), ('delta', 'samprate')] instrdict = AttribDict() for hdr, col in sac_instr: val = header.get(hdr, None) instrdict[col] = val if val != SACDEFAULT[hdr] else None # clean up try: instrdict['samprate'] = int(round(1.0 / instrdict['samprate'], 0)) except (TypeError, KeyError): pass return [instrdict] or []
[docs]def sachdr2origin(header): """ Provide a sac header dictionary, get a filled origin table dictionary. A few things: 1) If sac reference time isn't event-based, origin time is unknown 2) magnitude is taken first from hdr['mag'], hdr['imagtyp'] if defined, then replaced by hdr['user0'],hdr['kuser0'] 3) sac moment, duration, and user-defined magnitude headers aren't put into the origin table 4) origin.auth is taken first from hdr['imagsrc'], then replaced by hdr['kuser1'] if defined. the imagsrc->auth translations are: * INEIC -> ISC:NEIC * IPDE -> PDE * IISC -> ISC * IBRK -> ISC:BERK * IUSGS, ICALTECH, ILLNL, IEVLOC, IJSOP, IUSER, IUNKNOWN -> unchanged """ # simple SAC translations sac_origin = [('evla', 'lat'), ('evlo', 'lon'), ('norid', 'orid'), ('nevid', 'evid'), ('ievreg', 'grn'), ('evdp', 'depth')] origindict = AttribDict() for hdr, col in sac_origin: val = header.get(hdr, None) origindict[col] = val if val != SACDEFAULT[hdr] else None # depth try: origindict['depth'] /= 1000.0 except (TypeError, KeyError): # evdp is None or mising pass # etype translations edict = {37: 'en', 38: 'ex', 39: 'ex', 40: 'qt', 41: 'qt', 42: 'qt', 43: 'ec', 72: 'me', 73: 'me', 74: 'me', 75: 'me', 76: 'mb', 77: 'qt', 78: 'qt', 79: 'qt', 80: 'ex', 81: 'ex', 82: 'en', 83: 'mc'} try: origindict['etype'] = edict[header['ievtype']] except (TypeError, KeyError): # ievtyp is None, or not a key in edict pass # 1: try: t = get_sac_reftime(header) if header['iztype'] == 11: # reference time is an origin time o = header.get('o', None) o = o if (o != SACDEFAULT['o']) else 0.0 origindict['time'] = t.timestamp - o origindict['jdate'] = int((t - o).strftime('%Y%j')) except (ValueError, KeyError): # no trace.stats.sac, no iztype pass # 2: magnitude magdict = {52: 'mb', 53: 'ms', 54: 'ml'} try: origindict[magdict[header['imagtyp']]] = header['mag'] except (ValueError, KeyError): # imagtyp is None or not a key in magdict pass # is kuser0 is a recognized magnitude type, overwrite mag # XXX: this is a LANL wfdisc2sac thing try: magtype = header['kuser0'].strip() if magtype in list(magdict.values()): origindict[magtype] = header['user0'] except (KeyError, ValueError): # kuser0 is None pass # 3: origin author authdict = {58: 'ISC:NEIC', 61: 'PDE', 62: 'ISC', 63: 'REB-ICD', 64: 'IUSGS', 65: 'ISC:BERK', 66: 'ICALTECH', 67: 'ILLNL', 68: 'IEVLOC', 69: 'IJSOP', 70: 'IUSER', 71: 'IUNKNOWN'} try: origindict['auth'] = authdict[header['imagsrc']] except (KeyError, ValueError): # imagsrc not in authdict (i.e. sac default value) pass # XXX: this is LANL wfdisc2sac thing. maybe turn it off? if header.get('kuser1'): origindict['auth'] = header['kuser1'] origindict = _cast_float(origindict, ['lat', 'lon', 'depth']) origindict = _cast_int(origindict, ['evid', 'orid']) return [origindict] or []
[docs]def sachdr2event(header): sac_event = [('nevid', 'evid'), ('kevnm', 'evname')] eventdict = AttribDict() for hdr, col in sac_event: val = header.get(hdr, None) eventdict[col] = val if val != SACDEFAULT[hdr] else None eventdict = _cast_int(eventdict, ['evid']) return [eventdict] or []
[docs]def sachdr2assoc(header, pickmap=None): """ Takes a sac header dictionary, and produces a list of up to 10 Assoc instances. Header->phase mappings follow SAC2000, i.e.: * t0: P * t1: Pn * t2: Pg * t3: S * t4: Sn * t5: Sg * t6: Lg * t7: LR * t8: Rg * t9: pP An alternate mapping for some or all picks can be supplied, however, as a dictionary of strings in the above form. Note: arid values will not be filled in, so do: >>> for assoc in kbio.tables['assoc']: assoc.arid = lastarid+1 lastarid += 1 """ pick2phase = {'t0': 'P', 't1': 'Pn', 't2': 'Pg', 't3': 'S', 't4': 'Sn', 't5': 'Sg', 't6': 'Lg', 't7': 'LR', 't8': 'Rg', 't9': 'pP'} # overwrite defaults with supplied map if pickmap: pick2phase.update(pickmap) # geographic relations # obspy.read tries to calculate these values if lcalca is True and needed # header info is there, so we only need to try to if lcalca is False. # XXX: I just calculate it if no values are currently filled in. sac_assoc = [('az', 'esaz'), ('baz', 'seaz'), ('gcarc', 'delta')] assocdict = AttribDict() for hdr, col in sac_assoc: val = header.get(hdr, None) assocdict[col] = val if val != SACDEFAULT[hdr] else None # overwrite if any are None if not assocdict: try: delta = geod.locations2degrees(header['stla'], header['stlo'], header['evla'], header['evlo']) m, seaz, esaz = geod.gps2DistAzimuth(header['stla'], header['stlo'], header['evla'], header['evlo']) assocdict['esaz'] = esaz assocdict['seaz'] = seaz assocdict['delta'] = delta except (ValueError, TypeError): # some sac header values are None pass if header.get('kstnm', None): assocdict['sta'] = header['kstnm'] orid = header.get('norid', None) assocdict['orid'] = orid if orid != SACDEFAULT['norid'] else None # now, do the phase arrival mappings # for each pick in hdr, make a separate dictionary containing assocdict plus # the new phase info. assocs = [] for key in pick2phase: kkey = 'k' + key # if there's a value in t[0-9] if header.get(key, None) not in (SACDEFAULT[key], None): # if the phase name kt[0-9] is null if header[kkey] == SACDEFAULT[kkey]: # take it from the map iassoc = {'phase': pick2phase[key]} else: # take it directly iassoc = {'phase': header[kkey]} iassoc.update(assocdict) assocs.append(iassoc) return assocs
[docs]def sachdr2arrival(header, pickmap=None): """Similar to sachdr2assoc, but produces a list of up to 10 Arrival dictionaries. Same header->phase mapping applies, unless otherwise stated. """ # puts t[0-9] times into arrival.time if they're not null # puts corresponding kt[0-9] phase name into arrival.iphase # if a kt[0-9] phase name is null and its t[0-9] values isn't, # phase names are pulled from the pick2phase dictionary pick2phase = {'t0': 'P', 't1': 'Pn', 't2': 'Pg', 't3': 'S', 't4': 'Sn', 't5': 'Sg', 't6': 'Lg', 't7': 'LR', 't8': 'Rg', 't9': 'pP'} if pickmap: pick2phase.update(pickmap) # simple translations arrivaldict = AttribDict() if header.get('kstnm', None) not in (SACDEFAULT['kstnm'], None): arrivaldict['sta'] = header['kstnm'] if header.get('kcmpnm', None) not in (SACDEFAULT['kcmpnm'], None): arrivaldict['chan'] = header['kcmpnm'] # phases and arrival times t0 = get_sac_reftime(header) arrivals = [] for key in pick2phase: kkey = 'k' + key # if there's a value in t[0-9] if header.get('key', None) not in (SACDEFAULT[key], None): # TODO: This seems broken...t isn't defined yet itime = t + header[key] iarrival = {'time': itime.timestamp, 'jdate': int(itime.strftime('%Y%j'))} # if the phase name kt[0-9] is null if header[kkey] == SACDEFAULT[kkey]: # take it from the pick2phase map iarrival['iphase'] = pick2phase[key] else: # take it directly iarrival['iphase'] = header[kkey] iarrival.update(arrivaldict) # TODO: wtf is iassoc?? arrivals.append(iassoc) return arrivals
[docs]def sachdr2wfdisc(header): """Produce wfdisc kbcore table instance from sac header dictionary. Clearly this will be a skeleton instance, as the all-important 'dir' and 'dfile' and 'datatype' must be filled in later. """ t0 = get_sac_reftime(header) b = header.get('b', None) b = b if (b != SACDEFAULT['b']) else 0.0 starttime = t0 + b e = header.get('e', None) e = e if (e != SACDEFAULT['e']) else 0.0 endtime = t0 + e wfdict = AttribDict() wfdict['nsamp'] = int(header.get('npts', None)) wfdict['time'] = starttime.timestamp wfdict['endtime'] = endtime.timestamp wfdict['jdate'] = int(starttime.strftime('%Y%j')) wfdict['samprate'] = int(round(1.0 / header['delta'])) kstnm = header.get('kstnm', None) if kstnm not in (SACDEFAULT['kstnm'], None): wfdict['sta'] = kstnm.strip()[:6] kcmpnm = header.get('kcmpnm', None) if kcmpnm not in (SACDEFAULT['kcmpnm'], None): wfdict['chan'] = kcmpnm.strip()[:8] scale = header.get('scale', None) if scale not in (SACDEFAULT['scale'], None): wfdict['calib'] = float(scale) nwfid = header.get('nwfid', None) if nwfid not in (SACDEFAULT['nwfid'], None): wfdict['wfid'] = nwfid wfdict['foff'] = 632 if sys.byteorder == 'little': wfdict['datatype'] = 'f4' else: wfdict['datatype'] = 't4' return [wfdict] or []
[docs]def sachdr2tables(header, tables=None): """ Scrape SAC header dictionary into database table dictionaries. Parameters ---------- header : dict SAC header tables : list/tuple of strings, optional Table name strings to return. Default, ['affiliation', 'arrival', 'assoc', 'event', 'instrument', 'origin', 'site', 'sitechan', 'wfdisc'] Returns ------- dict Dictionary of lists of table dictionaries. If only default values are found for a table, it is omitted. Notes ----- Some things must be checked or filled in before adding to a database: - affiliation.time, endtime - arrival.arid - assoc.orid, arid - event.prefor, evid - instrument.inid, dir, dfile, rsptype - site.ondate - sitechan.ondate, chanid - wfdisc.dir, dfile, foff, datetype, wfid """ fns = {'affiliation': sachdr2affiliation, 'arrival': sachdr2arrival, 'assoc': sachdr2assoc, 'event': sachdr2event, 'instrument': sachdr2instrument, 'origin': sachdr2origin, 'site': sachdr2site, 'sitechan': sachdr2sitechan, 'wfdisc': sachdr2wfdisc} if tables is None: tables = list(fns.keys()) # for key in header: # if key.startswith('k'): # try: # header[key] = header[key].strip()[:6] # t = AttribDict() t = {} for table in tables: try: itab = fns[table](header) except KeyError: itab = [] if itab: t[table] = itab # t[table] doesn't exist if table's function didn't return anything return t
# ----------------- CONVERT TABLES TO SAC HEADER DICTIONARY ---------------# # TODO: make these functions able to gracefully handle None values as inputs
[docs]def site2sachdr(s): """ Accepts a fielded site table row and returns a dictionary of corresponding sac header field/value pairs. June 13, 2016 - added deast/dnorth output to SAC user7/8 header fields """ keymap = {'stel': 'elev', 'stal': 'lat', 'stlo': 'lon', 'user7': 'deast', 'user8': 'dnorth'} hdr = _buildhdr(keymap, s) return hdr
[docs]def sitechan2sachdr(sc): keymap = {'cmpaz': 'hang', 'cmpinc': 'vang'} hdr = _buildhdr(keymap, sc) return hdr
[docs]def affiliation2sachdr(af): keymap = {'network': 'net'} hdr = _buildhdr(keymap, af) return hdr
[docs]def instrument2sachdr(ins): return {}
[docs]def origin2sachdr(o): """ Accepts a fielded origin table record and produces a dictionary of corresponding sac header field/value pairs. """ keymap = {'evdp': 'depth', 'evla': 'lat', 'evlo': 'lon', 'kuser1': 'auth', 'nevid': 'evid', 'norid': 'orid', 'user0': 'mb'} hdr = _buildhdr(keymap, o) return hdr
[docs]def event2sachdr(evt): return {}
[docs]def assoc2sachdr(asc): return {}
[docs]def arrival2sachdr(ar): return {}
[docs]def wfdisc2sachdr(wf): pass
# functions that accept a table, return a dictionary of sac header values # the order of this dictionary matters KB2SAC = OrderedDict({'site': site2sachdr, 'sitechan': sitechan2sachdr, 'wfdisc': wfdisc2sachdr, 'affiliation': affiliation2sachdr, 'instrument': instrument2sachdr, 'origin': origin2sachdr, 'event': event2sachdr, 'assoc': assoc2sachdr, 'arrival': arrival2sachdr})
[docs]def tables2sachdr(tables): """Returns a sac header dictionary, including default values, from current table instances. SAC reference time is, in order of availability, origin time (origin.time), first sample time (wfdisc.time). """ hdr = SACDEFAULT.copy() for table, tabfun in KB2SAC.items(): hdr.update(tabfun(tables.get(table, None))) return hdr