The Vesuvius Challenge's dinovol model, run over every voxel of the PHerc0841 scan. Each small cube of the scroll now has a list of numbers that describes what it looks like. Stream them by URL and search, cluster or segment on a laptop, with no GPU.
Made and shared by Chase Hendrick, October 2026. Model and code by the dinovol authors in ScrollPrize/villa.
An X-ray CT scan tells you how dense each tiny point of a scroll is. That is a lot of gray, and it is hard to ask a question of it like "where else does it look like this?". A model can turn the gray into something easier to compare.
dinovol is a 3D model from the Vesuvius Challenge team, trained the DINO way: self-supervised, with no labels. Its main features come from learning which parts of a scan look alike, not from being told what papyrus is.
For each 8 × 8 × 8 voxel cube (about 75 µm across) the model writes 480 numbers, and for each 4 × 4 × 4 cube it writes 64 more. Those lists are the embeddings, or features. Here they cover the whole scan.
Each list has length one, so the dot product of two lists says how alike two cubes look: 1 is the same, 0 is unrelated. Click one point and you can find every place that looks like it, or train a small classifier from a few clicks.
CTfeatures as color
Left is the CT slice at height z 9,600. Right is the same slice with each cube's numbers turned into a color: the three directions in which the numbers vary most become red, green and blue. Cubes with similar colors have similar numbers.
In this slice the colors form bands that follow the rolled layers you can see in the CT. That is how the numbers vary here; it is not a check of the model.
Pick a point and the page lights up every cube in this slice whose numbers are most like it. Drag to move the point. It all runs in your browser, on a small copy of the slice's features that loads when you get here.
This reads the 480 numbers at your point straight from the bucket and compares them with every cube in a box about 10 mm across, centered on it. It runs anywhere Python does, logged out.
The same coloring as the slider above, for every slice of PHerc0841: each cube's 480 numbers become one color, from the three directions in which they vary most across the whole scan. It is a 3D color volume in OME-Zarr with four zoom levels, so neuroglancer streams it in a web browser and you can move through all 2,448 layers of cubes.
Array 0 is full detail, one color per 8-voxel cube, shape (3, 2448, 963, 963) as channel, z, y, x; arrays 1 to 3 are each half the size of the one before. 0 means outside the scroll. The colors show how the numbers vary; they are not labels.

The features and the scan they came from read straight from these URLs, logged out, with no download step. The features are one zarr v2 group in a public Hugging Face bucket; Use it below has a tested Python snippet that opens it.
https://huggingface.co/buckets/SharpMeow/dinovol-embeddings/resolve/PHerc0841/20250821151531-dinovol_8um_ps192_102126.zarr/index.jsonimport json, urllib.request
data = json.load(urllib.request.urlopen("https://huggingface.co/buckets/SharpMeow/dinovol-embeddings/resolve/PHerc0841/20250821151531-dinovol_8um_ps192_102126.zarr/index.json"))curl -L -O https://huggingface.co/buckets/SharpMeow/dinovol-embeddings/resolve/PHerc0841/20250821151531-dinovol_8um_ps192_102126.zarr/index.jsonhttps://huggingface.co/buckets/SharpMeow/dinovol-embeddings/resolve/PHerc0841/20250821151531-dinovol_8um_ps192_102126-rgb.ome.zarr/.zattrsimport json, urllib.request
data = json.load(urllib.request.urlopen("https://huggingface.co/buckets/SharpMeow/dinovol-embeddings/resolve/PHerc0841/20250821151531-dinovol_8um_ps192_102126-rgb.ome.zarr/.zattrs"))curl -L -O https://huggingface.co/buckets/SharpMeow/dinovol-embeddings/resolve/PHerc0841/20250821151531-dinovol_8um_ps192_102126-rgb.ome.zarr/.zattrshttps://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr# pip install "zarr>=3" fsspec aiohttp
import zarr
root = zarr.open_group("https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr", mode="r") # OME-Zarr, zarr v2
a = root["2"] # level 0 is full resolution; level L is 2**L times coarser
print(a.shape, a.dtype, a.chunks)
block = a[2400:2464, 1000:1064, 1000:1064] # (z, y, x); reads only the chunks it touches# pip install "vesuvius[volume-only]"
from vesuvius import Volume
v = Volume(type="zarr", path="https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr/2") # one pyramid level
block = v[2400:2464, 1000:1064, 1000:1064]In VC3D (villa's volume-cartographer), open or create a project, then
File > Attach Remote Zarr... and paste:
https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr
It accepts http, https and s3 OME-Zarr roots.https://neuroglancer-demo.appspot.com/#!%7B%22layers%22%3A%5B%7B%22type%22%3A%22image%22%2C%22source%22%3A%22zarr%3A%2F%2Fhttps%3A%2F%2Fvesuvius-challenge-open-data.s3.us-east-1.amazonaws.com%2FPHerc0841%2Fvolumes%2F20250821151531-9.366um-1.2m-113keV-masked.zarr%2F%22%2C%22name%22%3A%22X-ray%20CT%20the%20features%20came%20from%2C%20PHerc0841%209.366%20um%22%7D%5D%2C%22layout%22%3A%224panel%22%7Dcurl -s https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr/.zattrs # OME multiscales metadata
curl -s https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr/2/.zarray # one level: shape, chunks, dtype, codechttps://sharpmeow-dinovol-embeddings.static.hf.space/The bucket, with its README: https://huggingface.co/buckets/SharpMeow/dinovol-embeddings
The page's slice of features for search (32 numbers per cell, int8): https://huggingface.co/spaces/SharpMeow/dinovol-embeddings/resolve/main/assets/search.i8
Page source and build script: https://github.com/ChaseHendrick/Scrolls/tree/main/spaces/dinovol-embeddingsRunning dinovol over this scan took about 48 hours of GPU time. Now it is done once, and everyone can start from the result.
Pick a point, take its vector, and compute dot products over a region or the whole scan. High values mark cubes the model sees as alike.
Label a handful of points as "this" and "not this", fit a logistic regression or a small MLP on their vectors, and apply it everywhere. No backbone to train.
Cluster a region, reduce it with PCA or UMAP, or compare scans of other scrolls you embed yourself with the same model and settings.
pip install "zarr>=3" fsspec aiohttp numpy
It worked when it prints (2448, 963, 963, 480) (4896, 1926, 1926, 64).
v = g["tokens_unit"][i, j, k].astype("float32") * g["tokens_unit_scale"][i, j, k]
# i, j, k = z // 8, y // 8, x // 8 for tokens_unit; // 4 for dense and inkEverything streams, so most people never need a copy. When you want files on disk, the free hf command copies any folder of the store, logged out. A downloaded copy opens with the same zarr code, with the folder's path in place of the URL.
Chunk folders follow the scan: ARRAY/Z/Y/X with Z = z ÷ 192 and Y, X = y, x ÷ 96, in scan voxels, for every array. This strip at z 9,600 is 40 MB and took 7 s logged out.
Swap in dense or ink for the others. The vectors and their scales are separate arrays; take both.
Log in first with a free Hugging Face account: listing the whole store logged out gets rate limited.
Needs pip install -U huggingface_hub, which provides the hf command. Then read it locally: zarr.open_group("pherc0841-dinovol.zarr", mode="r", zarr_format=2).
Every step ran from scripts on Hugging Face Jobs, with a record for each part. Nothing was edited by hand.
Weights scrollprize/dinovol_8um_ps192_102126, teacher_step_640000.pt (sha256 2354c493…), run with villa's dinovol/dinovol_2/eval/embed_volume.py at commit 4ea26d9c on the dinovol-refactor branch.
The input is the masked level-0 scan, 9.366 µm voxels. Its intensity statistics were measured once over 1,216 sampled chunks and used for every slab, so a vector means the same thing everywhere.
The scan was cut into slabs of 192 slices. Each ran on its own, 192-voxel tiles on a 96-voxel grid, each tile reading 32 voxels past the slab's edges. 22 Jobs on NVIDIA L4 GPUs took about 48 GPU hours. Tiles with no scroll in them were skipped.
Each slab's chunk files were renamed into one scan-wide zarr (slab k is z chunk k), so the stored bytes are exactly what the model wrote, stored as int8 with a scale per vector.
One 24 × 48 × 48 block from each of the 102 slabs was read back over HTTPS with no account: 5.6 million cells. No vector was off unit length by more than 1 percent, and the cells with features matched the cells with scan data exactly. This check could have failed; it shows the files are complete and line up with the scan. It says nothing about how good the features are.
One zarr v2 group. Index i covers scan voxels [i × step, (i + 1) × step) on every axis, from voxel 0.
| Array | Shape (z, y, x, numbers) | One vector per | Stored as |
|---|---|---|---|
tokens_unit | 2448 × 963 × 963 × 480 | 8 × 8 × 8 voxels | int8, chunks 24 × 12 × 12 × 480, zstd |
dense | 4896 × 1926 × 1926 × 64 | 4 × 4 × 4 voxels | int8, chunks 48 × 24 × 24 × 64, zstd |
ink | 4896 × 1926 × 1926 × 32 | 4 × 4 × 4 voxels | int8, zstd |
*_scale | float16, one per vector: vector = int8 values × scale; after that each vector has length one | ||
.zattrs | The model, its sha256, villa's commit, the tiling, the normalization statistics and the slabs present | ||
index.json | Chunk file counts (156,480 per array) and the z range | ||
ink head's numbers are not an ink map. Each 192-slice slab was embedded on its own, so values can step slightly at multiples of z 192. Only PHerc0841 is here; for another scan, run villa's embed_volume.py with the same model.The scan is Vesuvius Challenge open data under CC BY-NC 4.0, and these features are shared under the same terms. The model is scrollprize/dinovol_8um_ps192_102126 and the code is dinovol in ScrollPrize/villa (MIT); credit to their authors. The features were computed and shared by Chase Hendrick.
Chase Hendrick (2026). dinovol embeddings of the PHerc0841 scan. Hugging Face. https://huggingface.co/buckets/SharpMeow/dinovol-embeddings Computed with ScrollPrize/villa dinovol (4ea26d9c) and scrollprize/dinovol_8um_ps192_102126 (teacher_step_640000.pt) on Vesuvius Challenge open data (CC BY-NC 4.0).