Repository: obspy/obspy

Issue:
SAC reading problem
Hi Obspy developer,

I tried to use the Obspy 1.0.3 to read a SAC file but it throw an error at me as shown below:

---------------------------------------------------------------------------
```
UnicodeEncodeError                        Traceback (most recent call last)
<ipython-input-6-91703a32c963> in <module>()
----> 1 st = obspy.read('1100-5001.sac.ds.bp.sml.2152.01.sml', debug_headers=True)

<decorator-gen-144> in read(pathname_or_url, format, headonly, starttime, endtime, nearest_sample, dtype, apply_calib, **kwargs)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/util/decorator.py in _map_example_filename(func, *args, **kwargs)
    297                         except IOError:
    298                             pass
--> 299         return func(*args, **kwargs)
    300     return _map_example_filename
    301

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/stream.py in read(pathname_or_url, format, headonly, starttime, endtime, nearest_sample, dtype, apply_calib, **kwargs)
    229         pathname = pathname_or_url
    230         for file in sorted(glob(pathname)):
--> 231             st.extend(_read(file, format, headonly, **kwargs).traces)
    232         if len(st) == 0:
    233             # try to give more specific information why the stream is empty

<decorator-gen-145> in _read(filename, format, headonly, **kwargs)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/util/decorator.py in uncompress_file(func, filename, *args, **kwargs)
    207     else:
    208         # no compressions
--> 209         result = func(filename, *args, **kwargs)
    210     return result
    211

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/stream.py in _read(filename, format, headonly, **kwargs)
    271     """
    272     stream, format = _read_from_plugin('waveform', filename, format=format,
--> 273                                        headonly=headonly, **kwargs)
    274     # set _format identifier for each element
    275     for trace in stream:

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/util/base.py in _read_from_plugin(plugin_type, filename, format, **kwargs)
    404         raise TypeError(msg % (format_ep.name, ', '.join(eps)))
    405     # read
--> 406     list_obj = read_format(filename, **kwargs)
    407     return list_obj, format_ep.name
    408

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/io/sac/core.py in _read_sac(filename, headonly, debug_headers, fsize, **kwargs)
    349             return _internal_read_sac(buf=fh, headonly=headonly,
    350                                       debug_headers=debug_headers, fsize=fsize,
--> 351                                       **kwargs)
    352     else:
    353         raise ValueError("Cannot open '%s'." % filename)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/io/sac/core.py in _internal_read_sac(buf, headonly, debug_headers, fsize, **kwargs)
    382     """
    383     # read SAC file
--> 384     sac = SACTrace.read(buf, headonly=headonly, ascii=False, checksize=fsize)
    385     # assign all header entries to a new dictionary compatible with an ObsPy
    386     tr = sac.to_obspy_trace(debug_headers=debug_headers)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/io/sac/sactrace.py in read(cls, source, headonly, ascii, byteorder, checksize, debug_strings)
   1162                 if val.startswith(native_str('-12345')):
   1163                     val = HD.SNULL
-> 1164                 hs[i] = val
   1165
   1166         sac = cls._from_arrays(hf, hi, hs, data)

UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-7: ordinal not in range(128)
```

This issue has been a while since 1.0.x, so I tried to do it myself. Somehow, when I took your code and write something similar, it works properly. I am not exactly sure what happened to the Obspy version, but I will post my solution here in case anyone has the same issue as I had for a long time.

Here is my code:
```Python
"""Read SAC file into Numpy array"""
import numpy as np

# Read SAC
from future.utils import native_str
from . import header as HD
import os
import sys


def read(file_name, byteorder=None, checksize=False):
    '''Customed version of obspy sac reader

    '''

    # Check byte order
    is_byteorder_specified = byteorder is not None
    if not is_byteorder_specified:
        byteorder = sys.byteorder

    if byteorder == 'little':
        endian_str = '<'
    elif byteorder == 'big':
        endian_str = '>'
    else:
        raise ValueError("Unrecognized byteorder. Use {'little', 'big'}")

    with open(file_name, 'rb') as fh:
        # --------------------------------------------------------------
        # READ HEADER
        # The sac header has 70 floats, 40 integers, then 192 bytes
        #    in strings. Store them in array (and convert the char to a
        #    list). That's a total of 632 bytes.
        # --------------------------------------------------------------

        hf = np.array(memoryview(fh.read(4 * 70))
                      ).view(native_str(endian_str + 'f4')).copy()
        hi = np.array(memoryview(fh.read(4 * 40))
                      ).view(native_str(endian_str + 'i4')).copy()
        hs = np.array(memoryview(fh.read(24 * 8))
                      ).view(native_str('|S8')).copy()

        if not is_valid_byteorder(hi):
            if is_byteorder_specified:
                # specified but not valid. you dun messed up.
                raise ValueError("Incorrect byteorder {}".format(byteorder))
            else:
                # not valid, but not specified.
                # swap the dtype interpretation (dtype.byteorder), but keep the
                # bytes, so the arrays in memory reflect the bytes on disk
                hf = hf.newbyteorder('S')
                hi = hi.newbyteorder('S')

        # check header lengths
        if len(hf) != 70 or len(hi) != 40 or len(hs) != 24:
            hf = hi = hs = None
            raise ValueError("Cannot read all header values")

        npts = hi[HD.INTHDRS.index('npts')]

        # check file size
        if checksize:
            cur_pos = fh.tell()
            fh.seek(0, os.SEEK_END)
            length = fh.tell()
            fh.seek(cur_pos, os.SEEK_SET)
            th_length = (632 + 4 * int(npts))
            if length != th_length:
                msg = "Actual and theoretical file size are inconsistent.\n" \
                      "Actual/Theoretical: {}/{}\n" \
                      "Check that headers are consistent with time series."
                raise ValueError(msg.format(length, th_length))

        # --------------------------------------------------------------
        # READ DATA
        # --------------------------------------------------------------
        data = np.array(memoryview(fh.read(int(npts) * 4))
                        ).view(native_str(endian_str + 'f4')).copy()

        if len(data) != npts:
            fh.close()
            print("Cannot read all data points")

    fh.close()

    return data


def is_valid_byteorder(hi):
    nvhdr = hi[HD.INTHDRS.index('nvhdr')]
    return (0 < nvhdr < 20)

if __name__ == '__main__':
    pass
```

It requires this header.py which I took directly from Obspy:

```Python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
from future.builtins import *  # NOQA

MODULE_DOCSTRING = """
SAC header specification, including documentation.

Header names, order, types, and nulls, as well as allowed enumerated values,
are specified here.  Header name strings, and their array order are contained
in separate float, int, and string tuples.  Enumerated values, and their
allowed string and integer values, are in dictionaries.  Header value
documentation is in a dictionary, for reuse throughout the package.

"""

# header documentation is large and used in several places, so we just write
# it once here and distributed it as needed.
DOC = {'npts': 'N    Number of points per data component. [required]',
       'nvhdr': '''N    Header version number. Current value is the integer 6.
                  Older version data (NVHDR < 6) are automatically updated
                  when read into sac. [required]''',
       'b': 'F    Beginning value of the independent variable. [required]',
       'e': 'F    Ending value of the independent variable. [required]',
       'iftype': '''I    Type of file [required]:

                  * ITIME {Time series file}
                  * IRLIM {Spectral file---real and imaginary}
                  * IAMPH {Spectral file---amplitude and phase}
                  * IXY {General x versus y data}
                  * IXYZ {General XYZ (3-D) file}''',
       'leven': 'L    TRUE if data is evenly spaced. [required]',
       'delta': 'F    Increment between evenly spaced samples (nominal value).'
                ' [required]',
       'odelta': 'F    Observed increment if different from nominal value.',
       'idep': '''I    Type of dependent variable:

                  * IUNKN (Unknown)
                  * IDISP (Displacement in nm)
                  * IVEL (Velocity in nm/sec)
                  * IVOLTS (Velocity in volts)
                  * IACC (Acceleration in nm/sec/sec)''',
       'scale': 'F    Multiplying scale factor for dependent variable '
                '[not currently used]',
       'depmin': 'F    Minimum value of dependent variable.',
       'depmax': 'F    Maximum value of dependent variable.',
       'depmen': 'F    Mean value of dependent variable.',
       'nzyear': 'N    GMT year corresponding to reference time in file.',
       'nzjday': 'N    GMT julian day.',
       'nzhour': 'N    GMT hour.',
       'nzmin': 'N    GMT minute.',
       'nzsec': 'N    GMT second.',
       'nzmsec': 'N    GMT millisecond.',
       'iztype': '''I    Reference time equivalence:

                  * IUNKN (5): Unknown
                  * IB (9): Begin time
                  * IDAY (10): Midnight of reference GMT day
                  * IO (11): Event origin time
                  * IA (12): First arrival time
                  * ITn (13-22): User defined time pick n, n=0,9''',
       'o': 'F    Event origin time (seconds relative to reference time.)',
       'ko': 'K    Event origin time identification.',
       'a': 'F    First arrival time (seconds relative to reference time.)',
       'ka': 'K    First arrival time identification.',
       'f': 'F    Fini or end of event time '
            '(seconds relative to reference time.)',
       'kf': 'F    Fini or end of event time identification.',
       't0': 'F    User defined time (seconds picks or markers relative to '
             'reference time).',
       't1': 'F    User defined time (seconds picks or markers relative to '
             'reference time).',
       't2': 'F    User defined time (seconds picks or markers relative to '
             'reference time).',
       't3': 'F    User defined time (seconds picks or markers relative to '
             'reference time).',
       't4': 'F    User defined time (seconds picks or markers relative to '
             'reference time).',
       't5': 'F    User defined time (seconds picks or markers relative to '
             'reference time).',
       't6': 'F    User defined time (seconds picks or markers relative to '
             'reference time).',
       't7': 'F    User defined time (seconds picks or markers relative to '
             'reference time).',
       't8': 'F    User defined time (seconds picks or markers relative to '
             'reference time).',
       't9': 'F    User defined time (seconds picks or markers relative to '
             'reference time).',
       'kt0': 'F    User defined time pick identification.',
       'kt1': 'F    User defined time pick identification.',
       'kt2': 'F    User defined time pick identification.',
       'kt3': 'F    User defined time pick identification.',
       'kt4': 'F    User defined time pick identification.',
       'kt5': 'F    User defined time pick identification.',
       'kt6': 'F    User defined time pick identification.',
       'kt7': 'F    User defined time pick identification.',
       'kt8': 'F    User defined time pick identification.',
       'kt9': 'F    User defined time pick identification.',
       'kinst': 'K    Generic name of recording instrument',
       'iinst': 'I    Type of recording instrument. [currently not used]',
       'knetwk': 'K    Name of seismic network.',
       'kstnm': 'K    Station name.',
       'istreg': 'I    Station geographic region. [not currently used]',
       'stla': 'F    Station latitude (degrees, north positive)',
       'stlo': 'F    Station longitude (degrees, east positive).',
       'stel': 'F    Station elevation (meters). [not currently used]',
       'stdp': 'F    Station depth below surface (meters). '
               '[not currently used]',
       'cmpaz': 'F    Component azimuth (degrees, clockwise from north).',
       'cmpinc': 'F    Component incident angle (degrees, from vertical).',
       'kcmpnm': 'K    Component name.',
       'lpspol': 'L    TRUE if station components have a positive polarity '
                 '(left-hand rule).',
       'kevnm': 'K    Event name.',
       'ievreg': 'I    Event geographic region. [not currently used]',
       'evla': 'F    Event latitude (degrees north positive).',
       'evlo': 'F    Event longitude (degrees east positive).',
       'evel': 'F    Event elevation (meters). [not currently used]',
       'evdp': 'F    Event depth below surface (meters). [not currently used]',
       'mag': 'F    Event magnitude.',
       'imagtyp': '''I    Magnitude type:

                  * IMB (52): Bodywave Magnitude
                  * IMS (53): Surfacewave Magnitude
                  * IML (54): Local Magnitude
                  * IMW (55): Moment Magnitude
                  * IMD (56): Duration Magnitude
                  * IMX (57): User Defined Magnitude''',
       'imagsrc': '''I    Source of magnitude information:

                  * INEIC (National Earthquake Information Center)
                  * IPDE (Preliminary Determination of Epicenter)
                  * IISC (International Seismological Centre)
                  * IREB (Reviewed Event Bulletin)
                  * IUSGS (US Geological Survey)
                  * IBRK (UC Berkeley)
                  * ICALTECH (California Institute of Technology)
                  * ILLNL (Lawrence Livermore National Laboratory)
                  * IEVLOC (Event Location (computer program) )
                  * IJSOP (Joint Seismic Observation Program)
                  * IUSER (The individual using SAC2000)
                  * IUNKNOWN (unknown)''',
       'ievtyp': '''I    Type of event:

                  * IUNKN (Unknown)
                  * INUCL (Nuclear event)
                  * IPREN (Nuclear pre-shot event)
                  * IPOSTN (Nuclear post-shot event)
                  * IQUAKE (Earthquake)
                  * IPREQ (Foreshock)
                  * IPOSTQ (Aftershock)
                  * ICHEM (Chemical explosion)
                  * IQB (Quarry or mine blast confirmed by quarry)
                  * IQB1 (Quarry/mine blast with designed shot
                    info-ripple fired)
                  * IQB2 (Quarry/mine blast with observed shot
                    info-ripple fired)
                  * IQMT (Quarry/mining-induced events:
                    tremors and rockbursts)
                  * IEQ (Earthquake)
                  * IEQ1 (Earthquakes in a swarm or aftershock
                    sequence)
                  * IEQ2 (Felt earthquake)
                  * IME (Marine explosion)
                  * IEX (Other explosion)
                  * INU (Nuclear explosion)
                  * INC (Nuclear cavity collapse)
                  * IO_ (Other source of known origin)
                  * IR (Regional event of unknown origin)
                  * IT (Teleseismic event of unknown origin)
                  * IU (Undetermined or conflicting information)
                  * IOTHER (Other)''',
       'nevid': 'N    Event ID (CSS 3.0)',
       'norid': 'N    Origin ID (CSS 3.0)',
       'nwfid': 'N    Waveform ID (CSS 3.0)',
       'khole': 'k    Hole identification if nuclear event.',
       'dist': 'F    Station to event distance (km).',
       'az': 'F    Event to station azimuth (degrees).',
       'baz': 'F    Station to event azimuth (degrees).',
       'gcarc': 'F    Station to event great circle arc length (degrees).',
       'lcalda': 'L    TRUE if DIST AZ BAZ and GCARC are to be calculated '
                 'from st event coordinates.',
       'iqual': '''I    Quality of data [not currently used]:

                  * IGOOD (Good data)
                  * IGLCH (Glitches)
                  * IDROP (Dropouts)
                  * ILOWSN (Low signal to noise ratio)
                  * IOTHER (Other)''',
       'isynth': '''I    Synthetic data flag [not currently used]:

                  * IRLDTA (Real data)
                  * ????? (Flags for various synthetic seismogram codes)''',
       'user0': 'F    User defined variable storage area 0.',
       'user1': 'F    User defined variable storage area 1.',
       'user2': 'F    User defined variable storage area 2.',
       'user3': 'F    User defined variable storage area 3.',
       'user4': 'F    User defined variable storage area 4.',
       'user5': 'F    User defined variable storage area 5.',
       'user6': 'F    User defined variable storage area 6.',
       'user7': 'F    User defined variable storage area 7.',
       'user8': 'F    User defined variable storage area 8.',
       'user9': 'F    User defined variable storage area 9.',
       'kuser0': 'K    User defined variable storage area 0.',
       'kuser1': 'K    User defined variable storage area 1.',
       'kuser2': 'K    User defined variable storage area 2.',
       'lovrok': 'L    TRUE if it is okay to overwrite this file on disk.'}

# because readable
_hdritems = sorted(DOC.items())
_hdrfmt = "{:10.10s} = {}"
HDR_DESC = '\n'.join([_hdrfmt.format(_hdr, _doc) for _hdr, _doc in _hdritems])

HEADER_DOCSTRING = """
============ ==== ===============================================
Field Name   Type Description
============ ==== ===============================================
""" + HDR_DESC + \
    "\n============ ==== ==============================================="

# Module documentation string
__doc__ = MODULE_DOCSTRING + HEADER_DOCSTRING

# ------------ NULL VALUES ----------------------------------------------------
FNULL = -12345.0
INULL = -12345
SNULL = '-12345  '

# ------------ HEADER NAMES, TYPES, ARRAY POSITIONS ---------------------------
# these are useful b/c they can be used forwards or backwards, like:
# FLOADHDRS.index('az') is 40, and FLOATHDRS[40] is 'az'.
FLOATHDRS = ('delta', 'depmin', 'depmax', 'scale', 'odelta', 'b', 'e', 'o',
             'a', 'internal0', 't0', 't1', 't2', 't3', 't4', 't5', 't6', 't7',
             't8', 't9', 'f', 'resp0', 'resp1', 'resp2', 'resp3', 'resp4',
             'resp5', 'resp6', 'resp7', 'resp8', 'resp9', 'stla', 'stlo',
             'stel', 'stdp', 'evla', 'evlo', 'evel', 'evdp', 'mag', 'user0',
             'user1', 'user2', 'user3', 'user4', 'user5', 'user6', 'user7',
             'user8', 'user9', 'dist', 'az', 'baz', 'gcarc', 'internal1',
             'internal2', 'depmen', 'cmpaz', 'cmpinc', 'xminimum', 'xmaximum',
             'yminimum', 'ymaximum', 'unused6', 'unused7', 'unused8',
             'unused9', 'unused10', 'unused11', 'unused12')

INTHDRS = ('nzyear', 'nzjday', 'nzhour', 'nzmin', 'nzsec', 'nzmsec', 'nvhdr',
           'norid', 'nevid', 'npts', 'internal3', 'nwfid', 'nxsize', 'nysize',
           'unused13', 'iftype', 'idep', 'iztype', 'unused14', 'iinst',
           'istreg', 'ievreg', 'ievtyp', 'iqual', 'isynth', 'imagtyp',
           'imagsrc', 'unused15', 'unused16', 'unused17', 'unused18',
           'unused19', 'unused20', 'unused21', 'unused22', 'leven', 'lpspol',
           'lovrok', 'lcalda', 'unused23')

STRHDRS = ('kstnm', 'kevnm', 'kevnm2', 'khole', 'ko', 'ka', 'kt0', 'kt1',
           'kt2', 'kt3', 'kt4', 'kt5', 'kt6', 'kt7', 'kt8', 'kt9', 'kf',
           'kuser0', 'kuser1', 'kuser2', 'kcmpnm', 'knetwk', 'kdatrd', 'kinst')

"""
NOTE:

kevnm also has a kevnm2 b/c it takes two array spaces.
'kevnm' lookups must be caught and handled differently.  This happens in the
SACTrace string property getters/setters, .io.dict_to_header_arrays
and .arrayio.header_arrays_to_dict.
"""
# NOTE: using namedtuples for header arrays sounds great, but they're immutable

# TODO: make a dict converter between {'<', '>', '='} and {'little', 'big'}

# ------------ ENUMERATED VALUES ----------------------------------------------
# These are stored in the header as integers.
# Their names and values are given in the mapping below.
# Some (many) are not used.
# TODO: this is ugly; rename things a bit
ENUM_VALS = {'itime': 1, 'irlim': 2, 'iamph': 3, 'ixy': 4, 'iunkn': 5,
             'idisp': 6, 'ivel': 7, 'iacc': 8, 'ib': 9, 'iday': 10, 'io': 11,
             'ia': 12, 'it0': 13, 'it1': 14, 'it2': 15, 'it3': 16, 'it4': 17,
             'it5': 18, 'it6': 19, 'it7': 20, 'it8': 21, 'it9': 22,
             'iradnv': 23, 'itannv': 24, 'iradev': 25, 'itanev': 26,
             'inorth': 27, 'ieast': 28, 'ihorza': 29, 'idown': 30, 'iup': 31,
             'illlbb': 32, 'iwwsn1': 33, 'iwwsn2': 34, 'ihglp': 35, 'isro': 36,
             'inucl': 37, 'ipren': 38, 'ipostn': 39, 'iquake': 40, 'ipreq': 41,
             'ipostq': 42, 'ichem': 43, 'iother': 44, 'igood': 45, 'iglch': 46,
             'idrop': 47, 'ilowsn': 48, 'irldta': 49, 'ivolts': 50, 'imb': 52,
             'ims': 53, 'iml': 54, 'imw': 55, 'imd': 56, 'imx': 57,
             'ineic': 58, 'ipdeq': 59, 'ipdew': 60, 'ipde': 61, 'iisc': 62,
             'ireb': 63, 'iusgs': 64, 'ibrk': 65, 'icaltech': 66, 'illnl': 67,
             'ievloc': 68, 'ijsop': 69, 'iuser': 70, 'iunknown': 71, 'iqb': 72,
             'iqb1': 73, 'iqb2': 74, 'iqbx': 75, 'iqmt': 76, 'ieq': 77,
             'ieq1': 78, 'ieq2': 79, 'ime': 80, 'iex': 81, 'inu': 82,
             'inc': 83, 'io_': 84, 'il': 85, 'ir': 86, 'it': 87, 'iu': 88,
             'ieq3': 89, 'ieq0': 90, 'iex0': 91, 'iqc': 92, 'iqb0': 93,
             'igey': 94, 'ilit': 95, 'imet': 96, 'iodor': 97, 'ios': 103}

# reverse look-up: you have the number, want the string
ENUM_NAMES = dict((v, k) for k, v in ENUM_VALS.items())


# accepted values, by header
ACCEPTED_VALS = {'iftype': ['itime', 'irlim', 'iamph', 'ixy'],
                 'idep': ['iunkn', 'idisp', 'ivel', 'ivolts', 'iacc'],
                 'iztype': ['iunkn', 'ib', 'iday', 'io', 'ia', 'it0', 'it1',
                            'it2', 'it3', 'it4', 'it5', 'it6', 'it7', 'it8',
                            'it9'],
                 'imagtyp': ['imb', 'ims', 'iml', 'imw', 'imd', 'imx'],
                 'imagsrc': ['ineic', 'ipde', 'iisc', 'ireb', 'iusgs', 'ipdeq',
                             'ibrk', 'icaltech', 'illnl', 'ievloc', 'ijsop',
                             'iuser', 'iunknown'],
                 'ievtyp': ['iunkn', 'inucl', 'ipren', 'ipostn', 'iquake',
                            'ipreq', 'ipostq', 'ichem', 'iqb', 'iqb1', 'iqb2',
                            'iqbx', 'iqmt', 'ieq', 'ieq1', 'ieq2', 'ime',
                            'iex', 'inu', 'inc', 'io_', 'il', 'ir', 'it', 'iu',
                            'iother'],
                 'iqual': ['igood', 'iglch', 'idrop', 'ilowsn', 'iother'],
                 'isynth': ['irldta']}

ACCEPTED_INT = ACCEPTED_VALS.copy()
for _hdr in ACCEPTED_INT:
    ACCEPTED_INT[_hdr] = [ENUM_VALS[_ival] for _ival in ACCEPTED_VALS[_hdr]]
```

Hope it may help.


-Lijun Zhu


Discussion:
can you also provide the sac file you try to read in the first example?
Sure. Basically, any SAC file I tried on my compute won't work. But the one that I used for create this issue is attached here. 


[1100-5001.sac.ds.bp.sml.2152.01.sml.zip](https://github.com/obspy/obspy/files/964821/1100-5001.sac.ds.bp.sml.2152.01.sml.zip)

By the way, my python environment is setup like this:

```
conda info
```

```
Current conda install:

               platform : osx-64
          conda version : 4.3.16
       conda is private : False
      conda-env version : 4.3.16
    conda-build version : not installed
         python version : 3.6.1.final.0
       requests version : 2.13.0
       root environment : /Users/lijun/anaconda3  (writable)
    default environment : /Users/lijun/anaconda3
       envs directories : /Users/lijun/anaconda3/envs
                          /Users/lijun/.conda/envs
          package cache : /Users/lijun/anaconda3/pkgs
                          /Users/lijun/.conda/pkgs
           channel URLs : https://conda.anaconda.org/obspy/osx-64
                          https://conda.anaconda.org/obspy/noarch
                          https://repo.continuum.io/pkgs/free/osx-64
                          https://repo.continuum.io/pkgs/free/noarch
                          https://repo.continuum.io/pkgs/r/osx-64
                          https://repo.continuum.io/pkgs/r/noarch
                          https://repo.continuum.io/pkgs/pro/osx-64
                          https://repo.continuum.io/pkgs/pro/noarch
            config file : /Users/lijun/.condarc
           offline mode : False
             user-agent : conda/4.3.16 requests/2.13.0 CPython/3.6.1 Darwin/16.5.0 OSX/10.12.4
                UID:GID : 501:20
```

And my packages are
```
conda list
```

```
# packages in environment at /Users/lijun/anaconda3:
#
_license                  1.1                      py36_1
alabaster                 0.7.10                   py36_0
anaconda                  custom                   py36_0
anaconda-client           1.6.3                    py36_0
anaconda-navigator        1.5.2                    py36_0
anaconda-project          0.4.1                    py36_0
appdirs                   1.4.3                     <pip>
appnope                   0.1.0                    py36_0
appscript                 1.0.1                    py36_0
args                      0.1.0                     <pip>
astroid                   1.4.9                    py36_0
astropy                   1.3.2               np112py36_0
babel                     2.4.0                    py36_0
backports                 1.0                      py36_0
beautifulsoup4            4.5.3                    py36_0
bitarray                  0.8.1                    py36_0
blaze                     0.10.1                   py36_0
bleach                    1.5.0                    py36_0
bokeh                     0.12.5                   py36_0
boto                      2.46.1                   py36_0
bottleneck                1.2.0               np112py36_0
cffi                      1.9.1                    py36_0
chardet                   3.0.2                    py36_0
chest                     0.2.3                    py36_0
click                     6.7                      py36_0
clint                     0.5.1                     <pip>
cloudpickle               0.2.2                    py36_0
clyent                    1.2.2                    py36_0
colorama                  0.3.7                    py36_0
conda                     4.3.16                   py36_0
conda-env                 2.6.0                         0
configobj                 5.0.6                    py36_0
contextlib2               0.5.4                    py36_0
cryptography              1.7.1                    py36_0
curl                      7.52.1                        0
cycler                    0.10.0                   py36_0
cython                    0.25.2                   py36_0
cytoolz                   0.8.2                    py36_0
dask                      0.14.1                   py36_0
datashape                 0.5.4                    py36_0
decorator                 4.0.11                   py36_0
dill                      0.2.6                    py36_0
docutils                  0.13.1                   py36_0
entrypoints               0.2.2                    py36_1
et_xmlfile                1.0.1                    py36_0
fastcache                 1.0.2                    py36_1
flask                     0.12.1                   py36_0
flask-cors                3.0.2                    py36_0
freetype                  2.5.5                         2
future                    0.16.0                   py36_1
get_terminal_size         1.0.0                    py36_0
gevent                    1.2.1                    py36_0
greenlet                  0.4.12                   py36_0
h5py                      2.7.0               np112py36_0
hdf5                      1.8.17                        1
heapdict                  1.0.0                    py36_1
html5lib                  0.999                    py36_0
icu                       54.1                          0
idna                      2.2                      py36_0
imagesize                 0.7.1                    py36_0
ipykernel                 4.6.1                    py36_0
ipython                   6.0.0                    py36_0
ipython_genutils          0.2.0                    py36_0
ipywidgets                6.0.0                    py36_0
isort                     4.2.5                    py36_0
itsdangerous              0.24                     py36_0
jbig                      2.1                           0
jdcal                     1.3                      py36_0
jedi                      0.9.0                    py36_1
jinja2                    2.9.6                    py36_0
jpeg                      9b                            0
jsonschema                2.6.0                    py36_0
jupyter                   1.0.0                    py36_3
jupyter_client            5.0.1                    py36_0
jupyter_console           5.1.0                    py36_0
jupyter_core              4.3.0                    py36_0
lazy-object-proxy         1.2.2                    py36_0
libiconv                  1.14                          0
libpng                    1.6.27                        0
libtiff                   4.0.6                         3
libxml2                   2.9.4                         0
libxslt                   1.1.29                        0
llvmlite                  0.17.0                   py36_0
locket                    0.2.0                    py36_1
lxml                      3.7.3                    py36_0
markupsafe                0.23                     py36_2
matplotlib                2.0.0               np112py36_0
mistune                   0.7.4                    py36_0
mkl                       2017.0.1                      0
mkl-service               1.1.2                    py36_3
mpmath                    0.19                     py36_1
multipledispatch          0.4.9                    py36_0
nbconvert                 5.1.1                    py36_0
nbformat                  4.3.0                    py36_0
networkx                  1.11                     py36_0
nltk                      3.2.2                    py36_0
nose                      1.3.7                    py36_1
notebook                  5.0.0                    py36_0
numba                     0.32.0              np112py36_0
numexpr                   2.6.2               np112py36_0
numpy                     1.12.1                   py36_0
numpy                     1.12.1                    <pip>
numpydoc                  0.6.0                    py36_0
obspy                     1.0.3.post0+1068.g04848ed0fc.obspy.master           <pip>
odo                       0.5.0                    py36_1
olefile                   0.44                     py36_0
openpyxl                  2.4.1                    py36_0
openssl                   1.0.2k                        1
packaging                 16.8                      <pip>
pandas                    0.19.2              np112py36_1
pandocfilters             1.4.1                    py36_0
partd                     0.3.7                    py36_0
path.py                   10.3.1                   py36_0
pathlib2                  2.2.1                    py36_0
patsy                     0.4.1                    py36_0
pep8                      1.7.0                    py36_0
pexpect                   4.2.1                    py36_0
pickleshare               0.7.4                    py36_0
pillow                    4.1.0                    py36_0
pip                       9.0.1                    py36_1
pkginfo                   1.4.1                     <pip>
ply                       3.10                     py36_0
prompt_toolkit            1.0.14                   py36_0
protobuf                  3.2.0                     <pip>
psutil                    5.2.2                    py36_0
ptyprocess                0.5.1                    py36_0
py                        1.4.33                   py36_0
pyasn1                    0.2.3                    py36_0
pycosat                   0.6.2                    py36_0
pycparser                 2.17                     py36_0
pycrypto                  2.6.1                    py36_4
pycurl                    7.43.0                   py36_2
pyflakes                  1.5.0                    py36_0
pygments                  2.2.0                    py36_0
pylint                    1.6.4                    py36_1
pyopenssl                 16.2.0                   py36_0
pyparsing                 2.2.0                     <pip>
pyparsing                 2.1.4                    py36_0
pyqt                      5.6.0                    py36_2
pyqtgraph                 0.10.0+gdd5a8bf           <pip>
pytables                  3.3.0               np112py36_0
pytest                    3.0.7                    py36_0
python                    3.6.1                         0
python-dateutil           2.6.0                    py36_0
python.app                1.2                      py36_4
pytz                      2017.2                   py36_0
pywavelets                0.5.2               np112py36_0
pyyaml                    3.12                     py36_0
pyzmq                     16.0.2                   py36_0
qt                        5.6.2                         0
qtawesome                 0.4.4                    py36_0
qtconsole                 4.3.0                    py36_0
qtpy                      1.2.1                    py36_0
readline                  6.2                           2
redis                     3.2.0                         0
redis-py                  2.10.5                   py36_0
requests                  2.13.0                   py36_0
requests-toolbelt         0.7.1                     <pip>
ricker                    0.1.2                     <pip>
rope                      0.9.4                    py36_1
ruamel_yaml               0.11.14                  py36_1
scikit-image              0.13.0              np112py36_0
scikit-learn              0.18.1              np112py36_1
scipy                     0.19.0              np112py36_0
seaborn                   0.7.1                    py36_0
SeisCM                    0.1.1                     <pip>
setuptools                27.2.0                   py36_0
setuptools                35.0.2                    <pip>
simplegeneric             0.8.1                    py36_1
singledispatch            3.4.0.3                  py36_0
sip                       4.18                     py36_0
six                       1.10.0                   py36_0
six                       1.10.0                    <pip>
snowballstemmer           1.2.1                    py36_0
sockjs-tornado            1.0.3                    py36_0
sphinx                    1.5.4                    py36_0
spyder                    3.1.3                    py36_0
sqlalchemy                1.1.9                    py36_0
sqlite                    3.13.0                        0
statsmodels               0.8.0               np112py36_0
sympy                     1.0                      py36_0
tensorflow                1.1.0                     <pip>
terminado                 0.6                      py36_0
testpath                  0.3                      py36_0
tk                        8.5.18                        0
toolz                     0.8.2                    py36_0
tornado                   4.4.2                    py36_0
traitlets                 4.3.2                    py36_0
twine                     1.8.1                     <pip>
typing                    3.5.3.0                   <pip>
unicodecsv                0.14.1                   py36_0
wcwidth                   0.1.7                    py36_0
werkzeug                  0.12.1                   py36_0
Werkzeug                  0.12.1                    <pip>
wheel                     0.29.0                    <pip>
wheel                     0.29.0                   py36_0
widgetsnbextension        2.0.0                    py36_0
wiggle                    0.1.0                     <pip>
wrapt                     1.10.10                  py36_0
xlrd                      1.0.0                    py36_0
xlsxwriter                0.9.6                    py36_0
xlwings                   0.10.4                   py36_0
xlwt                      1.2.0                    py36_0
xz                        5.2.2                         1
yaml                      0.1.6                         0
zlib                      1.2.8                         3
```

I installed the Obspy through github clone now. I also tried conda and pip versions. They all have the same issue.


This sounds like a previous issue Issue regarding ASCII versus other string encodings (which I can't seem to find right now) but that was for alphanumeric SAC files.  I think this is a binary file.

Maybe you could you check the string header value you're having trouble with by putting a print statement in `/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/io/sac/sactrace.py` at line 1164: `print("{}: {}, {}".format(i, hdr, type(hdr)))`.
By the way, the code I used to read this file is the standard code I found [here](https://docs.obspy.org/master/packages/obspy.io.sac.html)

```Python
from obspy import read
st = read('/path/to/test.sac', debug_headers=True)
```
@jkmacc-LANL When I tried to print those values, I got

```
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-2-3e739171fc21> in <module>()
----> 1 st = read('1100-5001.sac.ds.bp.sml.2152.01.sml', debug_headers=True)

<decorator-gen-144> in read(pathname_or_url, format, headonly, starttime, endtime, nearest_sample, dtype, apply_calib, **kwargs)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/util/decorator.py in _map_example_filename(func, *args, **kwargs)
    297                         except IOError:
    298                             pass
--> 299         return func(*args, **kwargs)
    300     return _map_example_filename
    301

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/stream.py in read(pathname_or_url, format, headonly, starttime, endtime, nearest_sample, dtype, apply_calib, **kwargs)
    229         pathname = pathname_or_url
    230         for file in sorted(glob(pathname)):
--> 231             st.extend(_read(file, format, headonly, **kwargs).traces)
    232         if len(st) == 0:
    233             # try to give more specific information why the stream is empty

<decorator-gen-145> in _read(filename, format, headonly, **kwargs)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/util/decorator.py in uncompress_file(func, filename, *args, **kwargs)
    207     else:
    208         # no compressions
--> 209         result = func(filename, *args, **kwargs)
    210     return result
    211

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/stream.py in _read(filename, format, headonly, **kwargs)
    271     """
    272     stream, format = _read_from_plugin('waveform', filename, format=format,
--> 273                                        headonly=headonly, **kwargs)
    274     # set _format identifier for each element
    275     for trace in stream:

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/util/base.py in _read_from_plugin(plugin_type, filename, format, **kwargs)
    404         raise TypeError(msg % (format_ep.name, ', '.join(eps)))
    405     # read
--> 406     list_obj = read_format(filename, **kwargs)
    407     return list_obj, format_ep.name
    408

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/io/sac/core.py in _read_sac(filename, headonly, debug_headers, fsize, **kwargs)
    349             return _internal_read_sac(buf=fh, headonly=headonly,
    350                                       debug_headers=debug_headers, fsize=fsize,
--> 351                                       **kwargs)
    352     else:
    353         raise ValueError("Cannot open '%s'." % filename)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/io/sac/core.py in _internal_read_sac(buf, headonly, debug_headers, fsize, **kwargs)
    382     """
    383     # read SAC file
--> 384     sac = SACTrace.read(buf, headonly=headonly, ascii=False, checksize=fsize)
    385     # assign all header entries to a new dictionary compatible with an ObsPy
    386     tr = sac.to_obspy_trace(debug_headers=debug_headers)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/io/sac/sactrace.py in read(cls, source, headonly, ascii, byteorder, checksize, debug_strings)
   1162                 if val.startswith(native_str('-12345')):
   1163                     val = HD.SNULL
-> 1164                 print("{}: {}, {}".format(i, hdr, type(hdr)))
   1165                 hs[i] = val
   1166

NameError: name 'hdr' is not defined
```

Basically, $hdr is undefined. So I change to print the $val instead and here is what I got:

Add
```Python
print("{}: {}, {}".format(i, val, type(val)))
```
to line 1164

```
0: ��������, <class 'str'>
---------------------------------------------------------------------------
UnicodeEncodeError                        Traceback (most recent call last)
<ipython-input-2-3e739171fc21> in <module>()
----> 1 st = read('1100-5001.sac.ds.bp.sml.2152.01.sml', debug_headers=True)

<decorator-gen-144> in read(pathname_or_url, format, headonly, starttime, endtime, nearest_sample, dtype, apply_calib, **kwargs)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/util/decorator.py in _map_example_filename(func, *args, **kwargs)
    297                         except IOError:
    298                             pass
--> 299         return func(*args, **kwargs)
    300     return _map_example_filename
    301

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/stream.py in read(pathname_or_url, format, headonly, starttime, endtime, nearest_sample, dtype, apply_calib, **kwargs)
    229         pathname = pathname_or_url
    230         for file in sorted(glob(pathname)):
--> 231             st.extend(_read(file, format, headonly, **kwargs).traces)
    232         if len(st) == 0:
    233             # try to give more specific information why the stream is empty

<decorator-gen-145> in _read(filename, format, headonly, **kwargs)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/util/decorator.py in uncompress_file(func, filename, *args, **kwargs)
    207     else:
    208         # no compressions
--> 209         result = func(filename, *args, **kwargs)
    210     return result
    211

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/stream.py in _read(filename, format, headonly, **kwargs)
    271     """
    272     stream, format = _read_from_plugin('waveform', filename, format=format,
--> 273                                        headonly=headonly, **kwargs)
    274     # set _format identifier for each element
    275     for trace in stream:

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/util/base.py in _read_from_plugin(plugin_type, filename, format, **kwargs)
    404         raise TypeError(msg % (format_ep.name, ', '.join(eps)))
    405     # read
--> 406     list_obj = read_format(filename, **kwargs)
    407     return list_obj, format_ep.name
    408

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/io/sac/core.py in _read_sac(filename, headonly, debug_headers, fsize, **kwargs)
    349             return _internal_read_sac(buf=fh, headonly=headonly,
    350                                       debug_headers=debug_headers, fsize=fsize,
--> 351                                       **kwargs)
    352     else:
    353         raise ValueError("Cannot open '%s'." % filename)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/io/sac/core.py in _internal_read_sac(buf, headonly, debug_headers, fsize, **kwargs)
    382     """
    383     # read SAC file
--> 384     sac = SACTrace.read(buf, headonly=headonly, ascii=False, checksize=fsize)
    385     # assign all header entries to a new dictionary compatible with an ObsPy
    386     tr = sac.to_obspy_trace(debug_headers=debug_headers)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/io/sac/sactrace.py in read(cls, source, headonly, ascii, byteorder, checksize, debug_strings)
   1163                     val = HD.SNULL
   1164                 print("{}: {}, {}".format(i, val, type(val)))
-> 1165                 hs[i] = val
   1166
   1167         sac = cls._from_arrays(hf, hi, hs, data)

UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-7: ordinal not in range(128)
```
Sorry, I meant `val`.  Good catch.  It seems that the `val` is a Python 3 `str`, which is Unicode, but apparently not copy/paste-able 😄 I think if you pass `debug_strings=True` to the main ObsPy `read` function, it'll get passed down to the SAC reader, and you can inspect the string values in your SAC header directly.  If any of them are not ASCII (which it looks like is the case), you should be able to tell.  After you read it, what does `trace.stats.sac.kstnm` give you?
@jkmacc-LANL Thanks a lot for the quick response.

> By the way, the code I used to read this file is the standard code I found here
> 
> from obspy import read
> st = read('/path/to/test.sac', debug_headers=True)


Like I mentioned in previous post, I already pass the `debug_headers=True` to obspy.read. The print on my screen is the same that I posted here:

> 0 : ��������, <class 'str'>

The string is not readable on my screen either. By the way, I used IPython to test the code. Let me know if it may be a problem. 

I cannot read the trace since it throw an error at me, so I don't know what is the value of `trace.stats.sac.kstnm`. The error message is shown in previous post.


`debug_strings` is a different argument, which is used in the underlying [`SACTrace.read` function](https://github.com/obspy/obspy/blob/master/obspy/io/sac/sactrace.py#L1102),  but I see now that you can only access it from `SACTrace.read` and not `obspy.read`.  This should be fixed, but for now, could you try this:
```python
from obspy.io.sac import SACTrace

sac = SACTrace.read(your_filename, debug_strings=True, headonly=False)
```
Could you let us know what `sac.kstnm` or `sac.kstnm.encode('ascii')` does/returns?
Here is the error message I got after trying to access `sac.kstnm`

```
---------------------------------------------------------------------------
UnicodeDecodeError                        Traceback (most recent call last)
<ipython-input-4-9d573e49fa53> in <module>()
----> 1 sac.kstnm

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/io/sac/sactrace.py in get_str(self)
    469         try:
    470             # value is a bytes
--> 471             value = native_str(self._hs[HD.STRHDRS.index(hdr)].decode())
    472         except AttributeError:
    473             # value is a str

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc7 in position 0: invalid continuation byte
```
(Thanks for your patience with all this back and forth.)  How about `sac._hs[0]`?  
Thanks for the help. `sac._hs[0]` has the following value:

```
b'\xc7\xcf\xff\xff\xc7\xcf\xff\xff'
```
@lijunzh One of the issues is that it's not clear which character encoding was used for the string header variables.  If you know this, or know what some specific header values _should_ be, that can help.  You can see from line 471 in your last error message, the SAC module uses the default system codec (often UTF-8) to interpret the headers.  This is actually more forgiving than the SAC spec, which I think uses ASCII encoding.  UTF-16 encoding "works", and this is what I get:
```python
In [67]: tmp.decode('utf16')
Out[67]: '쿇\uffff쿇\uffff'
```
But that doesn't make much sense as a station name.  Parts of the bytestring decode with GB18030 (a Chinese encoding I've seen before), but not all of it.
```python
n [47]: tmp[:2].decode('gb18030')
Out[47]: '窍'
In [56]: tmp[4:6].decode('gb18030')
Out[56]: '窍'
In [69]: tmp.decode('gb18030')
---------------------------------------------------------------------------
UnicodeDecodeError                        Traceback (most recent call last)
<ipython-input-69-9562f76f2279> in <module>()
----> 1 tmp.decode('gb18030')

UnicodeDecodeError: 'gb18030' codec can't decode byte 0xff in position 2: illegal multibyte sequence
```
Does this help at all?  What does `sac.kstnm` give you with your own reader that you posted?
@jkmacc-LANL Thanks for your reply. I was told by my friend that the following code will fix the bug in Python 2.x

```Python
import sys
reload(sys)
sys.setdefaultencoding('Cp1252')
```

It does not works for me since the change default encoding system is different in Python 3. I tried to get around it by something similar but failed. However, if it works for py2x, it may infer that the correct encoding to be used here is CP1252. 

In fact, I don't really care that much about the header file since I can easily get the info once for all traces using SAC program directly. However, the bug prevent me from reading the data which is the essential function that I want Obspy to work. That is why I made my hack version that do not read header at all. I think if you guys do not want to change too much existing code, at least add a parameter that let user only read the data to Numpy array (maybe it exists but I don't know of). 



You can change the file encoding when you open the file, and ObsPy can read from a file object.  It sounds like you've got a solution, but this might work:
```python
from obspy import read
with open(filename, encoding='CP1252') as f:
    tr = read(f, format='SAC')[0]
```
If you try it, please let us know if it works.

I'll try to think about a way to skip the string header.  I come across a few SAC writers that are not very careful about their encodings.

P.S.  'CP1252' still doesn't look right.
```python
In [74]: tmp
Out[74]: b'\xc7\xcf\xff\xff\xc7\xcf\xff\xff'

In [75]: tmp.decode('Cp1252')
Out[75]: 'ÇÏÿÿÇÏÿÿ'
```
@jkmacc-LANL  I am not quite sure if the calling of obspy.read in this way is correct. It gives me the following error. It can also be caused by using the wrong encoding. If that is the case, I have no idea what is the correct encoding should be here. 

```Python
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-9-b3e25ffdb552> in <module>()
      1 with open('1100-5001.sac.ds.bp.sml.2152.01.sml', encoding='CP1252') as f:
----> 2     tr = read(f, format='SAC')[0]
      3

<decorator-gen-144> in read(pathname_or_url, format, headonly, starttime, endtime, nearest_sample, dtype, apply_calib, **kwargs)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/util/decorator.py in _map_example_filename(func, *args, **kwargs)
    297                         except IOError:
    298                             pass
--> 299         return func(*args, **kwargs)
    300     return _map_example_filename
    301

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/stream.py in read(pathname_or_url, format, headonly, starttime, endtime, nearest_sample, dtype, apply_calib, **kwargs)
    208         try:
    209             # first try reading directly
--> 210             stream = _read(pathname_or_url, format, headonly, **kwargs)
    211             st.extend(stream.traces)
    212         except TypeError:

<decorator-gen-145> in _read(filename, format, headonly, **kwargs)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/util/decorator.py in uncompress_file(func, filename, *args, **kwargs)
    147     """
    148     if not isinstance(filename, (str, native_str)):
--> 149         return func(filename, *args, **kwargs)
    150     elif not os.path.exists(filename):
    151         msg = "File not found '%s'" % (filename)

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/stream.py in _read(filename, format, headonly, **kwargs)
    271     """
    272     stream, format = _read_from_plugin('waveform', filename, format=format,
--> 273                                        headonly=headonly, **kwargs)
    274     # set _format identifier for each element
    275     for trace in stream:

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/core/util/base.py in _read_from_plugin(plugin_type, filename, format, **kwargs)
    404         raise TypeError(msg % (format_ep.name, ', '.join(eps)))
    405     # read
--> 406     list_obj = read_format(filename, **kwargs)
    407     return list_obj, format_ep.name
    408

/Users/lijun/anaconda3/lib/python3.6/site-packages/obspy-1.0.3.post0+1068.g04848ed0fc.obspy.master-py3.6-macosx-10.7-x86_64.egg/obspy/io/sac/core.py in _read_sac(filename, headonly, debug_headers, fsize, **kwargs)
    351                                       **kwargs)
    352     else:
--> 353         raise ValueError("Cannot open '%s'." % filename)
    354
    355

ValueError: Cannot open '<_io.TextIOWrapper name='1100-5001.sac.ds.bp.sml.2152.01.sml' mode='r' encoding='CP1252'>'.
```

@jkmacc-LANL I think it is totally possible to bypass the reading header step as in the code I attached in the previous comment. The better question to ask is *how to do it in a clean manner and does not affect other code in Obspy*. I was going to fork the project and do it myself and send a pull request. But, I want to discuss with you guys first to understand the basic design requirements in Obspy before wasting my time on some useless coding. 
>  how to do it in a clean manner and does not affect other code in Obspy.

You're precisely right.  That ends up being the crux of any code changes in this widely-used code base:-)  I would certainly encourage you to give it a try, though 😄   ObsPy has lots of tests to help you avoid breaking things, and setting up a local testing environment is not too hard.  I (or any of us) would be very willing to help you do that.

That last error shouldn't happen.  Hmmm.  I'll have to mess with that later.  Thanks!
@jkmacc-LANL I was not aware of the existence of those test. Should I search them at the [DeveloperCorner](https://github.com/obspy/obspy/wiki#developer-corner)? If I came across some problems, where will be a good place to ask specific questions? I would very much like to fix this problem so I don't have to go through the hack code every time I need to read some SAC files. 

Regarding to the last error,  I attached the file that I used to create this error. You can create it using my conda setup:





> Current conda install:
> 
>                platform : osx-64
>           conda version : 4.3.16
>        conda is private : False
>       conda-env version : 4.3.16
>     conda-build version : not installed
>          python version : 3.6.1.final.0
>        requests version : 2.13.0
>        root environment : /Users/lijun/anaconda3  (writable)
>     default environment : /Users/lijun/anaconda3
>        envs directories : /Users/lijun/anaconda3/envs
>                           /Users/lijun/.conda/envs
>           package cache : /Users/lijun/anaconda3/pkgs
>                           /Users/lijun/.conda/pkgs
>            channel URLs : https://conda.anaconda.org/obspy/osx-64
>                           https://conda.anaconda.org/obspy/noarch
>                           https://repo.continuum.io/pkgs/free/osx-64
>                           https://repo.continuum.io/pkgs/free/noarch
>                           https://repo.continuum.io/pkgs/r/osx-64
>                           https://repo.continuum.io/pkgs/r/noarch
>                           https://repo.continuum.io/pkgs/pro/osx-64
>                           https://repo.continuum.io/pkgs/pro/noarch
>             config file : /Users/lijun/.condarc
>            offline mode : False
>              user-agent : conda/4.3.16 requests/2.13.0 CPython/3.6.1 Darwin/16.5.0 OSX/10.12.4
>                 UID:GID : 501:20


The [testing page](https://github.com/obspy/obspy/wiki/Testing%2C-Debugging-%26-Profiling) is a good one.  I use conda or pip to install all ObsPy's requirements in isolated Python 2 and 3 environments, then I `pip install -e .` in the repository root (in your feature branch), then just make changes to the code and run `obspy-runtests io.sac` or `obspy-runtests` whenever I've made a decent code change.  
@lijunzh Using the `open(filename, encoding=<whatever>)` syntax opens the file in text mode, of course.  Since this is a binary file, it'll fail.  I wasn't really thinking.  

Regarding fixing your issue, the minimum working solution would likely be to allow the `debug_strings=True` flag in the main `obspy.read` function by passing the `**kwargs` to `SACTrace.read` [here](https://github.com/obspy/obspy/blob/master/obspy/io/sac/core.py#L384).  That way, it'll read the header, but won't worry about what the values are (as long as you don't access them).  

If you actually want to bypass the header, I think you can just jump to byte 632 of a file, and read float32 values until the end (untested).

```python
import numpy as np
with open(myfile, 'rb') as f:
    f.seek(632)
    data = np.fromfile(f, dtype=np.float32)
```
@jkmacc-LANL Thanks for your reply. I didn't catch that mistake either. 

I will try your solution on Monday and let you know how it goes. If using the correct encoding can help the problem, maybe all we need to do is make a parameter for encoding available at the obspy.read or obspy.io.sac.core._read_sac() level. I think  it is OK to let the user go to the lower level interface if they have some specific needs, like a different encoding than the default utf-8.
>  If using the correct encoding can help the problem, maybe all we need to do is make a parameter for encoding available at the obspy.read or obspy.io.sac.core._read_sac() level.

A `string_encoding` flag in `obspy.read` sounds good to me.  String headers get stored in a `|S8` numpy string array, so I'm not sure how decoded values might get cast if stored into it.  Worth a try!
I changed the `sactrace.py` line 1159 to the following and it works for me. 
```Python
        if not debug_strings:
            for i, val in enumerate(hs):
                print("{}: {}, {}".format(i, val, type(val)))
                val = val.decode('Cp1252')
                # val = _ut._clean_str(val, strip_whitespace=False)
                if val.startswith(native_str('-12345')):
                    val = HD.SNULL
                print("{}: {}, {}".format(i, val, type(val)))
                hs[i] = val.encode('utf-8')
                print("{}: {}, {}".format(i, hs[i], type(hs[i])))
```

Basically, it is an encoding problem. The SAC file I had used some wired characters that is outside the ASCII encoding. It triggers the exception in `_clean_str()` from `util.py` here:
```Python
    try:
        value = value.decode('ASCII', 'replace')
    except AttributeError:
        pass
```
This results in `val` being replaced by another character that is also outside ASCII which causes problem here:
```Python
hs[i] = val
```
which assumes ASCII encoding. 


As we discussed before, if we allow users to specify a type of encoding here and add a new version of  `_clean_str()` method that uses `utf-8` instead of ASCII, this problem can be solved. However, I am not sure if this will cause any problem in other parts of the code. 
> I am not sure if this will cause any problem in other parts of the code.

If you allow a `string_encoding='ASCII'` keyword argument to `obspy.read`, which eventually gets passed to `SACTrace.read` and `_clean_str`, I suspect it'd be OK if the default value was ASCII.  Want to give it a try?
> I suspect it'd be OK if the default value was ASCII.

I think the default encoding is ASCII in this case. That is why I have a problem here:

> hs[i] = val

In the original code, even if I use the 'Cp1252' to decode the string, when it pass to the `hs[i]`, it automatically uses ASCII to encode it which tells me that the default encoding for `hs` is ASCII. I am suspecting that it is related the fact that py2's default encoding is ASCII so for the compatibility purpose, Obspy forces the default ASCII encoding (correct me if I am wrong). However, in py3, the default encoding has been changed to UTF-8. I am not sure if there will be a uniform solution for both py2 and py3. More importantly, since ASCII only has 256 characters, many special characters cannot be encoded by it. This is the reason why I used the UTF-8 here:

> hs[i] = val.encode('utf-8')

I will try to change Obspy code and test for both py2 and py3. I think we can close this issue now.

This Issue can be converted into a Pull Request once you've made your changes (even before then, actually), so let's leave it open if you intend to try to make changes.  Converting Issues reduces duplicate Issues and PRs for the same changes.  This [wiki page](https://github.com/obspy/obspy/wiki/Interesting-Reading-on-Git,-GitHub) describes how to do it.  I use the `curl` method.
Thanks for the tip. Yes, I will make those changes and would like you guys to review them. Hopefully, I can get some draft code this week. I will go ahead make the conversion.
Let me know if you have any trouble with the Issue -> PR conversion.  Since your fork is outside of the obspy Github organization, I don't know if you'll have permission to do it.  
@jkmacc-LANL Thanks a lot for asking. Yes, that is the problem I am having now. The validation was invalid:

```
curl --user lijunzh --request POST --data '{"issue": "1768", "head": "lijunzh:obspy", "base": "io_sac"}' https://api.github.com/repos/obspy/obspy/pulls
Enter host password for user 'lijunzh':
{
  "message": "Validation Failed",
  "errors": [
    {
      "resource": "PullRequest",
      "field": "head",
      "code": "invalid"
    }
  ],
  "documentation_url": "https://developer.github.com/v3/pulls/#create-a-pull-request"
}
```
I suspect I can't do this without assess to your repo.  Also, it looks like you're doing a PR from your master branch.  It may not matter, but I'd recommend making your changes in a feature branch, like `sac_string_encoding_flag`, or something.

@krischer or @megies What's the normal way to resolve this situation?  Just deal with the duplication?
Just open a new PR; there's no need to be stingy with issue numbers.
Add Fixes #1768 in the description and it will close this one when it's merged.
@QuLogic Thanks for the tip. I submitted a pull request. This is my first time doing that. Let me know if I made any mistake in that process. 
Sorry, joining the discussion late.. the main point is *whether SAC is allowing an encoding different from ASCII*. I only see it mentioned as "alphanumeric" in the SAC manual, so *my guess is.. no*. If there is doubt we should clarify with the SAC authors (e.g. via https://ds.iris.edu/message-center/topic/sac-help).
The SAC program will read in a file with non-ASCII headers just fine, but the values look like `��������` on screen, which looks like a Python `.decode('ascii', 'replace') as far as I can tell.
@megies Good point. I don't see any reason why someone will deliberately use a non-alphanumeric character in SAC's header. However, in my case, the SAC file was given by others and the station/channels name was in some wired characters encoded in 'Cp1252'. I guess they want to protect the station/channel name from being read by others. But, I think Obspy will want to have an ability to skip such "invalid" character and read the SAC data OK. Maybe, a better approach is to have a *default* invalid station/channel name to replace any characters that are outside the range of ASCII. I will hold my pull request until we have a decision on how to fix this problem.
@jkmacc-LANL You are right.  The problem that prevents Obspy continue the reading process is that `��������` is outside the range of ASCII which will throw an exemption like this:

```
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-7: ordinal not in range(128)
```
> the SAC file was given by others

Ah, OK, so I guess you should let them know of the bad values but I understand that you have to deal with the "broken" file for now.

I think doing `.decode('ascii', 'replace')` (or `ignore`) on all string fields upon reading might be the way to go. Also, showing a warning to users telling them about bad characters being ignored:

```python
try:
    x.decode('ascii', 'strict')
except UnicodeError:
    msg = 'Encountered bad characters in field "...": "..."'
    warnings.warn(msg)
    x.decode('ascii', 'replace')
```
> guess you should let them know of the bad values

I would guess that those bad values are intended to protect their station name. 

> .decode('ascii', 'replace')

This is where the bad values ` ��������` are produced. The original SAC has some characters like `ÇÏÿÿ`. After passing `.decode('ascii', 'replace')` statement, it replace those characters by  ` ��������` which is outside ASCII range. I guess it may be because py3's default encoding is "UTF-8". 

I would vote for `ignore` since `replace` does not seem work for me. I like the warning idea that let users decide what to do with the invalid characters. 
> I would guess that those bad values are intended to protect their station name.

Why would anybody need to obscure a station name? I can see reasons to obscure location (e.g. for anonymity of land owner).. but station name, I find that hard to believe.



> it replace those characters by ��������

That shouldn't be a problem because *encoding* should then again replace those characters with `?`.

@jkmacc-LANL basically I think we should read all string-type header fields from file in binary mode, use `.decode('ASCII', 'replace')` to decode them and when writing to file use `.encode('ASCII', 'replace')`.
To be more verbose to users about problems it might be a good idea to slap a try/except with strict de/encoding around it like posted above.
I see your point now.

```Python
In [7]: '�'.encode('ascii', 'replace')
Out[7]: b'?'
```

which will avoid the out of ASCII range problem. This is a better solution than our original "encode" flag one. I will go ahead and change my PR. Thanks a lot.
> I think we should read all string-type header fields from file in binary mode, use .decode('ASCII', 'replace') to decode them and when writing to file use .encode('ASCII', 'replace').

This is a reasonable way forward, but 1) this is different behavior from the way the SAC program behaves (it seems to allow arbitrary byte strings to pass through unchanged), which may trip up some users, and 2) we'd have to change a number existing tests.  I think both of these may lead to more Issues for ObsPy, so allowing the user to specify a string encoding if they know it doesn't sound so bad.  I may be under-thinking it 😉 


@jkmacc-LANL Is there a way to read the encoding information directly from SAC file? If that is the case, this probelm will be much easier to solve.
😞 Encodings can't accurately be guessed.
This means currently in Obspy, if the SAC encoding is different from ASCII or UTF-8, it will mostly like be an invalid character. In this case, it may be the best to have an optional parameter to let user decide whether or not that special character is important for them. 

This would be easy to do in py3 which defaults in UTF-8. My previous PR was trying to force the UTF-8 encoding after reading the character in user dedined encoding. However, it would need more polish for py2 which use ASCII as default. 

I am not exactly sure whether or not we should let users to use non-alphnumeric character at all. If not, than force an ASCII encoding is better.

Continued in #1773 
Added the magic `'Closes #...'` to #1773 description, so this should be closed automatically when it's merged. @QuLogic is right, since this is the actual bug report we should really keep it open till it's really done (I won't argue against a grain of paranoia ;-))
@megies I changed the PR#1773 and added `Close #1768` in the title. Let me know if I did it correctly.
I'd already added that magic to the PR description (top-most post), I'm not sure even if the PR title uses the same magic.

https://help.github.com/articles/closing-issues-via-commit-messages/

Resolving patch:
diff --git a/obspy/io/sac/sactrace.py b/obspy/io/sac/sactrace.py
--- a/obspy/io/sac/sactrace.py
+++ b/obspy/io/sac/sactrace.py
@@ -1092,7 +1092,7 @@ def read(cls, source, headonly=False, ascii=False, byteorder=None,
                 val = _ut._clean_str(val, strip_whitespace=False)
                 if val.startswith(native_str('-12345')):
                     val = HD.SNULL
-                hs[i] = val
+                hs[i] = val.encode('ASCII', 'replace')
 
         sac = cls._from_arrays(hf, hi, hs, data)
         if sac.dist is None:


Test patch:
diff --git a/obspy/io/sac/tests/data/test_encode.sac b/obspy/io/sac/tests/data/test_encode.sac
new file mode 100644
Binary files /dev/null and b/obspy/io/sac/tests/data/test_encode.sac differ
diff --git a/obspy/io/sac/tests/test_core.py b/obspy/io/sac/tests/test_core.py
--- a/obspy/io/sac/tests/test_core.py
+++ b/obspy/io/sac/tests/test_core.py
@@ -36,6 +36,7 @@ def setUp(self):
         self.fileseis = os.path.join(self.path, "data", "seism.sac")
         self.file_notascii = os.path.join(self.path, "data",
                                           "non_ascii.sac")
+        self.file_encode = os.path.join(self.path, "data", "test_encode.sac")
         self.testdata = np.array(
             [-8.74227766e-08, -3.09016973e-01,
              -5.87785363e-01, -8.09017122e-01, -9.51056600e-01,
@@ -915,6 +916,13 @@ def test_always_sac_reftime(self):
         self.assertAlmostEqual(tr1.stats.sac.a, a, places=5)
         self.assertEqual(tr1.stats.sac.b, b)
 
+    def test_wrong_encoding(self):
+        """
+        Read SAC file with wrong encoding
+        """
+        tr0 = read(self.file_encode)[0]
+        self.assertEqual(tr0.stats.get('channel'), '????????')
+
 
 def suite():
     return unittest.makeSuite(CoreTestCase, 'test')