Repository navigation
ndi.neuron and ndi.element.timeseries report the wrong ndi_element_class and are unregistered: neurons are silently lost in both directions #133
Description
Part of #90 (PR #61 catch-up), recovered by the triage correction. Filed first of that batch because it is the only one that loses data rather than merely lacking a feature.
The defect
ndi_neuron does not override ndi_element_class(), so it inherits "ndi.element" from ndi.element (src/ndi/element/__init__.py:186), and "ndi.neuron" is not in the class registry. ndi_element_timeseries has the identical defect. Verified on main at 429c062:
$ python -c "
import ndi
from ndi.class_registry import get_class
print(ndi.ndi_neuron().ndi_element_class())
print(ndi.ndi_element_timeseries().ndi_element_class())
for n in ('ndi.neuron', 'ndi.element.timeseries'):
print(n, '->', get_class(n))
"
ndi.element
ndi.element
ndi.neuron -> None
ndi.element.timeseries -> None
MATLAB stores class(obj), so a neuron written there carries element.ndi_element_class == 'ndi.neuron' (+ndi/+element/timeseries.m likewise gives 'ndi.element.timeseries').
Both directions lose data, neither loudly
Python writes, anyone reads. element/__init__.py:465 stores self.ndi_element_class(), so a Python-written neuron is labelled ndi.element. It round-trips as a plain ndi_element — no error, wrong class, and readtimeseries is gone.
MATLAB writes, Python reads. session_base.py:1109 looks the stored name up in the registry:
ndi_class = props.get("element", {}).get("ndi_element_class", "")
cls = get_class(ndi_class)
if cls is None:
raise ValueError(f"Unknown element class: {ndi_class!r}. Register it in ndi.class_registry.")get_class("ndi.neuron") is None, so this raises — and getelements (session_base.py:901) swallows it:
for doc in docs:
try:
obj = self._document_to_object(doc)
...
except Exception:
passThe neurons are dropped from the result. A user asking a MATLAB-written session for its neurons gets an empty list and no indication why.
main already knows, and works around it in one place
fun/ensemble_db.py:70 documents the exact defect and routes around it locally:
The class comes from the registry when the document names a registered one; otherwise ndi_element_timeseries, since every element read here is read as a timeseries. (Elements written as plain timeseries record ndi_element_class "ndi.element" because ndi_element_timeseries does not override it, so the registry alone would strand them without readtimeseries.)
That fallback rescues ensemble loading. getelements has no such fallback, so the root cause is still live everywhere else.
The fix
On catchup/ndi-python-2026-08 it is a 14-line ndi_element_class() override on ndi_neuron returning "ndi.neuron", plus registry entries. That branch's class_registry.py also carries the reason as a comment:
# ndi_element_timeseries and ndi_neuron MUST be present or a document whose
# element.ndi_element_class is "ndi.element.timeseries"/"ndi.neuron" cannot
# be reconstructed
Worth doing beyond a straight port:
- Give
ndi_element_timeseriesits ownndi_element_class()returning"ndi.element.timeseries", matching+ndi/+element/timeseries.m. The branch registers it but (likemain) does not add the override, so Python-written timeseries stay mislabelledndi.element. - Decide what
getelementsshould do with an unreconstructable document. Silently dropping it is what made this invisible. At minimum it should warn; a bareexcept Exception: passaround a whole document-to-object conversion hides more than this bug. See uploadDataset silently uploads zero documents and never writes the cloud link — plus the widerexcept Exceptionswallow sweep it came from #130, which is the same swallow pattern elsewhere. - Migration. Documents already written by Python carry
ndi.elementand will not be fixed by the override alone. Worth deciding whether anything reads them back by class.
Verification
A round-trip test in each direction: write a neuron in Python, read it back, assert the class; and a symmetry case for a MATLAB-written neuron document reaching getelements. Neither exists today — which is why five files' worth of this went unnoticed since ed78f8b.
Activity
Add the elementClass cross-language battery (MATLAB side)