Repository: obspy/obspy

Issue:
SEGY: write format error
I keep getting the following error. I'm trying to write 3 traces to a SEGY file.  Data are being converted from MSEED.

    stream.write(stream, "TEST.sgy", format="SEGY")
TypeError: write() got multiple values for argument 'format'

ObsPy version 3.6.1
Running on OSX Sierra, installed with Anaconda.





Discussion:
@s-harder First, you might be referring to the Python 3.6.1 instead of Obspy since we only have 1.0.3 released earlier this year. Secondly, it is not clear to me why you put `stream` as the first argument in your code here:


> ```
> stream.write(stream, "TEST.sgy", format="SEGY")
> ```

Since `write` is a method of `stream` class, there is no need to offer itself(stream) as an argument and may be the reason why you have `TypeError`

> `TypeError: write() got multiple values for argument 'format'`

Hopefully, this clears your doubts. More info can be found from [this entry](https://docs.obspy.org/packages/autogen/obspy.core.stream.Stream.write.html) in our docs.
You're right I'm referring to the Python version not the ObsPy version, which is 1.0.3.  However, if I remove the stream argument I get the following.  stream.write do not like my textual header.  My textual header is attached.
Thanks for your help.

    stream.write("TEST.sgy", format="SEGY", data_encoding=5, byteorder='>',textual_file_encoding='ASCII')
  File "/Applications/anaconda/envs/obspy/lib/python3.6/site-packages/obspy/core/stream.py", line 1444, in write
    write_format(self, filename, **kwargs)
  File "/Applications/anaconda/envs/obspy/lib/python3.6/site-packages/obspy/io/segy/core.py", line 435, in _write_segy
    segy_file.write(filename, data_encoding=data_encoding, endian=byteorder)
  File "/Applications/anaconda/envs/obspy/lib/python3.6/site-packages/obspy/io/segy/segy.py", line 232, in write
    self._write(file, data_encoding=data_encoding, endian=endian)
  File "/Applications/anaconda/envs/obspy/lib/python3.6/site-packages/obspy/io/segy/segy.py", line 243, in _write
    self._write_textual_header(file)
  File "/Applications/anaconda/envs/obspy/lib/python3.6/site-packages/obspy/io/segy/segy.py", line 287, in _write_textual_header
    textual_header = self.textual_file_header + b' ' * (3200 - length)
TypeError: must be str, not bytes

[header.txt](https://github.com/obspy/obspy/files/1062487/header.txt)

@s-harder Sorry for the late reply. I was caught up by something else.


> TypeError: must be str, not bytes

The error itself suggest that you might read the file `header.txt` in the byte mode instead of text mode. Recall that Python 3 distinguish string from bytes, which is different from what Python 2 does. In your code, did you use something like this to read the file

```Python
with open('header.txt', 'rb') as f:
    for line in f:
        #do something
```

Instead, try 
```Python
with open('header.txt', 'r') as f:
    for line in f:
        #do something
```

I am here only guessing your usage. If possible, could you please attach the entire code block that you used to write a stream?


Resolving patch:
diff --git a/obspy/io/segy/segy.py b/obspy/io/segy/segy.py
--- a/obspy/io/segy/segy.py
+++ b/obspy/io/segy/segy.py
@@ -305,12 +305,20 @@ def _write_textual_header(self, file):
         small it will be padded with zeros. If it is too long or an invalid
         encoding is specified an exception will be raised.
         """
-        length = len(self.textual_file_header)
+        textual_header = self.textual_file_header
+
+        # Convert to ASCII bytes if necessary - this will raise an error in
+        # case the textual file header has no representation in ASCII - this
+        # is then the users responsibility.
+        if hasattr(textual_header, "encode"):
+            textual_header = textual_header.encode()
+
+        length = len(textual_header)
         # Append spaces to the end if its too short.
         if length < 3200:
-            textual_header = self.textual_file_header + b' ' * (3200 - length)
+            textual_header = textual_header + b' ' * (3200 - length)
         elif length == 3200:
-            textual_header = self.textual_file_header
+            textual_header = textual_header
         # The length must not exceed 3200 byte.
         else:
             msg = 'self.textual_file_header is not allowed to be longer ' + \


Test patch:
diff --git a/obspy/io/segy/tests/test_core.py b/obspy/io/segy/tests/test_core.py
--- a/obspy/io/segy/tests/test_core.py
+++ b/obspy/io/segy/tests/test_core.py
@@ -15,17 +15,16 @@
 import numpy as np
 
 from obspy import UTCDateTime, read
-from obspy.core.util import NamedTemporaryFile
+from obspy.core.util import NamedTemporaryFile, AttribDict
 from obspy.io.segy.core import (SEGYCoreWritingError, SEGYSampleIntervalError,
                                 _is_segy, _is_su, _read_segy, _read_su,
                                 _write_segy, _write_su)
 from obspy.io.segy.segy import _read_segy as _read_segy_internal
 from obspy.io.segy.segy import SEGYError, SEGYFile, SEGYTrace, \
     SEGYBinaryFileHeader
+from obspy.io.segy.tests import _patch_header
 from obspy.io.segy.tests.header import DTYPES, FILES
 
-from . import _patch_header
-
 
 class SEGYCoreTestCase(unittest.TestCase):
     """
@@ -648,6 +647,32 @@ def test_reading_and_writing_with_unset_dates(self):
         # Results in 1970, 1, 1
         self.assertEqual(st[0].stats.starttime, UTCDateTime(0))
 
+    def test_writing_text_and_binary_textual_file_headers(self):
+        """
+        Make sure the textual file header can be written if has been passed
+        either as text or as a bytestring.
+        """
+        # Loop over bytes/text and the textual header encoding.
+        for textual_file_header in [b"12345", "12345"]:
+            for encoding in ["ASCII", "EBCDIC"]:
+                st = read()
+                for tr in st:
+                    tr.data = np.require(tr.data, dtype=np.float32)
+                st.stats = AttribDict()
+                st.stats.textual_file_header = textual_file_header
+                with io.BytesIO() as buf:
+                    # Warning raised to create a complete header.
+                    with warnings.catch_warnings(record=True):
+                        st.write(buf, format="SEGY", data_encoding=5,
+                                 textual_header_encoding=encoding)
+                    buf.seek(0, 0)
+                    # Read with SEG-Y to preserve the textual file header.
+                    st2 = _read_segy(buf)
+                self.assertEqual(
+                    # Ignore the auto-generated parts of the header.
+                    st2.stats.textual_file_header.decode().split()[0],
+                    "12345")
+
 
 def suite():
     return unittest.makeSuite(SEGYCoreTestCase, 'test')