Skip to content

One interface. Every connectome.

Ask the same question
of any brain

FlyWire, hemibrain, BANC, MICrONS and zebrafish speak different dialects — CAVE and neuPrint, materialization versions and body IDs. connecto normalises all of it into one API that hands back the same frame, in the same units, from every dataset.

Get started Tutorials

connecto

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:

hasattr(fw, "segmentation")   # True
hasattr(hb, "segmentation")   # False

How →

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 →

version="auto" is correct on both backends

ds.connectivity.edges(ids, version="auto")

Finds the newest materialization in which all your IDs are jointly valid — which matters on CAVE, where root IDs change every time someone edits a neuron. On neuPrint body IDs are immutable, so it is an identity. The same line is right either way. More →

Morphology is just navis

skels = ds.skeletons.get("DA1_lPN")   # navis.NeuronList
navis.plot3d(skels)

Skeletons are TreeNeurons, meshes are MeshNeurons, always in nanometres — so the whole navis ecosystem works without a conversion step. More →

Datasets

dataset species backend annot conn syn scores NT roi-conn rois skel mesh seg cgraph 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 · · · · ·

A · means the call raises, not that it returns something subtly wrong. The table is generated by cn.capability_matrix(), so it cannot drift from the code.

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:

ca3  = cn.CAVE("zheng_ca3", fields={"type": ("cell_type",)})
wasp = cn.NeuPrint("wasp3:v0.8", server="neuprint-pre.janelia.org")

Adding a dataset →

Get going

Install

pip install connecto

Then point it at your existing CAVE and neuPrint tokens — connecto reads from wherever they already live.

Installation → · Credentials →

Learn

Six short tutorials, from a first query to running one script unchanged across two species.

Tutorials →

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.

Reference →

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).