Repository: obspy/obspy

Issue:
_read_rg16 merge and contacts_north raises assertion error
The `merge` option is not compatible with the `contacts_north` option in `_read_rg16` if three component files are read because the native datatype of the rg16 files is `np.dtype('>f4')` but the data type of the array is changed when the Z component is multiplied by -1 to `np.dtype('float32')` [here](https://github.com/obspy/obspy/blob/a77b38443e8c5ec139adf82168cc026f4e4b47ed/obspy/io/rg16/core.py#L167-L168). `_merge_` then asserts there is only one dtype [here](https://github.com/obspy/obspy/blob/a77b38443e8c5ec139adf82168cc026f4e4b47ed/obspy/io/rg16/util.py#L168), raising an assertion error. 









Discussion:

Resolving patch:
diff --git a/obspy/io/rg16/core.py b/obspy/io/rg16/core.py
--- a/obspy/io/rg16/core.py
+++ b/obspy/io/rg16/core.py
@@ -163,7 +163,8 @@ def _make_traces(fi, data_block_start, gheader, head_only=False,
             data = np.array([])
         else:  # else read data
             data_start = trace_position + 20 + theader['num_ext_blocks'] * 32
-            data = _read(fi, data_start, theader['samples'] * 4, '>f4')
+            data = _read(fi, data_start, theader['samples'] * 4, '>f4',
+                         np.float32)
             if standard_orientation and stats.channel[-1] == 'Z':
                 data = -data
         traces.append(Trace(data=data, header=stats))
diff --git a/obspy/io/rg16/util.py b/obspy/io/rg16/util.py
--- a/obspy/io/rg16/util.py
+++ b/obspy/io/rg16/util.py
@@ -60,7 +60,7 @@ def _read_block(fi, spec, start_bit=0):
     return out
 
 
-def _read(fi, position, length, dtype):
+def _read(fi, position, length, dtype, new_dtype=None):
     """
     Read one or more bytes using provided datatype.
 
@@ -76,6 +76,7 @@ def _read(fi, position, length, dtype):
             >i3 - big endian 3 byte int
             >i. - 4 bit int, left four bits
     :type dtype: str
+    :param new_dtype: Any valid numpy data type.
     :return:
     """
     # if a list is passed as parameters then recurse through each
@@ -95,6 +96,8 @@ def _read(fi, position, length, dtype):
         return READ_FUNCS[dtype](fi, length)
     else:
         data = np.fromstring(fi.read(int(length)), dtype)
+        if new_dtype is not None:  # cast data to new_dtype (due to #2198)
+            data = data.astype(new_dtype)
         return data[0] if len(data) == 1 else data
 
 


Test patch:
diff --git a/obspy/io/rg16/tests/test_read_fcnt.py b/obspy/io/rg16/tests/test_read_fcnt.py
--- a/obspy/io/rg16/tests/test_read_fcnt.py
+++ b/obspy/io/rg16/tests/test_read_fcnt.py
@@ -25,7 +25,7 @@
 class TestReadRG16(unittest.TestCase):
 
     supported_samps = {250, 500, 1000, 2000}
-    supported_component_number = {1, 3}
+    supported_number_of_components = {1, 3}
 
     def test_rg16_files_identified(self):
         """
@@ -55,7 +55,7 @@ def test_component_number(self):
         """
         for fcnt_stream in FCNT_STREAMS:
             seed_ids = len({tr.id for tr in fcnt_stream})
-            self.assertIn(seed_ids, self.supported_component_number)
+            self.assertIn(seed_ids, self.supported_number_of_components)
 
     def test_channel_code(self):
         """
@@ -70,11 +70,11 @@ def test_channel_code(self):
                 self.assertEqual(len(channel), 3)
                 self.assertIn(component, expected_components)
             seed_ids = len({tr.id for tr in fcnt_stream})
-            self.assertIn(seed_ids, self.supported_component_number)
+            self.assertIn(seed_ids, self.supported_number_of_components)
 
-    def test_standard_orientation(self):
+    def test_contacts_north(self):
         """
-        Ensure the standard orientation maps channels and flips Z trace data.
+        Ensure the contacts north option maps channels and flips Z trace data.
         """
         components = {'Z', 'N', 'E'}
         for filename, st_default in zip(FCNT_FILES, FCNT_STREAMS):
@@ -90,6 +90,15 @@ def test_standard_orientation(self):
             if len(tr_2) and len(tr_z):
                 self.assertTrue(np.all(tr_2[0].data == -tr_z[0].data))
 
+    def test_contacts_north_and_merge(self):
+        """
+        Ensure the "contacts_north" and "merge" parameters can be used
+        together. See #2198.
+        """
+        for filename in FCNT_FILES:
+            st = _read_rg16(filename, contacts_north=True, merge=True)
+            assert isinstance(st, obspy.Stream)
+
     def test_can_write(self):
         """
         Ensure the resulting stream can be written as mseed.