How scFoundry works

Single-cell foundation models are published as independent research codebases. Each one pins its own PyTorch version, expects its own gene vocabulary, tokenises expression its own way, and writes its embedding somewhere different. Reproducing a comparison across even three of them normally means three conda environments, three preprocessing scripts, and three sets of assumptions you have to trust.

scFoundry removes that work. Every method is wrapped in a container image, every task takes the same .h5ad input contract, and every result lands in a predictable place under results/. Switching models means changing one word on the command line.

The same command, two different foundation models
scfoundry embed --method scgpt      --data demo/colon_1000.h5ad
scfoundry embed --method geneformer --data demo/colon_1000.h5ad

Two layers: a command and a pipeline

scfoundry is a small Python program with no dependencies beyond the standard library. It does three things: it keeps a workspace (your weights, image cache, results and run logs), it turns a command line into a parameter file, and it launches Nextflow on the pipeline that ships inside the package.

Nextflow is the execution engine. It pulls the container image a method needs, stages the inputs, runs the method’s own code, publishes the outputs, and — on a cluster — submits each process to the scheduler. You never call it directly, but everything it can do (resume, executors, resource limits) is reachable through scfoundry.

scfoundry embed --method scgpt --data cells.h5ad
        │
        ├─ finds the workspace, writes runs/embed/<time>_scgpt_cells/params.json
        │
        └─ nextflow run <pipeline>/main.nf --task embed -params-file params.json
                   │
                   └─ process embed_by_scgpt, in housy17/scgpt:0.2.4, on the GPU
                              │
                              └─ results/embeddings/scgpt/cells.h5ad

The pipeline is the same code the paper’s benchmark ran; the command is what makes it usable without reading it.

The tasks

Task

What it does

Guide

download

Fetches a model’s official pretrained checkpoint into the workspace. Run once per model.

Model weights

check

Reports whether an .h5ad satisfies the input contract, and what a given task or method would need from it. Advisory; changes nothing.

Input data format

embed

Cell embeddings: zero-shot with a frozen scFM or the pretrained Census scVI, with PCA, or with a batch-integration method trained on your data.

Embed

transfer

Labels a query set from a labelled reference using frozen embeddings and a lightweight classifier. No model parameters change.

Transfer

finetune

Updates a model’s parameters on labelled cells following its authors’ recipe, then predicts labels.

Fine-tune

benchmark

Scores embeddings against cell-type and batch labels with the thirteen metrics of the paper.

Benchmark

geometry

Measures the geometry of an embedding: effective dimension, anisotropy, neighbourhood preservation, intrinsic dimension, variance decomposition.

Geometry

One AnnData h5ad file feeds six scfoundry tasks, each running the selected method in its own container image inside a workspace, producing embedding files, prediction tables, or metric tables under results/.

The shape of every run: one input contract, one method flag, one predictable output path.

One container per method

--method does two things at once: it picks which code path runs, and it picks which container image that code runs inside. scGPT executes in {{ registry }}/scgpt:0.2.4; Geneformer executes in {{ registry }}/geneformer:latest. The two never share a Python environment, so their conflicting dependency pins cannot interfere.

Nextflow bind-mounts exactly two host directories into every container:

<pipeline>/bin/                 →  /code                  model code and helper scripts
<workspace>/data/model_weights/ →  /data/model_weights    pretrained checkpoints

This is the whole host–container contract, and it is why a checkpoint is always referred to as scGPT/scGPT_human — a path under /data/model_weights — regardless of where your workspace lives.

See also

The full method-to-image mapping is in the method reference.

Three kinds of method

scfoundry list methods groups the 22 methods into three categories, and the distinction matters when you read results:

  • zero-shot — the pretrained foundation models themselves, more than a dozen of them. Frozen weights, no labels, no batch information. This is what the benchmark ranks.

  • referencepca, a per-dataset HVG PCA: the cheap yardstick every foundation model has to beat. scvi sits here too: zero-shot projection onto the CELLxGENE Census scVI, a model pretrained on tens of millions of cells — not a baseline, but not one of the transformer scFMs the paper counts either.

  • integrationscgpt_integrated, scvi_denovo, harmony, seurat_cca, seurat_rpca. Trained on your data using its batch labels, and nothing else. They see information the zero-shot methods never do, so they are ranked separately.

Minimal preprocessing, on purpose

scFoundry does almost nothing to your data before handing it to a model. That is a design decision, not an omission.

Foundation models are pretrained on raw counts across the full transcriptome, and each one maps genes to tokens using its own vocabulary. If the framework silently normalised, scaled, or subset your matrix, every model would receive input outside its pretraining distribution — and the resulting benchmark would measure the preprocessing, not the model.

So the contract is narrow and strict: raw counts in adata.X, every gene retained, HGNC symbols in var. Quality control — filtering low-quality cells, removing doublets — is yours to do beforehand.

Warning

Input must be raw counts over the full transcriptome. Passing log-normalised values, or an object already subset to highly variable genes, does not raise an error — it produces a plausible-looking embedding that is silently wrong. See Input data format.

Where results go

Everything is written under results/ in the workspace (or under --outdir). Directory names are the method ids you typed, so a script that loops over methods can predict every path.

results/
├── embeddings/<method>/<dataset>.h5ad                    embedding in adata.X
├── transfer/
│   ├── models/<method>/<classifier>/<reference>/         fitted classifier
│   └── predictions/<method>/<classifier>/<query>_predicted_labels.tsv
├── finetune/
│   ├── finetuned_models/<method>/<reference>/            fine-tuned weights
│   └── prediction/<method>/<query>_predicted_labels.tsv
├── benchmark/<method>_bio_conservation_leiden_metrics_wide.csv
└── geometry/<method>/<dataset>_<method>.csv

Every launch also leaves a record under runs/<task>/ — the exact parameters, the Nextflow command and log — so a result can always be traced back to how it was made.

What scFoundry is not

It is not an analysis pipeline. There is no clustering for its own sake, no differential expression, no trajectory inference. It produces embeddings, label predictions and metric tables; what you do with them is your analysis.

It does not tune models for you. Every method runs with its authors’ defaults. That is the point of a benchmark, and it is also why a method that performs badly out of the box performs badly here.

It does not second-guess your data. If you pass log-normalised values or an HVG-subset object, the tasks will run to completion and give you a wrong answer.

Next steps

  • Install — the three requirements and the first run.

  • The workspace — where things live and how runs are recorded.

  • Quickstart — embed, score and annotate the demo data.