diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 01be54e9..5e1cb072 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -6,6 +6,7 @@ Release notes .. toctree:: :maxdepth: 1 + releases/0.4.1.rst releases/0.4.0.rst releases/0.3.2.rst releases/0.3.1.rst diff --git a/doc/releases/0.4.1.rst b/doc/releases/0.4.1.rst new file mode 100644 index 00000000..9eb0b6e7 --- /dev/null +++ b/doc/releases/0.4.1.rst @@ -0,0 +1,9 @@ +probeinterface 0.4.1 +-------------------- + +Oct, 13th 2026 + + +Bug fixes +^^^^^^^^^ +* OpenEphys: check channel maps on same signal chain as NP probe (#471) diff --git a/src/probeinterface/neuropixels_tools.py b/src/probeinterface/neuropixels_tools.py index f98c0866..aa35e5b0 100644 --- a/src/probeinterface/neuropixels_tools.py +++ b/src/probeinterface/neuropixels_tools.py @@ -1032,29 +1032,38 @@ def _parse_openephys_settings( channel_map = None channel_map_position = None record_node_position = None - proc_counter = 0 + channel_map_chain = None + record_node_chain = None + probe_chain = None - for signal_chain in root.findall("SIGNALCHAIN"): + for chain_index, signal_chain in enumerate(root.findall("SIGNALCHAIN")): + proc_counter = 0 for processor in signal_chain: if "PROCESSOR" == processor.tag: name = processor.attrib["name"] if "Neuropix-PXI" in name: neuropix_pxi_processor = processor + probe_chain = chain_index if "OneBox" in name: onebox_processor = processor + probe_chain = chain_index if "ONIX" in name: onix_processor = processor + probe_chain = chain_index if "Channel Map" in name: channel_map = processor channel_map_position = proc_counter + channel_map_chain = chain_index if "Record Node" in name: record_node_position = proc_counter + record_node_chain = chain_index proc_counter += 1 - # Check if Channel Map comes before Record Node + # Channel Map must be in the same signal chain as the probe and the Record Node, and come before the latter channel_map_before_record_node = ( channel_map_position is not None and record_node_position is not None + and channel_map_chain == record_node_chain == probe_chain and channel_map_position < record_node_position ) @@ -1720,14 +1729,14 @@ def get_saved_channel_indices_from_openephys_settings(settings_file: str | Path, tree = ET.parse(str(settings_file)) root = tree.getroot() - signal_chain = root.find("SIGNALCHAIN") record_node = None - for processor in signal_chain: - if "PROCESSOR" == processor.tag: - name = processor.attrib["name"] - if "Record Node" in name: + for signal_chain in root.findall("SIGNALCHAIN"): + for processor in signal_chain: + if "PROCESSOR" == processor.tag and "Record Node" in processor.attrib["name"]: record_node = processor break + if record_node is not None: + break chans_saved = None if record_node is not None: custom_params = record_node.find("CUSTOM_PARAMETERS") diff --git a/tests/data/openephys/OE_1.0_Neuropix-multiple-chains/settings.xml b/tests/data/openephys/OE_1.0_Neuropix-multiple-chains/settings.xml new file mode 100644 index 00000000..38e294a4 --- /dev/null +++ b/tests/data/openephys/OE_1.0_Neuropix-multiple-chains/settings.xml @@ -0,0 +1,772 @@ + + + + + 1.1.0-preview + 10 + 27 May 2026 11:17:04 + Windows 11 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_io/test_openephys.py b/tests/test_io/test_openephys.py index de51616b..13dc5d41 100644 --- a/tests/test_io/test_openephys.py +++ b/tests/test_io/test_openephys.py @@ -348,6 +348,21 @@ def test_quadbase_custom_names(): ) +def test_probe_with_channel_map_on_different_chain(): + # Tests that a NP probe is correctly instantiated if the settings has a channel map on a different signal chain + probe = read_openephys_neuropixels( + data_path / "OE_1.0_Neuropix-multiple-chains" / "settings.xml", probe_name="ProbeA" + ) + probe_dict = probe.to_dict(array_as_list=True) + validate_probe_dict(probe_dict) + assert probe.get_shank_count() == 4 + assert probe.get_contact_count() == 384 + assert set(probe.shank_ids) == set(["0", "1", "2", "3"]) + assert probe.name == "ProbeA" + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations + + def test_onebox(): # This dataset has a Neuropixels Ultra probe with a onebox probe = read_openephys_neuropixels(data_path / "OE_OneBox-NP-Ultra" / "settings.xml")