Skill Tracer
Skills › Scientific Agent Skills › pathml › graphs.md

graphs.md

352 lines · skills/pathml/references/graphs.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Graph construction and spatial schema
2
3This reference targets **PathML 3.0.8 stable**. The stable graph API is based on
4graph builders and PyTorch Geometric data objects; it does not contain the
5`CellGraph.from_instance_map()` abstraction found in older generated examples.
6

Graph construction and spatial schema

This reference targets PathML 3.0.8 stable. The stable graph API is based on graph builders and PyTorch Geometric data objects; it does not contain the CellGraph.from_instance_map() abstraction found in older generated examples.

Raw lines 1 to 6
7## Stable public exports
8
9```python
10from pathml.graph import (
11 ColorMergedSuperpixelExtractor,
12 Graph,
13 HACTPairData,
14 KNNGraphBuilder,
15 RAGGraphBuilder,
16 build_assignment_matrix,
17 get_full_instance_map,
19```
21Other classes documented under `pathml.graph.preprocessing` may be internal or
22not re-exported. Prefer the public names above and pin the PathML version.

Stable public exports

from pathml.graph import (
    ColorMergedSuperpixelExtractor,
    Graph,
    HACTPairData,
    KNNGraphBuilder,
    RAGGraphBuilder,
    build_assignment_matrix,
    get_full_instance_map,
)

Other classes documented under pathml.graph.preprocessing may be internal or not re-exported. Prefer the public names above and pin the PathML version.

Raw lines 7 to 23
24## Inputs and coordinate contract
26A cell/tissue graph starts with:
281. an instance map `(height, width)`, where 0 is background and each object has a
29 positive integer label;
302. one feature row per object;
313. optional node annotation rows and a graph target; and
324. explicit coordinate units and image level.
34For stable builders, make labels contiguous `1..N`. Both graph topology and
35feature alignment assume a deterministic object order. Build and persist a table:
37```text
38node_index,instance_label,centroid_x,centroid_y,feature_row
390,1,120.5,88.0,0
401,2,175.0,92.5,1
41```
43PathML computes centroids from `skimage.measure.regionprops` and stores them as
44`(x, y)` after integer rounding. This differs from tile `(i, j)` order.
46If the instance map comes from level `L`, KNN distances and centroids are in
47level-`L` pixels:
49```text
50x_um = x_L * downsample_L * mpp_x
51y_um = y_L * downsample_L * mpp_y
52```
54Never describe a radius/threshold as biological distance unless it has been
55converted to a physical unit.

Inputs and coordinate contract

A cell/tissue graph starts with:

  1. an instance map (height, width), where 0 is background and each object has a positive integer label;
  2. one feature row per object;
  3. optional node annotation rows and a graph target; and
  4. explicit coordinate units and image level.

For stable builders, make labels contiguous 1..N. Both graph topology and feature alignment assume a deterministic object order. Build and persist a table:

node_index,instance_label,centroid_x,centroid_y,feature_row
0,1,120.5,88.0,0
1,2,175.0,92.5,1

PathML computes centroids from skimage.measure.regionprops and stores them as (x, y) after integer rounding. This differs from tile (i, j) order.

If the instance map comes from level L, KNN distances and centroids are in level-L pixels:

x_um = x_L * downsample_L * mpp_x
y_um = y_L * downsample_L * mpp_y

Never describe a radius/threshold as biological distance unless it has been converted to a physical unit.

Raw lines 24 to 56
57## Avoid full-slide reconstruction by default
59`get_full_instance_map(wsi, patch_size, mask_name="cell")` reconstructs a dense
60image and instance map large enough to cover the slide. On a gigapixel WSI this
61can exhaust RAM and duplicate tile-overlap objects.
63Use it only for a bounded ROI or small image after estimating memory. For large
64slides:
66- construct graphs per nonoverlapping region;
67- reconcile boundary objects with stable global IDs;
68- optionally join regional graphs with a documented edge policy; and
69- use sparse coordinates/features rather than a dense whole-slide canvas.
71Do not use padded zero regions as tissue. Record crop origin so local coordinates
72can be mapped to the slide.

Avoid full-slide reconstruction by default

get_full_instance_map(wsi, patch_size, mask_name="cell") reconstructs a dense image and instance map large enough to cover the slide. On a gigapixel WSI this can exhaust RAM and duplicate tile-overlap objects.

Use it only for a bounded ROI or small image after estimating memory. For large slides:

  • construct graphs per nonoverlapping region;
  • reconcile boundary objects with stable global IDs;
  • optionally join regional graphs with a documented edge policy; and
  • use sparse coordinates/features rather than a dense whole-slide canvas.

Do not use padded zero regions as tissue. Record crop origin so local coordinates can be mapped to the slide.

Raw lines 57 to 73
74## KNN graph
76```python
77import numpy as np
78from pathml.graph import KNNGraphBuilder
80# instance_map labels are 0 background, then contiguous 1..N.
81n_nodes = int(instance_map.max())
82features = np.ones((n_nodes, 1), dtype=np.float32)
84builder = KNNGraphBuilder(
85 k=5,
86 thresh=80, # selected-level pixels
87 add_loc_feats=True,
88 return_networkx=False,
90graph = builder.process(
91 instance_map,
92 features=features,
93 annotation=None,
94 target=None,
96```
98Stable behavior:
100- `k` nearest neighbors are computed from centroids.
101- `thresh=None` keeps all KNN edges; otherwise edges longer than `thresh` are
102 removed.
103- `k` must be smaller than the available node count.
104- adjacency generated by nearest-neighbor queries may be directed; do not assume
105 every reverse edge exists.
106- `add_loc_feats=True` appends centroids normalized by image width/height.
107- `return_networkx` is a **builder constructor** option, not an argument to
108 `process()`.
110Distinct objects can round to the same centroid; zero-distance adjacency entries
111are discarded by the builder, so inspect coincident centroids and missing edges.
112For anisotropic MPP, pixel-space nearest neighbors are not physical nearest
113neighbors. Convert centroids to micrometres first and use
114`builder.process_with_centroids(centroids_xy_um, features=features)` with
115`centroids_xy_um` as a CPU Torch tensor and `add_loc_feats=False`; set `thresh`
116in micrometres and retain original pixel
117coordinates separately. Scaling only the reported distances after building
118the graph does not fix a topology constructed in the wrong metric.
120In v3.0.8 source, `BaseGraphBuilder.process()` reads `features.shape` before its
121nominal `features=None` branch. Pass an explicit `(N, F)` feature array.

KNN graph

import numpy as np
from pathml.graph import KNNGraphBuilder

# instance_map labels are 0 background, then contiguous 1..N.
n_nodes = int(instance_map.max())
features = np.ones((n_nodes, 1), dtype=np.float32)

builder = KNNGraphBuilder(
    k=5,
    thresh=80,              # selected-level pixels
    add_loc_feats=True,
    return_networkx=False,
)
graph = builder.process(
    instance_map,
    features=features,
    annotation=None,
    target=None,
)

Stable behavior:

  • k nearest neighbors are computed from centroids.
  • thresh=None keeps all KNN edges; otherwise edges longer than thresh are removed.
  • k must be smaller than the available node count.
  • adjacency generated by nearest-neighbor queries may be directed; do not assume every reverse edge exists.
  • add_loc_feats=True appends centroids normalized by image width/height.
  • return_networkx is a builder constructor option, not an argument to process().

Distinct objects can round to the same centroid; zero-distance adjacency entries are discarded by the builder, so inspect coincident centroids and missing edges. For anisotropic MPP, pixel-space nearest neighbors are not physical nearest neighbors. Convert centroids to micrometres first and use builder.process_with_centroids(centroids_xy_um, features=features) with centroids_xy_um as a CPU Torch tensor and add_loc_feats=False; set thresh in micrometres and retain original pixel coordinates separately. Scaling only the reported distances after building the graph does not fix a topology constructed in the wrong metric.

In v3.0.8 source, BaseGraphBuilder.process() reads features.shape before its nominal features=None branch. Pass an explicit (N, F) feature array.

Raw lines 74 to 122
123## Region adjacency graph
125```python
126from pathml.graph import RAGGraphBuilder
128builder = RAGGraphBuilder(
129 kernel_size=3,
130 hops=1,
131 add_loc_feats=False,
132 return_networkx=False,
134graph = builder.process(instance_map, features=features)
135```
137`RAGGraphBuilder` dilates each labeled instance and connects labels encountered at
138the boundary. `hops>1` expands neighborhoods. Stable implementation assumes
139contiguous positive instance IDs; relabel and validate first.
141Choose RAG for contact/near-contact topology and KNN for centroid proximity. A
142RAG edge is still an image-processing construct, not proof of biological
143interaction.

Region adjacency graph

from pathml.graph import RAGGraphBuilder

builder = RAGGraphBuilder(
    kernel_size=3,
    hops=1,
    add_loc_feats=False,
    return_networkx=False,
)
graph = builder.process(instance_map, features=features)

RAGGraphBuilder dilates each labeled instance and connects labels encountered at the boundary. hops>1 expands neighborhoods. Stable implementation assumes contiguous positive instance IDs; relabel and validate first.

Choose RAG for contact/near-contact topology and KNN for centroid proximity. A RAG edge is still an image-processing construct, not proof of biological interaction.

Raw lines 123 to 144
145## Tissue superpixels
147`ColorMergedSuperpixelExtractor` performs SLIC superpixels followed by
148color-based hierarchical merging. Its output depends on image color space,
149downsampling, blur, target superpixel size/count, merge threshold, and optional
150tissue mask.
152Fit/tune these choices on training slides only. Verify:
154- every retained superpixel overlaps tissue as intended;
155- object labels are contiguous;
156- tiny/huge regions and holes are handled;
157- downsampled boundaries map correctly to the source level;
158- stain normalization did not erase discriminative structure.
160The extractor is influenced by histocartography/HACT implementations. Review
161license obligations when redistributing derived code or artifacts.

Tissue superpixels

ColorMergedSuperpixelExtractor performs SLIC superpixels followed by color-based hierarchical merging. Its output depends on image color space, downsampling, blur, target superpixel size/count, merge threshold, and optional tissue mask.

Fit/tune these choices on training slides only. Verify:

  • every retained superpixel overlaps tissue as intended;
  • object labels are contiguous;
  • tiny/huge regions and holes are handled;
  • downsampled boundaries map correctly to the source level;
  • stain normalization did not erase discriminative structure.

The extractor is influenced by histocartography/HACT implementations. Review license obligations when redistributing derived code or artifacts.

Raw lines 145 to 162
163## Output schema
165Stable `pathml.graph.Graph` is a PyTorch Geometric `Data` subclass with:
167```text
168node_centroids # tensor [N, 2], (x, y)
169node_features # tensor [N, F] or None
170edge_index # tensor [2, E]
171edge_features # tensor/array or None
172node_labels # tensor/array or None
173target # graph target or None
174```
176It does not automatically expose canonical PyG `x`, `pos`, `edge_attr`, and `y`
177aliases. Adapt explicitly:
179```python
180from torch_geometric.data import Data
182pyg_graph = Data(
183 x=graph.node_features,
184 pos=graph.node_centroids,
185 edge_index=graph.edge_index,
186 edge_attr=graph.edge_features,
187 y=graph.target,
189```
191Validate shapes before training:
193```python
194assert graph.node_centroids.ndim == 2
195assert graph.node_centroids.shape[1] == 2
196assert graph.node_features.shape[0] == graph.node_centroids.shape[0]
197assert graph.edge_index.shape[0] == 2
198assert int(graph.edge_index.min()) >= 0
199assert int(graph.edge_index.max()) < graph.node_centroids.shape[0]
200```
202Handle empty/no-edge graphs before `min()`/`max()`. Check finite values,
203self-loops, duplicates, connected components, degree distribution, and edge
204direction.

Output schema

Stable pathml.graph.Graph is a PyTorch Geometric Data subclass with:

node_centroids   # tensor [N, 2], (x, y)
node_features    # tensor [N, F] or None
edge_index       # tensor [2, E]
edge_features    # tensor/array or None
node_labels      # tensor/array or None
target           # graph target or None

It does not automatically expose canonical PyG x, pos, edge_attr, and y aliases. Adapt explicitly:

from torch_geometric.data import Data

pyg_graph = Data(
    x=graph.node_features,
    pos=graph.node_centroids,
    edge_index=graph.edge_index,
    edge_attr=graph.edge_features,
    y=graph.target,
)

Validate shapes before training:

assert graph.node_centroids.ndim == 2
assert graph.node_centroids.shape[1] == 2
assert graph.node_features.shape[0] == graph.node_centroids.shape[0]
assert graph.edge_index.shape[0] == 2
assert int(graph.edge_index.min()) >= 0
assert int(graph.edge_index.max()) < graph.node_centroids.shape[0]

Handle empty/no-edge graphs before min()/max(). Check finite values, self-loops, duplicates, connected components, degree distribution, and edge direction.

Raw lines 163 to 205
206## Exchange schema and validator
208For safer exchange, use bounded JSON rather than a pickled `.pt` object:
210```json
212 "schema_version": "1.0",
213 "slide_id": "slide-001",
214 "coordinate_unit": "um",
215 "nodes": [
216 {"id": "cell-1", "x": 12.5, "y": 30.0, "features": [0.2, 1.3]},
217 {"id": "cell-2", "x": 15.0, "y": 32.0, "features": [0.4, 1.1]}
218 ],
219 "edges": [
220 {"source": "cell-1", "target": "cell-2"}
221 ]
223```
225Validate:
227```bash
228python scripts/validate_spatial_schema.py graph \
229 --input derived/graph.json \
230 --root . \
231 --max-nodes 100000 \
232 --max-edges 1000000
233```
235The validator checks strict JSON, bounded counts, unique node IDs, finite
236coordinates/features, explicit units, valid edge endpoints, self-loops, and
237duplicate edges. It never imports Torch or loads `.pt`.

Exchange schema and validator

For safer exchange, use bounded JSON rather than a pickled .pt object:

{
  "schema_version": "1.0",
  "slide_id": "slide-001",
  "coordinate_unit": "um",
  "nodes": [
    {"id": "cell-1", "x": 12.5, "y": 30.0, "features": [0.2, 1.3]},
    {"id": "cell-2", "x": 15.0, "y": 32.0, "features": [0.4, 1.1]}
  ],
  "edges": [
    {"source": "cell-1", "target": "cell-2"}
  ]
}

Validate:

python scripts/validate_spatial_schema.py graph \
  --input derived/graph.json \
  --root . \
  --max-nodes 100000 \
  --max-edges 1000000

The validator checks strict JSON, bounded counts, unique node IDs, finite coordinates/features, explicit units, valid edge endpoints, self-loops, and duplicate edges. It never imports Torch or loads .pt.

Raw lines 206 to 238
Runs code python scripts/validate_spatial_schema.py :228
239## HACT cell-to-tissue graphs
241HACT represents:
243- a cell graph;
244- a tissue/superpixel graph; and
245- an assignment from each cell to a tissue node.
247Stable helper:
249```python
250from pathml.graph import build_assignment_matrix
252assignment_sparse = build_assignment_matrix(
253 low_level_centroids=cell_centroids_xy,
254 high_level_map=tissue_instance_map,
255 matrix=False,
257```
259Inputs must share the same origin, level, orientation, and units.
260`cell_centroids_xy` is `(x, y)`; the helper indexes the image as `[y, x]`.
261Tissue labels should be contiguous positive IDs. Cells on background or outside
262the map require an explicit policy before calling the helper.
264The helper truncates centroid coordinates to integers and subtracts one from the
265sampled tissue label. Background label 0 therefore indexes the final tissue
266column rather than producing an unassigned cell. Reject/filter background and
267out-of-bounds centroids first and retain an explicit unassigned-cell record.
269`HACTPairData` stores:
271```text
272x_cell, edge_index_cell,
273x_tissue, edge_index_tissue,
274assignment, target
275```
277PathML's `EntityDataset` can assemble these from `.pt` files, but stable source
278uses unrestricted PyTorch object loading. Never use it on untrusted artifacts.

HACT cell-to-tissue graphs

HACT represents:

  • a cell graph;
  • a tissue/superpixel graph; and
  • an assignment from each cell to a tissue node.

Stable helper:

from pathml.graph import build_assignment_matrix

assignment_sparse = build_assignment_matrix(
    low_level_centroids=cell_centroids_xy,
    high_level_map=tissue_instance_map,
    matrix=False,
)

Inputs must share the same origin, level, orientation, and units. cell_centroids_xy is (x, y); the helper indexes the image as [y, x]. Tissue labels should be contiguous positive IDs. Cells on background or outside the map require an explicit policy before calling the helper.

The helper truncates centroid coordinates to integers and subtracts one from the sampled tissue label. Background label 0 therefore indexes the final tissue column rather than producing an unassigned cell. Reject/filter background and out-of-bounds centroids first and retain an explicit unassigned-cell record.

HACTPairData stores:

x_cell, edge_index_cell,
x_tissue, edge_index_tissue,
assignment, target

PathML's EntityDataset can assemble these from .pt files, but stable source uses unrestricted PyTorch object loading. Never use it on untrusted artifacts.

Raw lines 239 to 279
280## Graph feature extraction
282Node features may include:
284- morphology from the instance mask;
285- marker intensities from a validated channel manifest;
286- learned image embeddings from a trusted local model;
287- cell-type probabilities rather than hard labels; and
288- normalized position, when scientifically justified.
290Keep a schema with feature name, unit, transform, missing policy, and training-only
291fit provenance. PathML graph builders do not provide the broad fabricated helper
292catalog (`extract_morphology_features`, `extract_intensity_features`,
293`analyze_neighborhoods`, and similar) shown in older references. Use
294scikit-image/pandas or a reviewed feature package explicitly.
296Graph-level topology features can be extracted with
297`pathml.graph.preprocessing.GraphFeatureExtractor`, but disconnected graphs may
298make diameter/radius undefined and some centrality algorithms may not converge.
299Validate topology and handle exceptions rather than dropping graphs silently.

Graph feature extraction

Node features may include:

  • morphology from the instance mask;
  • marker intensities from a validated channel manifest;
  • learned image embeddings from a trusted local model;
  • cell-type probabilities rather than hard labels; and
  • normalized position, when scientifically justified.

Keep a schema with feature name, unit, transform, missing policy, and training-only fit provenance. PathML graph builders do not provide the broad fabricated helper catalog (extract_morphology_features, extract_intensity_features, analyze_neighborhoods, and similar) shown in older references. Use scikit-image/pandas or a reviewed feature package explicitly.

Graph-level topology features can be extracted with pathml.graph.preprocessing.GraphFeatureExtractor, but disconnected graphs may make diameter/radius undefined and some centrality algorithms may not converge. Validate topology and handle exceptions rather than dropping graphs silently.

Raw lines 280 to 300
301## Boundary and overlap policy
303Overlapping tiles can create duplicate cells and duplicated edges. Choose one:
305- keep only each tile's central crop;
306- reconcile objects by global coordinates and mask overlap;
307- run segmentation on a larger context but emit a nonoverlapping center;
308- construct per-region graphs and join only verified boundary nodes.
310Record:
312- context and emission windows;
313- global instance ID scheme;
314- duplicate matching threshold;
315- edge creation across boundaries;
316- excluded border-object count; and
317- stitching/reconciliation software version.

Boundary and overlap policy

Overlapping tiles can create duplicate cells and duplicated edges. Choose one:

  • keep only each tile's central crop;
  • reconcile objects by global coordinates and mask overlap;
  • run segmentation on a larger context but emit a nonoverlapping center;
  • construct per-region graphs and join only verified boundary nodes.

Record:

  • context and emission windows;
  • global instance ID scheme;
  • duplicate matching threshold;
  • edge creation across boundaries;
  • excluded border-object count; and
  • stitching/reconciliation software version.
Raw lines 301 to 318
319## Leakage and evaluation
321Graph construction must happen after patient/slide splits. Keep all subgraphs from
322one slide in one split. Fit feature scalers, dimensionality reduction,
323neighborhood thresholds, graph augmentations, and class balancing on training
324graphs only.
326Report:
328- patient and slide counts, not only graph counts;
329- node/edge distributions by split;
330- site/scanner/stain balance;
331- isolated/disconnected graph handling;
332- external-slide/site validation where relevant;
333- uncertainty and confidence intervals at the patient/slide unit.
335Do not treat thousands of correlated nodes or tiles as independent patients.

Leakage and evaluation

Graph construction must happen after patient/slide splits. Keep all subgraphs from one slide in one split. Fit feature scalers, dimensionality reduction, neighborhood thresholds, graph augmentations, and class balancing on training graphs only.

Report:

  • patient and slide counts, not only graph counts;
  • node/edge distributions by split;
  • site/scanner/stain balance;
  • isolated/disconnected graph handling;
  • external-slide/site validation where relevant;
  • uncertainty and confidence intervals at the patient/slide unit.

Do not treat thousands of correlated nodes or tiles as independent patients.

Raw lines 319 to 336
337## Sources and further reading
339API baseline reviewed 2026-10-01 using the released wheel/tag; hosted docs may lag.
341- Stable graph guide:
342 https://pathml.readthedocs.io/en/stable/graphs.html
343- Stable graph API:
344 https://pathml.readthedocs.io/en/stable/api_graph_reference.html
345- Stable graph builder source:
346 https://github.com/Dana-Farber-AIOS/pathml/blob/v3.0.8/pathml/graph/preprocessing.py
347- Stable graph schema/helpers:
348 https://github.com/Dana-Farber-AIOS/pathml/blob/v3.0.8/pathml/graph/utils.py
349- Pati et al. (2022), HACT:
350 https://doi.org/10.1016/j.media.2021.102264
351- Jaume et al. (2021), histocartography:
352 https://proceedings.mlr.press/v156/jaume21a.html

Sources and further reading

API baseline reviewed 2026-10-01 using the released wheel/tag; hosted docs may lag.

  • Stable graph guide: https://pathml.readthedocs.io/en/stable/graphs.html
  • Stable graph API: https://pathml.readthedocs.io/en/stable/api_graph_reference.html
  • Stable graph builder source: https://github.com/Dana-Farber-AIOS/pathml/blob/v3.0.8/pathml/graph/preprocessing.py
  • Stable graph schema/helpers: https://github.com/Dana-Farber-AIOS/pathml/blob/v3.0.8/pathml/graph/utils.py
  • Pati et al. (2022), HACT: https://doi.org/10.1016/j.media.2021.102264
  • Jaume et al. (2021), histocartography: https://proceedings.mlr.press/v156/jaume21a.html
Raw lines 337 to 352