Skip to content

Commit db8c348

Browse files
committed
Merge branch 'main' of github.com:SpikeInterface/probeinterface into add-regex-version-schema
2 parents 24eec63 + e76872a commit db8c348

4 files changed

Lines changed: 47 additions & 19 deletions

File tree

src/probeinterface/io.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -375,15 +375,6 @@ def write_BIDS_probe(folder: str | Path, probe_or_probegroup: Probe | ProbeGroup
375375
json.dump({"ProbeId": probes_dict}, f, indent=4)
376376

377377
# Step 3: GENERATION OF CONTACTS.TSV
378-
# ensure required contact identifiers are present
379-
for probe in probes:
380-
if probe.contact_ids is None:
381-
raise ValueError(
382-
"Contacts must have unique contact ids "
383-
"and not None for export to BIDS probe format."
384-
"Use `probegroup.auto_generate_contact_ids`."
385-
)
386-
387378
df = probegroup.to_dataframe()
388379
index = range(sum([p.get_contact_count() for p in probes]))
389380
df.rename(columns=tsv_label_map_to_BIDS, inplace=True)

src/probeinterface/probe.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,10 @@ def set_contacts(
337337
Defines the two axes of the contact plane for each electrode.
338338
The third dimension corresponds to the probe `ndim` (2d or 3d).
339339
contact_ids: array[str] | None, default: None
340-
Defines the contact ids for the contacts. If None, contact ids are not assigned.
340+
Defines the contact ids for the contacts. If None, contact ids are
341+
auto-generated as the zero-indexed strings ``["0", "1", ..., str(n - 1)]``
342+
so a Probe always carries a stable, slice-invariant handle for each
343+
contact. Pass an explicit array to override.
341344
shank_ids : array[str] | None, default: None
342345
Defines the shank ids for the contacts. If None, then
343346
these are assigned to a unique Shank.
@@ -379,8 +382,9 @@ def set_contacts(
379382
plane_axes = np.array(plane_axes)
380383
self._contact_plane_axes = plane_axes
381384

382-
if contact_ids is not None:
383-
self.set_contact_ids(contact_ids)
385+
if contact_ids is None:
386+
contact_ids = np.arange(n).astype(str)
387+
self.set_contact_ids(contact_ids)
384388

385389
if shank_ids is None:
386390
# self._shank_ids = np.zeros(n, dtype=str)
@@ -567,8 +571,9 @@ def set_contact_ids(self, contact_ids: np.ndarray | list):
567571
"""
568572
contact_ids = np.asarray(contact_ids)
569573
if np.all([c == "" for c in contact_ids]):
570-
self._contact_ids = None
571-
return
574+
# Backward compat: previous versions serialized "unset" as empty
575+
# strings. A Probe now always carries contact_ids, so regenerate.
576+
contact_ids = np.arange(self.get_contact_count()).astype(str)
572577

573578
if contact_ids.size != self.get_contact_count():
574579
raise ValueError(
@@ -1081,10 +1086,7 @@ def to_numpy(self, complete: bool = False) -> np.ndarray:
10811086
if self._contact_sides is not None:
10821087
arr["contact_sides"] = self.contact_sides
10831088

1084-
if self.contact_ids is None:
1085-
arr["contact_ids"] = [""] * self.get_contact_count()
1086-
else:
1087-
arr["contact_ids"] = self.contact_ids
1089+
arr["contact_ids"] = self.contact_ids
10881090

10891091
if complete:
10901092
arr["si_units"] = self.si_units

src/probeinterface/schema/probe.json.schema

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@
120120
"annotations",
121121
"contact_positions",
122122
"contact_shapes",
123-
"contact_shape_params"
123+
"contact_shape_params",
124+
"contact_ids"
124125
],
125126
"additionalProperties": false
126127
}

tests/test_probe.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,40 @@ def test_probe():
141141
# ~ plt.show()
142142

143143

144+
def test_set_contacts_auto_generates_contact_ids():
145+
"""When contact_ids is not supplied, Probe auto-generates ['0', ..., str(n-1)]."""
146+
probe = Probe(ndim=2, si_units="um")
147+
positions = np.array([[0, 0], [10, 0], [20, 0], [30, 0]])
148+
probe.set_contacts(positions=positions, shapes="circle", shape_params={"radius": 5})
149+
150+
assert probe.contact_ids is not None
151+
np.testing.assert_array_equal(probe.contact_ids, np.array(["0", "1", "2", "3"]))
152+
153+
154+
def test_set_contacts_respects_explicit_contact_ids():
155+
"""An explicit contact_ids argument is preserved verbatim."""
156+
probe = Probe(ndim=2, si_units="um")
157+
positions = np.array([[0, 0], [10, 0], [20, 0]])
158+
probe.set_contacts(
159+
positions=positions,
160+
shapes="circle",
161+
shape_params={"radius": 5},
162+
contact_ids=["a", "b", "c"],
163+
)
164+
165+
np.testing.assert_array_equal(probe.contact_ids, np.array(["a", "b", "c"]))
166+
167+
168+
def test_set_contact_ids_all_empty_strings_regenerates():
169+
"""Backward compat: older serialized probes used empty strings for 'unset'."""
170+
probe = Probe(ndim=2, si_units="um")
171+
positions = np.array([[0, 0], [10, 0], [20, 0]])
172+
probe.set_contacts(positions=positions, shapes="circle", shape_params={"radius": 5})
173+
probe.set_contact_ids(["", "", ""])
174+
175+
np.testing.assert_array_equal(probe.contact_ids, np.array(["0", "1", "2"]))
176+
177+
144178
def test_set_device_channel_indices_rejects_wrong_size():
145179
"""Setting device_channel_indices with wrong count raises ValueError."""
146180
probe = Probe(ndim=2, si_units="um")

0 commit comments

Comments
 (0)