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.

A5-MATCH: if the tokens decide the layout, choose tokens whose real layout matches the cartoon

Work package A5 of the SGH programme (operations/research/sgh-program-20260908/PLAN.md), 8-9 September 2026. One spot A100 (slot a), 160 generations, 0 failures. Slots b, c and d were never touched. Nothing was committed to git.

Engineering evidence only. Every instrument below measures shape, spacing, texture, colour or one pathology encoder. None of them diagnoses, grades or stages anything, no pathologist has seen any of it, and nothing here is "solved". Every "better" or "worse" names its instrument and its n.

One-sentence answer: the mechanism works exactly as designed, delivers essentially all of its own ceiling, and the ceiling is too low to matter for layout — but it is the best realism result in the programme. Layout-matched retrieval nearly doubles the cartoon lumen IoU at start_index 3 (0.0835 vs 0.0452 for the single real donor, better on 39 of 40 paired cartoons), which is 99-114% of what a perfect reproduction of the chosen donor windows could score (the assignment mosaic, 0.081) and 12% of the cartoon ceiling (0.694). It is still far below the F2 adapter's 2.6-5.4x-over-two-pass layout control. What it does buy is realism: envelope distance 3.86 against 6.30 for the single donor and 3.51 for real held-out fields, better on 37 of 40 paired cartoons, with 35 of 40 outputs inside the real held-out band against 14 of 40. The A4 repetition collapse is fixed (bag-diverse SSIM 0.61-0.67, not 0.98) and its envelope gain survived (D 3.35, better than A4's 3.96). The --inflamed arm failed for a reason worth recording: it ranked candidates on a density index that is anti-correlated with the density the evaluation reports.


0. What was built and run

deliverable path size
generator, one new token source + per-job bag seeding code/pixcell_generate.py 959 lines (was 781)
CPU tests, 13 of them code/test_generate_cpu.py 956 lines (was 768)
the matcher code/layout_match.py 509 lines (new)
manifest writer code/make_a5_jobs.py 192 lines (new)
assignments, 40 + 10 inflamed assignments/*.json, summary.json, summary-inflamed.json 520 KB
match sheets assignments/match-sheet.png, match-sheet-inflamed.png
fleet package packages/a5/ (code/, jobs/, assignments/, worker.sh, STAGE_LIST, MANIFEST-ORDER.txt) 1.2 MB
drivers packages/a5/run_a5.sh, wait_and_retrieve.sh, retrieve_a5.sh, eval_a5.sh
merged records, 160 packages/a5/retrieved/records.jsonl + coverage.json
retrieved outputs, 160 PNGs packages/a5/retrieved/out/a5/pass{1,2}/<category>/ 2.2 GB
the exact VM label maps the generator saw packages/a5/retrieved/aux/cartoons-vm/a1v2/ (60 maps) 18 MB
A5 measurements packages/a5/analyse_a5.py (793 lines) -> packages/a5/eval/
harness output eval/features.csv, scores.csv, screen-embed.csv, screen-ncc.csv, stage-*.json
A5 tables eval/a5-table.csv/.json, a5-headline.json, a5-layout-iou.csv/.json, a5-paired-vs-a1v2.csv/.json, a5-within-arm-ssim.json, a5-mosaic-features.csv/.json, a5-seams.csv, density-instrument-check.json
contact sheets eval/sheets/a5-<category>.png, a5-zoom-<category>.png, a5-mosaic-vs-output.png

Commands, in order, all from the programme root (P=../../../research/sgh-synthetic-histopathology/.venv/bin/python):

$P code/test_generate_cpu.py                                    # 13/13
$P code/layout_match.py --out assignments                       # 40 assignments, 8.2 s
$P code/layout_match.py --out assignments --inflamed            # 10 more, 0.8 s
python3 code/make_a5_jobs.py --verify                           # 160 jobs, verify OK
./packages/a5/run_a5.sh          # up, stage, symlink, render a1v2 x12, launch, watch
./packages/a5/wait_and_retrieve.sh   # -> retrieve_a5.sh: retrieve, VM label maps, down
./packages/a5/eval_a5.sh         # merge, features|screens|sheets, analyse_a5

1. The code (deliverable 1)

token_assignment — a new primary token source

"token_assignment": "assignments/<stem>.json"

The file names, for every canvas window origin, the real donor window that supplies that window's tokens:

{"cartoon": "...", "category": "...", "canvas": [4096, 2048], "windows": [
  {"origin": [0, 0], "file": "[local]/train<cat>/<field>.png",
   "oy": 0, "ox": 3072, "transform": "rot90",
   "score": 0.298531, "lumen_iou": 0.114297, "epithelium_iou": 0.368, "candidates": 4032}, ...]}

transform is one of id, rot90, rot180, rot270, flipH, flipV, transpose, antitranspose; the generator applies it with PIL's Image.transpose to the 1024 px donor crop before the UNI2-h encode. The token cache key carries the transform ((sha, oy, ox) for id, (sha, oy, ox, transform) otherwise), so one donor window encoded two ways is encoded twice and the two grids can never share a slot. Windows are grouped by donor file so a 4096x2048 PNG is decoded once per job.

Recorded per job: token_assignment, token_assignment_sha256, token_assignment_windows, token_assignment_donor_fields, token_assignment_donors, and token_assignment_picks — one entry per canvas window with origin, window_index, donor_file, donor_sha256, donor_origin, transform and the matcher's own score. A bare list, and a dict keyed "<oy>,<ox>", are also accepted. A file that does not cover every window, names an unknown transform, or asks for a crop outside the donor, raises rather than falling back.

Precedence is now token_file > token_patchbag > token_assignment > token_library > token_blend > token_reference > reference, then token_shuffle as a post-step. The pre-existing chain is unchanged, so no old manifest changes behaviour.

Per-job patch-bag seeding

A4's token_patchbag drew from default_rng(seed*1000 + window_index), which does not depend on the cartoon, so all five cartoons of a cell got the identical 21 token grids and collapsed to one image (A4_RESULT.md section 4: within-arm SSIM 0.98). A5 adds two optional fields and leaves the default stream exactly as A4's so A4's records stay reproducible:

"token_patchbag": {"library": "token-library/", "category": "<cat>",
                   "per_window": true, "seed": 52001}      // or "salt": "job_id"

seed replaces the job seed as the bag's base; salt adds an integer, or with the string "job_id" adds int(sha256(job id)[:16], 16) % 10**6 (hash() is per-process salted and cannot be used). Recorded: token_patchbag_seed, token_patchbag_salt, token_patchbag_salt_source, token_patchbag_seed_base. The a5-bag manifest sets one distinct seed (52001-52020) per cartoon.

Two supporting edits: code/resolve_jobs.py resolves and existence-checks token_assignment, and packages/sweep-v1/evaluate_sweep.py's _library_origins_for / _library_field_count read token_assignment_picks (section 10).

Tests — 13/13 pass

code/test_generate_cpu.py under research/sgh-synthetic-histopathology/.venv/bin/python, stub encoder and stub pipeline, no weights. The 11 A4 tests are unchanged and still pass; two are new.

PASS  old-schema jobs are byte-for-byte unchanged against the canonical generator
PASS  token_library picks the nearest composition per window
PASS  token_blend weights and 'self' resolve to the weighted sum
PASS  token_file broadcasts one grid to every window, both accepted shapes
PASS  token_shuffle permutes the 16 tokens of every window, from the documented RNG
PASS  token_patchbag assembles 16 real patches drawn from the library, per window or once
PASS  token_assignment encodes the named donor window under the named transform, per canvas window
      3 windows -> 2 donor fields; transpose/id on one crop give different grids; 8 distinct
      dihedral images; picks carry origin, donor, transform and the match score
PASS  token_patchbag seeds differ per job when the manifest says so, and A4's default is unchanged
      default stream identical to A4 (32 draws verified); explicit per-cartoon seeds and
      salt=job_id both give distinct bags; salt is reproducible and bounded
PASS  precedence is token_file > token_patchbag > token_assignment > token_library > token_blend
      > token_reference > reference
PASS  every record carries the provenance fields evaluation needs
PASS  resume skips ids already in records.jsonl and adds nothing on a rerun
PASS  resolve_jobs resolves PASS1 against out/ and drops jobs whose inputs are missing
PASS  split_manifests keeps every pass-2 job on its pass-1 slot and places every job

13/13 passed

The assignment test is arithmetic, not aesthetic. The stub encoder is a pure function of the pixels it is shown, so the test re-does the crop and the PIL transform itself and demands the tensors handed to the denoiser equal those. Two canvas windows name the same donor crop under transpose and id: their grids must differ (the cache-key check). All eight named transforms are checked against PIL.Image.transpose and shown to produce eight distinct images. The seeding test re-derives A4's 32 draws independently and demands the default stream still produces them.


2. The assignments (deliverable 2)

code/layout_match.py, CPU, 8.2 s for all 40 cartoons + 0.8 s for the 10 inflamed ones.

For each of the 40 a1v2 across/oblique cartoons (4 categories x seeds 11-15 x 2 cuts) and each of its 21 canvas windows, the cartoon's label map gives a lumen mask (label 1) and an epithelium mask (labels 2, 3, 4). Every candidate is one window of one real training field of the same category under one of the eight dihedral transforms: 24 fields x 21 origins x 8 = 4032 candidates per window, scored IoU(lumen) + 0.5 * IoU(epithelium) at 1/8 resolution. Donor label maps are F2's segmentations (pairs/data/train/<cat>/<stem>_labels.png).

Masks are downsampled by 8x8 block mean thresholded at 0.5, not by subsampling, because a block mean commutes exactly with the dihedral group on a 1024 px window (128 whole blocks a side) — verify_transforms() asserts both that commutation and that every one of the eight numpy transforms is pixel-identical to the PIL method the generator will apply. Empty-vs-empty windows score IoU 1.0.

How good are the matches?

Per window, over all 40 x 21 = 840 windows:

statistic value
best-candidate lumen IoU, median (p10 / p90 / max) 0.121 (0.077 / 0.188 / 0.340)
best-candidate epithelium IoU, median 0.304
best-candidate score, median 0.277
median candidate's score 0.101
gain of the best over the median candidate +0.176
99th-percentile candidate's score 0.218
category median best lumen IoU median best epi IoU donor fields per canvas
normal 0.107 0.279 11
hpylori_gastritis 0.123 0.320 10
intestinal_metaplasia 0.132 0.327 10
mixed 0.128 0.295 12
gastritis, --inflamed pool 0.111 0.325 8

Per canvas the assignment uses a median of 11 distinct donor fields (range 5-14, verified in the records) and 20 distinct donor windows out of 21 — no single field supplies the canvas, which was the point. A median of 7 of the 8 transforms appear in one canvas and only 2 of 21 windows keep the identity, so the dihedral group is doing real work.

The search works: the chosen candidate beats the median candidate by 0.176 of score and beats the 99th percentile of candidates by 0.06. But the best real window available is still only 0.12 IoU from the cartoon window it is matched to. That is a property of the data, not of the search: over 4032 real candidates there is no gastric window whose lumen mask lines up with a synthetic one.

The match sheet, described honestly

assignments/match-sheet.png (4 cartoons, one per category, 3 windows each; cartoon window label map | best donor window, transformed | its label map, transformed):

Sheet windows score lumen IoU 0.084-0.186 and epithelium IoU 0.29-0.50. assignments/match-sheet-inflamed.png is the same under the inflamed restriction; its middle row is visibly more cellular (dense small dark nuclei filling the lamina propria between two glands), the other two rows are not obviously different from the unrestricted pick.

Were the assignments computed on the right bytes?

They were computed from the local Mac label maps; the generator conditions on the VM's own render. Recomputing all 40 assignments from the sweep-v2 VM-rendered a1v2 label maps changes 3 of 840 windows (99.6% identical) and leaves the median best lumen IoU at 0.1221 to four decimals. (sweep-v2 had already measured VM-vs-Mac a1v2 lumen-mask IoU at median 1.000, min 0.99998.) Every IoU in section 5 is measured against retrieved/aux/cartoons-vm/a1v2, the exact bytes slot a rendered and conditioned on.


3. The manifests and the run (deliverable 3)

code/make_a5_jobs.py, 160 jobs in five manifests, --verify clean (no duplicate id or output, every PASS1/ reference produced by an earlier manifest, every token_assignment file present with 21 windows of the right category, 20 distinct patch-bag seeds).

manifest jobs what
a5-pass1 40 a1v2 across+oblique, img2img si12 seed 51001 on the cartoon, token_assignment — arm match
a5-pass2 80 each cartoon at si6 (seed 51002) and si3 (seed 51003) from its own pass-1 output, same assignment — arms match-si6, match-si3
a5-inflamed-pass1 10 the 10 gastritis cartoons, pass 1, inflamed assignment — arm inflamed
a5-inflamed-pass2 10 those at si3 — arm inflamed-si3
a5-bag 20 the 20 across cartoons, si3 from the a5 pass-1 output, token_patchbag per-window with a distinct seed 52001-52020 — arm bag-diverse

Deviation from the brief: the inflamed manifest is split in two. resolve_jobs.py checks every input before a manifest runs, so a pass-2 job whose pass-1 output is produced by the same manifest would be dropped as missing. Same 20 jobs, two files, run consecutively.

Setup and timeline (UTC, 8 September)

what time
instances start issued / GPU ready 20:25:53 / 20:26:49 (56 s)
stage, 6 items (1.2 MB incl. 50 assignments) 20:26:57-20:28:43
symlink token-library, token-stats, cartoons/{a1,d1,e2,b1} from slot a's slot0 20:29:06
render cartoons/a1v2 on the VM, 12 parallel chunks 20:29:06-20:35:53 (397 s)
worker start / first generation 20:36:10 / ~20:36:30
a5-pass1 done (40) 21:00:55
a5-pass2 done (80) 22:06:39
a5-inflamed-pass1 done (10) 22:12:42
a5-inflamed-pass2 done (10) 22:22:09
a5-bag done (20); WORKER_DONE, exit 0, {"ran":5,"skipped":0,"failed":0} 22:37:38
retrieve 2.2 GB, SHA256SUMS OK (160 files) 22:38:35-22:51:16 (761 s, 2.9 MB/s)
retrieve 60 VM label maps (18 MB) 22:51:17-22:51:27
down a -> TERMINATED, confirmed by fleet.sh status 22:51:32-22:52:30

No preemption; fleet.sh watch polled 96 times and exited on WATCH WORKER_DONE 0.

cartoons/a1v2 did not exist on slot a (sweep-v2 rendered it on slots b and c), so it had to be built here. Rendering it serially would have idled the A100 for 60-75 minutes; twelve concurrent render_chunk.py quarters on the 12 vCPUs did all 60 cartoons in 6.6 minutes, and manifest_dir.py published the set atomically so worker.sh could never see a half-built one.

Throughput and cost

stage n median s
pass 1, si12, token_assignment 40 35.97
pass 1, si12, inflamed assignment 10 34.05
pass 2, si6, token_assignment 40 49.47
pass 2, si3, token_assignment 40 48.16
pass 2, si3, inflamed assignment 10 54.34
pass 2, si3, token_patchbag 20 43.84

Total generation 7063 s = 1.96 GPU-hours over 160 jobs, mean 44.1 s. The ~4 s that a token_assignment pass-1 job costs over A4's 32 s single-donor pass 1 is the UNI2-h encode of up to 21 separate 1024 crops (against one cached donor field), which is cheaper than expected because the crops are cached across the jobs of a manifest.

Powered 20:25:53 to 22:52:30 = 146.3 min = 2.44 h = USD 5.17 at USD 2.12/h, plus about USD 0.27 of egress for the 2.2 GB retrieved. GPU utilisation 1.96 / 2.44 = 80%; the 20% is the 6.6-minute render and the 12.7-minute retrieval, both unavoidable on this slot.

Coverage and provenance: clean

packages/a5/merge_records.py: 160 records read, 160 written, 0 duplicates. Per manifest, expected = records = PNGs (40/80/10/10/20), no missing ids. All 160 PNGs were re-hashed on this Mac and all 160 match their record's output_sha256; no zero-byte file. All 160 token_source values equal their extra.expected_token_sourcetoken_assignment 140, token_patchbag 20. Every one of the 140 assignment records carries exactly 21 windows and 5-14 distinct donor fields; all 20 patch-bag records carry 20 distinct seeds, so A4's shared-seed collapse cannot recur by construction.


4. The ceiling: what layout-matched retrieval could possibly deliver

This measurement needs no GPU and it frames everything else. For each assignment, analyse_a5.py builds the assignment mosaic: the 21 transformed donor crops pasted at their canvas origins (hard edges, later windows over earlier — not the generator's Hann blend). The mosaic is real tissue, arranged exactly as the matcher chose, so it upper-bounds what any generator that reproduced its assigned donor windows perfectly could score.

Measured on the harness's own instrument (analysis_masks lumen against the cartoon's own VM label map, exactly as evaluate_sweep._d1_one measures every other arm in the programme). chance is the same output against the same label map rotated 180 degrees:

what n lumen IoU chance (rot180) epithelium IoU
the cartoon PNG itself (instrument ceiling) 40 0.6940 0.0173 0.3231
the assignment mosaic 40 0.0807 0.0373 0.3093
the inflamed mosaic 10 0.0760 0.0452 0.3071
a1v2 pass 1 si12, single donor 40 0.6170 0.0162 0.1773
a1v2 pass 2 si6, single donor 40 0.0065 0.0000 0.3010
a1v2 pass 2 si3, single donor 40 0.0452 0.0283 0.3122

By category the mosaic ceiling is normal 0.083, gastritis 0.090, IM 0.065, mixed 0.085.

Read that carefully. Layout-matched retrieval, executed perfectly, is worth 0.081 against a single-donor two-pass 0.045 and a cartoon ceiling of 0.694. The idea can at most take the programme from 6.5% of the cartoon ceiling to 12% of it.

The epithelium IoU is uninformative on this instrument: everything from the cartoon itself (0.323) to the si6 arm (0.301) sits in a 0.02 band, because analysis_masks' epithelium is the closed hematoxylin band and that band covers most of any tissue-filled field. Only the lumen IoU discriminates. Note also that the single-donor si3 arm's 0.0452 is only 1.6x its own chance level of 0.0283 — the number the programme has been quoting as "the layout survives a little" is barely above rotating the label map.

The mosaic is realistic, because it is real

The same C1/C2 feature pass, on the mosaics (eval/a5-mosaic-features.json):

set n ring frac ring/mm2 nuc/mm2 stromal nuc/mm2 Laplacian pale frac
assignment mosaic, all 40 0.877 120 6212 3510 21.4 0.272
— normal / gastritis / IM / mixed 10 each 0.904 / 0.951 / 0.846 / 0.813 97 / 132 / 116 / 137 6142 / 6212 / 5818 / 7069 4191 / 3426 / 3448 / 2842 21.8 / 21.3 / 18.7 / 22.9 0.272 / 0.288 / 0.299 / 0.215
gastritis, inflamed mosaic 10 0.951 141 5968 3169 20.7 0.312
real held-out, for scale 22 0.71-0.85 5996-6762 2134-4588 20.4-23.6 0.176-0.229

The mosaic sits in the real range on ring fraction, nuclear density and Laplacian detail, as it must. So the ceiling above is not an artefact of a broken ceiling image.


5. The table

Per category x arm, n = 10 except bag-diverse (5, across only) and the reference rows; medians. IoU is cartoon lumen IoU against the own (VM) label map; band counts outputs inside the category's real held-out envelope band; cos is phikon-v2 max cosine against 118 real reference fields (fail line 0.95); src is the fraction of the canvas's phikon tiles whose nearest real field is one the tokens actually came from. Full file: packages/a5/eval/a5-table.csv.

cell n ring env D band lap str nuc/mm2 IoU cos src
normal
a5 match (pass 1, si12) 10 0.149 6.90 0 16.83 1119 0.666 - -
a1v2 pass1 (si12) 10 0.247 7.92 0 17.50 1339 0.695 - -
a5 match-si6 10 0.000 8.52 0 17.91 1339 0.005 0.754 0.250
a1v2 si6 donor 10 0.000 9.43 0 18.80 1710 0.001 0.859 1.000
a5 match-si3 10 0.438 4.37 5 18.80 1843 0.069 0.771 0.438
a1v2 si3 donor 10 0.702 6.77 0 19.70 3187 0.041 0.873 1.000
a5 bag-diverse 5 0.417 3.87 3 20.38 2096 0.040 0.804 0.000
real held-out 4 0.884 3.33 4 22.78 4211 - - -
hpylori_gastritis
a5 match (pass 1) 10 0.164 10.89 0 16.96 1483 0.683 - -
a5 inflamed (pass 1) 10 0.182 10.89 0 16.88 1457 0.681 - -
a1v2 pass1 (si12) 10 0.233 10.76 0 17.38 1458 0.675 - -
a5 match-si6 10 0.018 9.22 0 17.65 1064 0.018 0.782 0.375
a1v2 si6 donor 10 0.000 8.28 4 18.60 1199 0.001 0.862 0.875
a5 match-si3 10 0.324 5.76 10 18.20 1311 0.089 0.805 0.438
a5 inflamed-si3 10 0.347 5.83 10 17.92 1237 0.080 0.810 0.438
a1v2 si3 donor 10 0.225 6.28 10 19.13 1325 0.046 0.864 1.000
a5 bag-diverse 5 0.281 5.86 5 19.33 1428 0.033 0.825 0.500
a4 bag-window si3 (a1 cartoons) 5 0.276 5.14 5 19.90 1815 - 0.822 0.500
real held-out 6 0.807 4.19 6 20.40 4588 - - -
intestinal_metaplasia
a5 match (pass 1) 10 0.246 8.56 0 15.53 1083 0.545 - -
a1v2 pass1 (si12) 10 0.227 8.60 0 15.95 1052 0.548 - -
a5 match-si6 10 0.053 5.24 0 15.86 1087 0.073 0.794 0.563
a1v2 si6 donor 10 0.000 5.72 0 18.61 1090 0.003 0.857 0.875
a5 match-si3 10 0.425 3.36 10 16.32 1399 0.084 0.818 0.500
a1v2 si3 donor 10 0.454 4.81 4 20.08 2692 0.046 0.871 0.938
a5 bag-diverse 5 0.432 3.24 5 18.15 1218 0.038 0.785 0.250
a4 bag-window si3 (a1 cartoons) 5 0.489 2.84 5 17.70 1259 - 0.790 0.125
real held-out 6 0.774 2.58 6 23.62 2134 - - -
mixed
a5 match (pass 1) 10 0.174 9.99 0 17.29 1588 0.574 - -
a1v2 pass1 (si12) 10 0.219 10.30 0 17.23 1610 0.569 - -
a5 match-si6 10 0.032 5.84 0 18.95 1241 0.077 0.810 0.438
a1v2 si6 donor 10 0.106 8.08 0 19.95 1125 0.075 0.860 0.875
a5 match-si3 10 0.323 3.65 10 20.18 1523 0.097 0.825 0.313
a1v2 si3 donor 10 0.472 5.75 0 21.65 1525 0.044 0.871 1.000
a5 bag-diverse 5 0.287 3.22 5 21.01 1558 0.039 0.800 0.500
real held-out 6 0.774 3.66 6 22.77 3528 - - -

Pooled over the four categories (eval/a5-headline.json):

arm n ring env D band lap str nuc IoU chance cos src seam
a5 match (pass 1, si12) 40 0.174 8.97 0/40 16.87 1286 0.6062 0.0152 - - 1.083
a1v2 pass1 (si12) 40 0.227 9.00 0/40 17.15 1386 0.6170 0.0162 - - 1.073
a5 match-si6 40 0.026 6.76 0/40 17.82 1203 0.0366 0.0020 0.790 0.375 1.056
a1v2 si6 donor 40 0.000 7.91 4/40 18.69 1497 0.0065 0.0000 0.860 0.875 1.052
a5 match-si3 40 0.354 3.86 35/40 18.76 1494 0.0835 0.0260 0.809 0.375 1.037
a1v2 si3 donor 40 0.450 6.30 14/40 19.88 1979 0.0452 0.0283 0.871 1.000 1.034
a5 inflamed-si3 10 0.346 5.83 10/10 17.92 1237 0.0799 0.0315 0.810 0.438 1.034
a5 bag-diverse 20 0.355 3.35 18/20 19.80 1527 0.0382 0.0257 0.801 0.375 1.041
a1 si3 donor (OLD a1 cartoons) 60 0.503 5.64 22/60 19.05 2193 - - 0.868 1.000 -
a4 bag-window si3 (a1 cartoons) 10 0.405 3.96 10/10 18.76 1508 - - 0.801 0.312 -
real held-out 22 0.841 3.51 22/22 22.77 3862 - - - - 1.027

Paired deltas, a5 minus the a1v2 single-donor arm on the same cartoon at the same depth

Medians of 40 per-cartoon deltas; n+ counts positive deltas (eval/a5-paired-vs-a1v2.csv). For IoU and ring, positive is better; for D, negative is better.

si arm n d IoU n+ d D n+ d ring n+ d lap n+ d str nuc n+
12 match (pass 1) 40 -0.0056 17 -0.05 19 -0.071 9 -0.19 10 -87 16
6 match-si6 40 +0.0171 24 -0.20 16 +0.005 20 -1.33 10 -162 13
3 match-si3 40 +0.0370 39 -1.66 3 -0.133 16 -1.81 9 -379 16

At si3 the layout gain is positive on 39 of 40 cartoons and the envelope gain on 37 of 40. Neither is noise. A4's best paired IoU delta was +0.0030, positive on 6 of 10.


6. Diversity: the A4 collapse is fixed

Mean pairwise SSIM inside each (category, arm) cell, 10 images / 45 pairs unless noted (eval/a5-within-arm-ssim.json):

set normal gastritis IM mixed
real held-out (n=4-6) 0.038 0.047 0.035 0.034
a1v2 pass1 (si12) 0.377 0.435 0.398 0.435
a5 match (pass 1) 0.390 0.433 0.408 0.447
a1v2 si6 donor 0.620 0.585 0.557 0.565
a5 match-si6 0.643 0.610 0.599 0.641
a1v2 si3 donor 0.619 0.574 0.548 0.557
a5 match-si3 0.631 0.597 0.578 0.639
a5 inflamed-si3 - 0.583 - -
a5 bag-diverse (n=5) 0.672 0.642 0.606 0.651
(A4 bag-window si3, shared seed) - 0.986 0.981 -

bag-diverse uses the same construction A4's bag-window did — 16 random (library window, patch) draws per canvas window — with one distinct seed per cartoon. A4's within-arm SSIM was 0.98; A5's is 0.61-0.67, the same range as every other pass-2 arm. The one-line seeding change removed the collapse completely, and the envelope gain that made A4 flag bag-window for follow-up survived it: D 3.35 pooled against A4's 3.96, 18 of 20 inside the band.

No arm is anywhere near real diversity (0.034-0.047). Layout-matched retrieval is slightly more repetitive than the single donor (+0.03 to +0.08 SSIM), which is expected: the assignment is a deterministic function of the cartoon and neighbouring cartoons of the same category draw on overlapping donor sets.


7. Window-boundary seams: measured, and absent

A4 recorded seams as "not measured, not absent"; A5's construction (21 windows from up to 14 slides on one canvas) is the one most prone to them. analyse_a5.py adds the instrument: for every column of the full-resolution grey image, the mean absolute horizontal difference, then the value at each window boundary (x = 512, 1024, ...) divided by the median of the 32 columns around it.

set n at window boundaries off-boundary null max
real held-out (no windows at all) 22 1.027 1.066 1.27
a1v2 si3 donor 40 1.034 1.032 1.26
a5 match-si3 40 1.037 1.046 1.21
a5 match-si6 40 1.056 1.047 1.29
a5 inflamed-si3 10 1.034 1.058 1.21
a5 bag-diverse 20 1.041 1.033 1.24
assignment mosaic (hard pastes — positive control) 40 5.953 1.035 10.92
inflamed mosaic 10 6.872 1.050 10.33

The mosaics score 6-7x their own off-boundary null, so the statistic detects seams when they exist. Every generated a5 arm sits at 1.03-1.06, indistinguishable from real fields and from the single-donor arm. The Hann-tapered MultiDiffusion blend absorbs a 14-slide mosaic without leaving a visible boundary. This is the cleanest positive result in the package after the envelope.


8. Gastritis: the inflamed pool was selected on the wrong instrument

The gastritis question is EVAL_RESULT.md (j): the infiltrate that defines the category is largely absent — stromal nuclear density 1325/mm2 for the a1v2 single-donor si3 arm against a real held-out 4588/mm2 — and the pale candidates game the ring metric by fading. A5's --inflamed arm restricts the pool to the densest 30% of gastritis windows and asks whether the infiltrate comes back.

It did not, and the reason is that the selector and the evaluator do not measure the same thing.

instrument what it is
token-library index composition[3] stroma_nucleus_density_norm: nucleus area as a fraction of stroma area, from build_token_library.window_composition, divided by 0.35 and clipped. This is what --inflamed ranks on.
F2 label map label-6 pixels over labels 5+6 — nucleus area fraction of stroma, from pairs/segment_real.py
morphometrics.extract str_nuc_density_per_mm2 nucleus instance count per mm2 of stroma. This is what every table in the programme reports and what "4588" is.

Measured on 60 gastritis windows drawn evenly across the whole library-index range (eval/density-instrument-check.json), each 1024 px window scored by all three:

pair Spearman Pearson
library index vs str_nuc_density_per_mm2 -0.341 -0.029
library index vs F2 label-6 fraction +0.199 -0.067
F2 label-6 fraction vs str_nuc_density_per_mm2 -0.637 -0.633

Selecting the top 30% by the library index gives windows whose str_nuc_density_per_mm2 median is 6816/mm2 against 10532/mm2 for the rest — the "inflamed" pool is less nucleated by the instrument the evaluation uses. (Window-level densities are much higher than canvas-level ones because morphometrics computes its percentile thresholds within the image; they are comparable to each other, not to the 4588 field-level figure.)

The mosaics confirm it end to end at canvas scale: the gastritis inflamed mosaic carries 3169 stromal nuclei/mm2 against the unrestricted assignment mosaic's 3426 — the restriction moved the source tissue away from the target, before any generation happened. And the outputs follow:

gastritis si3 arm n str nuc/mm2 nuc/mm2 pale frac ring ring count env D
a5 inflamed-si3 10 1237 5915 0.137 0.347 82.5 5.83
a5 match-si3 10 1311 5989 0.128 0.324 73.5 5.76
a5 bag-diverse 5 1428 6073 0.122 0.281 81.0 5.86
a1v2 si3 donor 10 1325 - 0.154 0.225 58.0 6.28
real held-out 6 4588 - 0.229 0.807 43.5 4.19

The inflamed arm is 74/mm2 below the unrestricted arm and 3351/mm2 below real. The mechanism was mis-aimed. This is a fault in this package's execution, not a property of the generator: the fix is to rank candidate windows on a nucleus count measure, which is one line in layout_match.inflamed_windows.

One thing the a5 arms do fix: the fading. The a1v2 single-donor gastritis arm has pale tissue fraction 0.154 and only 58 detected rings; the a5 arms are less pale (0.122-0.137) and carry 73-83 rings. But real gastritis has 43.5 rings and pale fraction 0.229, so a5 has moved away from real on both counts in the other direction — it over-produces small rings rather than fading into large ones.


9. The contact sheets, described honestly

eval/sheets/a5-<category>.png — five rows (cartoons s11-s15 across), eight columns: cartoon | match (pass 1) | match-si6 | match-si3 | inflamed-si3 (gastritis only) | bag-diverse | a1v2 si3 donor | real held-out, 768 px crops at the same canvas position (1664, 640, 2432, 1408), each labelled ring / D / lap / IoU. Four a5-zoom-* sheets show one cartoon at ~640 px, and a5-mosaic-vs-output.png puts the assignment mosaic beside what pass 2 made of it.

a5-mosaic-vs-output.png (cartoon | assignment mosaic | a5 match-si3 | a1v2 si3 donor | real). The mosaic column is unmistakably a tiling: in a 768 px crop you see two to four real windows meeting at hard vertical and horizontal edges, with abrupt changes of stain and scale across them. The match-si3 column beside it has no seams at all and reads as one continuous field — which is the 1.037 seam ratio, seen rather than computed. Comparing columns 3 and 4: the a5 output is consistently paler and less saturated than the single-donor output, which in normal, IM and mixed is a strongly over-stained purple. That difference is the -1.66 D.

a5-hpylori_gastritis.png. Column 2 (match, pass 1 si12) is a soft, low-contrast, faintly blurry version of the cartoon — a smoothed drawing, not tissue, exactly as its IoU 0.60-0.69 says. Column 3 (match-si6) is the worst region of the package: rows 2 and 4 are pale and almost structureless (ring 0.000-0.034). Column 4 (match-si3) is the first that reads as gastric mucosa: row 1 shows crowded epithelial cells with clear vacuoles and a cluster of red cells, row 2 a coiled gland with red cells in its wall, row 3 dense small dark nuclei among pale cells that reads as inflamed lamina propria, row 5 a cellular field with red cells. Column 5 (inflamed-si3) is of the same character and in rows 3 and 5 slightly more cellular. Column 7 (a1v2 si3 donor) is the comparison that matters: row 2 is a heavily over-stained purple gland, and row 4 is a very pale washed-out honeycomb at ring 1.000 — the exact metric-gaming failure EVAL_RESULT.md (j) flagged, and it does not appear in any a5 column. Column 8 (real) is still more organised and more delicate than any generated column, with wider gland lumens and thinner strands.

a5-zoom-intestinal_metaplasia.png at ~1:1.2. match-si3 shows crowded epithelial cells around a clear lumen with dark basally-placed nuclei and pale vacuolated cytoplasm; bag-diverse shows a gland with a columnar lining and a lumen; the a1v2 si3 donor is a dark over-stained purple palisade. The real column is paler and more delicate than all of them, with a wide gland lumen and thin fibrillar strands, and its nuclei carry speckled chromatin that none of the generated cells reproduces (lap 23.6 against 16.3-20.1).

What is still absent from every pass-2 cell. The cartoon's geometry. In none of the 120 pass-2 images can I find the cartoon's lumen blobs in their cartoon positions; the improvement from 0.045 to 0.084 is real and systematic on the instrument but is not visible as "this output follows that drawing". The pass-1 column, by contrast, visibly is the cartoon.


10. Harness and instrument changes made in this package

Four, all additive; none can change a sweep-v1, sweep-v2 or a4 number.

  1. packages/sweep-v1/evaluate_sweep.py: _library_origins_for() and _library_field_count() now also read token_assignment_picks, so a mosaic arm reports the fraction of tiles nearest any of its assigned donor fields and the true donor-field count per canvas, instead of the structural zero those functions returned for a source they had never seen. No sweep-v1/v2 or a4 record has that key.
  2. A rot180 chance level on every layout-IoU cell (_d1_one already supported unrot_labels; nothing supplied it before). It turns "IoU 0.045" into "0.045 against a chance level of 0.028".
  3. The assignment mosaic, which turns "what is the ceiling of this idea?" into a measurement.
  4. A window-boundary seam statistic, calibrated on real fields (1.027 at boundaries, 1.066 off-boundary) and validated on the hard-pasted mosaics (5.95).

evaluate_sweep.py's sheets stage is still hard-wired to a1/d1/a2/a3/d2/e2/b1 and produced 0 sheets for a5 (it logs no data: for each and exits 0), exactly as for a4. The sheets are written by analyse_a5.py, which also labels every cell with its layout IoU.


11. Verdicts

(1) Does layout-matched retrieval give cartoon-like layout at si3, at two-pass-si3 realism?

It gives a real, systematic, and small layout gain, and a large realism gain.

So: yes on realism, marginally on layout, and the ceiling says this cannot become the layout answer. If the programme wants layout, the F2 adapter or region_img2img's per-label lock remain the only candidates. If it wants the most realistic 4096x2048 field it can get from the two-pass recipe, match-si3 and bag-diverse are now the best arms in the programme.

(2) Did inflamed windows restore the gastritis infiltrate without fading?

No, and the arm could not have worked. inflamed-si3 gives stromal nuclear density 1237/mm2 against match-si3's 1311 and a real held-out 4588 — it is 74/mm2 below the unrestricted arm. Section 8 shows why: the library index the pool was ranked on is anti-correlated (Spearman -0.341, n=60) with the str_nuc_density_per_mm2 the evaluation reports, and the inflamed mosaic itself is less nucleated (3169) than the unrestricted mosaic (3426) before any generation happens. The arm did not fade (pale fraction 0.137 vs the single donor's 0.154, and it carries 82.5 rings against the single donor's 58), so the fading failure mode is absent — but that is a property of layout-matched retrieval in general, not of the inflamed restriction. Re-run with a nucleus-count ranking before drawing any conclusion about infiltrate.

(3) Is bag-diverse both diverse and realistic?

Diverse relative to A4, not relative to real; and yes, realistic. Within-arm SSIM 0.606-0.672 against A4's 0.981-0.986 — the per-cartoon seed removed the collapse entirely, and the five outputs of a cell are now as different from one another as every other pass-2 arm's. They are nowhere near real (0.034-0.047), and bag-diverse is in fact the most repetitive arm in this package by a small margin. On realism the A4 finding survived and improved: pooled D 3.35 against A4's 3.96, with 18 of 20 inside the band, and per category IM 3.24 and mixed 3.22 — the two best envelope distances anywhere in the programme. It buys nothing on layout (IoU 0.038 against its own chance level of 0.026), which is expected: its tokens do not depend on the cartoon at all.

(4) Copy and inheritance position of the mosaic outputs

Cleaner than every single-donor arm, on every screen.

Read honestly, this cuts both ways. It lowers the risk that any one output resembles any one real field, and it raises the number of real slides each output touches from 1 to ~11 — which is the more conservative reading for the parked PixCell/UNI licence question, not the less.


12. What this changes, and what to do next

  1. The two-pass recipe now has a better default. token_assignment at si3 dominates the single-donor arm on the envelope (37/40), on layout (39/40) and on every copy screen, at the cost of ring fraction and Laplacian detail. Nothing about it needs a GPU beyond the generation itself: the matcher is 8 seconds of CPU for 40 cartoons.
  2. The layout question is closed for retrieval. The assignment-mosaic ceiling of 0.081 is a property of the real data — over 4032 candidates per window there is no real gastric window whose lumen mask matches a synthetic one better than IoU 0.12. No smarter search over the same 24 fields per category can beat it by much. Widening the pool (all 118 fields, or whole slides rather than 96 pre-cut fields) would raise the ceiling somewhat; conditioning that the denoiser reads per position (F2's adapter, region_img2img's lock_mask) is the only thing that can raise it a lot.
  3. The seam risk is dead. A 14-slide mosaic on one canvas produces no measurable window-boundary artefact after the Hann blend (1.037 against a real-field null of 1.027). Future work can compose conditioning per window freely.
  4. Fix the inflamed ranking and re-run 20 jobs. One line in layout_match.inflamed_windows, ~15 minutes of A100. Until then nothing is known about whether more infiltrate in gives more infiltrate out.
  5. Stromal cellularity is now the biggest single gap and it is not category-specific: real 2134-4588/mm2 against 1218-2096 for every a5 arm and 1325-3187 for the single-donor arm. The programme has no lever aimed at it.
  6. bag-diverse deserves the follow-up A4 asked for, now that it works. D 3.22-3.87 in three of four categories with no donor field, no encoder pass and no single slide, at SSIM 0.61-0.67. A combination — patch-bag tokens for appearance, assignment for layout — is one manifest away and was not tried here.

13. Caveats

Download public Markdown export