Changelog¶
All notable changes to connecto.
The format is based on Keep a Changelog, and
this project adheres to Semantic Versioning.
Below 1.0 a minor bump can still change behaviour you were relying on; those changes
are listed under Changed rather than left for you to find.
0.3.0 - 2026-09-29¶
Mostly speed: skeletons, meshes and the L2 namespace now fetch in parallel. It is a minor rather than a patch release because two defaults change what you get back - the cell-class columns and the default mesh level of detail - and because the DracoPy floor goes up.
Added¶
meshes.get(lod_fallback=True): small neurons often don't get downsample to the level you ask for. Withlod_fallback=True, gets the coarsest level it does have, instead of raising. That is what a mixed batch wants -lod=2, lod_fallback=True.DatasetSpec.mesh_lod, the default level of detail for a dataset's meshes. It belongs to the mesh bucket rather than to a backend, so both doors of a dataset agree.MultiResMeshSource.get(clamp=True), and ahas_lodsflag on mesh sources so callers ask whether a level means anything rather than sniffing the URL.max_workers=onskeletons.get(), and onmeshes.get()for both doors.parallel=also reaches the graphene mesh reader, which previously accepted it and ignored it.- Progress bars on every method of the L2 namespace.
Changed¶
superclass,classandsubclassare three separate canonical columns, each fed from exactly one source column and never coalesced into one another. A neuron with no cell class now has none, rather than borrowing the tier above. MANC'sclassand FANC'sprimary classhold superclass-level values (descending neuron,glia, ...), so both now feed canonicalsuperclassand neither has a canonicalclass;ids("class:descending neuron")on MANC refuses rather than answering at the wrong level.annotations.searchcovers all three levels.- The default mesh level of detail is per dataset, and clamped per object.
hemibrain, male-CNS, MANC, fish2 and probed neuPrint datasets keep level 1; everything
else defaults to 0.
FlyWire()meshes are therefore finer than before and now identical toFlyWire(backend="cave"). A defaulted level is clamped to the levels each object has; a level you name explicitly is not, and still raises if it does not exist (seelod_fallbackabove). - Requires
DracoPy>=2.1.0(was>=1.4.0). 2.1 is the first release that frees the GIL while decoding, so mesh fragments decode across threads - 4.2x faster serially and 3.5-5.6x more across 8 threads. 2.0 does not, despite the major version. If cloud-volume shares the environment it must be>=12.15.1, since every earlier release pinsDracoPy<2.0.0; see the installation page. - Skeletons are fetched in parallel on every route. Over 16 neurons: FlyWire's bucket 2.2 s -> 0.22 s, MICrONS' skeleton service 52.6 s -> 5.3 s, hemibrain's neuPrint store 5.6 s -> 1.3 s. Results keep the order you asked for.
- Meshes are faster: fragment fan-out is no longer capped at 8, decoding runs on the
fetch workers, and seam welding is ~8x quicker with byte-identical output. One Aedes
neuron 5.3 s -> 2.3 s; eight 22.5 s -> 10.8 s; two hemibrain meshes at LOD 0
2.41 s -> 0.97 s.
DEFAULT_MESH_WORKERSgoes from 4 to 8. - The L2 namespace fans out per neuron, and
l2.skeletonoverlaps its two fetches. Over 6 MICrONS neurons, all four methods run about 2x faster. - neuPrint clients are pooled on the dataset rather than deep-copied per call, so the TLS handshakes are paid once: 9 hemibrain skeletons go 4.6 s -> ~1.3 s after the first call.
- CAVE's connection pool is raised to fit connecto's fan-out, through caveclient's
set_session_defaults. It only ever raises, and keeps any retry policy or larger pool you have already set. meshes.get()builds itsnavis.MeshNeurons withprocess=False.- male-CNS moved to the public neuPrint server,
neuprint.janelia.org. DEFAULT_SKELETON_WORKERSis nowconnecto.core.parallel.DEFAULT_NEURON_WORKERS.
Fixed¶
FlyWire().meshes.get(...)raised "Level of detail 1 out of range" for neurons whose octree has one level. neuPrint's mesh path hard-codedlod=1, which only holds for the Janelia buckets.- FlyWire's and BANC's neuPrint sources overwrote
classwithsuperclass- all 107,504 FlyWire cell classes and 89,409 BANC ones were lost, with noclass_rawkept. male-CNS put subclass values (lt,xn,pm) intoclassfor 14,067 bodies, and Aedes coalesced the other way round. - L2
dotpropsandskeletoncould carry garbage for chunks the L2 cache had no principal axis or position for: caveclient fills a missing attribute with uninitialised memory, not NaN. connecto now asks for the unsplit frame, where missing is honestly null. - Unknown keyword arguments to
skeletons.get()andmeshes.get()were swallowed silently -wrokers=2ran at the default. They now raiseTypeError. - "Connection pool is full, discarding connection" warnings from CAVE at raised
max_workers, each of which cost a fresh TLS handshake on the next read. - A race in which concurrent skeleton workers could leave cloud-volume's root check permanently stubbed out on a shared client.
- Cold-cache precomputed volumes are built under a lock, so parallel workers no longer
each fetch the same
info.
0.2.0 - 2026-08-21¶
Added¶
connecto.precomputed: an in-house reader for neuroglancer precomputed and graphene volumes over plain HTTPS. Sharded containers (both hash functions, delta-encoded minishard indices, compressed morton codes, per-shard index caching),compressed_segmentation, multi-resolution draco meshes, and graphene - watershed reads, label decoding, and meshes assembled from static shards plus the fragments re-meshed since a proofreader last touched the neuron, with the seams welded. Read-only by design: no writing, no image codecs, no cloud SDK credentials.- Per-synapse neurotransmitters for BANC, male-CNS and MANC through neuPrint. Their
Synapsenodes carry the eight-class probability vector, soCap.NT_PER_SYNAPSEis no longer denied backend-wide -synapses(transmitters=True)andtransmitters()work on all three. BackendSpec.nt_columnsandBackendSpec.nt_tabledeclare where transmitters live for a given (dataset, backend). A dataset that claimsNT_PER_SYNAPSEwithout either is now rejected at import rather than returning a frame with nontcolumn.- Synapse frames carry a transmitter provenance stamp in
df.attrs["connecto"]["transmitters"]. BANC's two doors serve different runs of the same model - they agree on the neuron-level call but on only ~57% of shared synapses - so a frame now says which run it holds. nt_sourcecolumn on neuron-level annotations, naming the column each value came from.known_ntis somebody's immunostaining,top_ntis a CNN's argmax; coalescing them silently let "this neuron is GABAergic" mean either.AnnotationSource.fields: per-source column spellings, and a way for a source to declare that it has not got a field at all.- neuPrint annotation sources for FlyWire and BANC.
fetch_neuprintnow borrows the spec's neuPrint door instead of assuming the query backend is neuPrint, so annotation source and backend are a genuine cross-product:FlyWire(backend="cave", annotations="neuprint")is a sentence, not a crash. DracoPyas a direct dependency (draco has no reasonable pure-Python fallback), andcompressed-segmentationin thevoxelsextra - about 25x quicker per chunk than the numpy decoder inconnecto.precomputed.codecs, which stays exact and is used otherwise.- The capability tables in the README and on the docs landing page are now checked
against the registry, per cell and per door, so a table that says it is generated has
to prove it.
NTis split intoNT/syn(the capability) andNT/neuron(an annotation column, marked dense / sparse / none), and thevoxcolumn was added.
Changed¶
- FlyWire and BANC default to their neuPrint annotations. For BANC the mirror is the
better table almost everywhere: 153,984 neurons with a type against codex's 118,446,
154,830 with a transmitter against 115,601, plus somas and status codex has not got.
Both datasets' previous sources are one
annotations=argument away. - BANC's neuPrint source declares
sideabsent - it covers 8,153 bodies against codex's 158,250.ids("PFNd", side="left")now refuses and names the source that can answer, instead of returning 0 where the truth is 18. - Annotation field precedence is stated in one resolver, in one order: spec, then
source, then the handle's
fields=, then the call's. The handle'sfields=is the escape hatchresolve_criterianames by name, so it now outranks the source. Annotations.fieldsreports the priorities in force for the source the handle will actually read, notspec.fields.- male-CNS's neuron-level
ntprefersconsensusNtoverpredictedNt. - neuPrint meshes are read from the published bucket directly rather than through navis, and mesh fetching is threaded instead of one neuron at a time.
PrecomputedMeta.resolutionno longer rounds to int. FANC's mip 0 is 17.2 x 17.2 x 45 nm, and rounding to 17 is a 1.2% error - a hundred voxels at the far edge of the volume, far enough to land a point lookup inside the neighbouring neuron.- Detecting list-valued annotation columns only scans object columns and stops at the first hit: 0.9 s -> 0.3 s on a 175k x 50 frame, which two datasets now read by default.
borrow(ds, kind)inconnecto.sourcesreplaces two hand-rolled copies of "reach the dataset's other backend". The CAVE side now gets the cached client, the real version rules and connecto's error translation rather than its own token lookup and a version guess.
Fixed¶
- neuPrint
synapses()andsynapse_counts()silently undercounted, by up to 5x: the unconstrained side was passed asNone, which neuprint-python turns into:Neuron-only criteria, dropping every:Segmentfragment partner (hemibrain 2,018 -> 4,903; male-CNS 1,186 -> 4,491; BANC 187 -> 994; MANC 3,190 -> 8,044). They now matchedges()exactly. soma_x/y/zwere all-null in neuPrint annotation frames on hemibrain, male-CNS, MANC and fish2.somaLocationcomes back as a raw Neo4j point on the unfiltered query, not the listfetch_neuronshands back for a body-ID list.- FlyWire's neuPrint door claimed
NT_PER_SYNAPSEwithout having the columns behind it, sotransmitters=Truereturned a frame with nontcolumn and no error. It now denies the capability on its ownBackendSpec. - A source's "declared empty" field rename runs after
spec.derive, so a suppressed column is no longer recreated from a regex two lines later. - The "another source has this column" suggestion carries the caller's
backend=through; annotation source and backend are independent axes, and it was resetting one. get_dataset("banc", fields={"side": (...)})was silently overruled by the annotation source that declaredsideabsent.
Removed¶
cloud-volumeis no longer a runtime dependency. Reading is a much smaller job than reading and writing to half a dozen cloud backends, and paying for the latter cost 56 packages and ~78 MB of a default install (boto3, the Google Cloud SDK, gevent, protobuf and the rest). It stays a dev dependency:tests/test_precomputed.pyreads the same cutouts and meshes through both readers and asserts they agree, so cloud-volume is the oracle the new reader is checked against.
0.1.1 - 2026-07-20¶
Added¶
ds.voxels: sparse volumes - every voxel belonging to a neuron - asnavis.VoxelNeurons, or asraw(N, 3)arrays orrleruns, in voxel or nm coordinates.ds.voxels.scales()lists the pyramid levels a dataset actually serves.Cap.VOXELS, kept separate fromSEGMENTATIONbecause "there is a segmentation volume" and "you can get one neuron's voxels out of it without reading the whole brain" are different claims. DVID keeps a live per-body index and answers in one request; a chunkedgraph keeps none, so the same question degrades to reading dense blocks and masking them - 100-1000x more voxels touched than kept.ds.voxels.estimate(), and a refusal rather than an apparent hang when a read would transfer billions of voxels.voxels.get()also picks a sensible default scale, since scale 0 for a hemibrain neuron is 1.17 billion voxels.SparseVolSource, for datasets served by an external per-body index - with the scales it really serves and a per-axisdownsamplefactor, because fly pyramids routinely halve X and Y while leaving Z alone.- DVID servers and nodes are discovered at runtime from neuPrint and clio metadata, so nothing is hard-coded and the node always matches the snapshot the rest of the query came from.
NoSuchBodyError, which is also aKeyErrorsoexcept KeyErroraround a per-neuron loop keeps working.- The
voxelsextra, installingfastremapto speed up masking a dense cutout down to one root.connecto.voxels.pcgfalls back to numpy without it.
0.1.0 - 2026-07-16¶
Initial release: one interface over CAVE and neuPrint.
Added¶
- Dataset handles for FlyWire, Hemibrain, BANC, FANC, MANC, male-CNS, MICrONS, Aedes and
fish2, plus generic
CAVEandNeuPrintconstructors andregister()for pointing connecto at any datastack or neuPrint server. Handles are immutable;ds.at(version)gives you another one. - Namespaces with normalised output:
annotations,connectivity,skeletons,meshes,rois,somas,viz, and - where the backend has them -segmentation,proofreadingandl2. Edges are alwayspre, post, weightasint64, int64, int32, positions are always nanometres, side is alwaysleft/right/center, and skeletons and meshes are always navis neurons. - The capability model:
Cap,DatasetSpec/BackendSpec,ds.supports()andcapability_matrix(). A capability belongs to a (dataset, backend) pair, soFlyWire(backend="neuprint")no longer claims a chunkedgraph it cannot reach. Nothing degrades silently - an explicit request a dataset cannot honour raisesCapabilityError, and whole namespaces are absent rather than broken, sohasattrworks as feature detection. NeuronCriteriaand the mini-language it desugars from, so the same selection reads the same on every dataset.- Version handling: materialization versions and neuPrint dataset tags behind one
Version,ds.versions(),ds.find_version(), and an error rather than a guess when a request is ambiguous. - Annotation sources as a separate axis from the backend - CAVE tables, neuPrint, GitHub TSVs, SeaTable/FlyTable and clio - with per-source freshness tokens.
- An on-disk cache under
~/.connecto/cachewhose key includes the materialization, so it can never serve you the wrong version; live sources join a freshness token to the key and keep one entry rather than one per edit. - Neuroglancer scene building via
ds.viz. - Token handling (
set_token,get_token,auth_status) and server status helpers (server_status,wait_until_available), which wait only when you ask - connecto never silently retries a 503. ds.cite(), because connecto knows exactly which dataset produced the numbers in your figure.