Hover the N-way matrix to point every panel at that cell-type pair; click to
pin it. Four sources — a SpatialData store, a converted MDV project, a CSV, or a
synthetic point process — all take the identical path to per-type clouds (ADR-0018: never
merged). The last of those is the only one whose answer is known: each is a named process with
a closed-form g(r), so the panels can be checked and not just admired. MuSpAn eqs 8–14, Mode 1;
derivation in docs/cell-stats.md, and the reasoning behind each number is under the
panel.
—
Everything on this page is about one pair of cell types at a time, inside one ROI. The N-way matrix is the index: every square is a pair, hovering one points the other five panels at it, and clicking pins it. So the usual loop is scan the matrix for a square that stands out → hover it → look at what the other panels say about that pair.
Reading them in this order gets you from evidence to claim:
1. centroids — the actual cells, so you can see how many there are and whether the section has holes in it. 2. the two KDE panels — where each type is concentrated; do the bright patches coincide? 3. Γ(x) — the same question answered as one signed map, red where A sits in B-rich surroundings. 4. g(r) — the same question again as a function of distance, with the two uncertainty bands, which is where the pair either is or is not statistically interesting. 5. the matrix — that whole judgement compressed to one square, for all pairs at once.
Each panel has its own how to read this underneath it. They are worth opening at least once: three of the six have a way of being read wrong that looks perfectly reasonable.
The cyan band hugging the curve is a bootstrap confidence interval: how well g(r) itself is known. It says nothing about whether the arrangement is unusual. The grey dashed band is the global rank envelope: where g(r) would lie under the null, so a curve leaving it anywhere is significant at α — which a pointwise band cannot claim. Neither implies the other. A tight CI inside a wide envelope means "well measured, not unusual"; the reverse means "barely measured, but extreme".
Only the CI has a published counterpart. It is SpOOx's Loh spatial block bootstrap: resample 100 µm tiles with replacement, not anchor cells, and take the ratio Σcontributions/Σanchors. That choice is the difference between a band that works and one that lies — on a clustered pattern, resampling anchors independently covers the truth 40% of the time while claiming 95%, because neighbouring anchors see the same cells. The tiled version measures 98%. (The paper says a 20 µm lattice; the source hard-codes 100.) The envelope has no counterpart at all: SpOOx runs no null simulation for the cross-PCF, so it is a stronger inference offered here, not a reproduction.
Random shift (Lotwick & Silverman 1982) translates B by a random vector,
wrapping around the ROI: each type keeps its own clustering exactly and only the two patterns'
relative position is destroyed, so what is tested is association. Random labelling
shuffles the A/B labels between the same cells — right when A and B are nested or exchangeable
subsets of one population (Fibroblast vs Prolif fibroblast), but for two
distinct self-clustered types it asks "are these one interleaved population?", to which the answer
is trivially no: on independent synthetic patterns that are each clustered it rejects 20 times out
of 20, where the shift null rejects 3.
MH_PC is the paper's headline: a partial correlation, measuring
A against B after conditioning on all 47 other types. Plain correlation is transitive, so
everything crowding into the same inflamed quadrats correlates with everything else and the matrix
fills with one "this is where the cells are" factor; the partial version keeps only what survives
that. Switch between QCM partial and QCM Pearson to watch the induced
associations drop out: on COVID_SAMPLE_16_ROI_3, 68 of 1,128 pairs exceed |r| = 0.3 but
only 8 exceed |partial| = 0.3, and after Benjamini–Hochberg 226 pairs survive on the plain
correlation against 23 on the partial. gate q<α dims the pairs that fail BH — 1,128
simultaneous tests on one ROI, so the correction is not optional.
A permutation p cannot fall below 1/(S+1), so BH can declare nothing at all until m/((S+1)·α) pairs pile up at that floor. At the paper's 1000 shuffles that bar is 23 pairs and exactly 23 clear it — the discovery set sits on the threshold, so it is unstable: two runs of the same null disagree on 6 of 1,128 pairs from Monte Carlo noise alone. At 199 shuffles the bar is 113 and the partial correlation returns zero discoveries, which looks exactly like "no associations" and is not. The matrix legend says so whenever the floor binds.
GPU null makes the fix affordable: the permutation is a Feistel cipher over
the cell indices, so π(i) is O(1) per thread with no Fisher-Yates and no scratch, and the count,
correlation and 49×49 inverse all run per-shuffle on the device. Measured back to back on
COVID_SAMPLE_16_ROI_3, 9,999 shuffles in 668 ms against 42,364 ms on the CPU — 63×.
The observed MH_PC is the identical f64 value either way; only the null is sampled on the device.
QCM null picks the sampler: the label shuffle, or SpOOx's 2×2 swap chain, which reproduces
the published MH_SES to within its own reproducibility.
Radius is top-hat-equivalent: switching kernel rescales it to hold the probed scale (μ₂) fixed, so "smoother" never quietly becomes "more local". α=5, world units.
The MDV project source loads a converted project (pnpm mdv:zarr).
Such a project holds many ROIs — covid is 545,400 cells across 32 — and every statistic here is
per ROI: ρB is a density over one ROI and the edge correction is against one
ROI's boundary, so pooling them would measure the gaps between tissue sections. Hence the ROI
picker. Scale comes from the project's own regions.scale rather than a default of 1;
for covid that is exactly 1 µm/unit, so r=100 µm and σ=50 µm are literal. The g(r)
this path produces is validated against the project's published gr10/gr20
— 74,567 comparisons, median 0.26% (pnpm mdv:parity).
The synthetic pattern source is a registry of standard 2-D point processes
(src/spatial/pointPatterns.ts), shared with the test suite. Each states the g(r) it
should produce, in green above the panels, so the page doubles as a check: CSR must read
1 everywhere, segregated and hard-core exactly 0 below their gap, co-located
a specific elevation. Two are traps worth playing with — independently clustered is two
clumped types with no association at all, and shared gradient is two independent types
that a homogeneous-ρ estimator (this one) reports at g > 1 regardless.
⚠ A remote store must be CORS-enabled. If it is unreachable, switch the source to a synthetic pattern or open a CSV.
One square per ordered pair of cell types: the row picks type A, the column picks type B. Hover a square and every other panel on the page jumps to that pair; click to keep it there while you look around.
—
Squares left as plain background have no answer for that pair — a type missing from this ROI, or a calculation that could not be done. That is not the same as "no association", which is the pale colour in the middle of the ramp.
One dot per cell, placed where that cell actually sits in the tissue. Cyan is the A type, orange is B, and every other cell is faint grey so you can see the shape of the section they sit in.
This is the raw material that every other panel summarises into colours and curves, which makes it the one to glance at first. A striking result computed from eleven cells is still a result computed from eleven cells — here you can see that, and nowhere else on the page.
Hover the g(r) chart and this panel answers "which cells?". The A and B cells that actually have a partner at the hovered distance stay bright and grow; the rest of the same two types fade but keep their colour. The circles draw that distance on the map, on a sample of the lit A cells — so you can see how far the radius reaches relative to the tissue, and which B cells fall on the ring. The dots are the complete set; only the rings are sampled.
The lit count climbs with radius no matter what the biology is doing — a bigger ring is more likely to catch something. On one COVID ROI the same pair goes from 245 lit A cells at 0–10 µm to 2,115 of 2,168 at 280–290 µm. So "nearly everything is lit" at a large radius means the ring is large, not that the association is strong. Strength is the curve's job; this panel only says who is involved.
A map, not a chart: same tissue and same orientation as the centroids above, so you can put the two side by side. Each A cell is asked how crowded with B its own surroundings are compared with the ROI as a whole, and those answers are smoothed into a continuous surface. Red = A cells sitting somewhere unusually rich in B; blue = A cells sitting somewhere unusually poor in it.
The matrix gives one number for the entire ROI. This shows where that number came from — and a pair can average out to almost nothing while being strongly red at one edge of the section and blue at the other.
Pale means one of two quite different things: "nothing unusual here" or "no A cells here at all". The centroid panel is how you tell which.
splatDensity op)A smoothed picture of where this one type is, in the same frame as the panels around it: bright where cells of that type are packed closely together, dark where they are sparse or absent. Individual cells are deliberately blurred away — the question it answers is "where is this type concentrated", not "where is each cell".
Each of the two KDE panels is scaled to its own brightest spot. So brightness compares one place with another within a panel, and never compares counts between the two: a rare type at its own maximum is drawn just as bright as a common one at its.
The same density picture as the panel to the left, for the other half of the pair. Compare the two by shape — do the bright regions sit on top of one another, or in different parts of the section? That overlap is what the Γ map turns into a single signed surface.
Do not compare the two by brightness. Each panel is scaled to its own maximum, so equal brightness does not mean equal numbers of cells.
Hovering also lights up the centroid panel with the cells that produced the value at that radius, and draws the radius itself as circles on the map.
Left to right is distance away from an A cell. The curve says how many B cells are found at that distance, compared with how many an even scattering would put there. The flat dashed line at 1 is "nothing going on": above it, B cells crowd towards A at that distance; below it, they keep away. Hover anywhere to read the numbers off.
Most patterns say the most at short range, so the interesting part is usually the left-hand end — a curve that starts at 3 and settles to 1 means the two types touch, not that they share a neighbourhood.
The two bands answer different questions. Cyan is how precisely the curve itself is pinned down by this much data. Grey dashed is how far the curve could wander by chance alone. So a tight cyan band sitting inside a wide grey one means "measured well, nothing unusual" — it is the curve leaving the grey band that means something.