diff --git a/src/spikeinterface/extractors/neoextractors/openephys.py b/src/spikeinterface/extractors/neoextractors/openephys.py index c6774e622e..b019280c9a 100644 --- a/src/spikeinterface/extractors/neoextractors/openephys.py +++ b/src/spikeinterface/extractors/neoextractors/openephys.py @@ -315,54 +315,36 @@ def __init__( # find settings file if "#" in stream_name: - record_node, oe_stream = stream_name.split("#") + record_node, oe_stream_name = stream_name.split("#") else: record_node = "" - oe_stream = stream_name - exp_ids = sorted(list(self.neo_reader.folder_structure[record_node]["experiments"].keys())) + oe_stream_name = stream_name + node_structure = self.neo_reader.folder_structure[record_node] + exp_ids = sorted(list(node_structure["experiments"].keys())) if block_index is None: exp_id = exp_ids[0] else: exp_id = exp_ids[block_index] - rec_ids = sorted( - list(self.neo_reader.folder_structure[record_node]["experiments"][exp_id]["recordings"].keys()) - ) + rec_ids = sorted(list(node_structure["experiments"][exp_id]["recordings"].keys())) # do not load probe for NIDQ stream or if load_sync_channel is True if "NI-DAQmx" not in stream_name and not load_sync_channel: - settings_file = self.neo_reader.folder_structure[record_node]["experiments"][exp_id]["settings_file"] + settings_file = node_structure["experiments"][exp_id]["settings_file"] if Path(settings_file).is_file(): probe = probeinterface.read_openephys( - settings_file=settings_file, stream_name=stream_name, raise_error=False + settings_file=settings_file, stream_name=oe_stream_name, raise_error=False ) else: probe = None if probe is not None: - # Ensure device channel index corresponds to channel_ids - probe_channel_names = probe.contact_annotations.get("channel_name", None) - if probe_channel_names is not None and not np.array_equal(probe_channel_names, self.channel_ids): - if set(probe_channel_names) == set(self.channel_ids): - device_channel_indices = [] - probe_channel_names = list(probe_channel_names) - device_channel_indices = np.zeros(len(self.channel_ids), dtype=int) - for i, ch in enumerate(self.channel_ids): - index_in_probe = probe_channel_names.index(ch) - device_channel_indices[index_in_probe] = i - probe.set_device_channel_indices(device_channel_indices) - else: - warnings.warn( - "Channel names in the probe do not match the channel ids from Neo. " - "Cannot set device channel indices, but this might lead to incorrect probe geometries" - ) - if probe.shank_ids is not None: self.set_probe(probe, in_place=True, group_mode="by_shank") else: self.set_probe(probe, in_place=True) # get inter-sample shifts based on the probe information and mux channels - sample_shifts = get_neuropixels_sample_shifts_from_probe(probe, stream_name=self.stream_name) + sample_shifts = get_neuropixels_sample_shifts_from_probe(probe) if sample_shifts is not None: self.set_property("inter_sample_shift", sample_shifts) @@ -371,7 +353,7 @@ def __init__( stream_folders = [] for segment_index, rec_id in enumerate(rec_ids): stream_folder = ( - recording_folder / f"experiment{exp_id}" / f"recording{rec_id}" / "continuous" / oe_stream + recording_folder / f"experiment{exp_id}" / f"recording{rec_id}" / "continuous" / oe_stream_name ) stream_folders.append(stream_folder) if load_sync_timestamps: diff --git a/src/spikeinterface/extractors/neoextractors/spikeglx.py b/src/spikeinterface/extractors/neoextractors/spikeglx.py index 79a7eb96e6..56e2dbb3fd 100644 --- a/src/spikeinterface/extractors/neoextractors/spikeglx.py +++ b/src/spikeinterface/extractors/neoextractors/spikeglx.py @@ -94,7 +94,7 @@ def __init__( self.set_probe(probe, in_place=True) # get inter-sample shifts based on the probe information and mux channels - sample_shifts = get_neuropixels_sample_shifts_from_probe(probe, stream_name=self.stream_name) + sample_shifts = get_neuropixels_sample_shifts_from_probe(probe) if sample_shifts is not None: self.set_property("inter_sample_shift", sample_shifts) else: diff --git a/src/spikeinterface/extractors/neuropixels_utils.py b/src/spikeinterface/extractors/neuropixels_utils.py index 9ecb71b414..39388e77ff 100644 --- a/src/spikeinterface/extractors/neuropixels_utils.py +++ b/src/spikeinterface/extractors/neuropixels_utils.py @@ -4,7 +4,7 @@ from probeinterface import Probe -def get_neuropixels_sample_shifts_from_probe(probe: Probe, stream_name: str = "ap") -> np.ndarray: +def get_neuropixels_sample_shifts_from_probe(probe: Probe) -> np.ndarray: """ Get the inter-sample shifts for Neuropixels probes based on the probe information. @@ -12,9 +12,6 @@ def get_neuropixels_sample_shifts_from_probe(probe: Probe, stream_name: str = "a ---------- probe : Probe The probe object containing channel and ADC information. - stream_name : str, default: "ap" - The name of the stream for which to calculate the sample shifts. - This is used for Neuropixels 1.0 technology to correctly set the number of cycles. Returns ------- diff --git a/src/spikeinterface/extractors/tests/test_neoextractors.py b/src/spikeinterface/extractors/tests/test_neoextractors.py index f40b4d05ab..b90a6da53b 100644 --- a/src/spikeinterface/extractors/tests/test_neoextractors.py +++ b/src/spikeinterface/extractors/tests/test_neoextractors.py @@ -6,6 +6,7 @@ import importlib.util import pytest +import numpy as np from spikeinterface import get_global_dataset_folder from spikeinterface.extractors.extractor_classes import ( @@ -121,9 +122,6 @@ class OpenEphysBinaryRecordingTest(RecordingCommonTestSuite, unittest.TestCase): ("openephysbinary/v0.5.x_two_nodes", {"stream_id": "0"}), ("openephysbinary/v0.5.x_two_nodes", {"stream_id": "1"}), ("openephysbinary/v0.6.x_neuropixels_multiexp_multistream", {"stream_id": "0", "block_index": 0}), - # TODO: block_indices 1/2 of v0.6.x_neuropixels_multiexp_multistream have a mismatch in the channel names between - # the settings files (starting with CH0) and structure.oebin (starting at CH1). - # Currently, the extractor will skip remapping to match order in oebin and settings file, raising a warning ("openephysbinary/v0.6.x_neuropixels_multiexp_multistream", {"stream_id": "1", "block_index": 1}), ( "openephysbinary/v0.6.x_neuropixels_multiexp_multistream", @@ -134,8 +132,30 @@ class OpenEphysBinaryRecordingTest(RecordingCommonTestSuite, unittest.TestCase): "openephysbinary/v0.6.x_neuropixels_multiexp_multistream", {"stream_id": "2", "block_index": 2, "load_sync_timestamps": True}, ), + ( + "openephysbinary/v0.6.x_onebox_neuropixels", + {"stream_name": "Record Node 101#OneBox-100.ProbeA-AP", "block_index": 0}, + ), + ( + "openephysbinary/v0.6.x_onebox_neuropixels_nontrivial_wiring", + {"stream_name": "Record Node 101#OneBox-111.ProbeA", "block_index": 0}, + ), ] + def test_non_trivial_wiring(self): + """ + Test that we can load the probe information and sample shifts for a one box neuropixels recording with + non trivial wiring. + """ + folder_path = local_folder / "openephysbinary/v0.6.x_onebox_neuropixels_nontrivial_wiring" + stream_name = "Record Node 101#OneBox-111.ProbeA" + block_index = 0 + + recording = self.ExtractorClass(folder_path, stream_name=stream_name, block_index=block_index) + # check that channel_ids and settings_channel_key contact annotations are correctly loaded + probe = recording.get_probe() + np.testing.assert_array_equal(recording.channel_ids, probe.contact_annotations["settings_channel_key"]) + class OpenEphysBinaryEventTest(EventCommonTestSuite, unittest.TestCase): ExtractorClass = OpenEphysBinaryEventExtractor