Repository: OSOceanAcoustics/echopype (language: python)

Issue:
Fix Xarray Duck Array Ops `invalid value encountered in cast` Runtime Warning in `ep.open_raw`
When opening and saving `.RAW` files from [OOI](https://rawdata.oceanobservatories.org/files/CE04OSPS/PC01B/ZPLSCB102_10.33.10.143/2017/08), I encountered some `duck_array_ops` warnings:

```
# Save Echodata objects locally
def open_and_save(raw_file, sonar_model, use_swap, save_path, open_kwargs={}):
    try:
        ed = ep.open_raw(raw_file=raw_file, sonar_model=sonar_model, use_swap=use_swap, **open_kwargs)
        ed.to_zarr(save_path, overwrite=True, compute=True)
    except Exception as e:
        print("Error with Exception: ", e)

# Parse EK60 `.RAW` file and save to Zarr Store
open_and_save_futures = []
for raw_file_url in desired_raw_file_urls:
    open_and_save_future = client.submit(
        open_and_save,
        raw_file=raw_file_url,
        sonar_model='ek60',
        use_swap=True,
        save_path=single_echodata_zarr_dpath
    )
    open_and_save_futures.append(open_and_save_future)
open_and_save_futures = client.gather(open_and_save_futures)
```

```
/home/exouser/miniforge3/envs/echopype/lib/python3.9/site-packages/xarray/core/duck_array_ops.py:216: RuntimeWarning: invalid value encountered in cast
  return data.astype(dtype, **kwargs)
/home/exouser/miniforge3/envs/echopype/lib/python3.9/site-packages/xarray/core/duck_array_ops.py:216: RuntimeWarning: invalid value encountered in cast
  return data.astype(dtype, **kwargs)
/home/exouser/miniforge3/envs/echopype/lib/python3.9/site-packages/xarray/core/duck_array_ops.py:216: RuntimeWarning: invalid value encountered in cast
  return data.astype(dtype, **kwargs)
```
using xarray version `'2024.5.0'`.

I'm not quite sure what this is about, but this is probably something small and it should be fixed before any new `echopype-examples` notebook PRs get merged. I think it can be very disheartening seeing all these warnings pop up when using our package for the first time.


Discussion / hints:
So the problem here is a bit convoluted. I'll detail it step-by-step here:

Values in the Parser's `ping_data_dict` can be of different lengths depending on the channel. Let's take transmit mode for example:

![image](https://github.com/OSOceanAcoustics/echopype/assets/87830138/8322517c-e1a2-400a-8de8-5836d17f43bb)

These are then turned into DataArrays in the setting of groups and concatenated together by channel. Any 'missing' values will be turned into `NaNs` in this process.

Some of these values are then stored as `np.byte` or `np.int` in `utils.coding.py`:

![image](https://github.com/OSOceanAcoustics/echopype/assets/87830138/ed36f3ea-5f9d-4c5c-99b8-1821c8572af5)

This is a problem since setting the type of these arrays with `np.byte` and `np.int` will spit out a warning if it encounters something that can't be encoded to either of these, and more specifically, it will spit out a warning when encountering `NaN` values and turn the `NaNs` into `0s`:

![image](https://github.com/OSOceanAcoustics/echopype/assets/87830138/e842bfa7-3af6-498f-9655-fe974a080997)

In a separate PR I will address this by not changing the type and instead changing these NaN values to the appropriate integer value, and I will focus just on `transmit_mode`/`channel_mode` since these values were the only ones that I noticed causing any problems. I will change any `NaN` values to `-1` since that is what is shown in the attributes:

```
                "channel_mode": (
                    ["ping_time"],
                    np.array(self.parser_obj.ping_data_dict["transmit_mode"][ch], dtype=np.byte),
                    {
                        "long_name": "Transceiver mode",
                        "flag_values": [-1, 0, 1, 2],
                        "flag_meanings": ["Unknown", "Active", "Passive", "Test"],
                        "comment": "From transmit_mode in the EK60 datagram",
                    },
                ),
```

Fix patch:
diff --git a/echopype/utils/coding.py b/echopype/utils/coding.py
index bccc79e00..d783e2fbe 100644
--- a/echopype/utils/coding.py
+++ b/echopype/utils/coding.py
@@ -41,7 +41,7 @@
     "channel": np.str_,
     "cal_channel_id": np.str_,
     "beam": np.str_,
-    "channel_mode": np.byte,
+    "channel_mode": np.float16,
     "beam_stabilisation": np.byte,
     "non_quantitative_processing": np.int16,
 }  # channel name  # beam name


Test patch:
diff --git a/echopype/tests/convert/test_convert_ek60.py b/echopype/tests/convert/test_convert_ek60.py
index b6e6a6d8d..adabddde6 100644
--- a/echopype/tests/convert/test_convert_ek60.py
+++ b/echopype/tests/convert/test_convert_ek60.py
@@ -1,9 +1,12 @@
+import warnings
+
 import numpy as np
 import pandas as pd
 from scipy.io import loadmat
-from echopype import open_raw
 import pytest
 
+from echopype import open_raw
+
 
 @pytest.fixture
 def ek60_path(test_path):
@@ -208,3 +211,28 @@ def test_convert_es60_no_unicode_error(es60_path):
         open_raw(raw_path, sonar_model='EK60')
     except UnicodeDecodeError:
         pytest.fail("UnicodeDecodeError raised")
+
+
+@pytest.mark.integration
+@pytest.mark.parametrize(
+    ("file_path"),
+    [
+        "DY1002_EK60-D20100318-T023008_rep_freq.raw",
+        "from_echopy/JR230-D20091215-T121917.raw"
+    ]
+)
+def test_convert_ek60_different_num_channel_mode_values(file_path, ek60_path):
+    """
+    Check that no runtime warning is called when there are different number of channel mode
+    values per channel and check that `channel_mode` is of type `np.float16`.
+    """
+    # Catch and throw error for any `RuntimeWarning`
+    with warnings.catch_warnings():
+        warnings.simplefilter(action="error", category=RuntimeWarning)
+        ed = open_raw(ek60_path / file_path, sonar_model="EK60")
+
+        # Check dtype
+        assert np.issubdtype(
+            ed["Sonar/Beam_group1"]["channel_mode"].data.dtype,
+            np.float16
+        )