Python client library for the Clio connectomics annotation store.
Install directly from GitHub:
pip install git+https://github.com/clio-janelia/clio-store-client.gitOr with uv:
uv pip install git+https://github.com/clio-janelia/clio-store-client.gitThe client authenticates using Google Application Default Credentials. Run this once (in any terminal):
gcloud auth application-default loginThis saves credentials to disk. You can run it at any time — even if Python is already running in another terminal.
Note:
gcloud auth loginalone is not sufficient. You need theapplication-defaultvariant.
Note: Do not use the
GOOGLE_APPLICATION_CREDENTIALSenvironment variable. If it is already set, unset it before creating aClient.
import clio
# Create a client for a dataset (auto-authenticates against the default server)
c = clio.Client("CNS")
# Or specify a different server
c = clio.Client("CNS", server="https://my-clio-store.run.app")
# Fetch body annotations by ID
df = c.get_body_annotations([10000, 10001, 10002])
# Fetch all body annotations for the dataset
df = c.get_all_body_annotations()
# Query by field values (conditions within a dict are ANDed)
df = c.query_body_annotations({"type": "CT1"})
df = c.query_body_annotations({"bodyid": [23, 101], "hemilineage": "0B"})
# OR multiple queries together
df = c.query_body_annotations([
{"status": "traced"},
{"hemilineage": "0B"},
])
# Update body annotations (merge by default — only touches fields you provide)
c.set_body_annotations({"bodyid": 10000, "proposed_type": "RGC", "comment": "Gorgeous!"})
# Update multiple bodies' annotations via a list
c.set_body_annotations([
{"bodyid": 10000, "proposed_type": "RGC"},
{"bodyid": 12345, "proposed_type": "PyN"},
])
# Remove a field from a body annotation
c.set_body_annotations({"bodyid": 10000, "proposed_type": None})
# Update from a DataFrame (must have a 'bodyid' column or index)
# Note:
# For DataFrame inputs, we consider null values to mean "don't overwrite".
# If you need to delete a field, provide a dict (or list) as shown above.
# Be careful not to provide fields you don't want to touch (not even with NaN or None values).
c.set_body_annotations(df)
# Conditionally write (only set a field if it's currently empty)
c.set_body_annotations({"bodyid": 10000, "type": "CT1"}, conditional="type")
# Delete a body annotation
c.delete_body_annotation(10000)
# Metadata
c.get_body_annotation_fields() # list of field names
c.get_body_annotation_versions() # {tag: uuid} mapping
c.get_body_annotation_head_tag() # e.g. "v0.3.33"All read methods return pandas DataFrames indexed by bodyid.