I propose that Probe should always have contact_ids. Currently, Probe.set_contacts accepts contact_ids=None. I propose that we auto-generate them as ["0", ..., f"{num_contacts - 1}"] in that case, matching the zero-indexed convention
The initial decision to allow contact_ids = None makes sense in retrospect: some probes genuinely do not have per-contact labels. The clearest case is hand-made tetrodes, where no manufacturer exists to supply labels. Some acquisition formats also do not ship per-contact identifiers (Maxwell, 3Brain), so there is a provenance-based argument against inventing them. I still think that in those cases we can just auto-generate as proposed. There is a precedent in python-neo for this pattern (e.g. AxonRawIO) and I see no downside to it.
The upside is for Probe to have a unique user-facing identifier that is slicing, aggregation and re-ordering invariant. Exposing an index as the identifier is a bad idea because it conflates an internal implementation detail (the array used to express the properties) with the object we are modelling.
The None default also creates concrete friction today. Every consumer of contact_ids has to either check for None at the application level (ProbeGroup.add_probe under PR #420 already branches on it) or assume the field is populated and absorb the failure later. Serialisation is non-uniform at the schema level: JSON encodes None as null, a different type than a string array, so round-trips through mixed-state probegroups require conditional handling rather than a single consistent format. Archival formats hit the same: BIDS already requires contact_ids, and future NWB exports will too. A guaranteed contact_id makes all of these uniform. Both probeinterface #420 and SpikeInterface #4465 currently work around the None default at the consumer side; this proposal fixes it at the source.
If preserving manufacturer labels explicitly matters, we can split the concepts as python-neo does: contact_name for the manufacturer label (optional), contact_id for the always-set unique identifier.
I propose that
Probeshould always havecontact_ids. Currently,Probe.set_contactsacceptscontact_ids=None. I propose that we auto-generate them as["0", ..., f"{num_contacts - 1}"]in that case, matching the zero-indexed conventionThe initial decision to allow
contact_ids = Nonemakes sense in retrospect: some probes genuinely do not have per-contact labels. The clearest case is hand-made tetrodes, where no manufacturer exists to supply labels. Some acquisition formats also do not ship per-contact identifiers (Maxwell, 3Brain), so there is a provenance-based argument against inventing them. I still think that in those cases we can just auto-generate as proposed. There is a precedent in python-neo for this pattern (e.g. AxonRawIO) and I see no downside to it.The upside is for
Probeto have a unique user-facing identifier that is slicing, aggregation and re-ordering invariant. Exposing an index as the identifier is a bad idea because it conflates an internal implementation detail (the array used to express the properties) with the object we are modelling.The
Nonedefault also creates concrete friction today. Every consumer ofcontact_idshas to either check forNoneat the application level (ProbeGroup.add_probeunder PR #420 already branches on it) or assume the field is populated and absorb the failure later. Serialisation is non-uniform at the schema level: JSON encodesNoneasnull, a different type than a string array, so round-trips through mixed-state probegroups require conditional handling rather than a single consistent format. Archival formats hit the same: BIDS already requirescontact_ids, and future NWB exports will too. A guaranteedcontact_idmakes all of these uniform. Both probeinterface #420 and SpikeInterface #4465 currently work around theNonedefault at the consumer side; this proposal fixes it at the source.If preserving manufacturer labels explicitly matters, we can split the concepts as python-neo does:
contact_namefor the manufacturer label (optional),contact_idfor the always-set unique identifier.