200 lines
6.9 KiB
Markdown
200 lines
6.9 KiB
Markdown
---
|
|
title: "DeepSpot-M API reference"
|
|
task: ""
|
|
lineage_type: import
|
|
upstream_source: https://github.com/K-Dense-AI/scientific-agent-skills/blob/991bd993/skills/deepspot-m/references/api.md
|
|
upstream_sha: 991bd993
|
|
imported_at: 2026-08-08
|
|
prompt_class: prompt
|
|
upstream_changes: accepted
|
|
author: upstream
|
|
validated: false
|
|
---
|
|
|
|
# DeepSpot-M API reference
|
|
|
|
Everything here builds on the two calls in `SKILL.md`: `DeepSpotM.from_pretrained` and
|
|
`model.predict_genes`.
|
|
|
|
## Loading a model
|
|
|
|
```python
|
|
from deepspotm import DeepSpotM
|
|
|
|
model, image_processor = DeepSpotM.from_pretrained("ratschlab/DeepSpotM", source="scgpt")
|
|
```
|
|
|
|
`from_pretrained` returns two objects:
|
|
|
|
- `model`: the PyTorch model that answers gene queries.
|
|
- `image_processor`: the transform that turns one 224x224 PIL tile into the tensor the
|
|
model reads. Always use the processor that came back with the model rather than a
|
|
hand-written transform, so normalisation matches the weights.
|
|
|
|
Arguments:
|
|
|
|
- The repository id, `"ratschlab/DeepSpotM"`. It is gated, so request access on the model
|
|
page and run `huggingface-cli login` before the first call.
|
|
- `source`: which frozen gene embedding the router builds gene-specific projections from.
|
|
One of `evo2`, `orthrus`, `prott5`, `scgpt`, `apertus`.
|
|
|
|
The first call downloads weights into the Hugging Face cache. Set `HF_HOME` to place that
|
|
cache on a volume with room for it, which matters on a shared cluster where the default
|
|
home directory is small.
|
|
|
|
## Choosing an embedding source
|
|
|
|
| `source` | Gene embedding |
|
|
| --------- | ---------------------- |
|
|
| `evo2` | genomic sequence |
|
|
| `orthrus` | RNA |
|
|
| `prott5` | protein sequence |
|
|
| `scgpt` | single-cell expression |
|
|
| `apertus` | language model |
|
|
|
|
The gene router turns whichever embedding you pick into per-gene projections, which is
|
|
what makes genes queryable rather than fixed outputs. Each source describes gene identity
|
|
from a different modality, so the same gene is represented differently under each one.
|
|
|
|
Pick one source per run and keep it fixed across every tile in a slide or cohort, so the
|
|
values stay comparable. When the choice matters to a conclusion, run the same tiles
|
|
through several sources and report the values side by side:
|
|
|
|
```python
|
|
genes = ["EPCAM", "CD3D", "PTPRC"]
|
|
|
|
per_source = {}
|
|
for source in ("scgpt", "prott5", "evo2"):
|
|
model, image_processor = DeepSpotM.from_pretrained("ratschlab/DeepSpotM", source=source)
|
|
tiles = torch.stack([image_processor(require_tile(t)) for t in pil_tiles])
|
|
per_source[source] = model.predict_genes(tiles, genes)
|
|
```
|
|
|
|
Reload the model when you change `source`, and rebuild the tile batch with the processor
|
|
returned alongside it.
|
|
|
|
## Predicting genes
|
|
|
|
```python
|
|
vals = model.predict_genes(image_processor(pil_tile).unsqueeze(0), ["EPCAM", "CD3D"])
|
|
```
|
|
|
|
The first argument is a batch tensor of processed tiles. The second is a list of gene
|
|
symbols. A single tile still needs the batch dimension, which is what `unsqueeze(0)` adds.
|
|
|
|
### Gene symbols
|
|
|
|
Pass HGNC gene symbols as uppercase strings, for example `EPCAM`, `CD3D`, `PTPRC`,
|
|
`MKI67`. The queryable genes are the ~19k-symbol panel shipped with the weights as
|
|
`tokens.csv`, exposed on the loaded model as `model.gene_names`. A symbol outside that
|
|
panel raises `KeyError` naming the offending genes, and predicting genes outside the
|
|
panel is not part of this release. Check membership up front when a gene list comes from
|
|
elsewhere:
|
|
|
|
```python
|
|
panel = set(model.gene_names)
|
|
missing = [g for g in genes if g not in panel]
|
|
if missing:
|
|
raise ValueError(f"Not in the DeepSpot-M panel: {missing}")
|
|
```
|
|
|
|
Two habits keep a run reproducible:
|
|
|
|
- Map aliases to current HGNC symbols before querying, so `CD45` becomes `PTPRC`. Reading
|
|
the list from a file keeps the mapping visible in the run.
|
|
- Keep the gene list beside the output. Values come back in the order requested, and the
|
|
list is the only label the array carries.
|
|
|
|
```python
|
|
genes = [line.strip() for line in open("genes.txt") if line.strip()]
|
|
vals = model.predict_genes(tiles, genes)
|
|
```
|
|
|
|
Ask for every gene you need in one call rather than looping one gene at a time. The tile
|
|
tokens are computed once per batch and reused across the gene queries.
|
|
|
|
## Batching
|
|
|
|
`image_processor` handles one tile, so build a batch by stacking:
|
|
|
|
```python
|
|
import torch
|
|
|
|
batch = torch.stack([image_processor(require_tile(t)) for t in pil_tiles])
|
|
vals = model.predict_genes(batch, genes)
|
|
```
|
|
|
|
Batch size trades throughput against memory. Start at 32 tiles on a GPU and 8 on CPU, then
|
|
raise it while memory allows. Memory grows with both the batch and the number of genes in
|
|
one call, so lower one when the other is large.
|
|
|
|
## Device placement
|
|
|
|
`from_pretrained` accepts a `device` argument and returns the model already in eval mode
|
|
on that device, and `predict_genes` runs under `no_grad` on its own. So device handling
|
|
is one argument plus putting each batch on the same device:
|
|
|
|
```python
|
|
import torch
|
|
|
|
device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
model, image_processor = DeepSpotM.from_pretrained(
|
|
"ratschlab/DeepSpotM", source="scgpt", device=device
|
|
)
|
|
|
|
vals = model.predict_genes(batch.to(device), genes)
|
|
```
|
|
|
|
Keeping the model on the device across batches is what makes a slide-scale run practical.
|
|
Move results back with `.cpu()` before converting to NumPy.
|
|
|
|
## Output units
|
|
|
|
Values are log1p-CPM, the same scale as `log1p` normalised counts per million in a
|
|
single-cell or spatial expression matrix. It is the scale most downstream tools expect, so
|
|
feed it straight into clustering, correlation or spatial statistics.
|
|
|
|
To read values as CPM instead, invert the transform:
|
|
|
|
```python
|
|
import numpy as np
|
|
|
|
cpm = np.expm1(vals.cpu().numpy())
|
|
```
|
|
|
|
Compare values across tiles and slides on the log1p-CPM scale, since that is the scale the
|
|
model produces.
|
|
|
|
## Handling the gated download
|
|
|
|
`from_pretrained` fails when the machine has no access token or the access request is
|
|
still pending. Report the whole path back to a working call rather than the raw error:
|
|
|
|
```python
|
|
DEEPSPOTM_HELP = (
|
|
"DeepSpot-M is unavailable. Install it with `uv pip install deepspotm==1.0.0`, request "
|
|
"access to the gated weights at https://huggingface.co/ratschlab/DeepSpotM, then "
|
|
"authenticate with `huggingface-cli login`."
|
|
)
|
|
|
|
def load_deepspotm(source="scgpt"):
|
|
try:
|
|
from deepspotm import DeepSpotM
|
|
except ImportError as exc:
|
|
raise RuntimeError(DEEPSPOTM_HELP) from exc
|
|
try:
|
|
return DeepSpotM.from_pretrained("ratschlab/DeepSpotM", source=source)
|
|
except Exception as exc:
|
|
raise RuntimeError(DEEPSPOTM_HELP) from exc
|
|
```
|
|
|
|
On a cluster node with no outbound network, download the weights once on a login node and
|
|
point `HF_HOME` at the shared cache.
|
|
|
|
## Primary sources
|
|
|
|
- Paper: <https://doi.org/10.64898/2026.06.19.26356060> (medRxiv, posted 22 June 2026)
|
|
- Code: <https://github.com/ratschlab/DeepSpotM>
|
|
- Weights: <https://huggingface.co/ratschlab/DeepSpotM>
|
|
- PyPI: <https://pypi.org/project/deepspotm/>
|