Spaces:
Running on Zero
Running on Zero
How-to guides with pictures; legend and title fixes
Browse files- .gitattributes +1 -0
- app.py +76 -26
- media/howto-slices.gif +3 -0
.gitattributes
CHANGED
|
@@ -50,3 +50,4 @@ assets/PHerc0841-xz.webp filter=lfs diff=lfs merge=lfs -text
|
|
| 50 |
assets/PHerc0846A-xz.webp filter=lfs diff=lfs merge=lfs -text
|
| 51 |
assets/PHerc0846B-xz.webp filter=lfs diff=lfs merge=lfs -text
|
| 52 |
media/example-result.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 50 |
assets/PHerc0846A-xz.webp filter=lfs diff=lfs merge=lfs -text
|
| 51 |
assets/PHerc0846B-xz.webp filter=lfs diff=lfs merge=lfs -text
|
| 52 |
media/example-result.png filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
media/howto-slices.gif filter=lfs diff=lfs merge=lfs -text
|
app.py
CHANGED
|
@@ -324,7 +324,7 @@ def make_field(volume: str, z: float, y: float, x: float, size: float = 256, mod
|
|
| 324 |
with one_run(request):
|
| 325 |
r = run_field(p, progress)
|
| 326 |
c = p["centre_level0_zyx"]
|
| 327 |
-
pic = L.compose(r["pictures"], f"{p['sample']} 路 {p['size_zyx'][0]}
|
| 328 |
return pic, r["receipt"], r["zip"]
|
| 329 |
|
| 330 |
|
|
@@ -346,29 +346,74 @@ field. Each will be marked here with what it was tested on.
|
|
| 346 |
"""
|
| 347 |
|
| 348 |
HOW_TO = """\
|
| 349 |
-
#
|
|
|
|
|
|
|
|
|
|
| 350 |
|
| 351 |
-
|
| 352 |
-
2. Press **Make the field**. The Space reads about 450 MB of CT from the open data, then runs villa's model
|
| 353 |
-
on the GPU for about half a minute.
|
| 354 |
-
3. Look at the three rows: the middle slice of your box along z, y and x. Each row shows the CT, then **cos**
|
| 355 |
-
(bright and dark bands, one pair per papyrus layer), **grad_mag** (how sharply cos changes) and the
|
| 356 |
-
**normal** as colour (red for x, green for y, blue for z: which way the sheet faces).
|
| 357 |
-
4. Read the check under the picture. The same box should agree with the open data's own field far better than
|
| 358 |
-
the control, which is the published field read a few layers away.
|
| 359 |
|
| 360 |
-
|
|
|
|
|
|
|
|
|
|
| 361 |
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
|
| 368 |
Boxes are whole 256-voxel blocks of the scan, because villa writes the field in 64-voxel chunks of its quarter
|
| 369 |
-
grid
|
|
|
|
| 370 |
|
| 371 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
|
| 373 |
```bash
|
| 374 |
unzip PHerc0139-*.zip -d box && cd box
|
|
@@ -376,17 +421,17 @@ python - <<'EOF'
|
|
| 376 |
import numpy as np, json
|
| 377 |
f = np.load("field.npz"); r = json.load(open("receipt.json"))
|
| 378 |
print({k: f[k].shape for k in f}, r["boxes"])
|
|
|
|
| 379 |
EOF
|
| 380 |
```
|
| 381 |
|
| 382 |
-
|
| 383 |
-
Its arrays have the whole scan's shape in level-0 coordinates, with only your box written.
|
| 384 |
-
|
| 385 |
-
### 4. Call it from Python or an agent
|
| 386 |
|
| 387 |
-
See **Agents and API**
|
|
|
|
|
|
|
| 388 |
|
| 389 |
-
|
| 390 |
|
| 391 |
The Space is for boxes. For a whole scan, run villa's lasagna on a GPU of your own or a cloud job:
|
| 392 |
|
|
@@ -400,11 +445,11 @@ python scripts/bootstrap_venv.py --venv .venv
|
|
| 400 |
Measured on an NVIDIA L4 (25 Oct 2026): about 2 million voxels a second end to end, so about ten minutes per
|
| 401 |
cubic centimetre at 9.4 um. It fits in 5.6 GB of GPU memory at its default tile, 2.3 GB at `--tile-size 128`.
|
| 402 |
|
| 403 |
-
|
| 404 |
|
| 405 |
| What you see | What to do |
|
| 406 |
| --- | --- |
|
| 407 |
-
| "ZeroGPU refused this run" | Your
|
| 408 |
| "the field is empty" | The box is outside the scroll, where the masked CT is all zero. Move the point toward the papyrus. |
|
| 409 |
| "the point is outside the scan" | The point is past the scan's level-0 shape; check you used level-0 voxels, not a coarser level. |
|
| 410 |
| "not a volume in the open data" | Use the 14-digit volume id (list it with `list_scans`). |
|
|
@@ -454,6 +499,11 @@ CSS = """
|
|
| 454 |
.lf-credit { text-align:center; font-size:13px; color: var(--body-text-color-subdued); padding:18px 0 6px; }
|
| 455 |
.lf-credit a { color: var(--lf-accent) !important; padding:0 !important; margin:0 !important; }
|
| 456 |
.lf-howto img { border-radius:10px; border:1px solid var(--border-color-primary); max-width:100%; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 457 |
@media (max-width: 640px) {
|
| 458 |
.lf-head h1 { font-size:24px; }
|
| 459 |
.lf-head span { display:none; }
|
|
|
|
| 324 |
with one_run(request):
|
| 325 |
r = run_field(p, progress)
|
| 326 |
c = p["centre_level0_zyx"]
|
| 327 |
+
pic = L.compose(r["pictures"], f"{p['sample']} 路 {p['size_zyx'][0]}-voxel box at z {c[0]}, y {c[1]}, x {c[2]}")
|
| 328 |
return pic, r["receipt"], r["zip"]
|
| 329 |
|
| 330 |
|
|
|
|
| 346 |
"""
|
| 347 |
|
| 348 |
HOW_TO = """\
|
| 349 |
+
<p class="lf-toc">Guides: <a href="#what">What a lasagna field is</a> 路 <a href="#first">Make your first field</a> 路
|
| 350 |
+
<a href="#read">Read the result</a> 路 <a href="#point">Pick your own point</a> 路 <a href="#download">Use the
|
| 351 |
+
download</a> 路 <a href="#agents">Call it from code or an agent</a> 路 <a href="#yourself">Run it yourself</a> 路
|
| 352 |
+
<a href="#wrong">When something goes wrong</a></p>
|
| 353 |
|
| 354 |
+
<h3 id="what">What a lasagna field is</h3>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
|
| 356 |
+
A scroll's CT shows papyrus layers rolled and squashed together. villa's lasagna model turns the CT into a smooth
|
| 357 |
+
field that surface tools can follow: **cos** goes once from bright to dark and back per papyrus layer, so counting
|
| 358 |
+
bands counts layers; **grad_mag** says how sharply cos changes (how tightly the layers are packed); the **normal**
|
| 359 |
+
says which way the sheet faces at each point. villa's lasagna surface fit and the spiral fit read these channels.
|
| 360 |
|
| 361 |
+

|
| 362 |
+
|
| 363 |
+
*PHerc0139 (not on any prize list), one box about 2.5 mm a side: the CT, cos and the normal as colour, stepping
|
| 364 |
+
through z. Model output: geometry, not a reading of any text.*
|
| 365 |
+
|
| 366 |
+
<h3 id="first">1. Make your first field (one minute)</h3>
|
| 367 |
+
|
| 368 |
+
1. Open **Make a field**. It starts on an example point in PHerc0139; more examples are in the first list.
|
| 369 |
+
2. Press **Make the field**. The Space reads about 450 MB of CT from the open data, then runs villa's model on the
|
| 370 |
+
GPU for about 20 seconds. The first run after the Space wakes up also loads the model (about a minute).
|
| 371 |
+
3. The picture, the check and the download appear on the right.
|
| 372 |
+
|
| 373 |
+

|
| 374 |
+
|
| 375 |
+
<h3 id="read">2. Read the result</h3>
|
| 376 |
+
|
| 377 |
+

|
| 378 |
+
|
| 379 |
+
- **Rows** are the middle slice of your box along z, y and x. **Columns** are the CT, cos, grad_mag and the
|
| 380 |
+
normal as colour (red for x, green for y, blue for z).
|
| 381 |
+
- **cos** should run along the papyrus in the CT, one bright and one dark band per layer. Where layers touch or
|
| 382 |
+
the CT is unclear, the bands bend or merge: that is where surface tracing is hard too.
|
| 383 |
+
- **The check** compares your field with the published one for the same box, and with a **control**: the same
|
| 384 |
+
published field read 48 voxels away. A correct run gives cos r = 1.0 and grad_mag r = 1.0 for the same box and
|
| 385 |
+
near 0 for the control.
|
| 386 |
+
- **Normals** are compared where the layer signal is strong; where it is weak the normal is barely defined. On the
|
| 387 |
+
open data's fields they can differ in parts of a box, because villa changed how lasagna estimates normals after
|
| 388 |
+
those fields were made (April 2026, by their run names). This Space runs the later code, the same as its ten
|
| 389 |
+
whole-scan fields; on PHerc0175A it matched that field on 99.9% of voxels, with a median normal angle of 0掳.
|
| 390 |
+
|
| 391 |
+

|
| 392 |
+
|
| 393 |
+
<h3 id="point">3. Pick your own point</h3>
|
| 394 |
+
|
| 395 |
+
1. Find a point inside the papyrus in the [Vesuvius Challenge data browser](https://scrollprize.org/data_browser/),
|
| 396 |
+
VC3D or neuroglancer, in full-resolution (level 0) voxels: z, then y, then x.
|
| 397 |
+
2. Choose the same scan in **Scan**: type part of the scroll name or the 14-digit volume id to filter the list.
|
| 398 |
+
3. Type the point. The line under the button says the box it will make, how much CT it reads, how many model
|
| 399 |
+
tiles it runs and how much GPU time it asks.
|
| 400 |
+
4. Choose **256** (about 2.5 mm a side at 9.4 um) or **512** (eight times the volume, about a minute of GPU).
|
| 401 |
+
|
| 402 |
+

|
| 403 |
|
| 404 |
Boxes are whole 256-voxel blocks of the scan, because villa writes the field in 64-voxel chunks of its quarter
|
| 405 |
+
grid and puts its tiles on one lattice over the whole scan: that is why a box's field is the whole-scan field
|
| 406 |
+
there. The box is the block nearest your point, so your point is inside it but not always at its centre.
|
| 407 |
|
| 408 |
+
<h3 id="download">4. Use the download</h3>
|
| 409 |
+
|
| 410 |
+
The zip holds three things:
|
| 411 |
+
|
| 412 |
+
| Item | What it is | Open it with |
|
| 413 |
+
| --- | --- | --- |
|
| 414 |
+
| `villa/` | villa's own output folder: `*.lasagna.json` and four OME-Zarr channels, in the scan's coordinates with only your box written | lasagna's fit, as you would a published run |
|
| 415 |
+
| `field.npz` | the box's four arrays (cos at half, the others at a quarter of the input level's grid) | numpy |
|
| 416 |
+
| `receipt.json` | the plan, villa commit, checkpoint hash, timings and the check | any JSON reader |
|
| 417 |
|
| 418 |
```bash
|
| 419 |
unzip PHerc0139-*.zip -d box && cd box
|
|
|
|
| 421 |
import numpy as np, json
|
| 422 |
f = np.load("field.npz"); r = json.load(open("receipt.json"))
|
| 423 |
print({k: f[k].shape for k in f}, r["boxes"])
|
| 424 |
+
nx, ny = (f["nx"] - 128) / 127, (f["ny"] - 128) / 127 # normals are stored as v x 127 + 128
|
| 425 |
EOF
|
| 426 |
```
|
| 427 |
|
| 428 |
+
<h3 id="agents">5. Call it from code or an agent</h3>
|
|
|
|
|
|
|
|
|
|
| 429 |
|
| 430 |
+
The page is also an API and an MCP server. See **Agents and API** for the calls, a Python example and the MCP
|
| 431 |
+
address. An agent can list the scans, plan a box without running it, then make it, and read the check from the
|
| 432 |
+
receipt. Each field uses the caller's own ZeroGPU time.
|
| 433 |
|
| 434 |
+
<h3 id="yourself">6. Run it yourself, for big regions or whole scans</h3>
|
| 435 |
|
| 436 |
The Space is for boxes. For a whole scan, run villa's lasagna on a GPU of your own or a cloud job:
|
| 437 |
|
|
|
|
| 445 |
Measured on an NVIDIA L4 (25 Oct 2026): about 2 million voxels a second end to end, so about ten minutes per
|
| 446 |
cubic centimetre at 9.4 um. It fits in 5.6 GB of GPU memory at its default tile, 2.3 GB at `--tile-size 128`.
|
| 447 |
|
| 448 |
+
<h3 id="wrong">When something goes wrong</h3>
|
| 449 |
|
| 450 |
| What you see | What to do |
|
| 451 |
| --- | --- |
|
| 452 |
+
| "ZeroGPU refused this run" | Your ZeroGPU time or runs are used up; visitors who are not signed in get very little. Sign in to Hugging Face for more, or come back tomorrow. |
|
| 453 |
| "the field is empty" | The box is outside the scroll, where the masked CT is all zero. Move the point toward the papyrus. |
|
| 454 |
| "the point is outside the scan" | The point is past the scan's level-0 shape; check you used level-0 voxels, not a coarser level. |
|
| 455 |
| "not a volume in the open data" | Use the 14-digit volume id (list it with `list_scans`). |
|
|
|
|
| 499 |
.lf-credit { text-align:center; font-size:13px; color: var(--body-text-color-subdued); padding:18px 0 6px; }
|
| 500 |
.lf-credit a { color: var(--lf-accent) !important; padding:0 !important; margin:0 !important; }
|
| 501 |
.lf-howto img { border-radius:10px; border:1px solid var(--border-color-primary); max-width:100%; }
|
| 502 |
+
.lf-howto { max-width: 920px; }
|
| 503 |
+
.lf-howto h3 { scroll-margin-top: 16px; margin-top: 1.6em !important; }
|
| 504 |
+
.lf-howto em { color: var(--body-text-color-subdued); font-size: 13.5px; }
|
| 505 |
+
.lf-toc { font-size: 14px; line-height: 1.9; padding: 10px 14px; border-radius: 10px;
|
| 506 |
+
background: var(--block-background-fill); border: 1px solid var(--border-color-primary); }
|
| 507 |
@media (max-width: 640px) {
|
| 508 |
.lf-head h1 { font-size:24px; }
|
| 509 |
.lf-head span { display:none; }
|
media/howto-slices.gif
ADDED
|
Git LFS Details
|