Repository: mne-tools/mne-python

Issue:
CI failing after new release of neo
### Description of the problem

failing neuralynx test seen on `pip-pre` job of #12411: https://github.com/mne-tools/mne-python/actions/runs/7759461839/job/21163563326?pr=12411

cc @KristijanArmeni 

### Steps to reproduce

```Python
I can locally reproduce by doing `pip install -U neo` to get neo v0.13, and then running `pytest mne/io/neuralynx/tests/test_neuralynx.py -k nx_gaps`
```


### Link to data

_No response_

### Expected results

test passes

### Actual results

test fails with 
```
FAILED mne/io/neuralynx/tests/test_neuralynx.py::test_neuralynx_gaps - TypeError: Object is a <class 'mne.io.neuralynx.neuralynx.AnalogSignalGap'>. It should be one of (<class 'neo.core.analogsignal.AnalogSignal'>,).
```

### Additional information

```
Platform             Linux-5.15.0-58-generic-x86_64-with-glibc2.35
Python               3.11.7 | packaged by conda-forge | (main, Dec 23 2023, 14:43:09) [GCC 12.3.0]
Executable           /opt/mambaforge/envs/mnedev/bin/python3.11
CPU                  x86_64 (16 cores)
Memory               125.7 GB

Core
├☑ mne               1.7.0.dev76+g52a905928 (devel, latest release is 1.6.1)
├☑ numpy             1.26.3 (OpenBLAS 0.3.23.dev with 16 threads)
├☑ scipy             1.11.4
├☑ matplotlib        3.8.2 (backend=QtAgg)
├☑ pooch             1.8.0
└☑ jinja2            3.1.3

Numerical (optional)
├☑ sklearn           1.3.2
├☑ numba             0.58.1
├☑ nibabel           5.2.0
├☑ nilearn           0.10.2
├☑ dipy              1.8.0
├☑ openmeeg          2.5.7
├☑ cupy              12.3.0
└☑ pandas            2.1.4

Visualization (optional)
├☑ pyvista           0.43.2 (OpenGL 4.3 (Core Profile) Mesa 23.0.4-0ubuntu1~22.04.1 via NV166)
├☑ pyvistaqt         0.11.0
├☑ vtk               9.3.0
├☑ qtpy              2.4.1 (PyQt6=6.6.0)
├☑ ipympl            0.9.3
├☑ pyqtgraph         0.13.3
├☑ mne-qt-browser    0.7.0.dev9+g547b976
├☑ ipywidgets        8.1.1
├☑ trame_client      2.14.2
├☑ trame_server      2.15.0
├☑ trame_vtk         2.7.0
└☑ trame_vuetify     2.3.1

Ecosystem (optional)
├☑ mne-bids          0.15.0.dev24+g05b8e6b8
├☑ mne-nirs          0.7.0.dev0
├☑ mne-connectivity  0.7.0.dev0
├☑ mne-bids-pipeline 1.6.0.dev8+g9055e9c
├☑ neo               0.13.0
└☐ unavailable       mne-features, mne-icalabel
```


Discussion:
Check, can reproduce on my end too. Will have a look.

Resolving patch:
diff --git a/mne/io/neuralynx/neuralynx.py b/mne/io/neuralynx/neuralynx.py
--- a/mne/io/neuralynx/neuralynx.py
+++ b/mne/io/neuralynx/neuralynx.py
@@ -12,53 +12,6 @@
 from ..base import BaseRaw
 
 
-class AnalogSignalGap:
-    """Dummy object to represent gaps in Neuralynx data.
-
-    Creates a AnalogSignalProxy-like object.
-    Propagate `signal`, `units`, and `sampling_rate` attributes
-    to the `AnalogSignal` init returned by `load()`.
-
-    Parameters
-    ----------
-    signal : array-like
-        Array of shape (n_samples, n_chans) containing the data.
-    units : str
-        Units of the data. (e.g., 'uV')
-    sampling_rate : quantity
-        Sampling rate of the data. (e.g., 4000 * pq.Hz)
-
-    Returns
-    -------
-    sig : instance of AnalogSignal
-        A AnalogSignal object representing a gap in Neuralynx data.
-    """
-
-    def __init__(self, signal, units, sampling_rate):
-        self.signal = signal
-        self.units = units
-        self.sampling_rate = sampling_rate
-
-    def load(self, **kwargs):
-        """Return AnalogSignal object."""
-        _soft_import("neo", "Reading NeuralynxIO files", strict=True)
-        from neo import AnalogSignal
-
-        # `kwargs` is a dummy argument to mirror the
-        # AnalogSignalProxy.load() call signature which
-        # accepts `channel_indexes`` argument; but here we don't need
-        # any extra data selection arguments since
-        # self.signal array is already in the correct shape
-        # (channel dimension is based on `idx` variable)
-
-        sig = AnalogSignal(
-            signal=self.signal,
-            units=self.units,
-            sampling_rate=self.sampling_rate,
-        )
-        return sig
-
-
 @fill_doc
 def read_raw_neuralynx(
     fname, *, preload=False, exclude_fname_patterns=None, verbose=None
@@ -258,8 +211,9 @@ def __init__(
 
     def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
         """Read a chunk of raw data."""
-        from neo import Segment
+        from neo import AnalogSignal, Segment
         from neo.io import NeuralynxIO
+        from neo.io.proxyobjects import AnalogSignalProxy
 
         # quantities is a dependency of neo so we are guaranteed it exists
         from quantities import Hz
@@ -338,7 +292,7 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
         )
 
         for seg, n in zip(gap_segments, gap_samples):
-            asig = AnalogSignalGap(
+            asig = AnalogSignal(
                 signal=np.zeros((n, n_chans)), units="uV", sampling_rate=sfreq * Hz
             )
             seg.analogsignals.append(asig)
@@ -351,13 +305,16 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
         segments_arr[~isgap] = neo_block[0].segments
         segments_arr[isgap] = gap_segments
 
-        # now load data from selected segments/channels via
-        # neo.Segment.AnalogSignal.load() or AnalogSignalGap.load()
+        # now load data for selected segments/channels via
+        # neo.Segment.AnalogSignalProxy.load() or
+        # pad directly as AnalogSignal.magnitude for any gap data
         all_data = np.concatenate(
             [
                 signal.load(channel_indexes=idx).magnitude[
                     samples[0] : samples[-1] + 1, :
                 ]
+                if isinstance(signal, AnalogSignalProxy)
+                else signal.magnitude[samples[0] : samples[-1] + 1, :]
                 for seg, samples in zip(
                     segments_arr[first_seg : last_seg + 1], sel_samples_local
                 )


Test patch: