Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions src/probeinterface/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,15 +386,6 @@ def write_BIDS_probe(folder: str | Path, probe_or_probegroup: Probe | ProbeGroup
json.dump({"ProbeId": probes_dict}, f, indent=4)

# Step 3: GENERATION OF CONTACTS.TSV
# ensure required contact identifiers are present
for probe in probes:
if probe.contact_ids is None:
raise ValueError(
"Contacts must have unique contact ids "
"and not None for export to BIDS probe format."
"Use `probegroup.auto_generate_contact_ids`."
)

df = probegroup.to_dataframe()
index = range(sum([p.get_contact_count() for p in probes]))
df.rename(columns=tsv_label_map_to_BIDS, inplace=True)
Expand Down
20 changes: 11 additions & 9 deletions src/probeinterface/probe.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,10 @@ def set_contacts(
Defines the two axes of the contact plane for each electrode.
The third dimension corresponds to the probe `ndim` (2d or 3d).
contact_ids: array[str] | None, default: None
Defines the contact ids for the contacts. If None, contact ids are not assigned.
Defines the contact ids for the contacts. If None, contact ids are
auto-generated as the zero-indexed strings ``["0", "1", ..., str(n - 1)]``
so a Probe always carries a stable, slice-invariant handle for each
contact. Pass an explicit array to override.
shank_ids : array[str] | None, default: None
Defines the shank ids for the contacts. If None, then
these are assigned to a unique Shank.
Expand Down Expand Up @@ -379,8 +382,9 @@ def set_contacts(
plane_axes = np.array(plane_axes)
self._contact_plane_axes = plane_axes

if contact_ids is not None:
self.set_contact_ids(contact_ids)
if contact_ids is None:
contact_ids = np.arange(n).astype(str)
self.set_contact_ids(contact_ids)

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

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

if self.contact_ids is None:
arr["contact_ids"] = [""] * self.get_contact_count()
else:
arr["contact_ids"] = self.contact_ids
arr["contact_ids"] = self.contact_ids

if complete:
arr["si_units"] = self.si_units
Expand Down
34 changes: 34 additions & 0 deletions tests/test_probe.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,40 @@ def test_probe():
# ~ plt.show()


def test_set_contacts_auto_generates_contact_ids():
"""When contact_ids is not supplied, Probe auto-generates ['0', ..., str(n-1)]."""
probe = Probe(ndim=2, si_units="um")
positions = np.array([[0, 0], [10, 0], [20, 0], [30, 0]])
probe.set_contacts(positions=positions, shapes="circle", shape_params={"radius": 5})

assert probe.contact_ids is not None
np.testing.assert_array_equal(probe.contact_ids, np.array(["0", "1", "2", "3"]))


def test_set_contacts_respects_explicit_contact_ids():
"""An explicit contact_ids argument is preserved verbatim."""
probe = Probe(ndim=2, si_units="um")
positions = np.array([[0, 0], [10, 0], [20, 0]])
probe.set_contacts(
positions=positions,
shapes="circle",
shape_params={"radius": 5},
contact_ids=["a", "b", "c"],
)

np.testing.assert_array_equal(probe.contact_ids, np.array(["a", "b", "c"]))


def test_set_contact_ids_all_empty_strings_regenerates():
"""Backward compat: older serialized probes used empty strings for 'unset'."""
probe = Probe(ndim=2, si_units="um")
positions = np.array([[0, 0], [10, 0], [20, 0]])
probe.set_contacts(positions=positions, shapes="circle", shape_params={"radius": 5})
probe.set_contact_ids(["", "", ""])

np.testing.assert_array_equal(probe.contact_ids, np.array(["0", "1", "2"]))


def test_set_device_channel_indices_rejects_wrong_size():
"""Setting device_channel_indices with wrong count raises ValueError."""
probe = Probe(ndim=2, si_units="um")
Expand Down
Loading