Transfer¶
What it does¶
Labels cells from a labelled reference without changing the model. The reference and the query are embedded with the frozen model, a lightweight classifier is fitted on the reference embeddings and their labels, and the query is predicted.
Because no parameter of the model is updated, this works identically for every method that produces an embedding, and it runs in minutes. When the reference is tiny — one cell per class — it is the few-shot setting; when the reference is a fully annotated atlas, it is ordinary reference mapping. The mechanics are the same.
It is also the right comparison for models whose authors never published a
weight-updating recipe (SCimilarity, GenePT, UCE, scPRINT): --classifier mlp trains the
same post-hoc head on top of any of them.
Supported methods¶
scfoundry list methods --task transfer:
c2s cellama cellfm cellplm geneformer genept langcell scbert sccello scfoundation
scgpt scimilarity scprint uce scvi
pca, novae and the integration methods are not available here.
Classifiers¶
|
Definition |
|---|---|
|
Embeddings are z-scored per dimension on the reference; an L2-regularised multinomial
logistic regression ( |
|
One prototype per class: the mean of the L2-normalised reference embeddings of that
class. A query cell is scored by cosine similarity to every prototype;
probabilities are |
|
The |
|
A two-layer MLP head trained with a stratified validation split (20% of each class, at least one cell held out per class) and early stopping. Needs at least one class with five or more reference cells; refuses to run otherwise. |
Note
knn uses a plain majority vote. On a small reference, set --knn-k yourself — with one
cell per class, k larger than 1 mixes classes by construction, and a k larger than the
smallest class can never predict that class unanimously. scFoundry warns when k exceeds
the smallest class or had to be capped, but it does not second-guess your choice.
Inputs¶
Two files, both satisfying the standard input contract:
Reference — must carry labels in
obs["cell_type"](or--label-key).Query — labels not required. If present they are ignored, which is convenient for scoring afterwards.
Both need a unique obs["barcode"]; the prediction tables are indexed by it.
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.
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
The liver pair is a genuine one-shot setup: the reference contains exactly five cells, one per class.
Running it¶
Note
Run scfoundry commands from inside a workspace created by scfoundry init (any
subdirectory works — the workspace is found by walking upwards, like a git repository), or
pass --workspace DIR. Weights, the image cache, results and run logs all live there. See
The workspace.
scfoundry transfer --method scgpt \
--reference demo/liver_1shot_support.h5ad \
--query demo/liver_1shot_query.h5ad \
--classifier prototype
[PROCESS 3b/0a7f1e] TRANSFER:EMBED:embed_by_scgpt (liver_1shot_support)
[PROCESS 91/c2d8b0] TRANSFER:EMBED:embed_by_scgpt (liver_1shot_query)
[PROCESS e7/5f13aa] TRANSFER:transfer_fit (scgpt prototype liver_1shot_support)
[PROCESS 2c/9d40b7] TRANSFER:transfer_predict (scgpt prototype liver_1shot_query)
[SUCCESS] completed=4 failed=0 cached=0
Both files are embedded in one pass; the fitted model and the predictions are both saved.
scfoundry transfer --method scgpt --reference atlas.h5ad
results/transfer/models/scgpt/logreg/atlas/
Fit once, then predict several query sets against the same reference.
scfoundry transfer --method scgpt \
--query new_sample.h5ad \
--fitted results/transfer/models/scgpt/logreg/atlas
--fitted is the model directory written by the fit stage. The classifier and the method
are read from its meta.json, so they need not be repeated.
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.
Parameters¶
Option |
Type |
Default |
Description |
|---|---|---|---|
|
string |
required |
Which model provides the embeddings. |
|
path |
none |
Labelled reference cells. Required to fit. |
|
path |
none |
Cells to label. Required to predict. |
|
path |
none |
Model directory from an earlier fit. Required when |
|
string |
|
|
|
string |
|
|
|
integer |
|
Neighbours for |
|
per method |
Passed to the embedding step, as in Embed. |
Every embedding parameter also applies, since embedding is the first step.
Outputs¶
results/transfer/
├── models/<method>/<classifier>/<reference>/
│ ├── meta.json classes, dimensions, hyperparameters
│ └── model.npz | mlp/ the fitted classifier
└── predictions/<method>/<classifier>/
├── <query>_predicted_labels.tsv
└── <query>_predicted_probs.tsv
For the one-shot command above:
results/transfer/models/scgpt/prototype/liver_1shot_support/meta.json
results/transfer/models/scgpt/prototype/liver_1shot_support/model.npz
results/transfer/predictions/scgpt/prototype/liver_1shot_query_predicted_labels.tsv
results/transfer/predictions/scgpt/prototype/liver_1shot_query_predicted_probs.tsv
The embeddings of both files are published to results/embeddings/<method>/ as a side
effect, so they need not be recomputed for benchmark or geometry.
The two TSVs are indexed by barcode:
_predicted_labels.tsv— one column,predicted_label, the argmax class._predicted_probs.tsv— one column per class, rows summing to 1.
meta.json records what the model was fitted on:
{
"format": "scfoundry_transfer/1",
"classifier": "logreg",
"method": "scgpt",
"label_key": "cell_type",
"classes": ["cholangiocyte", "mast cell", "mononuclear phagocyte",
"natural killer cell", "plasma cell"],
"dim": 512,
"n_reference": 5,
"cells_per_class": {"cholangiocyte": 1, "mast cell": 1, "...": 1},
"hyperparameters": {"C": 1.0, "max_iter": 2000, "scaling": "z-score", "n_iter": 22}
}
Reading results in Python¶
import anndata as ad
import pandas as pd
base = "results/transfer/predictions/scgpt/prototype/liver_1shot_query"
labels = pd.read_csv(f"{base}_predicted_labels.tsv", sep="\t", index_col=0)
probs = pd.read_csv(f"{base}_predicted_probs.tsv", sep="\t", index_col=0)
# Confidence of the winning call, useful for flagging uncertain cells.
labels["confidence"] = probs.max(axis=1)
print(labels.head())
Scoring against held-out truth:
truth = ad.read_h5ad("demo/liver_1shot_query.h5ad").obs["cell_type"]
accuracy = (labels["predicted_label"] == truth.reindex(labels.index)).mean()
print(f"accuracy: {accuracy:.1%}")
Gotchas¶
Every class you expect must be in the reference. A class absent from the reference can never be predicted — query cells of that type are silently assigned to whichever class is nearest. Compare the label sets before you trust a prediction.
Fitted models are method-specific. A classifier fitted on scGPT embeddings is
meaningless applied to Geneformer embeddings. meta.json records the method, and
--fitted refuses a mismatch when you pass --method explicitly, but it cannot tell you
that your query was embedded with different settings.
Class imbalance in the reference is not corrected. Prototypes are plain class means and
logreg is unweighted, so a class with 100 reference cells is better estimated than one
with 1. That is usually what you want; just be aware it is not normalised away.
Cosine similarity ignores magnitude. prototype and knn only look at embedding
direction. For models whose embeddings encode information in vector norm, this discards it;
logreg and mlp do not.
Note
Every launch gets its own run directory under runs/<task>/, and the workspace
nextflow.config sets cleanup = true, so the task work directory is deleted once the run
succeeds. After a failure it is kept: fix the cause, add --resume to the same command,
and Nextflow reuses every task that already completed. scfoundry runs lists the run
directories with their status; scfoundry runs --task <task> narrows the list.