# 3D ink predictions: ink96-im1-14000

Whole-scan 3D ink probability volumes, one per ESRF scan of the Herculaneum scrolls (every scan of the volcomp mirror,
`community-uploads/forrest/volcomp/`, except DLS scans), predicted at the scan's ~8-9.6 um level by our ufsm 3D ink network
(C23/CUDA, https://github.com/SuperOptimizer/ufsm).

## Model
- Checkpoint: im1-14000.ckpt (step 14000; md5 prefix 41b4667e3cbf): the best holdout-F1 checkpoint of run im1.
- Run im1: fine-tune of ink96-wk1 (7-level 3D U-Net, widths 16..256, 9.6 um input) on ink-splat labels: published 2D ink labels
  (human labels of Paris 4, PHerc0139, 1667, 0009B, 0500P2, 0814, MAN5; reader-v2 teacher maps of 0139, 0814, 0009B, 0500P2, 0343P)
  splatted into 3D along the published segment surfaces, band -10..+48 um along the surface normal, on papyrus voxels only.
  ink96-wk1 itself learned from the published Paris 4 3D ink prediction (a model-predicted target).
- Holdout F1 (p 0.5, regions excluded from training): im1-14000 PHerc0139 w039 ~0.45-0.48, Paris 4 ~0.68.
- Companion set: ink3d/ink96-im1-14000 (best holdout F1) and ink3d/ink96-im1-last (final step 22540).

## Recipe (per scan)
```
ufsm predict <ckpt> <ct-root> <scan>.zarr <out>.zarr --um <level-0 um> --level <L> --window 1024 --halo 0 --ema 1 --q 32 \
  --threads 16 --gpu 0 --full-grid 1 --resume 1 --occupancy 5 --levels 4 --shard 512
```
- Input level L = the scan's pyramid level nearest 8-9.6 um (8.64 / 9.36 um scans: L0; 2.2-2.4 um: L2; 4.3 um: L1; 1.13 um: L3;
  0.55 um: L4), raw uint8 CT exactly as stored on the mirror (no extra normalisation or mask, as in training).
- No test-time augmentation, no window overlap (1024^3 windows, halo 0): window seams every 1024 voxels may show.
- --occupancy 5: 512^3 output shards that are all zero (air) on CT level 5 are not computed (absent = 0).

## Format
- OME-Zarr v3 group, 4 levels: the input level's voxel size (e.g. `9.6`, `8.64`, `9.362`, `9.032`) then 2x, 4x, 8x
  (max-pooled pyramid). Level array names = voxel size in um. The output grid is the input level's grid (origin 0, same shape):
  ink voxel (z, y, x) at level k covers CT level-L voxels (z, y, x) * 2^k.
- uint8 value = round(p * 255), p = predicted ink probability. (The group attribute says "recto probability": that is the name of the
  network's output head; the content is ink.)
- 512^3 shards of 128^3 chunks, volcomp codec at q 32 (lossy DCT). Chosen from a q sweep on two holdout boxes (F1 vs human labels):
  Paris 4 F1 0.678 at q 0 (lossless) .. 0.677 at q 32 .. 0.677 at q 128; PHerc0139 w039 0.450 / 0.442 / 0.411; size per box q 32 =
  1/180 of lossless, 2.3x q 128. q 32 is the most compact setting that keeps F1 within 0.01 of lossless on both boxes.
- manifest.json: per scan the CT volume, level, voxel size, bytes, prediction seconds, URL.

## Renders
renders/<scan>/<segment>.jpg: every published segment surface (vesuvius-challenge-open-data <scroll>/segments/<segment>/mesh/
<segment>-on-<scan>-<um>.tifxyz, via the surfcomp mirror) rendered with this ink volume of the same scan: mesh scaled to the ink level,
max of the ink over normal offsets -1..+5 ink voxels (-10..+48 um, the training band; +N = d/dcol x d/drow of the tifxyz grid),
1 px = 2 ink voxels (~19 um, fast first pass; a few early scans at 1 px = 1 voxel), contrast p1..p99 over the surface's CT pixels
(one mapping per segment, display only); JPEGs downscaled to at most 4000 px on the longest side (q85) for upload;
<segment>_ct.jpg = CT on the surface (first pass: CT level L+2);
<segment>.txt = ink statistics (bytes / 255). raw-* segments (PHerc1203 segments/raw) and segments whose mesh names no volume
(PHercMANBp, MAN5_outer_3, PHerc0139 w030 2026-01-30) were placed on the only scan whose grid holds them / that existed at their date.

## Known artefacts
- Straight and wavy seam lines and brightness steps between neighbouring patches in the renders are prediction-window seams: every
  1024^3 window is predicted on its own (halo 0, no overlap). A sheet crossing a window's z plane gives a straight seam in the render,
  crossing an x / y plane a wavy one. The renderer applies one intensity mapping per segment.
- Concentric rings around holes in some inpainted meshes (e.g. PHerc0500P2 *_inp_hr) are in the mesh: the inpainted patch cuts
  across sheets (the CT render shows the same rings). Black squares are invalid mesh vertices (not rendered).
- PHerc0500P2: the model calls much of this fragment's papyrus ink (renders mostly white); a model issue, logged for the next
  ink training round. Smaller prediction windows (512) change it only slightly.
