Skip to content

Commit 7581f98

Browse files
authored
Merge pull request #1842 from alejoe91/spikeglx-probe-table
Spikeglx: use probe features from ProbeTable to infer Neuropixels type
2 parents e35a0d7 + ba2b86b commit 7581f98

1 file changed

Lines changed: 51 additions & 22 deletions

File tree

neo/rawio/spikeglxrawio.py

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,22 @@
3535
https://billkarsh.github.io/SpikeGLX/#metadata-guides
3636
https://github.com/SpikeInterface/spikeextractors/blob/master/spikeextractors/extractors/spikeglxrecordingextractor/spikeglxrecordingextractor.py
3737
38-
This reader handle:
39-
40-
imDatPrb_type=1 (NP 1.0)
41-
imDatPrb_type=21 (NP 2.0, single multiplexed shank)
42-
imDatPrb_type=24 (NP 2.0, 4-shank)
43-
imDatPrb_type=1030 (NP 1.0-NHP 45mm SOI90 - NHP long 90um wide, staggered contacts)
44-
imDatPrb_type=1031 (NP 1.0-NHP 45mm SOI125 - NHP long 125um wide, staggered contacts)
45-
imDatPrb_type=1032 (NP 1.0-NHP 45mm SOI115 / 125 linear - NHP long 125um wide, linear contacts)
46-
imDatPrb_type=1022 (NP 1.0-NHP 25mm - NHP medium)
47-
imDatPrb_type=1015 (NP 1.0-NHP 10mm - NHP short)
48-
49-
Author : Samuel Garcia
38+
For the "imec" device, this reader handles 1.0 and 2.0 Neuropixels probes.
39+
The probe-type is identified by the `imDatPrb_pn` field in the meta file
40+
and checked agains the ProbeTable info (https://raw.githubusercontent.com/billkarsh/ProbeTable/refs/heads/main/Tables/probe_features.json).
41+
It uses the "datasheet" field in the meta file to identify the whether the probe is 1.0 or 2.0.
42+
Neuropixels NXT/3.0 will return unscaled int16 data, since the gain for NP3.0 is not
43+
yet implemented as it is not yet clear how to get it from the meta file.
44+
45+
Author : Samuel Garcia, Alessio Buccino, Heberto Mayorquin
5046
Some functions are copied from Graham Findlay
5147
"""
5248

53-
from pathlib import Path
5449
import os
5550
import re
51+
from pathlib import Path
5652
from warnings import warn
53+
import json
5754

5855
import numpy as np
5956

@@ -68,6 +65,21 @@
6865
from .utils import get_memmap_shape
6966

7067

68+
neuropixels_probe_features_file = Path(__file__).parents[1] / "resources" / "neuropixels_probe_features.json"
69+
70+
71+
def _is_1_0_probe(features):
72+
"""
73+
Check if the probe is a Neuropixels 1.0 based on the datasheet /
74+
description / databus_decoder field in the features dict.
75+
"""
76+
datasheet_string = features.get("datasheet", "")
77+
description_string = features.get("description", "")
78+
databus_decoder_string = features.get("databus_decoder", "")
79+
search_string = datasheet_string + description_string + databus_decoder_string
80+
return "1.0" in search_string
81+
82+
7183
class SpikeGLXRawIO(BaseRawWithBufferApiIO):
7284
"""
7385
Class for reading data from a SpikeGLX system
@@ -655,11 +667,22 @@ def extract_stream_info(meta_file, meta):
655667
# metad['imroTbl'] contain two gain per channel AP and LF
656668
# except for the last fake channel
657669
per_channel_gain = np.ones(num_chan, dtype="float64")
658-
if (
659-
"imDatPrb_type" not in meta
660-
or meta["imDatPrb_type"] == "0"
661-
or meta["imDatPrb_type"] in ("1015", "1016", "1022", "1030", "1031", "1032", "1100", "1121", "1123", "1300")
662-
):
670+
probe_part_number = meta.get("imDatPrb_pn", None)
671+
with open(neuropixels_probe_features_file, "r") as f:
672+
probe_features = json.load(f)
673+
674+
probe_part_number = meta.get("imDatPrb_pn", None)
675+
if probe_part_number is None and meta.get("imProbeOpt") is not None:
676+
probe_part_number = "NP1010" # Phase3A remap, matches probeinterface
677+
if probe_part_number is None:
678+
raise ValueError("Could not determine probe part number from metadata.")
679+
680+
features = probe_features["neuropixels_probes"].get(probe_part_number)
681+
if features is None:
682+
raise ValueError(f"Probe part number {probe_part_number} not found in ProbeTable.")
683+
684+
# For NP 1.0 probes, the gain is stored in the imroTbl field of the metadata, and the scaling factor is imAiRangeMax / 512
685+
if _is_1_0_probe(features):
663686
# This work with NP 1.0 case with different metadata versions
664687
# https://github.com/billkarsh/SpikeGLX/blob/15ec8898e17829f9f08c226bf04f46281f106e5f/Markdown/Metadata_30.md
665688
if stream_kind == "ap":
@@ -671,20 +694,26 @@ def extract_stream_info(meta_file, meta):
671694
per_channel_gain[c] = 1.0 / float(v)
672695
gain_factor = float(meta["imAiRangeMax"]) / 512
673696
channel_gains = gain_factor * per_channel_gain * 1e6
674-
elif meta["imDatPrb_type"] in ("21", "24", "2003", "2004", "2013", "2014"):
697+
else:
675698
# This work with NP 2.0 case with different metadata versions
676699
# https://github.com/billkarsh/SpikeGLX/blob/15ec8898e17829f9f08c226bf04f46281f106e5f/Markdown/Metadata_30.md#imec
677700
# We allow also LF streams for NP2.0 because CatGT can produce them
678701
# See: https://github.com/SpikeInterface/spikeinterface/issues/1949
679702
if "imChan0apGain" in meta:
680703
per_channel_gain[:-1] = 1 / float(meta["imChan0apGain"])
681704
else:
682-
per_channel_gain[:-1] = 1 / 80.0
705+
# For 2.0+ probes, the gain is not configurable, so we can take it directly from the probe features.
706+
gain_list = features["ap_gain_list"].split(",")
707+
if len(gain_list) > 1:
708+
raise ValueError(
709+
f"Found multiple gains in probe features for stream kind {stream_kind}, but expected only one "
710+
f"since gain is not configurable for NP 2.0 probes. Gain list: {gain_list}"
711+
)
712+
default_gain = float(gain_list[0])
713+
per_channel_gain[:-1] = 1 / default_gain
683714
max_int = int(meta["imMaxInt"]) if "imMaxInt" in meta else 8192
684715
gain_factor = float(meta["imAiRangeMax"]) / max_int
685716
channel_gains = gain_factor * per_channel_gain * 1e6
686-
else:
687-
raise NotImplementedError("This meta file version of spikeglx" " is not implemented")
688717
elif meta.get("typeThis") == "obx":
689718
# OneBox case
690719
device = fname.split(".")[-2] if "." in fname else device

0 commit comments

Comments
 (0)