Spaces:
Running on Zero
Running on Zero
File size: 11,419 Bytes
7312066 a2130ce 7312066 a2130ce 7312066 a2130ce 7171bfb 7312066 a2130ce 7171bfb a2130ce 7171bfb a2130ce 7171bfb a2130ce 7171bfb a2130ce 7171bfb a2130ce 7171bfb fea4425 7171bfb 5136010 7171bfb a2130ce 7171bfb a2130ce 7171bfb a2130ce 7171bfb a2130ce 7171bfb a2130ce 7171bfb a2130ce 7171bfb a2130ce 7171bfb a2130ce 7171bfb a2130ce | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | ---
title: Scroll Lens
emoji: 🔎
colorFrom: yellow
colorTo: gray
sdk: gradio
sdk_version: 6.28.0
python_version: "3.12"
app_file: app.py
pinned: false
license: apache-2.0
suggested_hardware: zero-a10g
startup_duration_timeout: 1h
short_description: See scroll CT through 3D DINO, ink and surface models
models:
- scrollprize/dinovol_v2_ps8_with_paris4_352500
- scrollprize/ink_3d_dino_guided
- scrollprize/surface_recto_3dunet
tags:
- vesuvius-challenge
- herculaneum
- ct
- 3d
- dino
- self-supervised
- zerogpu
- mcp-server
---
# Scroll Lens
Look inside a carbonized Herculaneum scroll with the Vesuvius Challenge's newest 3D models, and click to search.
People use the page, flat or in 3D; scripts and AI agents use the same cubes through tools that are an API and an MCP server.
Scroll Lens reads a small cube (128³ or 256³ voxels, 0.3 or 0.6 mm a side) of a public X-ray CT scan straight from the
[Vesuvius Challenge open data](https://scrollprize.org/data) and runs three models on it, all released by the
Vesuvius Challenge team with their PHerc. Paris 4 work ([Angelotti et al. 2026](https://arxiv.org/abs/2606.29085)):
| Layer | Model | What it shows |
| --- | --- | --- |
| 3D DINO embeddings | [`scrollprize/dinovol_v2_ps8_with_paris4_352500`](https://huggingface.co/scrollprize/dinovol_v2_ps8_with_paris4_352500) | A self-supervised 3D ViT: 864 numbers for every 8 × 8 × 8 voxel patch. Alike patches get alike numbers. Shown as *DINO colors* (the three main directions as red, green, blue) and used by click search. |
| 3D ink | [`scrollprize/ink_3d_dino_guided`](https://huggingface.co/scrollprize/ink_3d_dino_guided) | Ink voxels marked directly in the scan, with no flattened surface. |
| Recto surface | [`scrollprize/surface_recto_3dunet`](https://huggingface.co/scrollprize/surface_recto_3dunet) | The written side of each papyrus sheet. |
**Click to search.** Click a patch with *More like this* and every patch whose embedding is close to it lights up;
*Not this* subtracts a patch. This is the organizers' own recipe: their ink reference (shown in amber) is the mean
embedding of 256 expert clicks on ink. Your concept follows you to other cubes and scans, and downloads as
`concept.npy` (and `concept_not.npy` for the Not this clicks), so you can click ink in one scroll and look for its like
in another.
**Everything shown is model output, not a reading.** A cube is smaller than one letter.
## Using it
1. **Gallery** (no GPU): 99 ready-made cubes. Pick one and it opens; *Previous*, *Next* and *Random* step through
them, and you can type in the list to search it. The page opens on the first one.
2. **Your own point**: pick a scan, type a point (level-0 voxels, z y x) or press *Surprise me*, then *Run the
models*. A 256³ cube uses about 5 seconds of your daily ZeroGPU time (a call takes 7 to 12 s), a 128³ cube under a
second. ZeroGPU starts a call only while about 21 s (256³) or 11 s (128³) of your quota is left. When it refuses,
or no GPU is free, the models run on the Space's CPU on the 128³ around your point instead (about 10 seconds, one
such run at a time).
3. **Look**: the three views are drawn in your browser, so everything answers at once. Drag a slider, or drag inside a
view to move the crosshair: the other views follow as you move. The wheel or the arrow keys step one slice (Shift:
8). Ctrl or Cmd with the wheel, or a pinch, zooms; right-drag or Shift-drag pans; double-click resets. The line
under the views reads out what is under the pointer: place in the cube and the scan, CT value, ink and surface
probability, and similarity.
4. **Search**: a click only moves the crosshair at first (*Move only*). Choose *More like this* and click a patch;
*Not this* subtracts one; *Undo click* (or Ctrl or Cmd with Z) forgets the last click. *Top % shown* sets how many of the most similar patches light up. The organizers' reference (amber)
never lights a patch below their own threshold, cosine 0.5.
5. **3D view**: turns the three views into one 3D space of the cube, as in Unroll: the three slices as planes that
move as you drag the sliders, ink and surface as points, and the patches like your concept or the organizers'
reference glowing. Drag to rotate, wheel to zoom, right-drag to pan, *Spin* to turn it slowly; click a slice to
search there.
6. **More**: brightness and contrast, *Center on point*, *Reset zoom*, *Use crosshair as point* (copies the
crosshair's scan position to Your own point), *Copy link* (a link that opens this cube at this crosshair), and
*Save files* (model outputs as `.npz`, the receipt `.json`, your concept).
Links: `?cube=ID` opens a gallery cube; `?v=PHerc0139&z=…&y=…&x=…&size=256` fills in Your own point (press Run the
models); `&s=z,y,x` puts the crosshair at that voxel of the cube.
## The scans
Only these twelve scans, all at 2.2 to 2.4 µm, the scale the models work at. None is on the First Letters or Grand
Prize eligible lists. PHerc. Paris 4's title is an open prize, so Scroll Lens itself only ever picks the eight Paris 4
points the organizers listed for training the 3D ink model; you can type any point.
| Id | Scroll | 3D DINO | 3D ink | Recto surface |
| --- | --- | --- | --- | --- |
| PHercParis4 | PHerc. Paris 4 | trained on this scan | made for this scroll | not trained on it |
| PHerc0139 | PHerc. 139 | trained on this scan | its teacher was | trained on it |
| PHerc1667 | PHerc. 1667 (Scroll 4) | **never saw it** | its teacher was | trained on it |
| PHerc0500P2 | PHerc. 500P2 | trained on this scan | its teacher was | trained on it |
| PHerc0343P | PHerc. 343P | trained on this scan | not trained on it | trained on it |
| PHerc0814 | PHerc. 814 | trained on this scan | its teacher was | not trained on it |
| PHerc0009B | PHerc. 9B | trained on this scan | its teacher was | not trained on it |
| PHercMAN5 | PHerc. MAN 5 | trained on this scan | its teacher was | not trained on it |
| PHerc1299 | PHerc. 1299 | trained on this scan | not trained on it | not trained on it |
| PHerc0332 | PHerc. 332 | trained on this scan | not trained on it | not trained on it |
| PHerc1451 | PHerc. 1451 | trained on this scan | not trained on it | not trained on it |
| PHercMANB | PHerc. MAN B | trained on this scan | not trained on it | trained on it (MAN Bp) |
"Its teacher was": the 3D ink model was distilled from a stage-1 teacher trained on that scroll's ink labels. From
the model cards and the checkpoints' own training configs, read 16 October 2026. The page shows this per cube.
## Checks
- Each cube shows how well the organizers' ink reference picks out the patches the 3D ink model marks as ink (AUC),
beside controls that must fail: the same ink mask moved away from its place. Papyrus is layered, so one move can
line the layers up again; the control is therefore taken over 16 fixed moves, and the AUC means something only
above their range (`auc_control_p95`). Your own concept gets the same check. This is agreement between two models,
not ground truth.
- The 3D DINO code is the organizers' own (dinovol, at a pinned commit) and reproduces their napari inspector's patch
embeddings to a cosine of 0.9999 on a Paris 4 cube.
- Weights are downloaded at pinned revisions and refused unless their SHA-256 matches; the model code is downloaded
at pinned commits. The receipt records all of it, plus the device, timings and the cube's place in the scan.
## For agents and scripts
Everything a person can do on the page, an agent can do too: each tool below is both an API endpoint
(`gradio_client`) and an MCP tool. Every answer carries `open_in_browser`, a link that opens the same cube on the page,
so an agent can hand its result to a person.
| Tool | What it does | GPU |
| --- | --- | --- |
| `list_scans` | the twelve scans, their sizes, what each model saw of them in training, the Paris 4 training points | no |
| `list_gallery` | the ready-made cubes, with ids | no |
| `random_point` | a point on the papyrus of a scan, fixed by a seed | no |
| `inspect_cube` | three slice views with chosen overlays (one PNG) and the cube's receipt, checks and layer fractions | gallery: no |
| `search_cube` | the patches most like example points, a `concept.npy`, or the organizers' ink reference; with the AUC against the 3D ink model and its control, an image through the best match, and the concept as `concept.npy` | gallery: no |
| `download_cube` | the model outputs as a `.npz` and the receipt | gallery: no |
| `look` | the `.npz` for any point, for batch scripts (`tools/build_gallery.py` uses it) | yes |
A cube is named by a gallery `cube_id`, or by `scan` and a point `z`, `y`, `x` (level-0 voxels) with `size` 128 or
256. Points for `search_cube` are scan coordinates inside the cube, written `"z,y,x; z,y,x"`. A point outside the
listed scans, outside the cube, or not three whole numbers is refused with a message saying why.
**MCP.** Add `https://sharpmeow-scroll-lens.hf.space/gradio_api/mcp/` to an MCP client (Claude, Cursor and others).
Calls on a new point use the caller's ZeroGPU time, so give the client a Hugging Face token
(`Authorization: Bearer hf_...`); gallery cubes need none.
**Python.**
```python
from gradio_client import Client
c = Client("SharpMeow/scroll-lens", token="hf_...") # your token: GPU time counts against your quota
cubes = c.predict(api_name="/list_gallery")["cubes"]
image, info = c.predict(cubes[0]["id"], "", 0, 0, 0, 256, "ink,reference", api_name="/inspect_cube")
image, hits, concept = c.predict("", "PHerc1667", 15108, 10100, 5532, 256, # cube_id, scan, z, y, x, size
"15108,10100,5532", "", None, 10, # positive, negative, concept, top_k
api_name="/search_cube")
path = c.predict("PHerc1667", 15108, 10100, 5532, # look: scan, z, y, x
256, "dino,ink,surface", # cube side, models
False, False, False, # mirror averaging, compact, allow the CPU fallback
api_name="/look")
```
Read a `.npz` with `lens.load_npz(path)` from this Space's `lens.py`: `grid` is (Z/8, Y/8, X/8, 864) float16 unit
embeddings (or `grid_reduced` and `grid_basis` when compact), `ink` and `surface` are (Z, Y, X) uint8 probabilities
times 255, and `receipt` is the JSON receipt. The scan itself is not included; read it from the bucket with
`lens.read_cube(volume, receipt["cube_origin_zyx"], receipt["cube_size"])`.
`lens.py` also runs on any computer: `python3 lens.py look PHerc0139 47316 9452 12796 --size 256` (CUDA if present).
## Credits and licenses
Code: Apache 2.0 (see `LICENSE` and `NOTICE`), by Chase Hendrick. Models and their code: MIT, by the Vesuvius
Challenge (dinovol includes parts under the DINOv2 Apache 2.0 license and the DINOv3 License Agreement). Scan data:
CC BY-NC 4.0, Giorgio Angelotti, Stephen Parsons, Sean Johnson, Elian Rafael Dal Prà, Johannes Rudolph, Paul
Tafforeau, Alessandro Mirone, Paul Henderson, Hendrik Schilling, Forrest McDonald, David Josey, Youssef Nader,
C. Seth Parker, W. Brent Seales, *Vesuvius Challenge: CT Scans of Herculaneum Papyri*. Gallery cubes are model output
derived from that data (CC BY-NC 4.0) and hold no scan voxels.
Source and tests: [github.com/ChaseHendrick/Scrolls](https://github.com/ChaseHendrick/Scrolls), `spaces/scroll-lens/`.
|