Use the Python client with Scanpy/AnnData objects.
Preprocessing your data
CyteType requires differential expression results computed per cluster before submission.
import scanpy as sc
# Assumes adata already has normalized counts in adata.X
# and cluster labels in adata.obs["leiden"]
sc.tl.rank_genes_groups(
adata,
groupby="leiden",
method="wilcoxon",
key_added="rank_genes_leiden",
n_genes=100,
)
# If gene symbols are stored separately from var_names:
# adata.var["gene_symbols"] = adata.var_names # or set during load
Initializing the annotator
The initialization step validates your data and precomputes expression percentages. This can take a few minutes on large datasets but only needs to run once per object.
from cytetype import CyteType
annotator = CyteType(
adata,
group_key="leiden", # column in adata.obs with cluster labels
rank_key="rank_genes_leiden", # key in adata.uns with DE results
gene_symbols_column="gene_symbols", # column in adata.var with gene symbols
n_top_genes=50, # top marker genes per cluster
aggregate_metadata=True, # include obs metadata in context
min_percentage=10, # min % threshold for metadata
coordinates_key="X_umap", # key in adata.obsm for UMAP coordinates
max_cells_per_group=1000, # cells sampled per cluster for visualization
auth_token="your-token", # Bearer token for the hosted service
)
Reviewing data before submission
The client builds obs.duckdb from the complete adata.obs table and vars.h5 from the normalized expression matrix, feature metadata, and raw counts when available. Work from a reviewed copy of the object and remove direct identifiers or observation columns that are not needed for annotation and report exploration before creating the CyteType instance.
aggregate_metadata=False disables aggregated metadata in the annotation request, but it does not remove columns from obs.duckdb. Review study_context and the metadata dictionary separately because both are submitted as written.
See Security and privacy for the complete data flow.
Running annotation
The study_context is the most important parameter for annotation quality. Describe your tissue, organism, disease state, and experimental setup in one or two sentences.
adata = annotator.run(
study_context="Human colorectal cancer biopsy, tumor microenvironment, 10X Genomics 5' scRNA-seq, treatment-naive patients",
metadata={
"Study": "My TME atlas",
"GEO": "https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE123456",
"DOI": "https://doi.org/10.1038/example"
},
n_parallel_clusters=4, # increase for faster annotation (watch rate limits)
results_prefix="cytetype", # prefix for result columns
timeout_seconds=7200,
require_artifacts=True, # stop if an artifact build or upload fails
show_progress=True,
)
require_artifacts=False is a failure-tolerance setting, not an upload opt-out. The client still attempts to build and upload both artifacts, and any successful artifact is submitted. Use the default value when the interactive report needs complete expression and metadata tools.
Accessing results
After a successful run, annotations are stored in your object and can be accessed immediately.
# Annotation columns added to adata.obs:
# cytetype_annotation_leiden: cell type label
# cytetype_cellOntologyTerm_leiden: Cell Ontology term name
# cytetype_cellOntologyTermID_leiden: CL:xxxxxxx ID
# cytetype_cellState_leiden: functional state
import json
results = json.loads(adata.uns["cytetype_results"]["result"])
for ann in results["annotations"]:
print(ann["clusterId"], ann["annotation"], ann["ontologyTermID"])
# If you need to re-fetch results after disconnecting:
results = annotator.get_results()
💡 Tip: Jobs continue running on the server even after your local session disconnects. Use annotator.get_results() to retrieve results after reconnecting.
Custom LLM configuration
By default CyteType uses its own hosted model. You can bring your own LLM from any supported provider.
# Single LLM configuration
adata = annotator.run(
study_context="...",
llm_configs=[{
"provider": "openai",
"name": "gpt-4o",
"apiKey": "sk-...",
"baseUrl": "https://api.openai.com/v1", # optional
"modelSettings": {"temperature": 0.0, "max_tokens": 4096}
}]
)
# Multiple providers (different agents can use different models)
adata = annotator.run(
study_context="...",
llm_configs=[
{
"provider": "anthropic",
"name": "claude-3-5-sonnet-20241022",
"apiKey": "sk-ant-...",
"targetAgents": ["annotator", "reviewer"]
},
{
"provider": "openai",
"name": "gpt-4o-mini",
"apiKey": "sk-...",
"targetAgents": ["summarizer", "clinician"]
}
]
)
# AWS Bedrock
adata = annotator.run(
study_context="...",
llm_configs=[{
"provider": "bedrock",
"name": "us.anthropic.claude-3-5-sonnet-20241022-v2:0",
"awsAccessKeyId": "AKIA...",
"awsSecretAccessKey": "...",
"awsDefaultRegion": "us-east-1"
}]
)
Supported providers: anthropic, bedrock, fireworks, google, groq, huggingface, mistral, openai, openrouter, vertex, xai.
Customer-supplied credentials and the relevant scientific prompts pass through the CyteType service so that it can orchestrate the workflow. A custom provider changes the inference account, but it does not bypass the CyteType backend or artifact upload.
Authentication
The hosted service requires a bearer token. Create and manage tokens in the CyteType Dashboard, and store them as secrets rather than directly in a shared script or notebook.
# Pass at initialization (applies to all run() calls)
annotator = CyteType(adata, group_key="leiden", auth_token="your-token")
# Or override at run time
adata = annotator.run(study_context="...", auth_token="your-token")
Learn more