omicverse-single-cell-cellvote-consensus
OmicVerse Single-Cell — CellVote Multi-Annotator Consensus
Goal
Take an annotated AnnData that already has labels from two or more annotators (e.g. obs['scsa_annotation'], obs['gpt_celltype'], obs['gbi_celltype']) and produce a per-cluster consensus label in obs['CellVote_celltype']. The class wraps the upstream annotators (so you can populate them via the same object) and arbitrates by either calling an LLM with the per-cluster candidate set + the cluster's marker genes (online; requires API key) or running a deterministic local-majority vote on lower-cased candidates (offline; tutorial-canonical demo).
CellVote is OmicVerse's own consensus layer; it's distinct from single-popv-annotation (POPV is a separate Bayesian voting tool from a different group). CellVote can include POPV as one of its inputs but is not a wrapper for POPV.
Quick Workflow
- Ensure each candidate annotator's labels are populated in
obscolumns (e.g.scsa_annotation,gpt_celltype,gbi_celltype). Either run the annotators yourself (see Branch Selection for the pre-bakedcv.scsa_anno()/cv.gpt_anno()/cv.gbi_anno()etc.) or import labels from elsewhere. - Compute cluster-level marker genes —
marker_dict[cluster_id] = [top_genes...]. CellVote uses these to give the LLM (or the local arbitrator) biological context per cluster. - Construct:
cv = CellVote(adata). Stores the AnnData reference; doesn't run anything yet. - Vote (online, LLM):
final_map = cv.vote(clusters_key='leiden', cluster_markers=marker_dict, celltype_keys=['scsa_annotation', 'gpt_celltype', 'gbi_celltype'], species='human', organization='PBMC', provider='openai', model='gpt-4o-mini', api_key='sk-...', result_key='CellVote_celltype'). Calls the LLM once per cluster with the candidate labels + markers + species/organization context; LLM returns a single arbitrated label. Result lands inadata.obs['CellVote_celltype']. - Vote (offline, local majority): monkey-patch
omicverse.single._cellvote.get_cluster_celltype = local_fnbefore callingcv.vote(...). The local function takes the same(cluster_celltypes, cluster_markers, species, organization, model, base_url, provider, api_key)signature and returns a dict mapping cluster → consensus label. The tutorial provides a one-linepd.Series.value_counts().idxmax()implementation that's deterministic and free. - Inspect: compare
obs[['leiden', 'scsa_annotation', 'gpt_celltype', 'gbi_celltype', 'CellVote_celltype']].head()and the per-cluster summaryobs.groupby('leiden')[annot_cols].agg(lambda s: s.value_counts().index[0]).