| --- |
| license: mit |
| library_name: pytorch |
| pipeline_tag: graph-ml |
| tags: |
| - graph-neural-networks |
| - histopathology |
| - self-supervised-learning |
| - pytorch-geometric |
| - graph-representation-learning |
| - edge-features |
| --- |
| |
| # GrapHist++: Edge-Informed Graph Self-Supervised Learning for Histopathology |
|
|
| Pre-trained weights, cell graphs and embeddings for the edge-informed extension of |
| [GrapHist](https://huggingface.co/papers/2603.00143). GrapHist encodes a slide as a sparse graph |
| of cells and pre-trains an ACM-GIN encoder with masked feature reconstruction. GrapHist++ keeps |
| that recipe and changes two things: each edge carries a 75-dimensional descriptor of the tissue |
| between the two cells, and a VICReg variance/covariance term is added to the objective to stop |
| the representation collapsing. |
|
|
| - **Original paper:** [arXiv:2603.00143](https://arxiv.org/abs/2603.00143) · [model](https://huggingface.co/ogutsevda/graphist) |
| - **Code:** [Ace3Z/GrapHist-V2](https://github.com/Ace3Z/GrapHist-V2) |
| - **Data manual:** [`DATA.md`](DATA.md), mapping every artifact here to the result it reproduces |
|
|
| ## Results |
|
|
| Slide-level MIL, transfer to unseen cohorts (test macro-F1 %, best of three MIL heads): |
|
|
| | | BRACS | BreakHis | BACH | |
| |---|:--:|:--:|:--:| |
| | GrapHist | 60.30 | 89.37 | **69.16** | |
| | **GrapHist++** | **69.00** | **95.53** | 68.98 | |
|
|
| Survival on TCGA-BRCA (Cox PH, C-index): **0.793**, against 0.763 for GrapHist, 0.724 MAE, |
| 0.632 DINOv2. Cell-type identification (macro-F1 %): PanNuke 20× breast **58.57**, 40× breast |
| **58.38**, NuCLS 7-class **27.09**. The encoder is 7.98 M parameters, 9.29 M with the projection `embed()` runs through, inside a |
| 10.53 M pre-training checkpoint; it embeds a patch in 0.093 ms at 0.327 GB peak memory (BACH, |
| batch size 48, H200). |
|
|
| Without VICReg the encoder collapses (`pca_1` ≈ 0.5, effective dimension ≈ 2 of 512); with it |
| the same 100-epoch run finishes at 0.17 and 11.9. The term is training-only. |
|
|
| ## What's here |
|
|
| ``` |
| graphist_v2.pt the released encoder (127 MB, md5 81a2e6b91cefff0bbbc13c6fd318ee78) |
| modeling/ build_model factory and the ACM-GINEConv backbone |
| graphs/ cell graphs: TCGA-BRCA, BACH, BRACS, BreakHis, SPIDER-breast |
| embeddings/ precomputed slide- and cell-level embeddings |
| baselines/ DINOv2, MAE and GrapHist v1 embeddings for comparison |
| labels/ slide labels and the TCGA-BRCA clinical export |
| studies/ homophily, AdapterGNN and preprocessing-runtime artifacts |
| upstream_v1/ the original GrapHist graphs, unchanged |
| ``` |
|
|
| The repository is ~1.05 TB, so fetch file by file rather than cloning it. `DATA.md` gives the |
| per-cohort commands; the model alone is the six files in the snippet below. |
|
|
| ## Requirements |
|
|
| ```bash |
| pip install torch torch-geometric huggingface_hub pandas numpy |
| ``` |
|
|
| `pandas` and `numpy` are needed only by `modeling/graphist_utils.py`, which holds the input |
| transforms. |
|
|
| Graphs are PyTorch Geometric objects with `x` `(n, 96)`, `edge_index` `(2, e)`, `edge_attr` |
| `(e, 75)` and `batch`. Column 0 of `edge_attr` is the centroid distance in µm and is used as the |
| message weight, not as a feature. |
|
|
| ## Usage |
|
|
| ```python |
| import os, sys, torch |
| from huggingface_hub import hf_hub_download |
| |
| # Fetch file by file. snapshot_download(allow_patterns=...) is equivalent on |
| # huggingface-hub >= 1.27, but silently skips LFS files on 1.7.1, which is the |
| # version the code repository pins, so it would return no checkpoint there. |
| for f in ["graphist_v2.pt", "modeling/graphist_utils.py", |
| "modeling/models/__init__.py", "modeling/models/acm_gin.py", |
| "modeling/models/acm_gineconv.py", "modeling/models/edcoder.py", |
| "modeling/models/utils.py"]: |
| hf_hub_download(repo_id="Ace3Z/graphist-v2", filename=f) |
| |
| path = os.path.dirname(hf_hub_download(repo_id="Ace3Z/graphist-v2", |
| filename="graphist_v2.pt")) |
| sys.path.insert(0, f"{path}/modeling") |
| from models import build_model |
| |
| class Args: |
| encoder = decoder = "acm_gineconv" |
| num_features = 96 # per-cell features |
| num_edge_features = 75 # projection sees 74; distance excluded |
| num_hidden = 512 |
| num_layers = 5 |
| concat_hidden = True # load-critical |
| encoder_norm = "layer" # load-critical |
| edge_distance_in_proj = False # load-critical |
| input_norm = "none" |
| batchnorm = False |
| activation = "prelu" |
| loss_fn = "sce" |
| alpha_l = 3 |
| mask_rate = 0.5 |
| replace_rate = 0.1 |
| drop_edge_rate = 0.0 |
| vicreg_var_weight = 0.05 # training only |
| vicreg_cov_weight = 0.002 |
| vicreg_gamma = 1.0 |
| |
| model = build_model(Args()) |
| ckpt = torch.load(f"{path}/graphist_v2.pt", map_location="cpu", weights_only=False) |
| model.load_state_dict(ckpt["model_state_dict"], strict=True) |
| model.eval() |
| |
| ``` |
|
|
| A graph has to go through the same three transforms the release was trained and evaluated with, |
| or the embeddings will not match. They ship here, in `modeling/graphist_utils.py`: |
|
|
| ```python |
| import json |
| from torch_geometric.data import Batch |
| from torch_geometric.transforms import ToUndirected |
| from graphist_utils import NormalizeData, AddVirtualNode |
| |
| scale_vals = json.load(open("normalization.json")) # ships beside each cohort's graphs |
| graph = torch.load("some_graph.pt", weights_only=False) |
| for t in (ToUndirected(), |
| NormalizeData(scale_vals, edge_attr_skip_cols=[0], min_std=0.01, clip=10), |
| AddVirtualNode(mean_edge_distance=scale_vals["edge_attr"]["mean"][0])): |
| graph = t(graph) |
| |
| batch = Batch.from_data_list([graph]) |
| with torch.no_grad(): |
| h = model.embed(batch.x, batch.edge_index, batch.edge_attr, batch.batch) |
| ``` |
|
|
| Skipping the transforms raises no error; it just yields different numbers. `embed()` returns one |
| row per node **including the synthetic virtual node**, so a 16-cell graph gives `(17, 512)`; drop |
| the last row before pooling to a patch vector. |
|
|
| Three arguments decide whether the weights load at all. `encoder_norm` must be `"layer"` and |
| `edge_distance_in_proj` must be `False` (both defaults are wrong for this checkpoint), and |
| `concat_hidden` must be `True`, which has no default. Anything else raises a shape or key error. |
|
|
| ## Licence and citation |
|
|
| Released under the MIT licence, matching the code repository. |
|
|
| That covers what this repository adds. It cannot relicense the source data, and three upstream |
| terms travel with the derivatives: |
|
|
| | What | Upstream terms | |
| |---|---| |
| | `upstream_v1/`, `studies/adaptergnn/graphs_v1/` | **`cc-by-nc-sa-4.0`** from the GrapHist v1 datasets: non-commercial **and share-alike** | |
| | `graphs/spider_breast/` | `cc-by-nc-4.0`, **research use only** | |
| | `baselines/graphist_v1/graphist_v1.pt` | `apache-2.0`, byte-identical to the released GrapHist v1 checkpoint | |
|
|
| `labels/tcga_brca_clinical.tsv` is the open-access GDC clinical export, redistributed under |
| TCGA's open-access terms. The source cohorts keep their own licences, so cite their papers |
| alongside GrapHist. |
|
|
| The architecture adapts [GraphMAE](https://github.com/THUDM/GraphMAE) and |
| [ACM-GNN](https://github.com/SitaoLuan/ACM-GNN); the regularizer follows |
| [VICReg](https://arxiv.org/abs/2105.04906). |
|
|
| ```bibtex |
| @misc{ogut2026graphist, |
| title = {GrapHist: Graph Self-Supervised Learning for Histopathology}, |
| author = {Sevda {\"O}{\u{g}}{\"u}t and C{\'e}dric Vincent-Cuaz and Natalia Dubljevic and |
| Carlos Hurtado and Vaishnavi Subramanian and Pascal Frossard and Dorina Thanou}, |
| year = {2026}, |
| eprint = {2603.00143}, |
| url = {https://arxiv.org/abs/2603.00143}, |
| } |
| ``` |
|
|