Repository: obspy/obspy

Issue:
SAC output ignores sampling rate from Trace metadata
When using the `trace.decimate()` function in v.1.0.0 on .SAC formatted traces, the new sampling rate isn't updated in the trace metadata. I've tried to work around it by decimating, then forcing the trace to update the stats with the new sampling rate using trace.stats.update. This does work for the current instance, however, when writing the trace out as a .SAC, the old sampling rate is written into the stats.



Discussion:
I have updated the title; nothing is wrong with `trace.decimate`. It is SAC output that is incorrect.

Oh, of course! Thanks for the clarification.


Resolving patch:
diff --git a/obspy/io/sac/header.py b/obspy/io/sac/header.py
--- a/obspy/io/sac/header.py
+++ b/obspy/io/sac/header.py
@@ -23,6 +23,7 @@
        '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}
@@ -33,6 +34,7 @@
                 ' [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)
@@ -50,6 +52,7 @@
        '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
@@ -116,6 +119,7 @@
        '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
@@ -123,6 +127,7 @@
                   * 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)
@@ -136,6 +141,7 @@
                   * IUSER (The individual using SAC2000)
                   * IUNKNOWN (unknown)''',
        'ievtyp': '''I    Type of event:
+
                   * IUNKN (Unknown)
                   * INUCL (Nuclear event)
                   * IPREN (Nuclear pre-shot event)
@@ -175,12 +181,14 @@
        '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.',
diff --git a/obspy/io/sac/util.py b/obspy/io/sac/util.py
--- a/obspy/io/sac/util.py
+++ b/obspy/io/sac/util.py
@@ -365,7 +365,7 @@ def obspy_to_sac_header(stats, keep_sac_header=True):
                 header.update(nztimes)
                 header['b'] = microsecond * 1e-6
                 header['e'] = header['b'] +\
-                    (header['npts'] - 1) * header['delta']
+                    (stats['npts'] - 1) * stats['delta']
 
             msg = "Old header has invalid reftime."
             warnings.warn(msg)
@@ -396,7 +396,7 @@ def obspy_to_sac_header(stats, keep_sac_header=True):
         header['b'] = microsecond * 1e-6
 
         # we now have correct b, npts, delta, and nz times
-        header['e'] = header['b'] + (header['npts'] - 1) * header['delta']
+        header['e'] = header['b'] + (stats['npts'] - 1) * stats['delta']
 
         header['scale'] = stats.get('calib', HD.FNULL)
 
@@ -413,6 +413,10 @@ def obspy_to_sac_header(stats, keep_sac_header=True):
     header['lovrok'] = 1
     header['iftype'] = 1
 
+    # ObsPy issue #1317
+    header['npts'] = stats['npts']
+    header['delta'] = stats['delta']
+
     return header
 
 


Test patch:
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
@@ -825,6 +825,20 @@ def test_merge_sac_obspy_headers(self):
         self.assertEqual(tr1.stats.starttime, tr.stats.starttime)
         self.assertEqual(tr1.stats.sac.o, o)
 
+    def test_decimate_resample(self):
+        """
+        Test that ObsPy Trace resampling and decimation is properly reflected
+        in the SAC file.
+        """
+        tr = read(self.file, format='SAC')[0]
+        tr.decimate(2)
+        with NamedTemporaryFile() as tf:
+            tempfile = tf.name
+            tr.write(tempfile, format='SAC')
+            tr1 = read(tempfile)[0]
+        self.assertEqual(tr1.stats.sac.npts, tr.stats.sac.npts / 2)
+        self.assertEqual(tr1.stats.sac.delta, tr.stats.sac.delta * 2)
+
 
 def suite():
     return unittest.makeSuite(CoreTestCase, 'test')