Grail Computer · research record
Dated experiment record. Statements describe this run, not current submission readiness. Historical H. pylori category names do not establish infection; copy screens are bounded tests. Claims that a source does not exist mean none was identified in that recorded search, not proof of absence. Licensing interpretations in the notes remain unconfirmed. Local access details have been omitted. Current limitations and remaining work.

C2-PROBES: pathology-encoder probes on the generated fields

Package C2 of the SGH programme (operations/research/sgh-program-20260908/PLAN.md). Everything here is engineering evidence from one pathology encoder. None of it is a clinical judgement, and no number here says an image is diagnostically correct. Run locally on the Mac (MPS), 8 September 2026.

Deliverables: probes/run_probes.py, probes/*.json, probes/*.png, embeddings cached under probes/embeddings/. Nothing was committed to git.

What was measured

phikon-v2 (owkin/phikon-v2, DINOv2 ViT-L/16, 224 px input, pathology-pretrained, non-commercial licence, internal evaluation only) embeds every 1024 px tile of every 4096x2048 field (8 tiles per field), CLS token, L2-normalised — the same code path as scripts/pixcell_embed_screen.py. A second scale, 512 px tiles (32 per field, also resized to 224, so twice the effective magnification), was embedded and every probe was run again on it.

292 fields, 2336 tiles at 1024 px and 9344 at 512 px, in 13 sets:

set kind fields categories provenance
real_train real 96 all 4 HiESD train slides, 24 per category, 13 slides
real_heldout real 22 all 4 3 slides never used for anything
gen_pass2 generated 20 all 4 two-pass recipe, pixcell-fullset-20260908 pass 2 (si6)
gen_pass1 generated 10 gastritis, mixed same run, pass 1 (si12); only these two were retrieved
lock_base / lock_ladder / lock_lora / lock_pass2 / lock_release generated 13/12/6/9/14 IM only pixcell-lock-20260907, arm names kept from its records
native_gen025 generated 40 all 4 pixcell-slot3-native025-20260906, pure generate, 10 sampled per category
native_i2i025 generated 20 IM, mixed same run, img2img si12 on a real field
finals generated 10 all 4 the ten preselection images, category from metadata.csv
cartoons control 20 all 4 renderer output; should be trivially separable

Provenance (category, pass, start_index, arm, cartoon, donor, seed) is read from each run's records.jsonl, never inferred from a filename. Full per-tile manifest: probes/manifest-fields.json.


Question 1. Can a pathology encoder tell our generated images from real ones?

Yes, perfectly, with no overlap at all.

A logistic regression on phikon embeddings, trained on real train tiles versus two-pass tiles from cartoons 0-2, tested on real held-out tiles versus two-pass tiles from cartoons 3-4 (split by cartoon, so no tile of a test image was ever seen):

1024 px 512 px
test accuracy (tile) 1.000 1.000
test AUC 1.000 1.000
balanced accuracy 1.000 1.000
field-level accuracy (30 fields) 1.000 1.000
tissue-rich tiles only (>=50% tissue) 1.000 1.000
mean score, real held-out 0.003 0.0005
mean score, generated 0.984 1.000

Chance is 0.5. The separation is not marginal, it is a gap: across all 944 real tiles the highest score is 0.087, and across all 160 two-pass tiles the lowest is 0.606. Nothing lies in between. On the log-odds scale real tiles sit near -9 and two-pass tiles near +9 (probes/probeA-score-histograms-1024.png).

Three controls say the result is real and not an artefact of the setup:

The tell is shared across every generator variant we have. A transfer matrix (train against set X, test against set Y, probes/probeA-transfer-matrix-1024.png) has no entry below 0.754; a probe trained only against cartoons detects two-pass images at AUC 0.948.

One arm needs care. Applying the two-pass probe to lock_lora (the LoRA-finetuned arm) gives a mean score of 0.437 — apparently undecided. That is not evidence the LoRA images are more real. A probe trained against lock_lora separates it from real at AUC 1.000, and probe D puts those same images at the 98.9th percentile of the real distance distribution. The LoRA arm is off the real manifold in a different direction from the one the two-pass probe learned. Reading a low probe score as realism would be a mistake.

What the tell is (probe B)

probes/tell-sheet.png (four rows of 512 px thumbnails: the 10 most generated-looking two-pass tiles, their nearest real train tiles, the 10 least generated-looking, their nearest real train tiles, each labelled with its score) and probes/tell-zoom.png (the top four pairs at 1:1 pixels).

Looking at them, the generated tiles differ from their real nearest neighbours in texture, not architecture:

The same tell is measurable without any encoder. A logistic regression on twelve hand-made colour and texture statistics, on the identical train/test split, reaches AUC 0.932 at 1024 px and 0.914 at 512 px (probes/probeA-pixel-statistics-1024.json). The single strongest feature is mean absolute Laplacian, i.e. local detail:

statistic (1024 px tiles) real held-out two-pass generated single-feature AUC
mean abs Laplacian (local detail) 21.94 15.45 0.938
high-frequency energy ratio 0.0494 0.0343 0.757
grey-level entropy 4.91 4.70 0.754
mean saturation 0.311 0.266 0.715

So a large part of the tell is that the two-pass images carry about 30% less fine detail and are paler than real tissue. It is a real deficiency, not a stain-normalisation artefact: native_gen025 (21.09) and native_i2i025 (20.43) sit at real levels (real train 21.40), so the detail loss belongs to the cartoon-conditioned two-pass path, not to PixCell in general. Note also that phikon still beats the pixel probe (1.000 vs 0.932), so there is a second component of the tell that the twelve statistics do not capture.

Package C1's morphometrics (morphometrics/features.csv, joined on 20 two-pass fields) agree: the field-level probe score correlates negatively with nuclear density (r = -0.56), nucleus count (-0.55), epithelial nucleus count (-0.53), stromal structure energy (-0.46) and mean colour saturation (-0.39). The more generated a field looks to phikon, the fewer and smoother the nuclei it contains.

Structure inheritance, re-measured on the two-pass recipe

For 89.4% of two-pass tiles the nearest real train tile lies inside the very field whose UNI tokens conditioned it (chance is about 1/24 = 4%); 98.1% land somewhere on the donor's slide. native_gen025 93.1%, native_i2i025 95.6%, finals 88.7%. This replicates the finding in operations/research/pixcell-native025-program/REPORT.md on the new recipe: the two-pass recipe did not reduce structure inheritance. lock_lora is the exception at 0.0% — but it is also the furthest off-manifold.


Question 2. Do generated images classify as the category we labelled them?

The honest answer is: the instrument is too weak to say, and once the leakage is removed the generated images are at the real ceiling, which is itself low.

A 4-way logistic regression on real train tiles only, slide-disjoint (GroupKFold on 13 slide ids), chance 0.25:

1024 px 512 px
slide-disjoint CV accuracy on real train 0.449 0.446
real held-out accuracy (the ceiling), tile / field 0.506 / 0.545 0.412 / 0.500

The ceiling is the headline. phikon can barely read these four categories off 1024 px gastric fields: 0.51 against a chance of 0.25, and the errors are the clinically sensible ones — mixed scatters into intestinal_metaplasia and hpylori_gastritis (it contains both), and hpylori_gastritis leaks into normal. Any statement about generated images is bounded by that.

Applying the classifier to the generated sets first looks flattering, then does not survive its control:

set tile accuracy vs label donor slide excluded field accuracy, donor excluded
real held-out (ceiling) 0.506 0.472 (matched handicap) 0.500
gen_pass2 0.713 0.456 0.450
native_gen025 0.678 0.472 0.525
finals 0.738 0.400 0.400
gen_pass1 0.412 0.388 0.400
native_i2i025 0.706 0.394 0.400
cartoons (control) 0.250 n/a n/a

Every generated field was conditioned on UNI tokens taken from one real train field whose category equals the label, and the classifier was trained on that same field. So the uncorrected 0.713 partly measures the classifier recognising the donor. Refitting per field with the donor's entire slide removed drops gen_pass2 to 0.456 tile / 0.450 field, against a matched real held-out ceiling of 0.472 / 0.500. The same classifier gives a generated field the same label as its own donor field 65% of the time.

Read plainly: two-pass images are about as category-consistent as real held-out fields, on an instrument that is only slightly better than chance at the task. That is a weak positive, not evidence of disease truth. It does not distinguish "the generator drew intestinal metaplasia" from "the generator reproduced the donor's appearance, and the donor had intestinal metaplasia".

The cartoons control behaves as it should: 0.250 accuracy, with 158 of 160 tiles predicted hpylori_gastritis — a degenerate prediction carrying no category information at all.

Two cautions on the table. Accuracy on the lock_* sets (all one category) is not a balanced measure: a classifier that always answered intestinal_metaplasia would score 1.0, which is roughly what happens at 512 px (lock_ladder 0.979). And 512 px is the worse scale for this question (real ceiling 0.412), so the 1024 px numbers are the ones to quote.


Question 3. How far are generated tiles from the real-tissue manifold?

For every tile: mean cosine distance to its k=5 nearest real train tiles of the same category. Real held-out tiles (slides absent from every bank) define the scale. probes/probeD-knn-distance-1024.png.

Calibration, real held-out at 1024 px: median 0.211, p90 0.273, p99 0.355, max 0.518.

set median distance percentile in held-out with donor slide removed from the bank percentile (matched calibration)
real train (leave-own-slide-out) 0.199 39 0.199 36
real held-out 0.211 50 0.211 46
native_i2i025 0.155 3 0.245 76
native_gen025 0.192 31 0.279 89
finals 0.197 38 0.273 87
lock_release 0.221 57 0.278 89
lock_ladder 0.221 57 0.275 88
gen_pass2 0.231 63 0.301 95
lock_pass2 0.251 80 0.287 93
lock_base 0.398 99 0.403 99
lock_lora 0.416 99 0.418 99
gen_pass1 0.451 99 0.453 99
cartoons (control) 0.604 100 0.604 100

Three things to take from it.

  1. The two-pass images are inside the real manifold's spread, at its outer edge. With the full real-train bank the median two-pass tile sits at the 63rd percentile of real held-out variation and only 1.3% of tiles exceed the held-out p99. But part of that closeness is the donor: remove the donor's slide from the bank and the median moves to the 95th percentile. The right summary is that two-pass tiles are further from unrelated real tissue than real tissue is, but still within its range.
  2. Pass 2 is what does the work. Pass 1 sits at the 99th percentile with 100% of its tiles beyond the held-out p99. Comparing only the two categories where pass 1 was retrieved, the same cartoons move from the 95.8th percentile (gastritis) and 100th (mixed) after pass 1 to the 62.5th and 77.1st after pass 2. On this axis the second pass is the difference between clearly off-manifold and inside the distribution — worth stating alongside the ring-topology result, where pass 2 makes gastritis worse (ring-summary.json: 0.628 -> 0.475). The two instruments disagree about pass 2 for gastritis, and both numbers should be carried forward rather than one being chosen.
  3. The LoRA arm and the light-repaint lock arms are off-manifold (99th percentile), even though the two-pass probe scores the LoRA arm as undecided. Probe A and probe D disagree about lock_lora, and probe D is the one that is calibrated in real units.

Does phikon have dynamic range here? Yes. Median distances span 0.155 (native_i2i025) to 0.604 (cartoons), with real held-out at 0.211 — a factor of about 4 between "light repaint of a real field" and "renderer cartoon", and clear ordering of every arm in between. This is the opposite of the DINOv2 saturation recorded in operations/research/pixcell-native025-program/REPORT.md, where DINOv2-giant at 448 px scored unrelated real fields at 0.987 cosine and exact copies at 1.000 and was called unusable. phikon-v2 at 1024 px tiles separates real-versus-real (0.66 cosine in that report's calibration) from copies (0.987) with room in between, and the numbers above use that room. The same holds at 512 px (held-out median 0.253, cartoons 0.60).


Caveats

What this changes for the programme

  1. Detectability is not a marginal question and should stop being framed as one. Any claim of the form "these pass a machine screen" must name the screen: the two-pass set passes the copy screen (pixcell-fullset-20260908/screen-pass2-phikon-fixed.json: max cosine 0.886, per-image 0.79-0.89, against the 0.95 fail line) and fails a real-versus-generated screen at AUC 1.000.
  2. The largest single lever on the tell is fine detail. Two-pass output carries ~30% less local Laplacian energy than real tissue while native canvases carry the same amount as real. Whatever the two-pass path does — the cartoon reference, the si6 repaint, or the 0.5 native_mpp habit — it costs detail, and recovering it would move the biggest measurable component of the tell. E2 (fidelity ladder) and F2 (structure adapter) are the arms that test this.
  3. Structure inheritance is unchanged by the two-pass recipe (89% of tiles nearest their own donor field). A2 (token library) and A3 (mean tokens) remain the right experiments, and the disclosure requirement in the native025 report still stands.
  4. Probe A alone must not be used to select images. lock_lora scores 0.44 on it and 99th percentile on probe D. Selection needs probe D (calibrated in real units) plus the morphometric envelope from C1.

Exact commands

Everything runs from the repository root with the SGH venv and the cached phikon weights, offline.

cd [local]/fund-raise
V=research/sgh-synthetic-histopathology/.venv/bin/python
P=operations/research/sgh-program-20260908/probes/run_probes.py

# full run: build the manifest, embed at both scales, run probes A-D, write json and png
HF_HUB_OFFLINE=1 $V $P all --scales 1024,512

# stages separately
HF_HUB_OFFLINE=1 $V $P embed  --scales 1024,512     # writes probes/embeddings/<set>__<size>.npz
HF_HUB_OFFLINE=1 $V $P probes --scales 1024,512     # reuses the cache
HF_HUB_OFFLINE=1 $V $P probes --scales 1024 --force-embed   # ignore the cache

# re-apply to a Phase-2 run by pointing at its records.jsonl (NAME=<records>[:<output root>][:<kind>])
HF_HUB_OFFLINE=1 $V $P all --scales 1024 \
  --add-set "a1_pass2=$PWD/operations/research/sgh-program-20260908/retrieved/out/records.jsonl:$PWD/operations/research/sgh-program-20260908/retrieved/out"

Runtime on the Mac (MPS, M-series, mps device): embedding all 292 fields from scratch takes 3 min 8 s at 1024 px and 8 min 30 s at 512 px; the probe stage costs a further 4 min 40 s the first time (the colour and texture statistics are computed then cached) and 28 s on every rerun with warm caches. Peak memory is small (the 1024 px embeddings are 9 MB, the whole cache 66 MB). A forced re-embed of both scales reproduced every number in this document exactly.

The added set is embedded, cached, and then included in every probe: probe A's per-set score table and transfer matrix, probe C's confusion matrices and donor-slide-excluded control, and probe D's percentiles. Provenance is refreshed from the live manifest on every probe run, so a provenance fix does not force a re-embed. Set names are read from the records, so a Phase-2 run's arms appear as their own rows.

Files

file contents
probes/run_probes.py the whole pipeline; rerunnable, --add-set for new runs
probes/manifest-fields.json 292 fields with set, category, slide, cartoon, arm, donor, pass, seed
probes/summary.json headline numbers for both scales, all probes
probes/probeA-real-vs-generated-{1024,512}.json probe A: test metrics, per-set scores, per-lock-arm scores, three controls, reverse direction, transfer matrix
probes/probeA-pixel-statistics-{1024,512}.json the 12-statistic no-encoder probe and per-feature AUCs
probes/probeB-tell.json the 20 extreme tiles with nearest real train tile, structure inheritance per set, morphometric correlations
probes/probeC-category-{1024,512}.json CV, real held-out ceiling, per-set confusions, donor-slide-excluded control
probes/probeD-manifold-{1024,512}.json calibration, per-set and per-category distances and percentiles
probes/tell-sheet.png 4 x 10 contact sheet, 512 px thumbnails, scores on every tile
probes/tell-zoom.png top four pairs at 1:1 pixels
probes/probeA-score-histograms-{1024,512}.png probe score by set, log-odds scale
probes/probeA-transfer-matrix-{1024,512}.png does one generator's tell detect another
probes/probeC-confusions-{1024,512}.png 9 confusion matrices incl. the donor-slide-excluded one
probes/probeD-knn-distance-{1024,512}.png distance to the manifold, with and without the donor slide
probes/embeddings/*.npz cached phikon embeddings and tile statistics (66 MB total)

What failed or was skipped

Download public Markdown export