From 5ca0db11dc9d4585459fe82fb2738757e4a7ca2e Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Tue, 21 Apr 2026 14:47:14 -0600 Subject: [PATCH 1/3] remove load_sync_channels_spikeglx --- neo/rawio/spikeglxrawio.py | 42 +++---------- neo/test/rawiotest/test_spikeglxrawio.py | 78 ++++++------------------ 2 files changed, 28 insertions(+), 92 deletions(-) diff --git a/neo/rawio/spikeglxrawio.py b/neo/rawio/spikeglxrawio.py index d59c2aa94..3cf2826ef 100644 --- a/neo/rawio/spikeglxrawio.py +++ b/neo/rawio/spikeglxrawio.py @@ -76,10 +76,6 @@ class SpikeGLXRawIO(BaseRawWithBufferApiIO): ---------- dirname: str, default: '' The spikeglx folder containing meta/bin files - load_sync_channel: bool, default: False - Can be used to load the synch stream as the last channel of the neural data. - This option is deprecated and will be removed in version 0.15. - From versions higher than 0.14.1 the sync channel is always loaded as a separate stream. load_channel_location: bool, default: False If True probeinterface is used to load the channel locations from the directory @@ -108,17 +104,9 @@ class SpikeGLXRawIO(BaseRawWithBufferApiIO): extensions = ["meta", "bin"] rawmode = "one-dir" - def __init__(self, dirname="", load_sync_channel=False, load_channel_location=False): + def __init__(self, dirname="", load_channel_location=False): BaseRawWithBufferApiIO.__init__(self) self.dirname = dirname - self.load_sync_channel = load_sync_channel - if load_sync_channel: - warn( - "The load_sync_channel=True option is deprecated and will be removed in version 0.15 \n" - "The sync channel is now loaded as a separate stream by default and should be accessed as such. ", - DeprecationWarning, - stacklevel=2, - ) self.load_channel_location = load_channel_location def _source_name(self): @@ -179,10 +167,8 @@ def _parse_header(self): chan_name = info["channel_names"][local_channel_index] chan_id = f"{stream_name}#{chan_name}" - # Separate sync channel in its own stream - is_sync_channel = "SY" in chan_name and not self.load_sync_channel - if is_sync_channel: - # This is a sync channel and should be added as its own stream + # Separate sync channel in its own stream. + if "SY" in chan_name: sync_stream_id = f"{stream_name}-SYNC" sync_stream_id_to_buffer_id[sync_stream_id] = buffer_id stream_id_for_chan = sync_stream_id @@ -207,19 +193,12 @@ def _parse_header(self): # None here means that the all the channels in the buffer will be included self._stream_buffer_slice[stream_id] = None - # Then we modify this if sync channel is present to slice the last channel - # out of the stream buffer - if "nidq" not in stream_name: - if not self.load_sync_channel and info["has_sync_trace"]: - # the last channel is removed from the stream but not from the buffer - self._stream_buffer_slice[stream_id] = slice(0, -1) - - # Add a buffer slice for the sync channel - sync_stream_id = f"{stream_name}-SYNC" - self._stream_buffer_slice[sync_stream_id] = slice(-1, None) - - if self.load_sync_channel and not info["has_sync_trace"]: - raise ValueError("SYNC channel is not present in the recording. " "Set load_sync_channel to False") + # If a sync trace is present, slice the last channel out of the parent + # stream and expose it as the companion -SYNC stream sharing the same buffer. + if "nidq" not in stream_name and info["has_sync_trace"]: + self._stream_buffer_slice[stream_id] = slice(0, -1) + sync_stream_id = f"{stream_name}-SYNC" + self._stream_buffer_slice[sync_stream_id] = slice(-1, None) signal_buffers = np.array(signal_buffers, dtype=_signal_buffer_dtype) @@ -318,9 +297,6 @@ def _parse_header(self): # only for ap channel probe = probeinterface.read_spikeglx(info["meta_file"]) loc = probe.contact_positions - if self.load_sync_channel: - # one fake channel for "sys0" - loc = np.concatenate((loc, [[0.0, 0.0]]), axis=0) for ndim in range(loc.shape[1]): sig_ann["__array_annotations__"][f"channel_location_{ndim}"] = loc[:, ndim] diff --git a/neo/test/rawiotest/test_spikeglxrawio.py b/neo/test/rawiotest/test_spikeglxrawio.py index 7f2b891a6..ef87cce9e 100644 --- a/neo/test/rawiotest/test_spikeglxrawio.py +++ b/neo/test/rawiotest/test_spikeglxrawio.py @@ -75,46 +75,25 @@ def test_with_location(self): assert any(have_location) def test_sync(self): - rawio_with_sync = SpikeGLXRawIO(self.get_local_path("spikeglx/NP2_with_sync"), load_sync_channel=True) - rawio_with_sync.parse_header() - stream_index = list(rawio_with_sync.header["signal_streams"]["name"]).index("imec0.ap") - - # AP stream has 385 channels - chunk = rawio_with_sync.get_analogsignal_chunk( - block_index=0, seg_index=0, i_start=0, i_stop=100, stream_index=stream_index - ) - assert chunk.shape[1] == 385 - - rawio_no_sync = SpikeGLXRawIO(self.get_local_path("spikeglx/NP2_with_sync"), load_sync_channel=False) - rawio_no_sync.parse_header() + # The sync trace is always split off into its own -SYNC stream; the parent + # AP stream has 384 channels (384 neural channels, SY0 excluded). + rawio = SpikeGLXRawIO(self.get_local_path("spikeglx/NP2_with_sync")) + rawio.parse_header() + stream_index = list(rawio.header["signal_streams"]["name"]).index("imec0.ap") - # AP stream has 384 channels - chunk = rawio_no_sync.get_analogsignal_chunk( + chunk = rawio.get_analogsignal_chunk( block_index=0, seg_index=0, i_start=0, i_stop=100, stream_index=stream_index ) assert chunk.shape[1] == 384 - def test_no_sync(self): - # requesting sync channel when there is none raises an error - with self.assertRaises(ValueError): - rawio_no_sync = SpikeGLXRawIO(self.get_local_path("spikeglx/NP2_no_sync"), load_sync_channel=True) - rawio_no_sync.parse_header() - def test_subset_with_sync(self): - rawio_sub = SpikeGLXRawIO(self.get_local_path("spikeglx/NP2_subset_with_sync"), load_sync_channel=True) - rawio_sub.parse_header() - stream_index = list(rawio_sub.header["signal_streams"]["name"]).index("imec0.ap") - - # AP stream has 121 channels - chunk = rawio_sub.get_analogsignal_chunk( - block_index=0, seg_index=0, i_start=0, i_stop=100, stream_index=stream_index - ) - assert chunk.shape[1] == 121 + # Channel-subset recording with SY: 121 saved channels total, 120 neural plus + # one SY that is split into the -SYNC stream, leaving 120 in the parent. + rawio = SpikeGLXRawIO(self.get_local_path("spikeglx/NP2_subset_with_sync")) + rawio.parse_header() + stream_index = list(rawio.header["signal_streams"]["name"]).index("imec0.ap") - rawio_sub_no_sync = SpikeGLXRawIO(self.get_local_path("spikeglx/NP2_subset_with_sync"), load_sync_channel=False) - rawio_sub_no_sync.parse_header() - # AP stream has 120 channels - chunk = rawio_sub_no_sync.get_analogsignal_chunk( + chunk = rawio.get_analogsignal_chunk( block_index=0, seg_index=0, i_start=0, i_stop=100, stream_index=stream_index ) assert chunk.shape[1] == 120 @@ -135,32 +114,13 @@ def test_nidq_digital_channel(self): assert np.allclose(on_diff, 1, atol=atol) def test_sync_channel_as_separate_stream(self): - """Test that sync channel is added as its own stream when load_sync_channel=False.""" - import warnings - - # Test with load_sync_channel=False (default) - rawio_no_sync = SpikeGLXRawIO(self.get_local_path("spikeglx/NP2_with_sync"), load_sync_channel=False) - rawio_no_sync.parse_header() - - # Get stream names - stream_names = rawio_no_sync.header["signal_streams"]["name"].tolist() - - # Check if there's a sync channel stream (should contain "SY0" or "SYNC" in the name) - sync_streams = [name for name in stream_names if "SY0" in name or "SYNC" in name] - assert len(sync_streams) > 0, "No sync channel stream found when load_sync_channel=False" - - # Test deprecation warning when load_sync_channel=True - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - rawio_with_sync = SpikeGLXRawIO(self.get_local_path("spikeglx/NP2_with_sync"), load_sync_channel=True) - - # Check if deprecation warning was raised - assert any( - issubclass(warning.category, DeprecationWarning) for warning in w - ), "No deprecation warning raised" - assert any( - "will be removed in version 0.15" in str(warning.message) for warning in w - ), "Deprecation warning message is incorrect" + """Sync trace is always exposed as its own -SYNC stream.""" + rawio = SpikeGLXRawIO(self.get_local_path("spikeglx/NP2_with_sync")) + rawio.parse_header() + + stream_names = rawio.header["signal_streams"]["name"].tolist() + sync_streams = [name for name in stream_names if "SYNC" in name] + assert len(sync_streams) > 0, "No -SYNC stream found" def test_t_start_reading(self): """Test that t_start values are correctly read for all streams and segments.""" From dcd5f0db782c3c9104207e9920686f61546568f7 Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Tue, 21 Apr 2026 14:56:14 -0600 Subject: [PATCH 2/3] comment --- neo/rawio/baserawio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neo/rawio/baserawio.py b/neo/rawio/baserawio.py index 47e3572eb..f68931504 100644 --- a/neo/rawio/baserawio.py +++ b/neo/rawio/baserawio.py @@ -1698,7 +1698,7 @@ def _get_analogsignal_chunk( else: raise NotImplementedError() - # this is a pre slicing when the stream do not contain all channels (for instance spikeglx when load_sync_channel=False) + # this is a pre slicing when the stream does not contain all channels in the buffer (for instance spikeglx -SYNC streams) if buffer_slice is not None: raw_sigs = raw_sigs[:, buffer_slice] From 1280284af9fa175c9c52e113b288826d20e795ca Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Tue, 21 Apr 2026 15:05:31 -0600 Subject: [PATCH 3/3] remove openephys --- neo/io/spikeglxio.py | 6 +- neo/rawio/openephysbinaryrawio.py | 27 ++------- .../rawiotest/test_openephysbinaryrawio.py | 57 +++++-------------- 3 files changed, 20 insertions(+), 70 deletions(-) diff --git a/neo/io/spikeglxio.py b/neo/io/spikeglxio.py index 534b7b602..a3bf2374e 100644 --- a/neo/io/spikeglxio.py +++ b/neo/io/spikeglxio.py @@ -6,8 +6,6 @@ class SpikeGLXIO(SpikeGLXRawIO, BaseFromRaw): __doc__ = SpikeGLXRawIO.__doc__ mode = "dir" - def __init__(self, dirname, load_sync_channel=False, load_channel_location=False): - SpikeGLXRawIO.__init__( - self, dirname=dirname, load_sync_channel=load_sync_channel, load_channel_location=load_channel_location - ) + def __init__(self, dirname, load_channel_location=False): + SpikeGLXRawIO.__init__(self, dirname=dirname, load_channel_location=load_channel_location) BaseFromRaw.__init__(self, dirname) diff --git a/neo/rawio/openephysbinaryrawio.py b/neo/rawio/openephysbinaryrawio.py index bd767f1e0..d2aefc5d7 100644 --- a/neo/rawio/openephysbinaryrawio.py +++ b/neo/rawio/openephysbinaryrawio.py @@ -36,9 +36,6 @@ class OpenEphysBinaryRawIO(BaseRawWithBufferApiIO): ---------- dirname : str Path to Open Ephys directory - load_sync_channel : bool - If False (default) and a SYNC channel is present (e.g. Neuropixels), this is not loaded. - If True, the SYNC channel is loaded and can be accessed in the analog signals. experiment_names : str or list or None If multiple experiments are available, this argument allows users to select one or more experiments. If None, all experiements are loaded as blocks. @@ -109,21 +106,13 @@ class OpenEphysBinaryRawIO(BaseRawWithBufferApiIO): extensions = ["xml", "oebin", "txt", "dat", "npy"] rawmode = "one-dir" - def __init__(self, dirname="", load_sync_channel=False, experiment_names=None): + def __init__(self, dirname="", experiment_names=None): BaseRawWithBufferApiIO.__init__(self) self.dirname = dirname if experiment_names is not None: if isinstance(experiment_names, str): experiment_names = [experiment_names] self.experiment_names = experiment_names - self.load_sync_channel = load_sync_channel - if load_sync_channel: - warn( - "The load_sync_channel=True option is deprecated and will be removed in version 0.15. " - "Use load_sync_channel=False instead, which will add sync channels as separate streams.", - DeprecationWarning, - stacklevel=2, - ) self.folder_structure = None self._use_direct_evt_timestamps = None @@ -200,7 +189,7 @@ def _parse_header(self): units = "uV" if "ADC" not in chan_id else "V" # Special cases for stream - if "SYNC" in chan_id and not self.load_sync_channel: + if "SYNC" in chan_id: # Every stream sync channel is added as its own stream sync_stream_id = f"{stream_name}SYNC" sync_stream_id_to_buffer_id[sync_stream_id] = buffer_id @@ -296,12 +285,6 @@ def _parse_header(self): has_sync_trace = self._sig_streams[block_index][seg_index][stream_index]["has_sync_trace"] - # check sync channel validity (only for AP and LF) - if not has_sync_trace and self.load_sync_channel and "NI-DAQ" not in info["stream_name"]: - raise ValueError( - "SYNC channel is not present in the recording. " "Set load_sync_channel to False" - ) - # Check if ADC and non-ADC channels are contiguous is_channel_adc = ["ADC" in ch["channel_name"] for ch in info["channels"]] if any(is_channel_adc): @@ -343,7 +326,7 @@ def _parse_header(self): num_adc_channels = 0 if num_adc_channels == 0: - if has_sync_trace and not self.load_sync_channel: + if has_sync_trace: # Exclude the sync channel from the main stream self._stream_buffer_slice[stream_id] = slice(None, -1) @@ -358,7 +341,7 @@ def _parse_header(self): self._stream_buffer_slice[stream_id_neural] = slice(0, num_neural_channels) - if has_sync_trace and not self.load_sync_channel: + if has_sync_trace: # Exclude the sync channel from the non-neural stream self._stream_buffer_slice[stream_id_non_neural] = slice(num_neural_channels, -1) @@ -564,7 +547,7 @@ def _parse_header(self): if has_sync_trace: values = values[:-1] - if "SYNC" in stream_name and not self.load_sync_channel: + if "SYNC" in stream_name: # This is the sync stream, we only keep the last value values = values[-1:] diff --git a/neo/test/rawiotest/test_openephysbinaryrawio.py b/neo/test/rawiotest/test_openephysbinaryrawio.py index 4300b9b78..0119a4b53 100644 --- a/neo/test/rawiotest/test_openephysbinaryrawio.py +++ b/neo/test/rawiotest/test_openephysbinaryrawio.py @@ -21,36 +21,21 @@ class TestOpenEphysBinaryRawIO(BaseTestRawIO, unittest.TestCase): ] def test_sync(self): - with self.assertWarns(DeprecationWarning): - rawio_with_sync = OpenEphysBinaryRawIO( - self.get_local_path("openephysbinary/v0.6.x_neuropixels_with_sync"), load_sync_channel=True - ) - rawio_with_sync.parse_header() - stream_name = [s_name for s_name in rawio_with_sync.header["signal_streams"]["name"] if "AP" in s_name][0] - stream_index = list(rawio_with_sync.header["signal_streams"]["name"]).index(stream_name) - - # AP stream has 385 channels - chunk = rawio_with_sync.get_analogsignal_chunk( - block_index=0, seg_index=0, i_start=0, i_stop=100, stream_index=stream_index - ) - assert chunk.shape[1] == 385 - - rawio_no_sync = OpenEphysBinaryRawIO( - self.get_local_path("openephysbinary/v0.6.x_neuropixels_with_sync"), load_sync_channel=False - ) - rawio_no_sync.parse_header() + # The sync trace is always split off into its own -SYNC stream; the parent + # AP stream has 384 channels (384 neural, SYNC excluded). + rawio = OpenEphysBinaryRawIO(self.get_local_path("openephysbinary/v0.6.x_neuropixels_with_sync")) + rawio.parse_header() + stream_name = [s_name for s_name in rawio.header["signal_streams"]["name"] if "AP" in s_name][0] + stream_index = list(rawio.header["signal_streams"]["name"]).index(stream_name) - # AP stream has 384 channels - chunk = rawio_no_sync.get_analogsignal_chunk( + chunk = rawio.get_analogsignal_chunk( block_index=0, seg_index=0, i_start=0, i_stop=100, stream_index=stream_index ) assert chunk.shape[1] == 384 def test_sync_channel_access(self): - """Test that sync channels can be accessed as separate streams when load_sync_channel=False.""" - rawio = OpenEphysBinaryRawIO( - self.get_local_path("openephysbinary/v0.6.x_neuropixels_with_sync"), load_sync_channel=False - ) + """Sync channels are exposed as their own streams.""" + rawio = OpenEphysBinaryRawIO(self.get_local_path("openephysbinary/v0.6.x_neuropixels_with_sync")) rawio.parse_header() # Find sync channel streams @@ -68,28 +53,14 @@ def test_sync_channel_access(self): # Sync channel should have only one channel assert chunk.shape[1] == 1, f"Expected sync channel to have 1 channel, got {chunk.shape[1]}" - def test_no_sync(self): - # requesting sync channel when there is none raises an error - with self.assertRaises(ValueError): - with self.assertWarns(DeprecationWarning): - rawio_no_sync = OpenEphysBinaryRawIO( - self.get_local_path("openephysbinary/v0.6.x_neuropixels_multiexp_multistream"), - load_sync_channel=True, - ) - rawio_no_sync.parse_header() - def test_missing_folders(self): - # missing folders should raise an error + # missing folders should raise a warning with self.assertWarns(UserWarning): - rawio = OpenEphysBinaryRawIO( - self.get_local_path("openephysbinary/v0.6.x_neuropixels_missing_folders"), load_sync_channel=False - ) + rawio = OpenEphysBinaryRawIO(self.get_local_path("openephysbinary/v0.6.x_neuropixels_missing_folders")) rawio.parse_header() def test_multiple_ttl_events_parsing(self): - rawio = OpenEphysBinaryRawIO( - self.get_local_path("openephysbinary/v0.6.x_neuropixels_with_sync"), load_sync_channel=False - ) + rawio = OpenEphysBinaryRawIO(self.get_local_path("openephysbinary/v0.6.x_neuropixels_with_sync")) rawio.parse_header() rawio.header = rawio.header # Testing co @@ -106,9 +77,7 @@ def test_multiple_ttl_events_parsing(self): assert np.allclose(ttl_events["durations"][ttl_events["labels"] == "7"], 0.016666, atol=0.001) def test_separating_stream_for_non_neural_data(self): - rawio = OpenEphysBinaryRawIO( - self.get_local_path("openephysbinary/neural_and_non_neural_data_mixed"), load_sync_channel=False - ) + rawio = OpenEphysBinaryRawIO(self.get_local_path("openephysbinary/neural_and_non_neural_data_mixed")) rawio.parse_header() # Check that the non-neural data stream is correctly separated assert len(rawio.header["signal_streams"]["name"]) == 2