File size: 5,202 Bytes
d6c7577
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
---
license: bsd-3-clause
library_name: interscale
tags:
  - spatial-transcriptomics
  - xenium
  - cell-cell-communication
  - graph-neural-network
  - transformer
  - single-cell
---

# InterScale — 10x Xenium human ovarian adenocarcinoma (held-out-split model)

Pre-trained [InterScale](https://github.com/theislab/interscale) model (Drummer, Jiménez *et al.*,
bioRxiv 2026) for the 10x Xenium human ovarian adenocarcinoma section used in the cell–cell
communication chapter of the single-cell best-practices book. It is the model trained in the
tutorial notebook `notebooks/psls_ccc/interscale_ovarian.ipynb`. Loading it lets you skip training
and go straight to the net-flow and gene-programme analyses.

InterScale combines a **local** GCN over the spatial neighbour graph with a **global** transformer
over all cells of a tissue window. Each component reconstructs masked expression through its own
decoder (`dual_decoder: true`). The transformer's cell-by-cell attention is what the net-flow
analysis reads as directed communication between cell types.

## Files

| file | contents |
| --- | --- |
| `model.pt` | trained weights, InterScale save format (`{"model_state_dict": ...}`), 20 MB |
| `config.yaml` | full resolved config the weights were trained with; only the two local paths are blanked |
| `genes.tsv` | the 3,000 input genes **in model order**, with their Moran's I |
| `metrics.json` | scores of this exact file on the held-out test windows |

## Loading

Needs `interscale` from `main` at or after commit `c98a307`, the version it was trained with.

```python
from huggingface_hub import snapshot_download
import interscale
from interscale.config import load_config

d = snapshot_download("theislab/InterScale", allow_patterns="ovarian_xenium/heldout_split/*")
d = f"{d}/ovarian_xenium/heldout_split"

cfg = load_config(f"{d}/config.yaml")

# `adata` must be prepared as described under "Input" below
interscale.model.CombinedModel._setup_anndata(
    adata=adata,
    prediction_task="regression",
    layer_key="log1p_norm",
    sample_key_list=["sliding_window"],
    split_key=None,
)
model = interscale.model.CombinedModel.load(
    d, adata, cfg, model_name="", local_component=True, global_component=True
)
result = model.get_model_output(adata)  # .obsm["_attn_matrix"], ["_local_emb"], ["_global_emb"],
                                        # .layers["_y_pred_local"], ["_y_pred_global"]
```

`load` raises if the config does not describe the checkpoint, so a model that loads is the trained
model. It will not silently keep random weights. The recipe above was checked by reloading this
folder and comparing all weights bit for bit.

## Input

The model expects the tutorial's preprocessing. Section 2 of `interscale_ovarian.ipynb` does all of
it:

* **Expression:** `adata.layers["log1p_norm"]` = `log1p(normalize_total(counts))`.
* **Genes:** exactly the 3,000 genes of `genes.tsv`, **in that order**
  (`adata = adata[:, genes].copy()`). They are the most spatially variable genes of the 4,447-gene
  panel by Moran's I (radius-30 µm graph, whole slide).
* **Windows:** `adata.obs["sliding_window"]`, 600 µm non-overlapping tiles of the slide made with
  `interscale.pp.sliding_window`, with windows of fewer than 50 cells dropped. The model builds a
  radius-30 µm neighbour graph inside each window.
* **Window size limit:** the largest window must have at most **3,167 cells**, the config's
  `max_seq_len`. For larger windows, raise `model.global_component.parameters.max_seq_len` in the
  config. A too-small value silently subsamples windows.
* `adata.obsm["spatial"]` in µm.

## Training

| | |
| --- | --- |
| data | one section, 400,600 cells, 199 windows (mean 2,013 cells) |
| split | over **windows**, 70 / 15 / 15 → 139 train / 29 val / 31 test, seed 44 |
| objective | node-level reconstruction, **cell masking at 30 %**, SmoothL1 on the masked cells |
| architecture | 2-layer GCN (hidden 512, dropout 0.1) → 2-layer transformer (4 heads, FF 512, dropout 0) → linear decoders, latent width 256 |
| optimisation | lr 0.003, weight decay 0.001, cosine schedule with 10-epoch warm-up, batch 2 windows, early stopping on val loss (stopped at epoch 242 of max 600) |
| hardware | 1× NVIDIA H100 80 GB, ~17 min |

The hyperparameters are the best of a 48-trial random sweep over masking strategy (cell 10/30/50 %,
gene 25/50/75 %), latent width (32–256), GCN/transformer size, dropout, learning rate, weight decay
and batch size. The best config was then re-trained at three seeds and was stable across them, with
a spread under 0.01 in r.

## Evaluation

Scored on the **31 held-out test windows** (65,804 cells), as the median over genes of the per-gene
Pearson r. The masks are fixed (seed 2026), so other models can be compared on the same entries.

| test condition | local decoder | global decoder |
| --- | --- | --- |
| 30 % of cells fully masked (the training task) | 0.163 | 0.165 |
| 50 % of (cell, gene) entries masked | 0.165 | 0.167 |
| no masking (what `get_model_output` returns) | 0.175 | 0.160 |

## Citation

Drummer, F., Jiménez, S. *et al.* InterScale. bioRxiv (2026). Code:
https://github.com/theislab/interscale