Skip to content

Commit 224b1c7

Browse files
author
Thinh Nguyen
committed
incorporate probeinterface and probe geometry for all npx probes
1 parent e4dd98a commit 224b1c7

File tree

2 files changed

+230
-108
lines changed

2 files changed

+230
-108
lines changed

element_array_ephys/probe.py

Lines changed: 19 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from __future__ import annotations
55

66
import datajoint as dj
7-
import numpy as np
7+
8+
from .readers import probe_geometry
9+
from .readers.probe_geometry import build_electrode_layouts
810

911
schema = dj.schema()
1012

@@ -96,60 +98,26 @@ def create_neuropixels_probe(probe_type: str = "neuropixels 1.0 - 3A"):
9698
9799
For electrode location, the (0, 0) is the
98100
bottom left corner of the probe (ignore the tip portion)
99-
Electrode numbering is 1-indexing
101+
Electrode numbering is 0-indexing
100102
"""
101103

102-
neuropixels_probes_config = {
103-
"neuropixels 1.0 - 3A": dict(
104-
site_count_per_shank=960,
105-
col_spacing=32,
106-
row_spacing=20,
107-
white_spacing=16,
108-
col_count_per_shank=2,
109-
shank_count=1,
110-
shank_spacing=0,
111-
),
112-
"neuropixels 1.0 - 3B": dict(
113-
site_count_per_shank=960,
114-
col_spacing=32,
115-
row_spacing=20,
116-
white_spacing=16,
117-
col_count_per_shank=2,
118-
shank_count=1,
119-
shank_spacing=0,
120-
),
121-
"neuropixels UHD": dict(
122-
site_count_per_shank=384,
123-
col_spacing=6,
124-
row_spacing=6,
125-
white_spacing=0,
126-
col_count_per_shank=8,
127-
shank_count=1,
128-
shank_spacing=0,
129-
),
130-
"neuropixels 2.0 - SS": dict(
131-
site_count_per_shank=1280,
132-
col_spacing=32,
133-
row_spacing=15,
134-
white_spacing=0,
135-
col_count_per_shank=2,
136-
shank_count=1,
137-
shank_spacing=250,
138-
),
139-
"neuropixels 2.0 - MS": dict(
140-
site_count_per_shank=1280,
141-
col_spacing=32,
142-
row_spacing=15,
143-
white_spacing=0,
144-
col_count_per_shank=2,
145-
shank_count=4,
146-
shank_spacing=250,
147-
),
148-
}
104+
npx_probes_config = probe_geometry.M
105+
npx_probes_config["neuropixels 1.0 - 3A"] = npx_probes_config["3A"]
106+
npx_probes_config["neuropixels 1.0 - 3B"] = npx_probes_config["NP1010"]
107+
npx_probes_config["neuropixels UHD"] = npx_probes_config["NP1100"]
108+
npx_probes_config["neuropixels 2.0 - SS"] = npx_probes_config["NP2000"]
109+
npx_probes_config["neuropixels 2.0 - MS"] = npx_probes_config["NP2010"]
149110

150111
probe_type = {"probe_type": probe_type}
151-
electrode_layouts = build_electrode_layouts(
152-
**{**neuropixels_probes_config[probe_type["probe_type"]], **probe_type}
112+
probe_params = {
113+
n: v
114+
for n, v in zip(
115+
probe_geometry.geom_param_names,
116+
npx_probes_config[probe_type["probe_type"]],
117+
)
118+
}
119+
electrode_layouts = probe_geometry.build_npx_probe(
120+
**{**probe_params, **probe_type}
153121
)
154122
with ProbeType.connection.transaction:
155123
ProbeType.insert1(probe_type, skip_duplicates=True)
@@ -205,60 +173,3 @@ class Electrode(dj.Part):
205173
-> master
206174
-> ProbeType.Electrode
207175
"""
208-
209-
210-
def build_electrode_layouts(
211-
probe_type: str,
212-
site_count_per_shank: int,
213-
col_spacing: float = None,
214-
row_spacing: float = None,
215-
white_spacing: float = None,
216-
col_count_per_shank: int = 1,
217-
shank_count: int = 1,
218-
shank_spacing: float = None,
219-
y_origin="bottom",
220-
) -> list[dict]:
221-
"""Builds electrode layouts.
222-
223-
Args:
224-
probe_type (str): probe type (e.g., "neuropixels 1.0 - 3A").
225-
site_count_per_shank (int): site count per shank.
226-
col_spacing (float): (um) horizontal spacing between sites. Defaults to None (single column).
227-
row_spacing (float): (um) vertical spacing between columns. Defaults to None (single row).
228-
white_spacing (float): (um) offset spacing. Defaults to None.
229-
col_count_per_shank (int): number of column per shank. Defaults to 1 (single column).
230-
shank_count (int): number of shank. Defaults to 1 (single shank).
231-
shank_spacing (float): (um) spacing between shanks. Defaults to None (single shank).
232-
y_origin (str): {"bottom", "top"}. y value decrements if "top". Defaults to "bottom".
233-
"""
234-
row_count = int(site_count_per_shank / col_count_per_shank)
235-
x_coords = np.tile(
236-
np.arange(0, (col_spacing or 1) * col_count_per_shank, (col_spacing or 1)),
237-
row_count,
238-
)
239-
y_coords = np.repeat(np.arange(row_count) * (row_spacing or 1), col_count_per_shank)
240-
241-
if white_spacing:
242-
x_white_spaces = np.tile(
243-
[white_spacing, white_spacing, 0, 0], int(row_count / 2)
244-
)
245-
x_coords = x_coords + x_white_spaces
246-
247-
shank_cols = np.tile(range(col_count_per_shank), row_count)
248-
shank_rows = np.repeat(range(row_count), col_count_per_shank)
249-
250-
return [
251-
{
252-
"probe_type": probe_type,
253-
"electrode": (site_count_per_shank * shank_no) + e_id,
254-
"shank": shank_no,
255-
"shank_col": c_id,
256-
"shank_row": r_id,
257-
"x_coord": x + (shank_no * (shank_spacing or 1)),
258-
"y_coord": {"top": -y, "bottom": y}[y_origin],
259-
}
260-
for shank_no in range(shank_count)
261-
for e_id, (c_id, r_id, x, y) in enumerate(
262-
zip(shank_cols, shank_rows, x_coords, y_coords)
263-
)
264-
]
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import numpy as np
2+
import pandas as pd
3+
4+
"""
5+
Geometry definition for Neuropixels probes
6+
The definition here are all from Jennifer Colonell
7+
See:
8+
https://github.com/jenniferColonell/SGLXMetaToCoords/blob/main/SGLXMetaToCoords.py
9+
10+
A better approach is to pip install and use as a package
11+
Unfortunately, the GitHub repo above is not yet packaged and pip installable
12+
13+
Better yet, full integration with ProbeInterface and the probes' geometry
14+
from Jennifer Colonell - this is in the making!
15+
16+
Latest update: 07-26-2023
17+
"""
18+
19+
# many part numbers have the same geometry parameters ;
20+
# define those sets in lists
21+
# [nShank, shankWidth, shankPitch, even_xOff, odd_xOff, horizPitch, vertPitch, rowsPerShank, elecPerShank]
22+
geom_param_names = [
23+
"nShank",
24+
"shankWidth",
25+
"shankPitch",
26+
"even_xOff",
27+
"odd_xOff",
28+
"horizPitch",
29+
"vertPitch",
30+
"rowsPerShank",
31+
"elecPerShank",
32+
]
33+
34+
# offset and pitch values in um
35+
np1_stag_70um = [1, 70, 0, 27, 11, 32, 20, 480, 960]
36+
nhp_lin_70um = [1, 70, 0, 27, 27, 32, 20, 480, 960]
37+
nhp_stag_125um_med = [1, 125, 0, 27, 11, 87, 20, 1368, 2496]
38+
nhp_stag_125um_long = [1, 125, 0, 27, 11, 87, 20, 2208, 4416]
39+
nhp_lin_125um_med = [1, 125, 0, 11, 11, 103, 20, 1368, 2496]
40+
nhp_lin_125um_long = [1, 125, 0, 11, 11, 103, 20, 2208, 4416]
41+
uhd_8col_1bank = [1, 70, 0, 14, 14, 6, 6, 48, 384]
42+
uhd_8col_16bank = [1, 70, 0, 14, 14, 6, 6, 768, 6144]
43+
np2_ss = [1, 70, 0, 27, 27, 32, 15, 640, 1280]
44+
np2_4s = [4, 70, 250, 27, 27, 32, 15, 640, 1280]
45+
NP1120 = [1, 70, 0, 6.75, 6.75, 4.5, 4.5, 192, 384]
46+
NP1121 = [1, 70, 0, 6.25, 6.25, 3, 3, 384, 384]
47+
NP1122 = [1, 70, 0, 6.75, 6.75, 4.5, 4.5, 24, 384]
48+
NP1123 = [1, 70, 0, 10.25, 10.25, 3, 3, 32, 384]
49+
NP1300 = [1, 70, 0, 11, 11, 48, 20, 480, 960]
50+
NP1200 = [1, 70, 0, 27, 11, 32, 20, 64, 128]
51+
NXT3000 = [1, 70, 0, 53, 53, 0, 15, 128, 128]
52+
53+
"""
54+
Electrode coordinate system - from Bill Karsh
55+
(https://github.com/billkarsh/SpikeGLX/blob/master/Markdown/Metadata_30.md)
56+
57+
The X-origin is the left edge of the shank
58+
The Y-origin is the center of the bottom-most elecrode row (closest to the tip)
59+
"""
60+
61+
62+
M = dict(
63+
[
64+
("3A", np1_stag_70um),
65+
("PRB_1_4_0480_1", np1_stag_70um),
66+
("PRB_1_4_0480_1_C", np1_stag_70um),
67+
("NP1010", np1_stag_70um),
68+
("NP1011", np1_stag_70um),
69+
("NP1012", np1_stag_70um),
70+
("NP1013", np1_stag_70um),
71+
("NP1015", nhp_lin_70um),
72+
("NP1016", nhp_lin_70um),
73+
("NP1017", nhp_lin_70um),
74+
("NP1020", nhp_stag_125um_med),
75+
("NP1021", nhp_stag_125um_med),
76+
("NP1030", nhp_stag_125um_long),
77+
("NP1031", nhp_stag_125um_long),
78+
("NP1022", nhp_lin_125um_med),
79+
("NP1032", nhp_lin_125um_long),
80+
("NP1100", uhd_8col_1bank),
81+
("NP1110", uhd_8col_16bank),
82+
("PRB2_1_4_0480_1", np2_ss),
83+
("PRB2_1_2_0640_0", np2_ss),
84+
("NP2000", np2_ss),
85+
("NP2003", np2_ss),
86+
("NP2004", np2_ss),
87+
("PRB2_4_2_0640_0", np2_4s),
88+
("PRB2_4_4_0480_1", np2_4s),
89+
("NP2010", np2_4s),
90+
("NP2013", np2_4s),
91+
("NP2014", np2_4s),
92+
("NP1120", NP1120),
93+
("NP1121", NP1121),
94+
("NP1122", NP1122),
95+
("NP1123", NP1123),
96+
("NP1300", NP1300),
97+
("NP1200", NP1200),
98+
("NXT3000", NXT3000),
99+
]
100+
)
101+
102+
103+
def build_npx_probe(
104+
nShank: int,
105+
shankWidth: float,
106+
shankPitch: float,
107+
even_xOff: float,
108+
odd_xOff: float,
109+
horizPitch: float,
110+
vertPitch: float,
111+
rowsPerShank: int,
112+
elecPerShank: int,
113+
probe_type: str = "neuropixels",
114+
):
115+
row_offset = np.tile([even_xOff, odd_xOff], int(rowsPerShank / 2))
116+
117+
elec_pos_df = build_electrode_layouts(
118+
probe_type=probe_type,
119+
site_count_per_shank=elecPerShank,
120+
col_spacing=horizPitch,
121+
row_spacing=vertPitch,
122+
row_offset=row_offset,
123+
col_count_per_shank=int(elecPerShank / rowsPerShank),
124+
shank_count=nShank,
125+
shank_spacing=shankPitch,
126+
y_origin="bottom",
127+
as_dataframe=True,
128+
)
129+
130+
return elec_pos_df
131+
132+
133+
def to_probeinterface(electrodes_df):
134+
from probeinterface import Probe
135+
136+
probe_df = electrodes_df.copy()
137+
probe_df.rename(
138+
columns={
139+
"electrode": "contact_ids",
140+
"shank": "shank_ids",
141+
"x_coord": "x",
142+
"y_coord": "y",
143+
},
144+
inplace=True,
145+
)
146+
probe_df["contact_shapes"] = "square"
147+
probe_df["width"] = 12
148+
149+
return Probe.from_dataframe(probe_df)
150+
151+
152+
def build_electrode_layouts(
153+
probe_type: str,
154+
site_count_per_shank: int,
155+
col_spacing: float = None,
156+
row_spacing: float = None,
157+
row_offset: list = None,
158+
col_count_per_shank: int = 1,
159+
shank_count: int = 1,
160+
shank_spacing: float = None,
161+
y_origin="bottom",
162+
as_dataframe=False,
163+
) -> list[dict]:
164+
"""Builds electrode layouts.
165+
166+
Args:
167+
probe_type (str): probe type (e.g., "neuropixels 1.0 - 3A").
168+
site_count_per_shank (int): site count per shank.
169+
col_spacing (float): (um) horizontal spacing between sites. Defaults to None (single column).
170+
row_spacing (float): (um) vertical spacing between columns. Defaults to None (single row).
171+
row_offset (list): (um) per-row offset spacing. Defaults to None.
172+
col_count_per_shank (int): number of column per shank. Defaults to 1 (single column).
173+
shank_count (int): number of shank. Defaults to 1 (single shank).
174+
shank_spacing (float): (um) spacing between shanks. Defaults to None (single shank).
175+
y_origin (str): {"bottom", "top"}. y value decrements if "top". Defaults to "bottom".
176+
as_dataframe (bool): if True, returns as pandas DataFrame, otherwise as list of dict
177+
"""
178+
row_count = int(site_count_per_shank / col_count_per_shank)
179+
x_coords = np.tile(
180+
np.arange(0, (col_spacing or 1) * col_count_per_shank, (col_spacing or 1)),
181+
row_count,
182+
)
183+
y_coords = np.repeat(np.arange(row_count) * (row_spacing or 1), col_count_per_shank)
184+
185+
if row_offset is None:
186+
row_offset = np.zeros_like(x_coords)
187+
else:
188+
assert len(row_offset) == row_count
189+
row_offset = np.tile(row_offset, col_count_per_shank)
190+
x_coords = x_coords + row_offset
191+
192+
shank_cols = np.tile(range(col_count_per_shank), row_count)
193+
shank_rows = np.repeat(range(row_count), col_count_per_shank)
194+
195+
electrode_layout = [
196+
{
197+
"probe_type": probe_type,
198+
"electrode": (site_count_per_shank * shank_no) + e_id,
199+
"shank": shank_no,
200+
"shank_col": c_id,
201+
"shank_row": r_id,
202+
"x_coord": x + (shank_no * (shank_spacing or 1)),
203+
"y_coord": {"top": -y, "bottom": y}[y_origin],
204+
}
205+
for shank_no in range(shank_count)
206+
for e_id, (c_id, r_id, x, y) in enumerate(
207+
zip(shank_cols, shank_rows, x_coords, y_coords)
208+
)
209+
]
210+
211+
return pd.DataFrame(electrode_layout) if as_dataframe else electrode_layout

0 commit comments

Comments
 (0)