Install
$ agentstack add skill-awslabs-hcls-agent-skills-cell-type-annotation ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Cell Type Annotation
Overview
This pipeline skill generates code for annotating cell types in single-cell RNA-seq data. It covers four complementary approaches:
- Automated — CellTypist (logistic-regression classifiers trained on curated atlases).
- Reference-based — SingleR (R) or scANVI/
sc.tl.ingest(Python) label transfer. - Marker-based — Manual assignment from canonical marker gene panels.
- Hierarchical — Coarse compartment assignment followed by specialized fine-grained annotation.
Use it when the user has a clustered AnnData (or SingleCellExperiment) and needs cell-type labels in adata.obs.
Usage
Decision Tree: Choosing an Annotation Approach
Follow this decision tree to select the right method:
- Is there a CellTypist model matching your tissue and species?
- YES → Use CellTypist (Steps 1–6 below)
- NO → Go to step 2
- Do you have a labeled reference AnnData from the same tissue?
- YES, same batch/technology → Use
sc.tl.ingest(Step 12) - YES, different batches → Use scANVI label transfer (Step 13)
- NO → Go to step 3
- Are you working in R with bulk or sorted-cell references?
- YES → Use SingleR (Steps 7–9)
- NO → Go to step 4
- Do you have canonical marker gene panels for expected cell types?
- YES → Use marker-based annotation (Steps 10–11)
- NO → Start with CellTypist's broadest model (
Immune_All_Low.pklorDeveloping_Human_Brain.pkl), then refine with markers
Annotation Workflow (15 Steps)
Step 1 — Confirm normalization. CellTypist requires log1p(CP10k). Check adata.uns['log1p'] exists or run normalization. If data is in adata.raw or a layer, subset appropriately.
Step 2 — Verify gene symbols. Confirm adata.var_names are HGNC (human) or MGI (mouse) symbols, not Ensembl IDs. Convert if needed with sc.queries.biomart_annotations.
Step 3 — Select CellTypist model. Match species + tissue. Use celltypist.models.models_description() to list options. If unsure, start with the broadest model for the compartment.
Step 4 — Run CellTypist annotation. Use celltypist.annotate() with majority_voting=True and appropriate p_thres (default 0.5; raise to 0.7 for stringent calls).
Step 5 — Extract results via to_adata(). Call predictions.to_adata() to get annotations in .obs. Access confidence via adata_result.obs['conf_score'].
Step 6 — Filter low-confidence cells. Mask cells with conf_score 30% of cells get conf_score 80% of cells unexpectedly, suspect wrong model or un-normalized data.
- At Step 12: if query UMAP doesn't align with reference after ingest, batch effects are too strong → switch to scANVI (Step 13).
- At Step 14: if proportions are biologically implausible (e.g., 60% fibroblasts in PBMC), re-examine normalization and model choice.
Response Format
- Lead with the command or code the user needs — explain after.
- Structure as: confirm inputs → working code → key parameters explained → gotchas.
- One complete working example per task; do not show every alternative.
- Keep code comments minimal and functional.
- Target: 50–100 lines of code with brief surrounding explanation.
- Always include confidence score extraction and a filtering step.
Core Concepts
1. CellTypist (automated, Python)
import scanpy as sc
import celltypist
from celltypist import models
# Input: log1p-normalized to 10,000 counts/cell
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
# Download models (first run only)
models.download_models()
print(models.models_description()) # list available models
# Load tissue-matched model
model = models.Model.load(model='Immune_All_Low.pkl')
# Annotate with majority voting
predictions = celltypist.annotate(
adata,
model=model,
majority_voting=True, # smooth labels over neighborhood graph
over_clustering='leiden', # obs key for majority voting resolution
p_thres=0.5, # minimum probability threshold (raise for stringency)
)
# Extract results — modern API (CellTypist >=1.3)
adata_result = predictions.to_adata()
# Annotations now in adata_result.obs:
# 'predicted_labels' — per-cell raw predictions
# 'majority_voting' — smoothed labels
# 'conf_score' — max probability per cell
# Transfer to original adata
adata.obs['cell_type'] = adata_result.obs['majority_voting']
adata.obs['cell_type_conf'] = adata_result.obs['conf_score']
# Filter low-confidence assignments
adata.obs.loc[adata.obs['cell_type_conf'] 3× literature range | Suspect wrong model or normalization error |
## Common Mistakes
1. **Wrong:** Feeding raw counts to CellTypist
**Right:** Always run `sc.pp.normalize_total(adata, target_sum=1e4)` + `sc.pp.log1p(adata)` before annotation
**Why:** CellTypist expects log1p of counts normalized to 10,000/cell — raw or SCTransform'd data produces nonsense labels
2. **Wrong:** Using a reference model that doesn't match the tissue or species
**Right:** Match species (`Human_*` vs `Mouse_*`) and tissue type when selecting CellTypist models or SingleR references
**Why:** Using `Immune_All_Low` on epithelial tumor cells or `HumanPrimaryCellAtlasData` on mouse data produces confident but wrong labels
3. **Wrong:** Passing Ensembl IDs or mixed-case gene symbols to CellTypist/SingleR
**Right:** Convert `adata.var_names` to HGNC (human) or MGI (mouse) symbols before annotation
**Why:** Ensembl IDs or wrong case (`Cd3d` vs `CD3D`) silently drop features, degrading model performance without warning
4. **Wrong:** Using `predictions.probability_matrix.max(axis=1)` to get confidence scores
**Right:** Use `predictions.to_adata().obs['conf_score']` which is the stable API since CellTypist ≥1.3
**Why:** The probability matrix approach is fragile and version-dependent — it may break or return incorrect values across updates
5. **Wrong:** Assigning a rare subtype label to a cluster of fewer than 20 cells
**Right:** Collapse to the parent cell type or label as `'Unknown'` when cluster size is below 20 cells
**Why:** Rare subtype assignments on tiny clusters lack statistical power and are unreliable without strong distinguishing markers
6. **Wrong:** Forcing a label on clusters that lack clear markers after dotplot review
**Right:** Keep ambiguous clusters as `'Unknown'` rather than picking the next-best guess
**Why:** Forced labels propagate errors into downstream analyses (DE, trajectory) and create false biological narratives
7. **Wrong:** Running CellTypist with `majority_voting=False` and using per-cell labels directly
**Right:** Always enable `majority_voting=True` to smooth labels over the local neighborhood graph
**Why:** Per-cell predictions are noisy; majority voting leverages cluster structure to produce coherent, stable annotations
8. **Wrong:** Running `sc.tl.ingest` for label transfer without batch correction
**Right:** Integrate query and reference first, or use scANVI's `load_query_data` path which handles batch natively
**Why:** `ingest` assumes query and reference share the same embedding — batch effects cause misalignment and wrong label transfers
9. **Wrong:** Not validating cell-type proportions against biological expectations
**Right:** Always cross-check per-sample cell-type proportions against known tissue composition
**Why:** A PBMC sample with 80% fibroblasts is a red flag indicating wrong model, normalization error, or data quality issues
10. **Wrong:** Annotating at one resolution only without hierarchical refinement
**Right:** Annotate coarse compartments first (immune/stromal/epithelial), then refine each with specialized models or markers
**Why:** Fine labels on poorly separated clusters are unstable — hierarchical annotation produces more reliable results
## References
- CellTypist: Dominguez Conde et al. Science 2022, https://doi.org/10.1126/science.abl5197
- SingleR: Aran et al. Nat Immunol 2019, https://doi.org/10.1038/s41590-018-0276-y
- scANVI: Xu et al. Mol Syst Biol 2021, https://doi.org/10.15252/msb.20209620
- celldex: Aran et al. 2019, https://bioconductor.org/packages/celldex
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [awslabs](https://github.com/awslabs)
- **Source:** [awslabs/hcls-agent-skills](https://github.com/awslabs/hcls-agent-skills)
- **License:** MIT-0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.