@@ -4,37 +4,84 @@ The Neuropixels catalogue pattern
44.. currentmodule :: probeinterface
55
66
7- The catalogue: :py:func: `build_neuropixels_probe `
8- -------------------------------------------------
9-
10- The foundation of every Neuropixels reader in probeinterface is
11- :py:func: `build_neuropixels_probe `. Given a probe part number (a specific
12- stock-keeping unit identifier such as ``"NP1000" ``, ``"NP2000" ``, ``"NP2014" ``),
13- it returns a :py:class: `Probe ` carrying the full silicon geometry for that
14- part number: every catalogue contact (960 for Neuropixels 1.0, 1280 per shank
15- for Neuropixels 2.0), the planar contour of the shanks, the contact shapes and
16- sizes, the analog-to-digital converter (ADC) multiplexer (MUX) routing table,
17- and the probe-level annotations (``manufacturer ``, ``model_name ``,
18- ``part_number ``, ``description ``).
19-
20- The numbers behind that geometry come from the
7+ Two kinds of Neuropixels probe
8+ ------------------------------
9+
10+ Probeinterface distinguishes two kinds of Neuropixels probe.
11+
12+ **Catalogue probe. ** The probe as it appears in the IMEC catalogue, with
13+ every contact on the silicon present (960 on Neuropixels 1.0, 1280 per
14+ shank on Neuropixels 2.0). Built via :py:func: `build_neuropixels_probe(part_number)
15+ <build_neuropixels_probe> `. It carries:
16+
17+ * contact positions
18+ * shank contour and dimensions
19+ * contact shapes and sizes
20+ * the analog-to-digital converter (ADC) multiplexer (MUX) routing on the
21+ silicon
22+ * identity metadata (manufacturer, model name, part number, description)
23+
24+ A catalogue probe is pure geometry, the same for every recording made with
25+ that variant. Use it to plot the probe layout, compute distances between
26+ contacts, or run any analysis that does not depend on a specific recording.
27+
28+ **Recording-setup probe. ** The catalogue probe specialised for one
29+ recording session: only the contacts actually recorded are present
30+ (typically 384 of the 960 or more catalogue contacts), and per-contact
31+ recording state is attached:
32+
33+ * per-contact analog band (AP) and local field potential (LFP) gains
34+ * reference configuration
35+ * per-contact sampling order
36+ * probe wiring (the mapping from each contact to the recording channel
37+ that captured its data)
38+
39+ Use a recording-setup probe to hand the recording to SpikeInterface so
40+ spike sorters see both the geometry and the correct channel mapping, to
41+ convert raw samples to microvolts via the per-contact gains, or to plot
42+ the recorded contacts alongside the recorded traces.
43+
44+
45+ How readers connect the two
46+ ---------------------------
47+
48+ A format reader turns a Neuropixels recording into a recording-setup probe
49+ in three steps:
50+
51+ 1. **Fetch the catalogue probe. ** Look up the probe part number (SKU) in
52+ the recording's metadata, then call
53+ :py:func: `build_neuropixels_probe(part_number) <build_neuropixels_probe> `.
54+ 2. **Identify the active electrodes. ** Read the recording's channel
55+ configuration to find which catalogue contacts were actually recorded,
56+ and slice the catalogue probe down to that subset via
57+ :py:meth: `probe.get_slice(active_indices) <Probe.get_slice> `.
58+ 3. **Attach the recording-setup metadata. ** Add the per-contact gains,
59+ reference settings, and sampling order, and set the probe wiring.
60+
61+ The sections below cover where the part number comes from per reader
62+ (step 1) and where the catalogue data itself comes from.
63+
64+
65+ The catalogue
66+ -------------
67+
68+ A probe part number (SKU) is an IMEC identifier such as ``"NP1000" ``,
69+ ``"NP2000" ``, or ``"NP2014" ``. The part number determines the silicon
70+ geometry: 960 contacts on Neuropixels 1.0, 1280 per shank on Neuropixels
71+ 2.0, plus all the per-variant pitch and shank dimensions.
72+
73+ The data behind the catalogue comes from the
2174`ProbeTable <https://github.com/billkarsh/ProbeTable >`_ repository maintained
2275by `Bill Karsh <https://github.com/billkarsh >`_ (author of SpikeGLX).
2376ProbeTable is the canonical machine-readable inventory of IMEC Neuropixels
24- probe specifications: contact positions, electrode dimensions, shank geometry,
25- MUX routing, ADC configuration, all keyed by part number. Probeinterface
26- mirrors a postprocessed snapshot of that data into the package via
27- ``resources/postprocess_neuropixels_probe_features.py ``, which is re-run after
28- each ProbeTable sync. Without ProbeTable, every reader would have to carry
29- its own hand-written copy of the manufacturer specs, which is exactly the
30- situation the catalogue pattern is designed to avoid.
77+ probe specifications, all keyed by part number.
3178
3279
33- The format readers
34- ------------------
80+ The readers
81+ -----------
3582
36- Four entry points read Neuropixels recordings (or recording configurations)
37- and produce a probe ready to use with SpikeInterface :
83+ Four readers produce a probe from a Neuropixels recording. They differ in
84+ where they look up the part number :
3885
3986.. list-table ::
4087 :header-rows: 1
@@ -58,91 +105,47 @@ and produce a probe ready to use with SpikeInterface:
58105 for the format transition.
59106 * - :py:func: `read_spikegadgets_neuropixels `
60107 - SpikeGadgets ``.rec `` XML header
61- - Not present in the file; the reader picks a geometry-equivalent stand-in
62- based on ``(SpikeConfiguration.device, deviceSubType) ``: ``NP1000 `` for
63- Neuropixels 1.0, ``NP2000 `` for Neuropixels 2.0 single-shank, ``NP2014 ``
64- for Neuropixels 2.0 4-shank
65-
66- The first three readers read the part number directly from the recording
67- metadata. SpikeGadgets is the exception: its ``.rec `` XML does not carry a
68- part number field, so the reader cannot know which specific variant produced
69- the recording. It picks one representative per geometry-equivalent family
70- (all Neuropixels 1.0 staggered variants share contact positions; all
71- Neuropixels 2.0 single-shank variants share contact positions; all
72- Neuropixels 2.0 4-shank variants share contact positions) and clears the
73- ``model_name ``, ``description ``, and ``part_number `` annotations on the
74- returned probe so downstream code does not read the stand-in as an
75- attribution.
76-
77-
78- From catalogue probe to probe in a recording setup
79- --------------------------------------------------
80-
81- The catalogue probe is pure geometry, divorced from any recording session. A real
82- recording uses only a subset of those contacts: the Neuropixels headstage
83- acquires 384 channels at a time, and the recording configuration selects
84- which catalogue contacts those 384 are drawn from (384 of 960 on Neuropixels
85- 1.0, 384 of 1280 per shank on Neuropixels 2.0 single-shank, 384 of 5120 on
86- Neuropixels 2.0 4-shank). The selection mechanism differs by recording
87- format (an IMRO table for SpikeGLX, a channel map in ``settings.xml `` for
88- Open Ephys, the ``SpikeNTrode `` list in SpikeGadgets's ``.rec `` XML); each
89- reader's docstring covers the specifics for that format. On top of the
90- selection, the recording adds session-specific state:
91- per-contact analog band (AP) and local field potential (LFP) gains, ADC
92- sample order, reference configuration, and the channel-to-file mapping that
93- says where each contact's data lives in the saved binary. Probeinterface
94- calls the result a probe in a recording setup, to distinguish it from the
95- catalogue.
96-
97- Each reader produces the recording-setup probe in the same three steps:
98-
99- 1. Build the catalogue probe by calling
100- :py:func: `build_neuropixels_probe(part_number) <build_neuropixels_probe> `
101- with the part number obtained from the recording metadata.
102- 2. Slice the catalogue probe to the active electrodes for this recording session via
103- :py:meth: `probe.get_slice(active_indices) <Probe.get_slice> `. The slice
104- drops the unrecorded contacts but preserves the probe-level annotations
105- and the per-contact catalogue annotations (ADC group, sample order) on the
106- contacts that survive.
107- 3. Attach the recording-specific state: per-contact AP/LFP gains, any
108- reference annotations, and finally
109- :py:meth: `probe.set_device_channel_indices(...) <Probe.set_device_channel_indices> `
110- to record where each surviving contact's data lives in the saved file.
108+ - Not present in the file; the reader picks a geometry-equivalent
109+ stand-in based on ``(SpikeConfiguration.device, deviceSubType) ``:
110+ ``NP1000 `` for Neuropixels 1.0, ``NP2000 `` for Neuropixels 2.0
111+ single-shank, ``NP2014 `` for Neuropixels 2.0 4-shank
112+
113+ The first three readers read the part number directly. SpikeGadgets is the
114+ exception (its ``.rec `` XML does not carry a part number) and falls back to
115+ a geometry-equivalent stand-in; the variants within each Neuropixels family
116+ share identical 2D contact geometry, so any representative produces correct
117+ positions.
111118
112119
113120What the pattern solves
114121-----------------------
115122
116- Constructing geometry from scratch inside each format reader (the situation
117- before the catalogue pattern) had three problems:
118-
119- * **Geometry drift across readers. ** Each reader carried its own copy of the
120- manufacturer specs. A Neuropixels 2.0 4-shank probe loaded through SpikeGLX
121- and through SpikeGadgets could return contact positions that disagreed in
122- the third decimal because the two readers had been updated against
123- different snapshots of the IMEC spec. Centralising the geometry in
124- :py:func: `build_neuropixels_probe ` and sourcing it from ProbeTable means
125- every reader returns the same positions for the same part number.
126- * **Conflated geometry and wiring bugs. ** When a saved recording looked wrong
127- on the probe, it was difficult to say whether the geometry was off
128- (catalogue issue) or the channel-to-contact mapping was off (wiring issue).
129- With the two phases separated, a geometry bug is a bug in
130- :py:func: `build_neuropixels_probe `; a wiring bug is a bug in the reader's
131- matching step. The two can be diagnosed and fixed independently.
132- * **Hidden active-electrode selection. ** Readers that built a 384-contact
133- probe directly hid the fact that 576 catalogue contacts were silently
134- dropped. The explicit ``probe.get_slice(active_indices) `` step makes the
135- selection visible and inspectable: callers can ask "which catalogue
136- contacts did this recording session record?" and get a direct answer.
137-
138- The pattern also pays out on the upgrade path. When IMEC ships a new probe
139- variant, the integration work is "add the part number to ProbeTable, re-run
140- the postprocess script".
123+ Building geometry from scratch inside each reader (the situation before this
124+ pattern) caused three problems:
125+
126+ * **Drift across readers. ** Each reader carried its own copy of the
127+ manufacturer specs, and the copies could disagree. Centralising the
128+ geometry in :py:func: `build_neuropixels_probe ` and sourcing it from
129+ ProbeTable means every reader returns the same positions for the same
130+ part number.
131+ * **Confused bugs. ** When a saved recording looked wrong on the probe, it
132+ was hard to tell whether the geometry was off or the channel-to-contact
133+ mapping was off. With the two phases separated, a geometry bug is a bug
134+ in :py:func: `build_neuropixels_probe `; a wiring bug is a bug in the
135+ reader's slicing or wiring step. The two can be diagnosed independently.
136+ * **Hidden electrode selection. ** A reader that built a 384-contact probe
137+ directly hid the fact that 576 catalogue contacts were silently dropped.
138+ The explicit slice step makes the selection visible: callers can ask
139+ which catalogue contacts the recording captured and get a direct answer.
140+
141+ The pattern also helps on the upgrade path. When IMEC ships a new probe
142+ variant, the integration work is to add the part number to ProbeTable; the
143+ readers do not change.
141144
142145
143146Discussion
144147----------
145148
146149This pattern was proposed and is tracked in issue
147- `#405 <https://github.com/SpikeInterface/probeinterface/issues/405 >`_; if you have
148- any discussion point to add please re-open the issue so the maintainers can discuss.
150+ `#405 <https://github.com/SpikeInterface/probeinterface/issues/405 >`_.
151+ Reopen the issue if you want to discuss changes .
0 commit comments