From 373741c4dca30f541138c202c462a3e5a3eda3f5 Mon Sep 17 00:00:00 2001 From: Greg Knoll Date: Thu, 28 Mar 2024 12:46:09 +0100 Subject: [PATCH 1/8] NEW: probe reader for Neuropixels 1.0 in SpikeGadgets .rec file. --- src/probeinterface/__init__.py | 2 + src/probeinterface/io.py | 181 ++++++++++++++++++++++++++++++++- 2 files changed, 181 insertions(+), 2 deletions(-) diff --git a/src/probeinterface/__init__.py b/src/probeinterface/__init__.py index a35ebe0b..57987173 100644 --- a/src/probeinterface/__init__.py +++ b/src/probeinterface/__init__.py @@ -16,6 +16,8 @@ write_imro, read_BIDS_probe, write_BIDS_probe, + read_spikegadgets, + parse_spikegadgets_header, read_spikeglx, parse_spikeglx_meta, get_saved_channel_indices_from_spikeglx_meta, diff --git a/src/probeinterface/io.py b/src/probeinterface/io.py index 7aa2c5bd..8c7a2300 100644 --- a/src/probeinterface/io.py +++ b/src/probeinterface/io.py @@ -19,6 +19,7 @@ from collections import OrderedDict from packaging.version import Version, parse import numpy as np +from xml.etree import ElementTree from . import __version__ from .probe import Probe @@ -1263,12 +1264,188 @@ def write_imro(file: str | Path, probe: Probe): f.write("".join(ret)) +def read_spikegadgets(file: str | Path) -> ProbeGroup: + """ + Find active channels of the given Neuropixels probe from a SpikeGadgets .rec file. + SpikeGadgets headstages support up to three Neuropixels 1.0 probes (as of March 28, 2024), + and information for all probes will be returned in a ProbeGroup object. + + Within the SpikeConfiguration header element (sconf), there is a SpikeNTrode element + for each electrophysiology channel that contains information relevant to scaling and + otherwise displaying the information from that channel, as well as the id of the electrode + from which it is recording ('id'). + + Nested within each SpikeNTrode element is a SpikeChannel element with information about + the electrode dynamically connected to that channel. This contains information relevant + for spike sorting, i.e., its spatial location along the probe shank and the hardware channel + to which it is connected. + + Excerpt of a sample SpikeConfiguration element: + + + + + + ... + + + Parameters + ---------- + file : Path or str + The .rec file path + + Returns + ------- + probe_group : ProbeGroup object + + """ + # ------------------------- # + # Npix 1.0 constants # + # ------------------------- # + TOTAL_NPIX_ELECTRODES = 960 + MAX_ACTIVE_CHANNELS = 384 + CONTACT_WIDTH = 16 # um + CONTACT_HEIGHT = 20 # um + # ------------------------- # + + # Read the header and get Configuration elements + header_txt = parse_spikegadgets_header(file) + root = ElementTree.fromstring(header_txt) + hconf = root.find("HardwareConfiguration") + sconf = root.find("SpikeConfiguration") + + # Get number of probes present (each has its own Device element) + probe_configs = [device for device in hconf if device.attrib['name'] == 'NeuroPixels1'] + n_probes = len(probe_configs) + print(n_probes, "Neuropixels 1.0 probes found:") + + # Container to store Probe objects + probe_group = ProbeGroup() + + for curr_probe in range(1, n_probes + 1): + print(f'Reading probe{curr_probe}...', flush=True, end='') + probe_config = probe_configs[curr_probe - 1] + + # Get number of active channels from probe Device element + active_channel_str = [option for option in probe_config if option.attrib['name'] == 'channelsOn'][0].attrib['data'] + active_channels = [int(ch) for ch in active_channel_str.split(' ') if ch] + n_active_channels = sum(active_channels) + assert len(active_channels) == TOTAL_NPIX_ELECTRODES + assert n_active_channels <= MAX_ACTIVE_CHANNELS + + # Find all channels/electrodes that belong to the current probe + contact_ids = [] + device_channels = [] + positions = np.zeros((n_active_channels, 2)) + + nt_i = 0 # Both probes are in sconf, so need an independent counter of probe electrodes while iterating through + for ntrode in sconf: + electrode_id = ntrode.attrib['id'] + if int(electrode_id[0]) == curr_probe: # first digit of electrode id is probe number + contact_ids.append(electrode_id) + positions[nt_i, :] = (ntrode[0].attrib['coord_ml'], ntrode[0].attrib['coord_dv']) + device_channels.append(ntrode[0].attrib['hwChan']) + nt_i += 1 + assert len(contact_ids) == n_active_channels + + # Construct Probe object + probe = Probe(ndim=2, si_units="um", model_name="Neuropixels 1.0", manufacturer="IMEC") + probe.set_contacts( + contact_ids=contact_ids, + positions=positions, + shapes="square", + shank_ids=None, + shape_params={"width": CONTACT_WIDTH, "height": CONTACT_HEIGHT}, + ) + + # Wire it (i.e., point contact/electrode ids to corresponding hardware/channel ids) + probe.set_device_channel_indices(device_channels) + + # Create a nice polygon background when plotting the probes + x_min = positions[:, 0].min() + x_max = positions[:, 0].max() + x_mid = 0.5 * (x_max + x_min) + y_min = positions[:, 1].min() + y_max = positions[:, 1].max() + polygon_default = [ + (x_min - 20, y_min - CONTACT_HEIGHT / 2), + (x_mid, y_min - 100), + (x_max + 20, y_min - CONTACT_HEIGHT / 2), + (x_max + 20, y_max + 20), + (x_min - 20, y_max + 20), + ] + probe.set_planar_contour(polygon_default) + + # If there are multiple probes, they must be shifted such that they don't occupy the same coordinates. + probe.move([250 * (curr_probe - 1), 0]) + + # Add the probe to the probe container + print(probe.get_contact_count(), 'active channels found.') + probe_group.add_probe(probe) + + return probe_group + + +def parse_spikegadgets_header(file: str | Path) -> str: + """ + Parse file (SpikeGadgets .rec format) into a string until "", + which is the last tag of the header, after which the binary data begins. + """ + header_size = None + with open(file, mode="rb") as f: + while True: + line = f.readline() + if b"" in line: + header_size = f.tell() + break + + if header_size is None: + ValueError("SpikeGadgets: the xml header does not contain ''") + + f.seek(0) + return f.read(header_size).decode("utf8") + + def read_spikeglx(file: str | Path) -> Probe: """ Read probe position for the meta file generated by SpikeGLX See http://billkarsh.github.io/SpikeGLX/#metadata-guides for implementation. - The x_pitch/y_pitch/width are set automatically depending the NP version. + The x_pitch/y_pitch/width are set automatically depending on the NP version. The shape is auto generated as a shank. @@ -1333,7 +1510,7 @@ def read_spikeglx(file: str | Path) -> Probe: def parse_spikeglx_meta(meta_file: str | Path) -> dict: """ Parse the "meta" file from spikeglx into a dict. - All fiields are kept in txt format and must also parsed themself. + All fields are kept in txt format and must also be parsed themselves. """ meta_file = Path(meta_file) with meta_file.open(mode="r") as f: From 0273d9b47e3e1b08a302d21956136ba499d24884 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 12:01:09 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/probeinterface/io.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/probeinterface/io.py b/src/probeinterface/io.py index 8c7a2300..951c0e8e 100644 --- a/src/probeinterface/io.py +++ b/src/probeinterface/io.py @@ -1349,7 +1349,7 @@ def read_spikegadgets(file: str | Path) -> ProbeGroup: sconf = root.find("SpikeConfiguration") # Get number of probes present (each has its own Device element) - probe_configs = [device for device in hconf if device.attrib['name'] == 'NeuroPixels1'] + probe_configs = [device for device in hconf if device.attrib["name"] == "NeuroPixels1"] n_probes = len(probe_configs) print(n_probes, "Neuropixels 1.0 probes found:") @@ -1357,12 +1357,14 @@ def read_spikegadgets(file: str | Path) -> ProbeGroup: probe_group = ProbeGroup() for curr_probe in range(1, n_probes + 1): - print(f'Reading probe{curr_probe}...', flush=True, end='') + print(f"Reading probe{curr_probe}...", flush=True, end="") probe_config = probe_configs[curr_probe - 1] # Get number of active channels from probe Device element - active_channel_str = [option for option in probe_config if option.attrib['name'] == 'channelsOn'][0].attrib['data'] - active_channels = [int(ch) for ch in active_channel_str.split(' ') if ch] + active_channel_str = [option for option in probe_config if option.attrib["name"] == "channelsOn"][0].attrib[ + "data" + ] + active_channels = [int(ch) for ch in active_channel_str.split(" ") if ch] n_active_channels = sum(active_channels) assert len(active_channels) == TOTAL_NPIX_ELECTRODES assert n_active_channels <= MAX_ACTIVE_CHANNELS @@ -1374,11 +1376,11 @@ def read_spikegadgets(file: str | Path) -> ProbeGroup: nt_i = 0 # Both probes are in sconf, so need an independent counter of probe electrodes while iterating through for ntrode in sconf: - electrode_id = ntrode.attrib['id'] + electrode_id = ntrode.attrib["id"] if int(electrode_id[0]) == curr_probe: # first digit of electrode id is probe number contact_ids.append(electrode_id) - positions[nt_i, :] = (ntrode[0].attrib['coord_ml'], ntrode[0].attrib['coord_dv']) - device_channels.append(ntrode[0].attrib['hwChan']) + positions[nt_i, :] = (ntrode[0].attrib["coord_ml"], ntrode[0].attrib["coord_dv"]) + device_channels.append(ntrode[0].attrib["hwChan"]) nt_i += 1 assert len(contact_ids) == n_active_channels @@ -1414,7 +1416,7 @@ def read_spikegadgets(file: str | Path) -> ProbeGroup: probe.move([250 * (curr_probe - 1), 0]) # Add the probe to the probe container - print(probe.get_contact_count(), 'active channels found.') + print(probe.get_contact_count(), "active channels found.") probe_group.add_probe(probe) return probe_group From 123e0c571c623e68bbfed46f301808b51819dc57 Mon Sep 17 00:00:00 2001 From: Greg Knoll Date: Thu, 28 Mar 2024 15:21:31 +0100 Subject: [PATCH 3/8] ADD: spikegadgets reader tests and test header file. --- ..._2xNpix1.0_20240318_173658_header_only.rec | 2389 +++++++++++++++++ tests/test_io/test_spikegadgets.py | 35 + 2 files changed, 2424 insertions(+) create mode 100644 tests/data/spikegadgets/SpikeGadgets_test_data_2xNpix1.0_20240318_173658_header_only.rec create mode 100644 tests/test_io/test_spikegadgets.py diff --git a/tests/data/spikegadgets/SpikeGadgets_test_data_2xNpix1.0_20240318_173658_header_only.rec b/tests/data/spikegadgets/SpikeGadgets_test_data_2xNpix1.0_20240318_173658_header_only.rec new file mode 100644 index 00000000..7ce6737e --- /dev/null +++ b/tests/data/spikegadgets/SpikeGadgets_test_data_2xNpix1.0_20240318_173658_header_only.rec @@ -0,0 +1,2389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_io/test_spikegadgets.py b/tests/test_io/test_spikegadgets.py new file mode 100644 index 00000000..fae20232 --- /dev/null +++ b/tests/test_io/test_spikegadgets.py @@ -0,0 +1,35 @@ +from pathlib import Path +from xml.etree import ElementTree + +import pytest + +from probeinterface import ( + read_spikegadgets, + parse_spikegadgets_header, +) + +data_path = Path(__file__).absolute().parent.parent / "data" / "spikegadgets" +test_file = "SpikeGadgets_test_data_2xNpix1.0_20240318_173658_header_only.rec" + + +def test_parse_meta(): + header_txt = parse_spikegadgets_header(data_path / test_file) + root = ElementTree.fromstring(header_txt) + assert root.find("GlobalConfiguration") is not None + assert root.find("HardwareConfiguration") is not None + assert root.find("SpikeConfiguration") is not None + + +def test_neuropixels_1_reader(): + probe_group = read_spikegadgets(data_path / test_file) + assert len(probe_group.probes) == 2 + for probe in probe_group.probes: + assert "1.0" in probe.model_name + assert probe.get_shank_count() == 1 + assert probe.get_contact_count() == 384 + assert probe_group.get_contact_count() == 768 + + +if __name__ == "__main__": + test_parse_meta() + test_neuropixels_1_reader() From 1b11861c1dfb721b42076d173e641fb7b863ac64 Mon Sep 17 00:00:00 2001 From: Greg Knoll Date: Thu, 28 Mar 2024 16:21:08 +0100 Subject: [PATCH 4/8] Update src/probeinterface/__init__.py Co-authored-by: Alessio Buccino --- src/probeinterface/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/probeinterface/__init__.py b/src/probeinterface/__init__.py index 57987173..c9bfe389 100644 --- a/src/probeinterface/__init__.py +++ b/src/probeinterface/__init__.py @@ -17,7 +17,6 @@ read_BIDS_probe, write_BIDS_probe, read_spikegadgets, - parse_spikegadgets_header, read_spikeglx, parse_spikeglx_meta, get_saved_channel_indices_from_spikeglx_meta, From 9eab1a7489b2dfcb8e6af55187e202ebfd2fac8d Mon Sep 17 00:00:00 2001 From: Greg Knoll Date: Thu, 28 Mar 2024 16:34:40 +0100 Subject: [PATCH 5/8] Move sample element out of docstring, remove print. --- src/probeinterface/io.py | 110 +++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/src/probeinterface/io.py b/src/probeinterface/io.py index 951c0e8e..13a866b6 100644 --- a/src/probeinterface/io.py +++ b/src/probeinterface/io.py @@ -1270,58 +1270,6 @@ def read_spikegadgets(file: str | Path) -> ProbeGroup: SpikeGadgets headstages support up to three Neuropixels 1.0 probes (as of March 28, 2024), and information for all probes will be returned in a ProbeGroup object. - Within the SpikeConfiguration header element (sconf), there is a SpikeNTrode element - for each electrophysiology channel that contains information relevant to scaling and - otherwise displaying the information from that channel, as well as the id of the electrode - from which it is recording ('id'). - - Nested within each SpikeNTrode element is a SpikeChannel element with information about - the electrode dynamically connected to that channel. This contains information relevant - for spike sorting, i.e., its spatial location along the probe shank and the hardware channel - to which it is connected. - - Excerpt of a sample SpikeConfiguration element: - - - - - - ... - Parameters ---------- @@ -1351,13 +1299,12 @@ def read_spikegadgets(file: str | Path) -> ProbeGroup: # Get number of probes present (each has its own Device element) probe_configs = [device for device in hconf if device.attrib["name"] == "NeuroPixels1"] n_probes = len(probe_configs) - print(n_probes, "Neuropixels 1.0 probes found:") + print(n_probes, "Neuropixels 1.0 probes found.") # Container to store Probe objects probe_group = ProbeGroup() for curr_probe in range(1, n_probes + 1): - print(f"Reading probe{curr_probe}...", flush=True, end="") probe_config = probe_configs[curr_probe - 1] # Get number of active channels from probe Device element @@ -1369,6 +1316,60 @@ def read_spikegadgets(file: str | Path) -> ProbeGroup: assert len(active_channels) == TOTAL_NPIX_ELECTRODES assert n_active_channels <= MAX_ACTIVE_CHANNELS + """ + Within the SpikeConfiguration header element (sconf), there is a SpikeNTrode element + for each electrophysiology channel that contains information relevant to scaling and + otherwise displaying the information from that channel, as well as the id of the electrode + from which it is recording ('id'). + + Nested within each SpikeNTrode element is a SpikeChannel element with information about + the electrode dynamically connected to that channel. This contains information relevant + for spike sorting, i.e., its spatial location along the probe shank and the hardware channel + to which it is connected. + + Excerpt of a sample SpikeConfiguration element: + + + + + + ... + + """ # Find all channels/electrodes that belong to the current probe contact_ids = [] device_channels = [] @@ -1416,7 +1417,6 @@ def read_spikegadgets(file: str | Path) -> ProbeGroup: probe.move([250 * (curr_probe - 1), 0]) # Add the probe to the probe container - print(probe.get_contact_count(), "active channels found.") probe_group.add_probe(probe) return probe_group From 4ce4791e6d7e557e409d7bd4b44fb3db9350be63 Mon Sep 17 00:00:00 2001 From: Greg Knoll Date: Thu, 28 Mar 2024 16:43:07 +0100 Subject: [PATCH 6/8] Update imports in test to access internal function. --- tests/test_io/test_spikegadgets.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_io/test_spikegadgets.py b/tests/test_io/test_spikegadgets.py index fae20232..5cb1814e 100644 --- a/tests/test_io/test_spikegadgets.py +++ b/tests/test_io/test_spikegadgets.py @@ -3,10 +3,8 @@ import pytest -from probeinterface import ( - read_spikegadgets, - parse_spikegadgets_header, -) +from probeinterface import read_spikegadgets +from probeinterface.io import parse_spikegadgets_header data_path = Path(__file__).absolute().parent.parent / "data" / "spikegadgets" test_file = "SpikeGadgets_test_data_2xNpix1.0_20240318_173658_header_only.rec" From 790bff27f17d97f9117d34f32538e76f49389aac Mon Sep 17 00:00:00 2001 From: Greg Knoll Date: Thu, 28 Mar 2024 16:47:35 +0100 Subject: [PATCH 7/8] Removes last print statement in read_spikegadgets. --- src/probeinterface/io.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/probeinterface/io.py b/src/probeinterface/io.py index 13a866b6..ef79f451 100644 --- a/src/probeinterface/io.py +++ b/src/probeinterface/io.py @@ -1299,7 +1299,6 @@ def read_spikegadgets(file: str | Path) -> ProbeGroup: # Get number of probes present (each has its own Device element) probe_configs = [device for device in hconf if device.attrib["name"] == "NeuroPixels1"] n_probes = len(probe_configs) - print(n_probes, "Neuropixels 1.0 probes found.") # Container to store Probe objects probe_group = ProbeGroup() From fdf725d11aad267b5b8cb95173db3964d4568758 Mon Sep 17 00:00:00 2001 From: Greg Knoll Date: Fri, 29 Mar 2024 19:32:48 +0100 Subject: [PATCH 8/8] ADD: check if no probes found. If so, return None or raise Exception. --- src/probeinterface/io.py | 7 ++++++- tests/test_io/test_spikegadgets.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/probeinterface/io.py b/src/probeinterface/io.py index ef79f451..0646c757 100644 --- a/src/probeinterface/io.py +++ b/src/probeinterface/io.py @@ -1264,7 +1264,7 @@ def write_imro(file: str | Path, probe: Probe): f.write("".join(ret)) -def read_spikegadgets(file: str | Path) -> ProbeGroup: +def read_spikegadgets(file: str | Path, raise_error: bool = True) -> ProbeGroup: """ Find active channels of the given Neuropixels probe from a SpikeGadgets .rec file. SpikeGadgets headstages support up to three Neuropixels 1.0 probes (as of March 28, 2024), @@ -1300,6 +1300,11 @@ def read_spikegadgets(file: str | Path) -> ProbeGroup: probe_configs = [device for device in hconf if device.attrib["name"] == "NeuroPixels1"] n_probes = len(probe_configs) + if n_probes == 0: + if raise_error: + raise Exception("No Neuropixels 1.0 probes found") + return None + # Container to store Probe objects probe_group = ProbeGroup() diff --git a/tests/test_io/test_spikegadgets.py b/tests/test_io/test_spikegadgets.py index 5cb1814e..7415db9e 100644 --- a/tests/test_io/test_spikegadgets.py +++ b/tests/test_io/test_spikegadgets.py @@ -19,7 +19,7 @@ def test_parse_meta(): def test_neuropixels_1_reader(): - probe_group = read_spikegadgets(data_path / test_file) + probe_group = read_spikegadgets(data_path / test_file, raise_error=False) assert len(probe_group.probes) == 2 for probe in probe_group.probes: assert "1.0" in probe.model_name