Skip to content

Commit 216df95

Browse files
committed
Add spec from the sheet
1 parent 0191594 commit 216df95

7 files changed

Lines changed: 135 additions & 99 deletions

File tree

diagnosticbiochips/DA128-1/DA128-1.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"si_units": "um",
88
"annotations": {
99
"model_name": "DA128-1",
10-
"manufacturer": "diagnosticbiochips"
10+
"manufacturer": "diagnosticbiochips",
11+
"shank_material": "stainless steel"
1112
},
1213
"contact_annotations": {},
1314
"contact_positions": [
@@ -2325,7 +2326,7 @@
23252326
"probe_planar_contour": [
23262327
[
23272328
-100.0,
2328-
5180.0
2329+
90000.0
23292330
],
23302331
[
23312332
-100.0,
@@ -2341,7 +2342,7 @@
23412342
],
23422343
[
23432344
100.0,
2344-
5180.0
2345+
90000.0
23452346
]
23462347
],
23472348
"contact_ids": [

diagnosticbiochips/DA128-2/DA128-2.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"si_units": "um",
88
"annotations": {
99
"model_name": "DA128-2",
10-
"manufacturer": "diagnosticbiochips"
10+
"manufacturer": "diagnosticbiochips",
11+
"shank_material": "stainless steel"
1112
},
1213
"contact_annotations": {},
1314
"contact_positions": [
@@ -2325,7 +2326,7 @@
23252326
"probe_planar_contour": [
23262327
[
23272328
-100.0,
2328-
3275.0
2329+
90000.0
23292330
],
23302331
[
23312332
-100.0,
@@ -2341,7 +2342,7 @@
23412342
],
23422343
[
23432344
100.0,
2344-
3275.0
2345+
90000.0
23452346
]
23462347
],
23472348
"contact_ids": [

diagnosticbiochips/DA32-1/DA32-1.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"si_units": "um",
88
"annotations": {
99
"model_name": "DA32-1",
10-
"manufacturer": "diagnosticbiochips"
10+
"manufacturer": "diagnosticbiochips",
11+
"shank_material": "stainless steel"
1112
},
1213
"contact_annotations": {},
1314
"contact_positions": [
@@ -597,7 +598,7 @@
597598
"probe_planar_contour": [
598599
[
599600
-100.0,
600-
2115.0
601+
90000.0
601602
],
602603
[
603604
-100.0,
@@ -613,7 +614,7 @@
613614
],
614615
[
615616
100.0,
616-
2115.0
617+
90000.0
617618
]
618619
],
619620
"contact_ids": [

diagnosticbiochips/DA32-2/DA32-2.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"si_units": "um",
88
"annotations": {
99
"model_name": "DA32-2",
10-
"manufacturer": "diagnosticbiochips"
10+
"manufacturer": "diagnosticbiochips",
11+
"shank_material": "stainless steel"
1112
},
1213
"contact_annotations": {},
1314
"contact_positions": [
@@ -597,7 +598,7 @@
597598
"probe_planar_contour": [
598599
[
599600
-100.0,
600-
3200.0
601+
90000.0
601602
],
602603
[
603604
-100.0,
@@ -613,7 +614,7 @@
613614
],
614615
[
615616
100.0,
616-
3200.0
617+
90000.0
617618
]
618619
],
619620
"contact_ids": [

diagnosticbiochips/DA64-1/DA64-1.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"si_units": "um",
88
"annotations": {
99
"model_name": "DA64-1",
10-
"manufacturer": "diagnosticbiochips"
10+
"manufacturer": "diagnosticbiochips",
11+
"shank_material": "stainless steel"
1112
},
1213
"contact_annotations": {},
1314
"contact_positions": [
@@ -1173,7 +1174,7 @@
11731174
"probe_planar_contour": [
11741175
[
11751176
-100.0,
1176-
6400.0
1177+
90000.0
11771178
],
11781179
[
11791180
-100.0,
@@ -1189,7 +1190,7 @@
11891190
],
11901191
[
11911192
100.0,
1192-
6400.0
1193+
90000.0
11931194
]
11941195
],
11951196
"contact_ids": [

diagnosticbiochips/DA64-2/DA64-2.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"si_units": "um",
88
"annotations": {
99
"model_name": "DA64-2",
10-
"manufacturer": "diagnosticbiochips"
10+
"manufacturer": "diagnosticbiochips",
11+
"shank_material": "stainless steel"
1112
},
1213
"contact_annotations": {},
1314
"contact_positions": [
@@ -1173,7 +1174,7 @@
11731174
"probe_planar_contour": [
11741175
[
11751176
-100.0,
1176-
1675.0
1177+
90000.0
11771178
],
11781179
[
11791180
-100.0,
@@ -1189,7 +1190,7 @@
11891190
],
11901191
[
11911192
100.0,
1192-
1675.0
1193+
90000.0
11931194
]
11941195
],
11951196
"contact_ids": [

scripts/generate_diagnostic_biochips.py

Lines changed: 111 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,148 @@
11
"""Generator for Diagnostic Biochips Deep Array probes.
22
3-
Builds the Deep Array models from the 2022 Diagnostic Biochips catalog
4-
datasheets and writes each as diagnosticbiochips/<model>/<model>.json in the
5-
probeinterface format.
6-
7-
Two layout families are produced:
8-
9-
- Linear models (DA128-1, DA64-1, DA32-1, DA32-2): a single column of sites,
10-
pitch from the datasheet, span = (n_contacts - 1) * pitch.
11-
12-
- Staggered models (DA128-2, DA64-2): a two-column triangular (hexagonal)
13-
lattice. The columns are 43.3 um apart horizontally, sites alternate columns
14-
going down the shank with a 25 um vertical step (so 50 um within a column),
15-
and the diagonal nearest-neighbor distance is sqrt(43.3**2 + 25**2) = 50 um.
16-
The datasheet schematic is "not drawn to scale", but span = (n_contacts - 1)
17-
* 25 um matches the printed recording span exactly for both models, which
18-
fixes the layout to one site per 25 um level, i.e. exactly two columns.
3+
The source of truth is the 2022 Diagnostic Biochips catalog (one datasheet page
4+
per model). Each model's spec table and schematic dimensions are transcribed
5+
verbatim below into a DeepArraySpec, in the exact units printed on the sheet.
6+
Everything written to diagnosticbiochips/<model>/<model>.json is derived from
7+
those values; nothing geometric is invented here.
8+
9+
The probeinterface JSON follows the same convention as the Neuropixels and
10+
Cambridge NeuroTech entries in this library: the geometry is stored
11+
geometrically (contact positions, contact shape, full-shank contour) and the
12+
annotations carry only what the geometry cannot express. For these passive
13+
probes that is just the shank material; site diameter, shank diameter, max
14+
shank length and recording span are all recoverable from the geometry.
15+
16+
Two layout families appear on the datasheets:
17+
18+
- Linear (DA128-1, DA64-1, DA32-1, DA32-2): a single column of sites at the
19+
printed vertical pitch.
20+
21+
- Staggered (DA128-2, DA64-2): a two-column triangular (hexagonal) lattice.
22+
The columns are 43.3 um apart horizontally and sites alternate columns going
23+
up the shank with a 25 um vertical step (so 50 um within a column); the
24+
diagonal nearest-neighbor distance is sqrt(43.3**2 + 25**2) = 50 um. The
25+
schematic is "not drawn to scale", but (n_channels - 1) * 25 um equals the
26+
printed recording span exactly, which fixes the layout to one site per 25 um
27+
level, i.e. exactly two columns. This is checked against the datasheet value
28+
for every model before writing (see _build_probe).
1929
2030
Run with: uv run --with probeinterface scripts/generate_diagnostic_biochips.py
2131
"""
2232

33+
from dataclasses import dataclass
2334
from pathlib import Path
2435

2536
from probeinterface import Probe, ProbeGroup, write_probeinterface
2637

2738
MANUFACTURER = "diagnosticbiochips"
28-
SITE_DIAMETER_UM = 20.0
29-
SHANK_WIDTH_UM = 200.0 # 0.2 mm stainless steel shank
30-
TIP_LENGTH_UM = 150.0 # cosmetic pointed tip below the deepest site
31-
32-
# (model, n_contacts, vertical_pitch_um)
33-
LINEAR_MODELS = [
34-
("DA128-1", 128, 40.0),
35-
("DA64-1", 64, 100.0),
36-
("DA32-1", 32, 65.0),
37-
("DA32-2", 32, 100.0),
38-
]
39-
40-
# (model, n_contacts) -- shared staggered lattice constants below.
41-
STAGGERED_MODELS = [
42-
("DA128-2", 128),
43-
("DA64-2", 64),
39+
TIP_LENGTH_UM = 150.0 # cosmetic pointed tip below the shank base (not on the sheet)
40+
41+
42+
@dataclass(frozen=True)
43+
class DeepArraySpec:
44+
"""Verbatim transcription of one 2022 Diagnostic Biochips catalog page.
45+
46+
Spec-table fields keep the units printed on the datasheet; the two layout
47+
fields are read from the schematic dimension callouts.
48+
"""
49+
50+
model: str
51+
num_channels: int
52+
layout: str # "linear" or "staggered"
53+
# Spec table
54+
site_diameter_um: float
55+
shank_diameter_mm: float
56+
max_shank_length_mm: float
57+
recording_span_mm: float
58+
shank_material: str
59+
# Schematic dimension callouts
60+
vertical_pitch_um: float # linear: column pitch; staggered: step between alternating sites
61+
horizontal_offset_um: float | None = None # staggered only: full column separation
62+
63+
64+
# One entry per datasheet page, values exactly as printed on the 2022 catalog.
65+
DATASHEETS = [
66+
DeepArraySpec("DA128-1", 128, "linear", 20.0, 0.2, 90.0, 5.08, "stainless steel", 40.0),
67+
DeepArraySpec("DA64-1", 64, "linear", 20.0, 0.2, 90.0, 6.3, "stainless steel", 100.0),
68+
DeepArraySpec("DA32-1", 32, "linear", 20.0, 0.2, 90.0, 2.015, "stainless steel", 65.0),
69+
DeepArraySpec("DA32-2", 32, "linear", 20.0, 0.2, 90.0, 3.1, "stainless steel", 100.0),
70+
DeepArraySpec("DA128-2", 128, "staggered", 20.0, 0.2, 90.0, 3.175, "stainless steel", 25.0, 43.3),
71+
DeepArraySpec("DA64-2", 64, "staggered", 20.0, 0.2, 90.0, 1.575, "stainless steel", 25.0, 43.3),
4472
]
45-
STAGGERED_X_OFFSET_UM = 21.65 # 43.3 um column separation -> +/-21.65
46-
STAGGERED_VERTICAL_STEP_UM = 25.0 # step between alternating columns
4773

4874

49-
def _shank_contour(span_um: float) -> list:
50-
half_width = SHANK_WIDTH_UM / 2.0
51-
y_top = span_um + half_width
75+
def _contact_positions(spec: DeepArraySpec) -> list:
76+
# Site 0 sits at the tip (y = 0) and the band grows up the shank.
77+
if spec.layout == "linear":
78+
# Single column centered on the shank.
79+
return [[0.0, index * spec.vertical_pitch_um] for index in range(spec.num_channels)]
80+
if spec.layout == "staggered":
81+
# Two columns +/-(horizontal_offset / 2), alternating every site.
82+
half_offset = spec.horizontal_offset_um / 2.0
83+
positions = []
84+
for index in range(spec.num_channels):
85+
x = -half_offset if index % 2 == 0 else half_offset
86+
positions.append([x, index * spec.vertical_pitch_um])
87+
return positions
88+
raise ValueError(f"unknown layout {spec.layout!r} for {spec.model}")
89+
90+
91+
def _shank_contour(spec: DeepArraySpec) -> list:
92+
# Outline of the full physical shaft (tip to body): the printed shank
93+
# diameter sets the width, the printed max shank length sets the height.
94+
half_width = spec.shank_diameter_mm * 1000.0 / 2.0
95+
length_um = spec.max_shank_length_mm * 1000.0
5296
return [
53-
[-half_width, y_top],
97+
[-half_width, length_um],
5498
[-half_width, -half_width],
5599
[0.0, -half_width - TIP_LENGTH_UM],
56100
[half_width, -half_width],
57-
[half_width, y_top],
101+
[half_width, length_um],
58102
]
59103

60104

61-
def build_linear_probe(model_name: str, n_contacts: int, pitch_um: float) -> Probe:
62-
# Single column centered on the shank; site 0 at the tip, y increasing up.
63-
positions = [[0.0, index * pitch_um] for index in range(n_contacts)]
105+
def _build_probe(spec: DeepArraySpec) -> Probe:
106+
positions = _contact_positions(spec)
64107

65-
probe = Probe(ndim=2, si_units="um")
66-
probe.set_contacts(
67-
positions=positions,
68-
shapes="circle",
69-
shape_params={"radius": SITE_DIAMETER_UM / 2.0},
108+
# The contact extent must reproduce the printed recording span.
109+
span_um = positions[-1][1] - positions[0][1]
110+
expected_um = spec.recording_span_mm * 1000.0
111+
assert abs(span_um - expected_um) < 1e-6, (
112+
f"{spec.model}: derived span {span_um} um != datasheet {expected_um} um"
70113
)
71-
probe.annotate(model_name=model_name, manufacturer=MANUFACTURER)
72-
probe.set_contact_ids([str(index) for index in range(n_contacts)])
73-
probe.set_planar_contour(_shank_contour((n_contacts - 1) * pitch_um))
74-
return probe
75-
76-
77-
def build_staggered_probe(model_name: str, n_contacts: int) -> Probe:
78-
# Two columns +/-STAGGERED_X_OFFSET_UM apart; sites alternate columns going
79-
# up the shank with a STAGGERED_VERTICAL_STEP_UM step (site 0 at the tip).
80-
positions = []
81-
for index in range(n_contacts):
82-
x = -STAGGERED_X_OFFSET_UM if index % 2 == 0 else STAGGERED_X_OFFSET_UM
83-
positions.append([x, index * STAGGERED_VERTICAL_STEP_UM])
84114

85115
probe = Probe(ndim=2, si_units="um")
86116
probe.set_contacts(
87117
positions=positions,
88118
shapes="circle",
89-
shape_params={"radius": SITE_DIAMETER_UM / 2.0},
119+
shape_params={"radius": spec.site_diameter_um / 2.0},
90120
)
91-
probe.annotate(model_name=model_name, manufacturer=MANUFACTURER)
92-
probe.set_contact_ids([str(index) for index in range(n_contacts)])
93-
probe.set_planar_contour(_shank_contour((n_contacts - 1) * STAGGERED_VERTICAL_STEP_UM))
121+
# Geometry encodes site diameter (radius), shank diameter (contour width),
122+
# max shank length (contour height) and recording span (contact extent), so
123+
# shank material is the only spec stored as an annotation.
124+
probe.annotate(
125+
model_name=spec.model,
126+
manufacturer=MANUFACTURER,
127+
shank_material=spec.shank_material,
128+
)
129+
probe.set_contact_ids([str(index) for index in range(spec.num_channels)])
130+
probe.set_planar_contour(_shank_contour(spec))
94131
return probe
95132

96133

97-
def _write(root: Path, probe: Probe, model_name: str, n_contacts: int, span_um: float) -> None:
98-
group = ProbeGroup()
99-
group.add_probe(probe)
100-
out_dir = root / MANUFACTURER / model_name
101-
out_dir.mkdir(parents=True, exist_ok=True)
102-
out_file = out_dir / f"{model_name}.json"
103-
write_probeinterface(out_file, group)
104-
print(f"wrote {out_file} ({n_contacts}ch, {span_um / 1000.0:g} mm span)")
105-
106-
107134
def main() -> None:
108135
root = Path(__file__).resolve().parent.parent
109-
for model_name, n_contacts, pitch_um in LINEAR_MODELS:
110-
probe = build_linear_probe(model_name, n_contacts, pitch_um)
111-
_write(root, probe, model_name, n_contacts, (n_contacts - 1) * pitch_um)
112-
for model_name, n_contacts in STAGGERED_MODELS:
113-
probe = build_staggered_probe(model_name, n_contacts)
114-
span_um = (n_contacts - 1) * STAGGERED_VERTICAL_STEP_UM
115-
_write(root, probe, model_name, n_contacts, span_um)
136+
for spec in DATASHEETS:
137+
probe = _build_probe(spec)
138+
group = ProbeGroup()
139+
group.add_probe(probe)
140+
141+
out_dir = root / MANUFACTURER / spec.model
142+
out_dir.mkdir(parents=True, exist_ok=True)
143+
out_file = out_dir / f"{spec.model}.json"
144+
write_probeinterface(out_file, group)
145+
print(f"wrote {out_file} ({spec.num_channels}ch, {spec.layout}, {spec.recording_span_mm:g} mm span)")
116146

117147

118148
if __name__ == "__main__":

0 commit comments

Comments
 (0)