Repository: mne-tools/mne-python

Issue:
[BUG] `read_raw_neuralynx()` fails when a single channel is picked prior to loading data with temporal gaps
### Description of the problem

`read_raw_neuralynx()` data loading fails when 1 (non-initial) channel is picked prior to calling `raw.load_data()`.

This is because `AnalogSignalGap.load()`, which loads zero-arrays representing data gaps, indexes along channel axis in `self.signal` with `idx`. But this is not needed since `self.signal` is constructed based on the size of `idx` variable. indexing with `slice(1, 2)` on a 1-dimensional array yields a 0-dim array along channel axis which breaks concatenation with real data.

PR: #12357

### Steps to reproduce

```Python
from mne.io import read_raw_neuralynx
from mne.datasets.testing import data_path

testing_path = data_path(download=False) / "neuralynx"
exclude = ['LAHC1.ncs', 'LAHC2.ncs', 'LAHC3.ncs', 'xAIR1.ncs', 'xEKG1.ncs', 'LAHCu1.ncs'] # ignore channels with no gaps

raw = read_raw_neuralynx(testing_path, preload=False, exclude_fname_patterns=exclude) # channel names: ['LACH1', 'LAHC2']
raw.pick(picks="LAHC2")
raw.load_data()
```


### Link to data

_No response_

### Expected results

Loads the data and returns raw instance.

### Actual results

`np.concatenate()` in l. 353 of `neuralynx.py` will yield dimension-mismatch error along channel dimension (real data arrays will be 1-dimensional and zero-arrays will be 0-dimensional).

### Additional information

Platform             Linux-5.10.102.1-microsoft-standard-WSL2-x86_64-with-glibc2.31
Python               3.11.6 | packaged by conda-forge | (main, Oct  3 2023, 10:40:35) [GCC 12.3.0]
Executable           /home/kriarm/miniconda3/envs/mnedev/bin/python3.11
CPU                  x86_64 (20 cores)
Memory               31.2 GB

Core
├☒ mne               1.6.0.dev163+g7ff8c586e (outdated, release 1.6.0 is available!)
├☑ numpy             1.24.4 (OpenBLAS 0.3.24 with 20 threads)
├☑ scipy             1.11.3
├☑ matplotlib        3.8.0 (backend=agg)
├☑ pooch             1.8.0
└☑ jinja2            3.1.2

Numerical (optional)
├☑ sklearn           1.3.2
├☑ numba             0.57.1
├☑ nibabel           5.1.0
├☑ nilearn           0.10.2
├☑ dipy              1.7.0
├☑ openmeeg          2.5.6
├☑ pandas            2.1.2
└☐ unavailable       cupy

Visualization (optional)
├☑ pyvista           0.42.3 (OpenGL unavailable)
├☑ pyvistaqt         0.0.0
├☑ vtk               9.2.6
├☑ qtpy              2.4.1 (None=None)
├☑ ipympl            0.9.3
├☑ pyqtgraph         0.13.3
├☑ mne-qt-browser    0.6.0
├☑ ipywidgets        8.1.1
├☑ trame_client      2.12.6
├☑ trame_server      2.12.1
├☑ trame_vtk         2.5.9
└☑ trame_vuetify     2.3.1

Ecosystem (optional)
├☑ mne-bids          0.14.dev0
├☑ mne-connectivity  0.6.0dev0
├☑ neo               0.12.0
└☐ unavailable       mne-nirs, mne-features, mne-icalabel, mne-bids-pipeline


Discussion:

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
@@ -22,7 +22,7 @@ class AnalogSignalGap:
     Parameters
     ----------
     signal : array-like
-        Array of shape (n_channels, n_samples) containing the data.
+        Array of shape (n_samples, n_chans) containing the data.
     units : str
         Units of the data. (e.g., 'uV')
     sampling_rate : quantity
@@ -39,13 +39,20 @@ def __init__(self, signal, units, sampling_rate):
         self.units = units
         self.sampling_rate = sampling_rate
 
-    def load(self, channel_indexes):
+    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[:, channel_indexes],
+            signal=self.signal,
             units=self.units,
             sampling_rate=self.sampling_rate,
         )
@@ -141,8 +148,7 @@ def __init__(
             sfreq=nlx_reader.get_signal_sampling_rate(),
         )
 
-        # find total number of samples per .ncs file (`channel`) by summing
-        # the sample sizes of all segments
+        # Neo reads only valid contiguous .ncs samples grouped as segments
         n_segments = nlx_reader.header["nb_segment"][0]
         block_id = 0  # assumes there's only one block of recording
 
@@ -160,7 +166,7 @@ def __init__(
         seg_diffs = next_start_times - previous_stop_times
 
         # mark as discontinuous any two segments that have
-        # start/stop delta larger than sampling period (1/sampling_rate)
+        # start/stop delta larger than sampling period (1.5/sampling_rate)
         logger.info("Checking for temporal discontinuities in Neo data segments.")
         delta = 1.5 / info["sfreq"]
         gaps = seg_diffs > delta


Test patch:
diff --git a/mne/io/neuralynx/tests/test_neuralynx.py b/mne/io/neuralynx/tests/test_neuralynx.py
--- a/mne/io/neuralynx/tests/test_neuralynx.py
+++ b/mne/io/neuralynx/tests/test_neuralynx.py
@@ -219,3 +219,14 @@ def test_neuralynx_gaps():
     assert_allclose(
         mne_y, mat_y, rtol=1e-6, err_msg="MNE and Nlx2MatCSC.m not all close"
     )
+
+    # test that channel selection works
+    raw = read_raw_neuralynx(
+        fname=testing_path,
+        preload=False,
+        exclude_fname_patterns=ignored_ncs_files,
+    )
+
+    raw.pick("LAHC2")
+    assert raw.ch_names == ["LAHC2"]
+    raw.load_data()  # before gh-12357 this would fail