Quickstart¶
This walkthrough takes you from a fresh workspace to a scored embedding and an annotated query set, using only the demo data. Expect roughly fifteen minutes, most of it spent pulling container images on first use.
Before you start¶
You should have completed installation: scfoundry --version works and
scfoundry init my_project has created a workspace. Everything below runs inside it:
cd my_project
Fetch the demo files once (about 112 MB):
mkdir -p demo
for f in colon_1000 colon_50 liver_1shot_support liver_1shot_query; do
curl -L -o demo/$f.h5ad https://github.com/Svvord/scFoundry/raw/main/data/demo/$f.h5ad
done
Every example on this page runs against the four demo files, downloaded once into a
demo/ directory of the workspace as shown in Demo datasets:
demo/colon_1000.h5ad 1,000 human colon cells, labelled, 7 batches
demo/colon_50.h5ad 50 human colon cells, labelled
demo/liver_1shot_support.h5ad one labelled cell per class (5 cells)
demo/liver_1shot_query.h5ad 75 liver cells to annotate
Any file you bring later can be checked against the input contract before it costs GPU
time — scfoundry check --data your.h5ad prints a line-by-line report; see
Input data format.
Step 1 — Download a checkpoint¶
We will use scGPT, the smallest of the transformer models at 198 MB:
scfoundry download --method scgpt
[PROCESS 5c/4583c5] DOWNLOAD:download_scgpt_checkpoints
scGPT_human checkpoints downloaded!
[SUCCESS] completed=1 failed=0 cached=0
[scfoundry] done (ok).
You now have data/model_weights/scGPT/scGPT_human/. This is a one-time step; every later
scGPT run reuses these files.
Step 2 — Embed a dataset¶
scfoundry embed --method scgpt --data demo/colon_1000.h5ad
Note
The first run of any method pulls its container image, which can take several minutes and
a few gigabytes. The image is cached under cache/.shared/nxf_singularity/ in the workspace
(or wherever --cache-dir points) and reused by every later run, so this cost is paid once
per method.
[PIPELINE] scFoundry | profile=standard
[WORKDIR] /home/you/my_project/runs/embed/20260828-162920_scgpt_colon_1000/work
[PROCESS 75/2315a7] EMBED:embed_by_scgpt (colon_1000)
[SUCCESS] completed=1 failed=0 cached=0
[scfoundry] task=embed method=scgpt input=colon_1000
[scfoundry] workspace: /home/you/my_project
[scfoundry] run directory: runs/embed/20260828-162920_scgpt_colon_1000
[scfoundry] done (ok).
The result is a new AnnData file whose X is the embedding rather than expression:
results/embeddings/scgpt/colon_1000.h5ad
The output name comes from the input’s basename, under a directory named for the method. The run directory printed on the last lines holds the exact parameters and the Nextflow log, should you ever need to know how a result was produced.
Step 3 — Look at what you got¶
import anndata as ad
adata = ad.read_h5ad("results/embeddings/scgpt/colon_1000.h5ad")
print(adata.shape) # (1000, 512): cells × embedding dimensions
print(adata.X[:3, :5]) # the embedding matrix itself
print(adata.obs["cell_type"][:5]) # your original metadata, carried through
Two things to notice. The embedding is in adata.X, not adata.obsm — this is consistent
across every method, which is what lets one benchmark score all of them. And your original
obs is preserved untouched, so cell type, batch and donor annotations travel with the
embedding.
Step 4 — Score the embedding¶
benchmark scores every embedding file in a directory against the cell-type labels in
obs["cell_type"], and — with --batch-key — against the batch labels as well:
scfoundry benchmark --embedding results/embeddings/scgpt --batch-key batch_id
[PROCESS aa/c3c25c] BENCHMARK:benchmark_embeddings (scgpt)
[run] colon_1000: bio,batch
[done] wrote out/scgpt_bio_conservation_leiden_metrics_long.csv
[done] wrote out/scgpt_batch_mixing_leiden_metrics_long.csv
[SUCCESS] completed=1 failed=0 cached=0
The wide tables have one row per dataset and one column per metric:
results/benchmark/scgpt_bio_conservation_leiden_metrics_wide.csv
results/benchmark/scgpt_batch_mixing_leiden_metrics_wide.csv
sample_id,method,ARI,ASW,Acc@kNN,COM,FMI,GC,HOM,NMI,cLISI
colon_1000,scgpt,0.589,0.142,0.906,0.650,0.702,0.824,0.655,0.652,0.990
sample_id,method,BRAS,CiLISI,iLISI,kBET
colon_1000,scgpt,0.686,0.173,0.125,0.429
Thirteen metrics, all oriented so that higher is better. What each one measures is
explained in Evaluation metrics; the _long.csv next to each
wide table records, per metric, the Leiden resolution that was selected and the cluster
count it produced.
Note
A thousand cells is a small sample, so treat these numbers as a smoke test rather than evidence about any model. The paper’s benchmark uses 26 tissues and 548,977 cells.
Step 5 — Compare against a baseline¶
The interesting question is never “what did one model score” but “did it beat a simple baseline”. PCA needs no checkpoint, so this costs nothing but a minute of CPU:
scfoundry embed --method pca --data demo/colon_1000.h5ad
scfoundry benchmark --embedding results/embeddings/pca --batch-key batch_id
Each method writes its own tables, so nothing collides. Put the two side by side:
import pandas as pd
tables = [
pd.read_csv(f"results/benchmark/{m}_bio_conservation_leiden_metrics_wide.csv")
for m in ("scgpt", "pca")
]
wide = pd.concat(tables).set_index("method").drop(columns="sample_id")
print(wide.T.round(3))
method scgpt pca
ARI 0.589 0.327
ASW 0.142 0.085
Acc@kNN 0.906 0.912
COM 0.650 0.469
FMI 0.702 0.487
GC 0.824 0.728
HOM 0.655 0.575
NMI 0.652 0.517
cLISI 0.990 0.987
Whether a foundation model beats PCA — and on which metrics, and on which tissues — is exactly the question the accompanying paper sets out to answer across 26 tissues. On a thousand colon cells scGPT wins most metrics; on the full benchmark it does not, which is why one dataset is a smoke test.
Step 6 — Annotate cells from a handful of labels¶
The liver pair is a one-shot problem: the reference holds exactly one labelled cell per
class. transfer embeds reference and query with the frozen model, fits a classifier on
the reference labels, and predicts the query:
scfoundry transfer --method scgpt \
--reference demo/liver_1shot_support.h5ad \
--query demo/liver_1shot_query.h5ad \
--classifier prototype
results/transfer/models/scgpt/prototype/liver_1shot_support/
results/transfer/predictions/scgpt/prototype/liver_1shot_query_predicted_labels.tsv
results/transfer/predictions/scgpt/prototype/liver_1shot_query_predicted_probs.tsv
The query file carries ground-truth labels that the prediction never reads, so you can score it:
import anndata as ad
import pandas as pd
pred = pd.read_csv(
"results/transfer/predictions/scgpt/prototype/liver_1shot_query_predicted_labels.tsv",
sep="\t", index_col=0,
)
truth = ad.read_h5ad("demo/liver_1shot_query.h5ad").obs["cell_type"]
print(f"1-shot accuracy: {(pred['predicted_label'] == truth.reindex(pred.index)).mean():.1%}")
Where to go next¶
The .h5ad contract your files must satisfy, with a validation snippet.
All 22 methods, their checkpoints, and which tasks each one supports.
When you have enough labels to update the weights.
Running many datasets and methods on a Slurm cluster.