Skip to content

Commit 3b8efe5

Browse files
authored
Merge pull request #169 from ttngu207/new_spikeglx_and_probeinterface
New spikeglx and probeinterface
2 parents e4dd98a + f42f1fc commit 3b8efe5

File tree

3 files changed

+303
-110
lines changed

3 files changed

+303
-110
lines changed

element_array_ephys/probe.py

Lines changed: 16 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"""
22
Neuropixels Probes
33
"""
4-
from __future__ import annotations
5-
64
import datajoint as dj
7-
import numpy as np
5+
6+
from .readers import probe_geometry
7+
from .readers.probe_geometry import build_electrode_layouts
88

99
schema = dj.schema()
1010

@@ -96,60 +96,23 @@ def create_neuropixels_probe(probe_type: str = "neuropixels 1.0 - 3A"):
9696
9797
For electrode location, the (0, 0) is the
9898
bottom left corner of the probe (ignore the tip portion)
99-
Electrode numbering is 1-indexing
99+
Electrode numbering is 0-indexing
100100
"""
101101

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-
}
102+
npx_probes_config = probe_geometry.M
103+
npx_probes_config["neuropixels 1.0 - 3A"] = npx_probes_config["3A"]
104+
npx_probes_config["neuropixels 1.0 - 3B"] = npx_probes_config["NP1010"]
105+
npx_probes_config["neuropixels UHD"] = npx_probes_config["NP1100"]
106+
npx_probes_config["neuropixels 2.0 - SS"] = npx_probes_config["NP2000"]
107+
npx_probes_config["neuropixels 2.0 - MS"] = npx_probes_config["NP2010"]
149108

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

0 commit comments

Comments
 (0)