The same query, the same frame¶
Connectomic datasets each speak their own dialect. CAVE has materialization
versions, root IDs that change under you, and per-datastack table names. neuPrint
has immutable body IDs, Cypher, and dataset strings like hemibrain:v1.2.1. And
every dataset has its own idea of what "type" or "side" means.
connecto is one interface over all of it.
import connecto as cn
fw = cn.FlyWire() # neuPrint (backend="cave" for the other door)
hb = cn.Hemibrain() # neuPrint
fw.connectivity.edges("DA1_lPN") # pre, post, weight
hb.connectivity.edges("DA1_lPN") # the same frame - different backend, different species conventions
Both calls return pre, post, weight as int64, int64, int32. Positions are
always nanometres. Side is always left / right / center. Skeletons and
meshes are always navis neurons.
Two promises¶
It is the same frame, and that is tested¶
BANC is served by both backends at the same snapshot — CAVE materialization 888
is neuPrint banc:v888, and its neuPrint body IDs are valid CAVE root IDs. So the
promise is checkable rather than aspirational:
cave = cn.BANC(backend="cave").connectivity.edges(ids)
np_ = cn.BANC(backend="neuprint").connectivity.edges(ids)
pd.testing.assert_frame_equal(cave, np_) # passes
That test has already caught two real divergences. How →
Nothing degrades silently¶
If a dataset cannot do what you asked, it says so. It does not quietly hand you an unfiltered result.
fw.connectivity.synapses(x, min_score=100) # fine
mic.connectivity.synapses(x, min_score=100) # CapabilityError
Defaults never raise; only an explicit request for something the dataset lacks does. Whole namespaces are absent rather than broken, so feature detection works:
One mini-language for selecting neurons¶
ids() takes anything¶
ds.ids(720575940604407468) # a root/body ID
ds.ids("DA1_lPN") # a type
ds.ids("/^AOTU00.*") # a regex
ds.ids("super_class:visual_projection")
ds.ids("DA1_lPN", side="left")
Anything you can pass to ids() you can pass to any query. It is a pure function:
it returns IDs and mutates nothing.
More →
Datasets¶
| dataset | species | backend | annot | conn | syn | scores | NT/syn | NT/neuron | roi-conn | rois | skel | mesh | seg | cgraph | vox | proof | soma | live |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
flywire |
fly | neuprint | ✅ | ✅ | ✅ | ✅ | · | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | · | · | · | ✅ | · |
| cave | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | · | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | · | ||
flywire-production |
fly | cave | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | · | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
banc |
fly | neuprint | ✅ | ✅ | ✅ | · | ✅ | ✅ | ✅ | ✅ | · | · | · | · | · | · | ✅ | · |
| cave | ✅ | ✅ | ✅ | · | ✅ | ✅ | ✅ | · | ✅ | ✅ | ✅ | ✅ | ✅ | · | ✅ | · | ||
fanc |
fly | cave | ✅ | ✅ | ✅ | ✅ | · | · | · | · | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
hemibrain |
fly | neuprint | ✅ | ✅ | ✅ | ✅ | · | · | ✅ | ✅ | ✅ | ✅ | ✅ | · | ✅ | · | ✅ | · |
malecns |
fly | neuprint | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | · | ✅ | · | ✅ | · |
manc |
fly | neuprint | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | · | ✅ | · | ✅ | · |
microns |
mouse | cave | ✅ | ✅ | ✅ | · | · | · | · | · | ✅ | ✅ | ✅ | ✅ | ✅ | · | ✅ | · |
aedes |
mosquito | cave | ✅ | ✅ | ✅ | · | · | ◐ | · | · | ✅ | ✅ | ✅ | ✅ | ✅ | · | ✅ | ✅ |
fish2 |
zebrafish | neuprint | ✅ | ✅ | ✅ | ✅ | · | · | ✅ | ✅ | ✅ | ✅ | · | · | ✅ | · | ✅ | · |
The capability columns come from cn.capability_matrix(), and a · there means the
call raises — not that it returns something subtly wrong.
NT/syn and NT/neuron are different claims, and the difference matters. NT/syn
is a capability: a prediction per synapse, so synapses(x, transmitters=True) and
connectivity.transmitters(x) work, and raise where they can't. NT/neuron is one call
per neuron and is not a capability — it arrives as the nt column of
annotations.get(), and it depends on the annotation source rather than the backend,
so it has no cell in capability_matrix(). Ask ds.annotations.fields["nt"] for the
columns behind it.
NT/neuron has a third mark, because "this dataset has transmitters" hides a difference
you cannot afford to miss when you aggregate:
| ✅ | dense — a classifier was run over the volume, so essentially every neuron has a call, and most of them are model output |
| ◐ | sparse — curated only: immunostaining, RT-PCR and the literature, transferred by cell type. Real evidence, but it covers a fraction of the dataset and a null means "nobody has looked", not "no transmitter" |
| · | nothing at all |
aedes is the ◐: 6,981 of its 17,397 neurons carry a transmitter, every one of them
traceable to a paper, and no model has ever run on that volume. hemibrain is the ·.
Treating those two the same — or treating aedes's 40% like FlyWire's 99% — is how a
"GABAergic fraction" ends up being a statement about annotation effort. nt_source
names the exact column behind every value, so the distinction survives into the frame.
aedes's CAVE datastack ships no annotation table — so its cell typing comes from
FlyTable (the lab's SeaTable) instead, keyed by root ID. It is lab-internal, so it
needs a SEATABLE_TOKEN: without one you get a clear missing-token error, not a
silently empty frame. What aedes still has no · for is a per-synapse score — its
synapse table records size, not a confidence — so min_score= raises.
Anything not listed works too, without writing a class:
Get going¶
Install¶
Then point it at your existing CAVE and neuPrint tokens — connecto reads from wherever they already live.
Learn¶
Six short tutorials, from a first query to running one script unchanged across two species.
Look things up¶
The API reference is generated from the docstrings, so there is no second copy of the documentation to drift out of sync.
What it doesn't do¶
Data fetching only. No clustering, no matching, no connectivity vectors, no
curation policy — connecto will backfill a type from the columns your dataset
declares, but it will never decide that some types are "bad" and null them out.
That is analysis, and it belongs upstream (see
cocoa).