3535https://billkarsh.github.io/SpikeGLX/#metadata-guides
3636https://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
5046Some functions are copied from Graham Findlay
5147"""
5248
53- from pathlib import Path
5449import os
5550import re
51+ from pathlib import Path
5652from warnings import warn
53+ import json
5754
5855import numpy as np
5956
6865from .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+
7183class 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