-
Notifications
You must be signed in to change notification settings - Fork 272
Spikeglx: use probe features from ProbeTable to infer Neuropixels type #1842
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
2bb4745
f8b4334
4e2b453
8c2738e
8266a29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -35,25 +35,22 @@ | |||||||||||||||
| https://billkarsh.github.io/SpikeGLX/#metadata-guides | ||||||||||||||||
| https://github.com/SpikeInterface/spikeextractors/blob/master/spikeextractors/extractors/spikeglxrecordingextractor/spikeglxrecordingextractor.py | ||||||||||||||||
|
|
||||||||||||||||
| This reader handle: | ||||||||||||||||
|
|
||||||||||||||||
| imDatPrb_type=1 (NP 1.0) | ||||||||||||||||
| imDatPrb_type=21 (NP 2.0, single multiplexed shank) | ||||||||||||||||
| imDatPrb_type=24 (NP 2.0, 4-shank) | ||||||||||||||||
| imDatPrb_type=1030 (NP 1.0-NHP 45mm SOI90 - NHP long 90um wide, staggered contacts) | ||||||||||||||||
| imDatPrb_type=1031 (NP 1.0-NHP 45mm SOI125 - NHP long 125um wide, staggered contacts) | ||||||||||||||||
| imDatPrb_type=1032 (NP 1.0-NHP 45mm SOI115 / 125 linear - NHP long 125um wide, linear contacts) | ||||||||||||||||
| imDatPrb_type=1022 (NP 1.0-NHP 25mm - NHP medium) | ||||||||||||||||
| imDatPrb_type=1015 (NP 1.0-NHP 10mm - NHP short) | ||||||||||||||||
|
|
||||||||||||||||
| Author : Samuel Garcia | ||||||||||||||||
| For the "imec" device, this reader handles 1.0 and 2.0 Neuropixels probes. | ||||||||||||||||
| The probe-type is identified by the `imDatPrb_pn` field in the meta file | ||||||||||||||||
| and checked agains the ProbeTable info (https://raw.githubusercontent.com/billkarsh/ProbeTable/refs/heads/main/Tables/probe_features.json). | ||||||||||||||||
| It uses the "datasheet" field in the meta file to identify the whether the probe is 1.0 or 2.0. | ||||||||||||||||
| Neuropixels NXT/3.0 will return unscaled int16 data, since the gain for NP3.0 is not | ||||||||||||||||
| yet implemented as it is not yet clear how to get it from the meta file. | ||||||||||||||||
|
|
||||||||||||||||
| Author : Samuel Garcia, Alessio Buccino, Heberto Mayorquin | ||||||||||||||||
| Some functions are copied from Graham Findlay | ||||||||||||||||
| """ | ||||||||||||||||
|
|
||||||||||||||||
| from pathlib import Path | ||||||||||||||||
| import os | ||||||||||||||||
| import re | ||||||||||||||||
| from pathlib import Path | ||||||||||||||||
| from warnings import warn | ||||||||||||||||
| import json | ||||||||||||||||
|
|
||||||||||||||||
| import numpy as np | ||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -68,6 +65,9 @@ | |||||||||||||||
| from .utils import get_memmap_shape | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| neuropixels_probe_features_file = Path(__file__).parents[1] / "resources" / "neuropixels_probe_features.json" | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| class SpikeGLXRawIO(BaseRawWithBufferApiIO): | ||||||||||||||||
| """ | ||||||||||||||||
| Class for reading data from a SpikeGLX system | ||||||||||||||||
|
|
@@ -679,36 +679,60 @@ def extract_stream_info(meta_file, meta): | |||||||||||||||
| # metad['imroTbl'] contain two gain per channel AP and LF | ||||||||||||||||
| # except for the last fake channel | ||||||||||||||||
| per_channel_gain = np.ones(num_chan, dtype="float64") | ||||||||||||||||
| if ( | ||||||||||||||||
| "imDatPrb_type" not in meta | ||||||||||||||||
| or meta["imDatPrb_type"] == "0" | ||||||||||||||||
| or meta["imDatPrb_type"] in ("1015", "1016", "1022", "1030", "1031", "1032", "1100", "1121", "1123", "1300") | ||||||||||||||||
| ): | ||||||||||||||||
| # This work with NP 1.0 case with different metadata versions | ||||||||||||||||
| # https://github.com/billkarsh/SpikeGLX/blob/15ec8898e17829f9f08c226bf04f46281f106e5f/Markdown/Metadata_30.md | ||||||||||||||||
| if stream_kind == "ap": | ||||||||||||||||
| index_imroTbl = 3 | ||||||||||||||||
| elif stream_kind == "lf": | ||||||||||||||||
| index_imroTbl = 4 | ||||||||||||||||
| for c in range(num_chan - 1): | ||||||||||||||||
| v = meta["imroTbl"][c].split(" ")[index_imroTbl] | ||||||||||||||||
| per_channel_gain[c] = 1.0 / float(v) | ||||||||||||||||
| gain_factor = float(meta["imAiRangeMax"]) / 512 | ||||||||||||||||
| channel_gains = gain_factor * per_channel_gain * 1e6 | ||||||||||||||||
| elif meta["imDatPrb_type"] in ("21", "24", "2003", "2004", "2013", "2014"): | ||||||||||||||||
| # This work with NP 2.0 case with different metadata versions | ||||||||||||||||
| # https://github.com/billkarsh/SpikeGLX/blob/15ec8898e17829f9f08c226bf04f46281f106e5f/Markdown/Metadata_30.md#imec | ||||||||||||||||
| # We allow also LF streams for NP2.0 because CatGT can produce them | ||||||||||||||||
| # See: https://github.com/SpikeInterface/spikeinterface/issues/1949 | ||||||||||||||||
| if "imChan0apGain" in meta: | ||||||||||||||||
| per_channel_gain[:-1] = 1 / float(meta["imChan0apGain"]) | ||||||||||||||||
| probe_part_number = meta.get("imDatPrb_pn", None) | ||||||||||||||||
| with open(neuropixels_probe_features_file, "r") as f: | ||||||||||||||||
| probe_features = json.load(f) | ||||||||||||||||
|
|
||||||||||||||||
| if probe_part_number is not None: | ||||||||||||||||
| features = probe_features["neuropixels_probes"].get(probe_part_number) | ||||||||||||||||
| if features is not None: | ||||||||||||||||
| datasheet = features.get("datasheet", "unknown") | ||||||||||||||||
| if "1.0" in datasheet: | ||||||||||||||||
| # This work with NP 1.0 case with different metadata versions | ||||||||||||||||
| # https://github.com/billkarsh/SpikeGLX/blob/15ec8898e17829f9f08c226bf04f46281f106e5f/Markdown/Metadata_30.md | ||||||||||||||||
| if stream_kind == "ap": | ||||||||||||||||
| index_imroTbl = 3 | ||||||||||||||||
| elif stream_kind == "lf": | ||||||||||||||||
| index_imroTbl = 4 | ||||||||||||||||
| for c in range(num_chan - 1): | ||||||||||||||||
| v = meta["imroTbl"][c].split(" ")[index_imroTbl] | ||||||||||||||||
| per_channel_gain[c] = 1.0 / float(v) | ||||||||||||||||
| gain_factor = float(meta["imAiRangeMax"]) / 512 | ||||||||||||||||
| channel_gains = gain_factor * per_channel_gain * 1e6 | ||||||||||||||||
| elif "2.0" in datasheet: | ||||||||||||||||
| # This work with NP 2.0 case with different metadata versions | ||||||||||||||||
| # https://github.com/billkarsh/SpikeGLX/blob/15ec8898e17829f9f08c226bf04f46281f106e5f/Markdown/Metadata_30.md#imec | ||||||||||||||||
| # We allow also LF streams for NP2.0 because CatGT can produce them | ||||||||||||||||
| # See: https://github.com/SpikeInterface/spikeinterface/issues/1949 | ||||||||||||||||
| if "imChan0apGain" in meta: | ||||||||||||||||
| per_channel_gain[:-1] = 1 / float(meta["imChan0apGain"]) | ||||||||||||||||
| else: | ||||||||||||||||
| per_channel_gain[:-1] = 1 / 80.0 | ||||||||||||||||
| max_int = int(meta["imMaxInt"]) if "imMaxInt" in meta else 8192 | ||||||||||||||||
| gain_factor = float(meta["imAiRangeMax"]) / max_int | ||||||||||||||||
| channel_gains = gain_factor * per_channel_gain * 1e6 | ||||||||||||||||
| else: | ||||||||||||||||
| warn( | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same argument applies inside the gain branch: I think we should raise an error Raising matches master's behaviour which I think is better than letting wrong gains silently propagate.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will become easier with a logic like the one in: |
||||||||||||||||
| f"Unknown probe datasheet version for probe part number {probe_part_number}. " | ||||||||||||||||
| f"Unitary gains will be used.", | ||||||||||||||||
| UserWarning, | ||||||||||||||||
| stacklevel=2, | ||||||||||||||||
| ) | ||||||||||||||||
| channel_gains = np.ones(num_chan, dtype="float64") | ||||||||||||||||
| else: | ||||||||||||||||
| per_channel_gain[:-1] = 1 / 80.0 | ||||||||||||||||
| max_int = int(meta["imMaxInt"]) if "imMaxInt" in meta else 8192 | ||||||||||||||||
| gain_factor = float(meta["imAiRangeMax"]) / max_int | ||||||||||||||||
| channel_gains = gain_factor * per_channel_gain * 1e6 | ||||||||||||||||
| warn( | ||||||||||||||||
| f"Unknown probe part number {probe_part_number}. Unitary gains will be used.", | ||||||||||||||||
| UserWarning, | ||||||||||||||||
| stacklevel=2, | ||||||||||||||||
| ) | ||||||||||||||||
| channel_gains = np.ones(num_chan, dtype="float64") | ||||||||||||||||
| else: | ||||||||||||||||
| raise NotImplementedError("This meta file version of spikeglx" " is not implemented") | ||||||||||||||||
| warn( | ||||||||||||||||
| "Could not find probe part number in metadata. Unitary gains will be used.", | ||||||||||||||||
| UserWarning, | ||||||||||||||||
| stacklevel=2, | ||||||||||||||||
| ) | ||||||||||||||||
| channel_gains = np.ones(num_chan, dtype="float64") | ||||||||||||||||
| elif meta.get("typeThis") == "obx": | ||||||||||||||||
| # OneBox case | ||||||||||||||||
| device = fname.split(".")[-2] if "." in fname else device | ||||||||||||||||
|
|
||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two outer
if probe_part_number is not None:andif features is not None:wrappers can collapse if we mirror probeinterface's Phase3A remap and raise instead of warning. This also picks up the Phase3A case (imProbeOptinstead ofimDatPrb_pn), which probeinterface handles here but #1842 currently drops to unitary gain.This replaces the warn-and-unitary-gain fallbacks with hard failures. We were doing this on master already with
NotImplementedErrorfor unsupported probes, and silently scaled-wrong traces are harder to catch downstream.