Source code for eqcorrscan.utils.sac_util

"""
Part of the EQcorrscan package: tools to convert SAC headers to obspy event \
objects.

.. note:: This functionality is not supported for obspy versions below \
    1.0.0 as references times are not read in by SACIO, which are needed \
    for defining pick times.

:copyright:
    Calum Chamberlain, Chet Hopp.

:license:
    GNU Lesser General Public License, Version 3
    (https://www.gnu.org/copyleft/lesser.html)
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import warnings


def _version_check():
    import obspy
    if int(obspy.__version__.split('.')[0]) < 1:
        msg = 'Obspy version: ' + obspy.__version__ + ' does not have ' +\
            'correct reference time handling, please upgrade to ' +\
            'version > 1.0.0'
        raise NotImplementedError(msg)


[docs]def sactoevent(st, debug=0): """ Convert SAC headers (picks only) to obspy event class. Picks \ are taken from header values a, t[0-9]. :type st: obspy.core.stream.Stream :param st: Stream of waveforms including SAC headers. :type debug: int :pram debug: Debug level, larger number = more output. :returns: obspy.core.evebt.Event .. note:: This functionality is not supported for obspy versions below \ 1.0.0 as references times are not read in by SACIO, which are needed \ for defining pick times. .. note:: Takes the event origin information from the first trace in the \ stream - to ensure this works as you expect, please populate the \ evla, evlo, evdp and nzyear, nzjday, nzhour, nzmin, nzsec, nzmsec \ for all traces with the same values. >>> from obspy import read, UTCDateTime >>> st = read('eqcorrscan/tests/test_data/SAC/2014p611252/*') >>> event = sactoevent(st) >>> print(event.origins[0].time) 2014-08-15T03:55:21.057000Z >>> print(event.picks[0].phase_hint) S """ from obspy.core.event import Event, Origin, WaveformStreamID, Pick from obspy import Stream, UTCDateTime import numpy as np # Check the version _version_check() # Set the default SAC nan values float_nan = -12345.0 if not isinstance(st, Stream): msg = 'st must be a stream object, if you have just read in ' +\ 'multiple SAC files you may have a list of streams, convert ' +\ 'using: st = Stream([tr[0] for tr in st])' raise IOError(msg) # Note, don't do this internally as we need to ensure that we are not # taking traces from other events, the user should check this. for tr in st: if not tr.stats._format == 'SAC': msg = tr.stats.station + '.' + tr.stats.channel + ' is not SAC ' +\ 'formatted' raise IOError(msg) # Now we need to create an event! event = Event() event.origins.append(Origin()) # print(st[0].stats.sac.keys()) event.origins[0].time = UTCDateTime(year=st[0].stats.sac.nzyear, julday=st[0].stats.sac.nzjday, hour=st[0].stats.sac.nzhour, minute=st[0].stats.sac.nzmin, second=st[0].stats.sac.nzsec, microsecond=st[0].stats. sac.nzmsec * 1000) try: event.origins[0].latitude = st[0].stats.sac.evla event.origins[0].longitude = st[0].stats.sac.evlo event.origins[0].depth = st[0].stats.sac.evdp # Catch filled with 12345.0 as nan if event.origins[0].latitude == float_nan: event.origins[0].latitude = np.nan if event.origins[0].longitude == float_nan: event.origins[0].longitude = np.nan if event.origins[0].depth == float_nan: event.origins[0].depth = np.nan except KeyError: event.origins[0].latitude = np.nan event.origins[0].longitude = np.nan event.origins[0].depth = np.nan except AttributeError: event.origins[0].latitude = np.nan event.origins[0].longitude = np.nan event.origins[0].depth = np.nan # Add in the picks for tr in st: reference_time = UTCDateTime(year=tr.stats.sac.nzyear, julday=tr.stats.sac.nzjday, hour=tr.stats.sac.nzhour, minute=tr.stats.sac.nzmin, second=tr.stats.sac.nzsec, microsecond=tr.stats.sac.nzmsec * 1000) # Possible pick locations are in the t[0-9] slot for pick_number in range(10): pick_key = 't' + str(pick_number) phase_key = 'kt' + str(pick_number) try: if tr.stats.sac[pick_key] == float_nan: # in version 0.10.2 and before. rather than not include # the keys, the variables are filled with SAC nans. if debug > 1: msg = 'No pick in position ' + pick_key + \ ' for trace: ' + tr.stats.station + '.' + \ tr.stats.channel warnings.warn(msg) continue pick_time = reference_time + tr.stats.sac[pick_key] phase_hint = tr.stats.sac[phase_key].split()[0] except KeyError: if debug > 1: msg = 'No pick in position ' + pick_key + ' for trace: ' +\ tr.stats.station + '.' + tr.stats.channel warnings.warn(msg) continue if debug > 0: msg = 'Found pick in position ' + pick_key + ' for trace: ' +\ tr.stats.station + '.' + tr.stats.channel print(msg) waveform_id = WaveformStreamID(station_code=tr.stats.station, network_code=tr.stats.network, channel_code=tr.stats.channel) pick = Pick(waveform_id=waveform_id, phase_hint=phase_hint, time=pick_time) event.picks.append(pick) # Also check header slots 'a' and 'ka' try: if tr.stats.sac['a'] == float_nan: if debug > 1: msg = 'No pick in position ' + pick_key + \ ' for trace: ' + tr.stats.station + '.' + \ tr.stats.channel warnings.warn(msg) continue pick_time = reference_time + tr.stats.sac['a'] phase_hint = tr.stats.sac['ka'].split()[0] except KeyError: if debug > 1: msg = 'No pick in position ' + pick_key + ' for trace: ' +\ tr.stats.station + '.' + tr.stats.channel warnings.warn(msg) continue if debug > 0: msg = 'Found pick in position a for trace: ' +\ tr.stats.station + '.' + tr.stats.channel print(msg) waveform_id = WaveformStreamID(station_code=tr.stats.station, network_code=tr.stats.network, channel_code=tr.stats.channel) pick = Pick(waveform_id=waveform_id, phase_hint=phase_hint, time=pick_time) event.picks.append(pick) return event
if __name__ == '__main__': import doctest doctest.testmod()