Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions neo/io/spikeglxio.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 1 addition & 1 deletion neo/rawio/baserawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
27 changes: 5 additions & 22 deletions neo/rawio/openephysbinaryrawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand Down Expand Up @@ -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:]

Expand Down
42 changes: 9 additions & 33 deletions neo/rawio/spikeglxrawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -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]

Expand Down
57 changes: 13 additions & 44 deletions neo/test/rawiotest/test_openephysbinaryrawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
78 changes: 19 additions & 59 deletions neo/test/rawiotest/test_spikeglxrawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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."""
Expand Down
Loading