Source code for obspy.io.sac.core

# -*- coding: utf-8 -*-
"""
SAC bindings to ObsPy core module.

:copyright:
    The ObsPy Development Team (devs@obspy.org) & C. J. Ammon & J. MacCarthy
:license:
    GNU Lesser General Public License, Version 3
    (https://www.gnu.org/copyleft/lesser.html)
"""
import io
import os
import struct

from obspy import Stream

from .sactrace import SACTrace


[docs]def _is_sac(filename): """ Checks whether a file is a SAC file or not. :param filename: SAC file to be checked. :type filename: str, open file, or file-like object :rtype: bool :return: ``True`` if a SAC file. .. rubric:: Example >>> from obspy.core.util import get_example_file >>> _is_sac(get_example_file('test.sac')) True >>> _is_sac(get_example_file('test.mseed')) False """ if isinstance(filename, io.BufferedIOBase): return _internal_is_sac(filename) elif isinstance(filename, (str, bytes)): with open(filename, "rb") as fh: return _internal_is_sac(fh) else: raise ValueError("Cannot open '%s'." % filename)
[docs]def _internal_is_sac(buf): """ Checks whether a file-like object contains a SAC file or not. :param buf: SAC file to be checked. :type buf: file-like object or open file :rtype: bool :return: ``True`` if a SAC file. """ starting_pos = buf.tell() try: # read delta (first header float) delta_bin = buf.read(4) delta = struct.unpack('<f', delta_bin)[0] # read nvhdr (70 header floats, 6 position in header integers) buf.seek(starting_pos + 4 * 70 + 4 * 6, 0) nvhdr_bin = buf.read(4) nvhdr = struct.unpack('<i', nvhdr_bin)[0] # read leven (70 header floats, 35 header integers, 0 position in # header bool) buf.seek(starting_pos + 4 * 70 + 4 * 35, 0) leven_bin = buf.read(4) leven = struct.unpack('<i', leven_bin)[0] # read lpspol (70 header floats, 35 header integers, 1 position in # header bool) buf.seek(starting_pos + 4 * 70 + 4 * 35 + 4 * 1, 0) lpspol_bin = buf.read(4) lpspol = struct.unpack('<i', lpspol_bin)[0] # read lovrok (70 header floats, 35 header integers, 2 position in # header bool) buf.seek(starting_pos + 4 * 70 + 4 * 35 + 4 * 2, 0) lovrok_bin = buf.read(4) lovrok = struct.unpack('<i', lovrok_bin)[0] # read lcalda (70 header floats, 35 header integers, 3 position in # header bool) buf.seek(starting_pos + 4 * 70 + 4 * 35 + 4 * 3, 0) lcalda_bin = buf.read(4) lcalda = struct.unpack('<i', lcalda_bin)[0] # check if file is big-endian if nvhdr < 0 or nvhdr > 20: nvhdr = struct.unpack('>i', nvhdr_bin)[0] delta = struct.unpack('>f', delta_bin)[0] leven = struct.unpack('>i', leven_bin)[0] lpspol = struct.unpack('>i', lpspol_bin)[0] lovrok = struct.unpack('>i', lovrok_bin)[0] lcalda = struct.unpack('>i', lcalda_bin)[0] # check again nvhdr if nvhdr < 1 or nvhdr > 20: return False if delta <= 0: return False if leven != 0 and leven != 1 and leven != -12345: return False if lpspol != 0 and lpspol != 1 and lpspol != -12345: return False if lovrok != 0 and lovrok != 1 and lovrok != -12345: return False if lcalda != 0 and lcalda != 1 and lcalda != -12345: return False except Exception: return False finally: # Reset buffer head position after reading. buf.seek(starting_pos, 0) return True
[docs]def _is_sac_xy(filename): """ Checks whether a file is alphanumeric SAC file or not. :param filename: Alphanumeric SAC file to be checked. :type filename: str, open file, or file-like object :rtype: bool :return: ``True`` if a alphanumeric SAC file. .. rubric:: Example >>> from obspy.core.util import get_example_file >>> _is_sac_xy(get_example_file('testxy.sac')) True >>> _is_sac_xy(get_example_file('test.sac')) False """ if isinstance(filename, io.BufferedIOBase): return _internal_is_sac_xy(filename) elif isinstance(filename, (str, bytes)): with open(filename, "rb") as fh: return _internal_is_sac_xy(fh) else: raise ValueError("Cannot open '%s'." % filename)
[docs]def _internal_is_sac_xy(buf): """ Checks whether a file is alphanumeric SAC file or not. :param buf: Alphanumeric SAC file to be checked. :type buf: file-like object or open file :rtype: bool :return: ``True`` if a alphanumeric SAC file. """ cur_pos = buf.tell() try: try: hdcards = [] # read in the header cards for _i in range(30): hdcards.append(buf.readline()) npts = int(hdcards[15].split()[-1]) # read in the seismogram seis = buf.read(-1).split() except Exception: return False # check that npts header value and seismogram length are consistent if npts != len(seis): return False return True finally: buf.seek(cur_pos, 0)
[docs]def _read_sac_xy(filename, headonly=False, debug_headers=False, **kwargs): # @UnusedVariable """ Reads an alphanumeric SAC file and returns an ObsPy Stream object. .. warning:: This function should NOT be called directly, it registers via the ObsPy :func:`~obspy.core.stream.read` function, call this instead. :param filename: Alphanumeric SAC file to be read. :type filename: str, open file, or file-like object :param headonly: If set to True, read only the head. This is most useful for scanning available data in huge (temporary) data sets. :type headonly: bool :param debug_headers: Extracts also the SAC headers ``'nzyear', 'nzjday', 'nzhour', 'nzmin', 'nzsec', 'nzmsec', 'delta', 'scale', 'npts', 'knetwk', 'kstnm', 'kcmpnm'`` which are usually directly mapped to the :class:`~obspy.core.stream.Stream` object if set to ``True``. Those values are not synchronized with the Stream object itself and won't be used during writing of a SAC file! Defaults to ``False``. :type debug_headers: bool :rtype: :class:`~obspy.core.stream.Stream` :return: A ObsPy Stream object. .. rubric:: Example >>> from obspy import read >>> st = read("/path/to/testxy.sac") """ if isinstance(filename, io.BufferedIOBase): return _internal_read_sac_xy(buf=filename, headonly=headonly, debug_headers=debug_headers, **kwargs) else: with open(filename, "rb") as fh: return _internal_read_sac_xy(buf=fh, headonly=headonly, debug_headers=debug_headers, **kwargs)
[docs]def _internal_read_sac_xy(buf, headonly=False, debug_headers=False, **kwargs): # @UnusedVariable """ Reads an alphanumeric SAC file and returns an ObsPy Stream object. .. warning:: This function should NOT be called directly, it registers via the ObsPy :func:`~obspy.core.stream.read` function, call this instead. :param buf: Alphanumeric SAC file to be read. :type buf: file or file-like object :param headonly: If set to True, read only the head. This is most useful for scanning available data in huge (temporary) data sets. :type headonly: bool :param debug_headers: Extracts also the SAC headers ``'nzyear', 'nzjday', 'nzhour', 'nzmin', 'nzsec', 'nzmsec', 'delta', 'scale', 'npts', 'knetwk', 'kstnm', 'kcmpnm'`` which are usually directly mapped to the :class:`~obspy.core.stream.Stream` object if set to ``True``. Those values are not synchronized with the Stream object itself and won't be used during writing of a SAC file! Defaults to ``False``. :type debug_headers: bool :rtype: :class:`~obspy.core.stream.Stream` :return: A ObsPy Stream object. .. rubric:: Example >>> from obspy import read >>> st = read("/path/to/testxy.sac") """ sac = SACTrace.read(buf, headonly=headonly, ascii=True) # assign all header entries to a new dictionary compatible with ObsPy tr = sac.to_obspy_trace(debug_headers=debug_headers) return Stream([tr])
[docs]def _write_sac_xy(stream, filename, **kwargs): # @UnusedVariable """ Writes a alphanumeric SAC file. .. warning:: This function should NOT be called directly, it registers via the the :meth:`~obspy.core.stream.Stream.write` method of an ObsPy :class:`~obspy.core.stream.Stream` object, call this instead. :param stream: The ObsPy Stream object to write. :type stream: :class:`~obspy.core.stream.Stream` :param filename: Name of file to write. In case an open file or file-like object is passed, this function only supports writing Stream objects containing a single Trace. This is a limitation of the SAC file format. An exception will be raised in case it's necessary. :type filename: str, open file, or file-like object .. rubric:: Example >>> from obspy import read >>> st = read() >>> st.write("testxy.sac", format="SACXY") #doctest: +SKIP """ # SAC can only store one Trace per file. if isinstance(filename, io.BufferedIOBase): if len(stream) > 1: raise ValueError("If writing to a file-like object in the SAC " "format, the Stream object can only contain " "one Trace.") _internal_write_sac_xy(stream[0], filename, **kwargs) return elif isinstance(filename, (str, bytes)): # Otherwise treat it as a filename # Translate the common (renamed) entries base, ext = os.path.splitext(filename) for i, trace in enumerate(stream): if len(stream) != 1: filename = "%s%02d%s" % (base, i + 1, ext) with open(filename, "wb") as fh: _internal_write_sac_xy(trace, fh, **kwargs) else: raise ValueError("Cannot open '%s'." % filename)
[docs]def _internal_write_sac_xy(trace, buf, **kwargs): # @UnusedVariable """ Writes a single trace to alphanumeric SAC file. .. warning:: This function should NOT be called directly, it registers via the the :meth:`~obspy.core.stream.Stream.write` method of an ObsPy :class:`~obspy.core.stream.Stream` object, call this instead. :param trace: The ObsPy Trace object to write. :type trace: :class:`~obspy.core.trace.Trace` :param buf: Object to write to. :type buf: file-like object """ sac = SACTrace.from_obspy_trace(trace, keep_sac_header=True) sac.write(buf, ascii=True, flush_headers=False)
[docs]def _read_sac(filename, headonly=False, debug_headers=False, fsize=True, **kwargs): # @UnusedVariable """ Reads an SAC file and returns an ObsPy Stream object. .. warning:: This function should NOT be called directly, it registers via the ObsPy :func:`~obspy.core.stream.read` function, call this instead. :param filename: SAC file to be read. :type filename: str, open file, or file-like object :param headonly: If set to True, read only the head. This is most useful for scanning available data in huge (temporary) data sets. :type headonly: bool :param debug_headers: Extracts also the SAC headers ``'nzyear', 'nzjday', 'nzhour', 'nzmin', 'nzsec', 'nzmsec', 'delta', 'scale', 'npts', 'knetwk', 'kstnm', 'kcmpnm'`` which are usually directly mapped to the :class:`~obspy.core.stream.Stream` object if set to ``True``. Those values are not synchronized with the Stream object itself and won't be used during writing of a SAC file! Defaults to ``False``. :type debug_headers: bool :param fsize: Check if file size is consistent with theoretical size from header. Defaults to ``True``. :type fsize: bool :rtype: :class:`~obspy.core.stream.Stream` :return: A ObsPy Stream object. .. rubric:: Example >>> from obspy import read >>> st = read("/path/to/test.sac") """ # Only byte buffers for binary SAC. if isinstance(filename, io.BufferedIOBase): return _internal_read_sac(buf=filename, headonly=headonly, debug_headers=debug_headers, fsize=fsize, **kwargs) elif isinstance(filename, (str, bytes)): with open(filename, "rb") as fh: return _internal_read_sac(buf=fh, headonly=headonly, debug_headers=debug_headers, fsize=fsize, **kwargs) else: raise ValueError("Cannot open '%s'." % filename)
[docs]def _internal_read_sac(buf, headonly=False, debug_headers=False, fsize=True, byteorder=None, **kwargs): # @UnusedVariable """ Reads an SAC file and returns an ObsPy Stream object. .. warning:: This function should NOT be called directly, it registers via the ObsPy :func:`~obspy.core.stream.read` function, call this instead. :param buf: SAC file to be read. :type buf: file or file-like object :param headonly: If set to True, read only the head. This is most useful for scanning available data in huge (temporary) data sets. :type headonly: bool :param debug_headers: Extracts also the SAC headers ``'nzyear', 'nzjday', 'nzhour', 'nzmin', 'nzsec', 'nzmsec', 'delta', 'scale', 'npts', 'knetwk', 'kstnm', 'kcmpnm'`` which are usually directly mapped to the :class:`~obspy.core.stream.Stream` object if set to ``True``. Those values are not synchronized with the Stream object itself and won't be used during writing of a SAC file! Defaults to ``False``. :type debug_headers: bool :param fsize: Check if file size is consistent with theoretical size from header. Defaults to ``True``. :type fsize: bool :param byteorder: If omitted or None, automatic byte-order checking is done, starting with native order. If byteorder is specified, {'little', 'big'}, and incorrect, a :class:`obspy.io.sac.util.SacIOError` is raised. Only valid for binary files. :type byteorder: str, optional :rtype: :class:`~obspy.core.stream.Stream` :return: A ObsPy Stream object. """ # Extract encoding flag (default to ascii) encoding_str = kwargs.get('encoding', 'ascii') # read SAC file sac = SACTrace.read(buf, headonly=headonly, ascii=False, byteorder=byteorder, checksize=fsize, encoding=encoding_str) # assign all header entries to a new dictionary compatible with an ObsPy tr = sac.to_obspy_trace(debug_headers=debug_headers, encoding=encoding_str) return Stream([tr])
[docs]def _write_sac(stream, filename, byteorder="<", **kwargs): # @UnusedVariable """ Writes a SAC file. .. warning:: This function should NOT be called directly, it registers via the the :meth:`~obspy.core.stream.Stream.write` method of an ObsPy :class:`~obspy.core.stream.Stream` object, call this instead. :param stream: The ObsPy Stream object to write. :type stream: :class:`~obspy.core.stream.Stream` :param filename: Name of file to write. In case an open file or file-like object is passed, this function only supports writing Stream objects containing a single Trace. This is a limitation of the SAC file format. An exception will be raised in case it's necessary. :type filename: str, open file, or file-like object :param byteorder: Must be either ``0`` or ``'<'`` for LSBF or little-endian, ``1`` or ``'>'`` for MSBF or big-endian. Defaults to little endian. :type byteorder: int or str .. rubric:: Example >>> from obspy import read >>> st = read() >>> st.write("test.sac", format="SAC") #doctest: +SKIP """ # Bytes buffer are ok, but only if the Stream object contains only one # Trace. SAC can only store one Trace per file. if isinstance(filename, io.BufferedIOBase): if len(stream) > 1: raise ValueError("If writing to a file-like object in the SAC " "format, the Stream object can only contain " "one Trace.") _internal_write_sac(stream[0], filename, byteorder=byteorder, **kwargs) return elif isinstance(filename, (str, bytes)): # Otherwise treat it as a filename # Translate the common (renamed) entries base, ext = os.path.splitext(filename) for i, trace in enumerate(stream): if len(stream) != 1: filename = "%s%02d%s" % (base, i + 1, ext) with open(filename, "wb") as fh: _internal_write_sac(trace, fh, byteorder=byteorder, **kwargs) else: raise ValueError("Cannot open '%s'." % filename)
[docs]def _internal_write_sac(trace, buf, byteorder="<", keep_sac_header=True, **kwargs): """ Writes a single trace to an open file or file-like object .. warning:: This function should NOT be called directly, it registers via the the :meth:`~obspy.core.stream.Stream.write` method of an ObsPy :class:`~obspy.core.stream.Stream` object, call this instead. :param trace: The ObsPy Trace object to write. :type trace: :class:`~obspy.core.trace.Trace` :param buf: Object to write to. :type buf: open file or file-like object :param byteorder: Must be either ``0`` or ``'<'`` for LSBF or little-endian, ``1`` or ``'>'`` for MSBF or big-endian. Defaults to little endian. :type byteorder: int or str :param keep_sac_header: Whether to merge the ``Stats`` header with an existing ``Stats.sac`` SAC header, if one exists. Defaults to ``True``. See :func:`~obspy.io.sac.util.obspy_to_sac_header` for more. :type keep_sac_header: bool """ if byteorder in ("<", 0, "0"): byteorder = 'little' elif byteorder in (">", 1, "1"): byteorder = 'big' else: msg = "Invalid byte order. It must be either '<', '>', 0 or 1" raise ValueError(msg) sac = SACTrace.from_obspy_trace(trace, keep_sac_header) sac.write(buf, ascii=False, byteorder=byteorder)