Open one wheel step farther out
Browse files- LICENSE +25 -202
- README.md +7 -55
- app.js +0 -0
- data/gpu.json +12 -353
- index.html +27 -175
- og.jpg +2 -2
- style.css +0 -117
- tools/build.py +23 -120
- tools/fetch.py +11 -30
- tools/gpu_layer.py +5 -5
LICENSE
CHANGED
|
@@ -1,202 +1,25 @@
|
|
| 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 |
-
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
-
including but not limited to software source code, documentation
|
| 29 |
-
source, and configuration files.
|
| 30 |
-
|
| 31 |
-
"Object" form shall mean any form resulting from mechanical
|
| 32 |
-
transformation or translation of a Source form, including but
|
| 33 |
-
not limited to compiled object code, generated documentation,
|
| 34 |
-
and conversions to other media types.
|
| 35 |
-
|
| 36 |
-
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
-
Object form, made available under the License, as indicated by a
|
| 38 |
-
copyright notice that is included in or attached to the work
|
| 39 |
-
(an example is provided in the Appendix below).
|
| 40 |
-
|
| 41 |
-
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
-
form, that is based on (or derived from) the Work and for which the
|
| 43 |
-
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
-
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
-
of this License, Derivative Works shall not include works that remain
|
| 46 |
-
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
-
the Work and Derivative Works thereof.
|
| 48 |
-
|
| 49 |
-
"Contribution" shall mean any work of authorship, including
|
| 50 |
-
the original version of the Work and any modifications or additions
|
| 51 |
-
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
-
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
-
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
-
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
-
means any form of electronic, verbal, or written communication sent
|
| 56 |
-
to the Licensor or its representatives, including but not limited to
|
| 57 |
-
communication on electronic mailing lists, source code control systems,
|
| 58 |
-
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
-
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
-
excluding communication that is conspicuously marked or otherwise
|
| 61 |
-
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
-
|
| 63 |
-
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
-
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
-
subsequently incorporated within the Work.
|
| 66 |
-
|
| 67 |
-
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
-
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
-
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
-
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
-
Work and such Derivative Works in Source or Object form.
|
| 73 |
-
|
| 74 |
-
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
-
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
-
(except as stated in this section) patent license to make, have made,
|
| 78 |
-
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
-
where such license applies only to those patent claims licensable
|
| 80 |
-
by such Contributor that are necessarily infringed by their
|
| 81 |
-
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
-
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
-
institute patent litigation against any entity (including a
|
| 84 |
-
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
-
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
-
or contributory patent infringement, then any patent licenses
|
| 87 |
-
granted to You under this License for that Work shall terminate
|
| 88 |
-
as of the date such litigation is filed.
|
| 89 |
-
|
| 90 |
-
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
-
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
-
modifications, and in Source or Object form, provided that You
|
| 93 |
-
meet the following conditions:
|
| 94 |
-
|
| 95 |
-
(a) You must give any other recipients of the Work or
|
| 96 |
-
Derivative Works a copy of this License; and
|
| 97 |
-
|
| 98 |
-
(b) You must cause any modified files to carry prominent notices
|
| 99 |
-
stating that You changed the files; and
|
| 100 |
-
|
| 101 |
-
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
-
that You distribute, all copyright, patent, trademark, and
|
| 103 |
-
attribution notices from the Source form of the Work,
|
| 104 |
-
excluding those notices that do not pertain to any part of
|
| 105 |
-
the Derivative Works; and
|
| 106 |
-
|
| 107 |
-
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
-
distribution, then any Derivative Works that You distribute must
|
| 109 |
-
include a readable copy of the attribution notices contained
|
| 110 |
-
within such NOTICE file, excluding those notices that do not
|
| 111 |
-
pertain to any part of the Derivative Works, in at least one
|
| 112 |
-
of the following places: within a NOTICE text file distributed
|
| 113 |
-
as part of the Derivative Works; within the Source form or
|
| 114 |
-
documentation, if provided along with the Derivative Works; or,
|
| 115 |
-
within a display generated by the Derivative Works, if and
|
| 116 |
-
wherever such third-party notices normally appear. The contents
|
| 117 |
-
of the NOTICE file are for informational purposes only and
|
| 118 |
-
do not modify the License. You may add Your own attribution
|
| 119 |
-
notices within Derivative Works that You distribute, alongside
|
| 120 |
-
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
-
that such additional attribution notices cannot be construed
|
| 122 |
-
as modifying the License.
|
| 123 |
-
|
| 124 |
-
You may add Your own copyright statement to Your modifications and
|
| 125 |
-
may provide additional or different license terms and conditions
|
| 126 |
-
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
-
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
-
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
-
the conditions stated in this License.
|
| 130 |
-
|
| 131 |
-
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
-
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
-
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
-
this License, without any additional terms or conditions.
|
| 135 |
-
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
-
the terms of any separate license agreement you may have executed
|
| 137 |
-
with Licensor regarding such Contributions.
|
| 138 |
-
|
| 139 |
-
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
-
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
-
except as required for reasonable and customary use in describing the
|
| 142 |
-
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
-
|
| 144 |
-
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
-
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
-
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
-
implied, including, without limitation, any warranties or conditions
|
| 149 |
-
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
-
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
-
appropriateness of using or redistributing the Work and assume any
|
| 152 |
-
risks associated with Your exercise of permissions under this License.
|
| 153 |
-
|
| 154 |
-
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
-
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
-
unless required by applicable law (such as deliberate and grossly
|
| 157 |
-
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
-
liable to You for damages, including any direct, indirect, special,
|
| 159 |
-
incidental, or consequential damages of any character arising as a
|
| 160 |
-
result of this License or out of the use or inability to use the
|
| 161 |
-
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
-
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
-
other commercial damages or losses), even if such Contributor
|
| 164 |
-
has been advised of the possibility of such damages.
|
| 165 |
-
|
| 166 |
-
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
-
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
-
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
-
or other liability obligations and/or rights consistent with this
|
| 170 |
-
License. However, in accepting such obligations, You may act only
|
| 171 |
-
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
-
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
-
defend, and hold each Contributor harmless for any liability
|
| 174 |
-
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
-
of your accepting any such warranty or additional liability.
|
| 176 |
-
|
| 177 |
-
END OF TERMS AND CONDITIONS
|
| 178 |
-
|
| 179 |
-
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
-
|
| 181 |
-
To apply the Apache License to your work, attach the following
|
| 182 |
-
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
-
replaced with your own identifying information. (Don't include
|
| 184 |
-
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
-
comment syntax for the file format. We also recommend that a
|
| 186 |
-
file or class name and description of purpose be included on the
|
| 187 |
-
same "printed page" as the copyright notice for easier
|
| 188 |
-
identification within third-party archives.
|
| 189 |
-
|
| 190 |
-
Copyright [yyyy] [name of copyright owner]
|
| 191 |
-
|
| 192 |
-
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
-
you may not use this file except in compliance with the License.
|
| 194 |
-
You may obtain a copy of the License at
|
| 195 |
-
|
| 196 |
-
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
-
|
| 198 |
-
Unless required by applicable law or agreed to in writing, software
|
| 199 |
-
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
-
See the License for the specific language governing permissions and
|
| 202 |
-
limitations under the License.
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 SharpMeow
|
| 4 |
+
|
| 5 |
+
This licence covers the code in this repository: index.html, style.css, app.js and tools/.
|
| 6 |
+
The files in data/ and og.jpg are derived from Vesuvius Challenge open data and are licensed
|
| 7 |
+
CC BY-NC 4.0 (https://creativecommons.org/licenses/by-nc/4.0/); see README.md.
|
| 8 |
+
|
| 9 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 10 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 11 |
+
in the Software without restriction, including without limitation the rights
|
| 12 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 13 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 14 |
+
furnished to do so, subject to the following conditions:
|
| 15 |
+
|
| 16 |
+
The above copyright notice and this permission notice shall be included in all
|
| 17 |
+
copies or substantial portions of the Software.
|
| 18 |
+
|
| 19 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 20 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 21 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 22 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 23 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 24 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 25 |
+
SOFTWARE.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -6,7 +6,7 @@ colorTo: gray
|
|
| 6 |
sdk: static
|
| 7 |
app_file: index.html
|
| 8 |
pinned: false
|
| 9 |
-
license:
|
| 10 |
short_description: Unroll a carbonized Herculaneum scroll in your browser
|
| 11 |
thumbnail: https://huggingface.co/spaces/SharpMeow/unroll/resolve/main/og.jpg
|
| 12 |
tags:
|
|
@@ -21,73 +21,25 @@ models:
|
|
| 21 |
|
| 22 |
# Unroll
|
| 23 |
|
| 24 |
-
PHerc. 139 is a papyrus roll carbonized when Vesuvius buried Herculaneum in 79 CE. It cannot be opened, so it was scanned with X-rays at the ESRF synchrotron. This page rebuilds 37 of its windings in 3D from the public Vesuvius Challenge meshes and lets you unroll them in the browser
|
| 25 |
|
| 26 |
- **Shapes**: the 9.362 µm tifxyz meshes of the 37 traced windings (`w023` to `w059`), decimated to 579,450 vertices.
|
| 27 |
- **X-ray layer**: the CT intensity on each surface, from the public 9.362 µm surface volumes.
|
| 28 |
-
- **
|
| 29 |
-
- **
|
| 30 |
|
| 31 |
Unrolling lays the windings end to end, outermost first. Each traced winding runs a little past one full turn, so roughly a tenth of the 2.8 m strip repeats surface from its neighbour.
|
| 32 |
|
| 33 |
-
## Release
|
| 34 |
-
|
| 35 |
-
Unroll is a released tool: an interactive 3D viewer that virtually unrolls Herculaneum scroll scans with ink layers, an Ink Bench for comparing ink models, and an agent API.
|
| 36 |
-
|
| 37 |
-
- Status: released, v1.0.0.
|
| 38 |
-
- Version: 1.1.0, which adds Install app (`version.json`, [CHANGELOG.md](CHANGELOG.md)). The page shows it in the About panel and `window.unroll.version()` returns it.
|
| 39 |
-
- Licence: Apache 2.0 for all code (`LICENSE`, `NOTICE`). The scan data in `data/` and `og.jpg` are CC BY-NC 4.0. three.js keeps its own licence (see `NOTICE`).
|
| 40 |
-
- Install app: open [the app page](https://sharpmeow-unroll.static.hf.space/) and press **Install app** (View panel or Settings), or use the browser's install icon. It works offline after the first visit; see [Install app](#install-app).
|
| 41 |
-
- Listed in the public repository: [ChaseHendrick/Scrolls](https://github.com/ChaseHendrick/Scrolls#released-tools).
|
| 42 |
-
|
| 43 |
-
## Install app
|
| 44 |
-
|
| 45 |
-
Unroll is a progressive web app. Install it and it opens in its own window and works with no network connection.
|
| 46 |
-
|
| 47 |
-
1. Open [sharpmeow-unroll.static.hf.space](https://sharpmeow-unroll.static.hf.space/) (the Space page embeds the app in a frame, where browsers do not offer installation; the **Install app** button there opens this page for you).
|
| 48 |
-
2. Press **Install app** in the View panel or in Settings. In Chrome and Edge you can also use the install icon in the address bar. In Safari on iPhone and iPad: Share, then Add to Home Screen. In Safari on macOS: File, then Add to Dock.
|
| 49 |
-
3. The page, three.js and the default scroll are kept on your device. Other scrolls and close-ups are kept after you view them once.
|
| 50 |
-
|
| 51 |
-
Agents can call `window.unrollView.action('install')` and read `window.unrollView.pwa.state()`. The service worker (`sw.js`) is registered as `sw.js?v=<version>-<sha>` from `version.json`, so each stamped build uses fresh caches (`unroll-shell-<tag>`, `unroll-data-<tag>`, `unroll-runtime-<tag>`) and deletes the old ones.
|
| 52 |
-
|
| 53 |
-
## Scrolls, windings and layers
|
| 54 |
-
|
| 55 |
-
The page is data driven. `scrolls/index.json` lists the scrolls and each one has a manifest (`scrolls/PHerc0139/manifest.json`) naming its windings, meshes, ink layers, sources, resolutions, credits and data terms. With more than one scroll a picker appears. `node tools/validate_scroll.mjs` checks the registry and every manifest with the same validator the page uses. See [docs/ADDING_A_SCROLL.md](docs/ADDING_A_SCROLL.md) to add one; `tools/precompute_template.py` is a starting point for building a package.
|
| 56 |
-
|
| 57 |
-
The atlases ship as WebP (`data/atlas_*.webp`), with the JPGs kept as a fallback.
|
| 58 |
-
|
| 59 |
-
## Open your own
|
| 60 |
-
|
| 61 |
-
Press **+** to unroll your own scroll: a zip, a folder, or a manifest with OBJ or PLY meshes (with UVs) and PNG, JPG or TIFF layers, or the URL of one (the server must allow CORS). Everything is read in your browser and kept in its storage; nothing is uploaded. The sheet has a downloadable synthetic example package. Details and limits are in [docs/ADDING_A_SCROLL.md](docs/ADDING_A_SCROLL.md#open-your-own).
|
| 62 |
-
|
| 63 |
-
## Destroy mode
|
| 64 |
-
|
| 65 |
-
Just for fun, not a real unrolling. Press **Destroy** (T) to tear (X), crush (C), burn (B) or soak (W) the scroll at any unroll amount; the damage follows the sheet as it rolls and unrolls. Smoke, ash and burnt flakes rise from each ember where it is on the sheet right now. Debris lands on a soft floor that sits just below the scroll as currently drawn, rolled or unrolled, and fades soon after landing.
|
| 66 |
-
|
| 67 |
-
**Crumble** (K, or `unrollView.crumble()` in the console) breaks the whole scroll up from its edges inward over about four seconds; the pieces are pulled in toward the middle and fall into a heap of dust on the floor. **Reset** restores the scroll and clears all damage, debris and the heap. Particle and fragment counts stay capped (about 1,800 particles, 600 fragments and 2,600 heap grains on desktop, less on phones).
|
| 68 |
-
|
| 69 |
-
Why the tools behave as they do, with sources: [docs/REALISM.md](docs/REALISM.md).
|
| 70 |
-
|
| 71 |
-
For testing, `unrollView.damageStats()` reports the counts, the floor height, `stillAirMax` (longest any debris hung still above the floor, should stay 0) and `stillMax`.
|
| 72 |
-
|
| 73 |
## Rebuilding the data
|
| 74 |
|
| 75 |
From the Space's root, with Python 3, `numpy`, `scipy`, `tifffile`, `Pillow` and `curl`:
|
| 76 |
|
| 77 |
1. `python tools/fetch.py` lists the public bucket and downloads the meshes and ink previews into `raw/` (about 110 MB).
|
| 78 |
-
2. `python tools/build.py` writes `data/scroll.bin`, `data/scroll.json`, the atlases and `data/hi/`. It reads the X-ray layer straight from the bucket.
|
| 79 |
-
3. `python tools/gpu_layer.py` rebuilds the
|
| 80 |
|
| 81 |
## Licence and credits
|
| 82 |
|
| 83 |
Data: 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.* Vesuvius Challenge. [Data browser for PHerc. 139](https://scrollprize.org/data_browser/PHerc0139). PHerc. 139 is held by the Biblioteca Nazionale di Napoli.
|
| 84 |
|
| 85 |
-
`data/` and `og.jpg` are derived from that data and are licensed [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/). The code (`index.html`, `style.css`, `app.js`
|
| 86 |
-
|
| 87 |
-
## Ink Bench
|
| 88 |
-
|
| 89 |
-
Score your own ink prediction for any winding against the published ink, in the browser (nothing is uploaded): automatic UV alignment, side by side, swipe and difference views, Pearson r, IoU, Dice and a precision and recall curve (agreement with another model's output, not ground truth), letter boxes marked "candidate for expert review", and a one-click reproducible bundle. `tools/inkbench.py` gives identical results in Python and on the command line. See [docs/BENCH.md](docs/BENCH.md) and the open bundle format [docs/BENCH_FORMAT.md](docs/BENCH_FORMAT.md). The Bench code and format are Apache-2.0 (`LICENSE`, `NOTICE`, by Chase Hendrick), like the rest of the code; the data stays CC BY-NC 4.0.
|
| 90 |
-
|
| 91 |
-
### Researcher takeaway
|
| 92 |
-
|
| 93 |
-
On the public ink_9um run over all 37 windings (`docs/examples/bench/`), against the published 2.4 µm ink: median AP 0.319 for ink_9um, 0.117 for raw X-ray intensity and 0.089 for uniform noise, and ink_9um beats X-ray intensity on AP on every winding. On Pearson r the medians are 0.422, 0.266 and 0.000, but X-ray intensity outranks ink_9um on `w039`, `w053`, `w054` and `w057`. So: rank ink models by AP (threshold-free and robust to brightness), always report the X-ray floor next to your model, and look at windings where the floor wins before trusting a correlation. These are agreement scores with another model's output, not ground truth.
|
|
|
|
| 6 |
sdk: static
|
| 7 |
app_file: index.html
|
| 8 |
pinned: false
|
| 9 |
+
license: cc-by-nc-4.0
|
| 10 |
short_description: Unroll a carbonized Herculaneum scroll in your browser
|
| 11 |
thumbnail: https://huggingface.co/spaces/SharpMeow/unroll/resolve/main/og.jpg
|
| 12 |
tags:
|
|
|
|
| 21 |
|
| 22 |
# Unroll
|
| 23 |
|
| 24 |
+
PHerc. 139 is a papyrus roll carbonized when Vesuvius buried Herculaneum in 79 CE. It cannot be opened, so it was scanned with X-rays at the ESRF synchrotron. This page rebuilds 37 of its windings in 3D from the public Vesuvius Challenge meshes and lets you unroll them in the browser, with the ink that neural networks found on them.
|
| 25 |
|
| 26 |
- **Shapes**: the 9.362 µm tifxyz meshes of the 37 traced windings (`w023` to `w059`), decimated to 579,450 vertices.
|
| 27 |
- **X-ray layer**: the CT intensity on each surface, from the public 9.362 µm surface volumes.
|
| 28 |
+
- **Ink layer (amber)**: the Vesuvius Challenge's public ink detection on the 2.4 µm scan. In 3D it is drawn through a local-contrast filter so letter-sized strokes stand out. The close-up view shows the published preview without filtering, downscaled to 2,400 px on its long side and re-saved as JPEG.
|
| 29 |
+
- **ZeroGPU layer (cyan)**: windings `w046` to `w051`, inked by running the public [`scrollprize/ink_9um`](https://huggingface.co/scrollprize/ink_9um) model (seed 42, step 75000) on the 9.362 µm scan. It used all 8 of a free account's daily ZeroGPU runs: 37,141 patches in 57 s of measured GPU time (ZeroGPU charged about 66 of the 300 daily GPU-seconds). The 9.362 µm scan is about four times coarser than the 2.4 µm one, so this layer is noisier. `w047` and `w050` ran with patch overlap 0.5; the other four ran with 0.25 or 0.2, and their 128 px patch grid shows as faint square tiles. Per-run records (checkpoint sha256, villa commit, requested and measured GPU seconds) are in `data/gpu/receipts/`; `data/gpu.json` is the summary the page reads.
|
| 30 |
|
| 31 |
Unrolling lays the windings end to end, outermost first. Each traced winding runs a little past one full turn, so roughly a tenth of the 2.8 m strip repeats surface from its neighbour.
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
## Rebuilding the data
|
| 34 |
|
| 35 |
From the Space's root, with Python 3, `numpy`, `scipy`, `tifffile`, `Pillow` and `curl`:
|
| 36 |
|
| 37 |
1. `python tools/fetch.py` lists the public bucket and downloads the meshes and ink previews into `raw/` (about 110 MB).
|
| 38 |
+
2. `python tools/build.py` writes `data/scroll.bin`, `data/scroll.json`, the atlases and `data/hi/`. It reads the X-ray layer straight from the bucket.
|
| 39 |
+
3. `python tools/gpu_layer.py` rebuilds the ZeroGPU layer. It needs the ink_9um output TIFFs in `gpu/wNNN/` (about 25 MB each, not shipped). Regenerate them with the model card's inference command on the surface-volume URLs in `data/gpu/receipts/`.
|
| 40 |
|
| 41 |
## Licence and credits
|
| 42 |
|
| 43 |
Data: 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.* Vesuvius Challenge. [Data browser for PHerc. 139](https://scrollprize.org/data_browser/PHerc0139). PHerc. 139 is held by the Biblioteca Nazionale di Napoli.
|
| 44 |
|
| 45 |
+
`data/` and `og.jpg` are derived from that data and are licensed [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/). The code (`index.html`, `style.css`, `app.js` and `tools/`) is MIT, see `LICENSE`. The page uses [three.js](https://threejs.org) 0.160 from jsDelivr.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.js
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/gpu.json
CHANGED
|
@@ -1,258 +1,5 @@
|
|
| 1 |
{
|
| 2 |
"windings": {
|
| 3 |
-
"w023": {
|
| 4 |
-
"tex": "data/gpu/w023.jpg",
|
| 5 |
-
"receipt": "data/gpu/receipts/w023.json",
|
| 6 |
-
"hi": "hi/w023_gpu.jpg",
|
| 7 |
-
"patches": 9227,
|
| 8 |
-
"gpuSeconds": 8.4,
|
| 9 |
-
"calls": 2,
|
| 10 |
-
"overlap": 0.5,
|
| 11 |
-
"started": "2026-10-16T03:28:07Z",
|
| 12 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 9,227 patches of 128\u00d7128 px, overlap 0.5 \u00b7 8.4 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 13 |
-
},
|
| 14 |
-
"w024": {
|
| 15 |
-
"tex": "data/gpu/w024.jpg",
|
| 16 |
-
"receipt": "data/gpu/receipts/w024.json",
|
| 17 |
-
"hi": "hi/w024_gpu.jpg",
|
| 18 |
-
"patches": 9758,
|
| 19 |
-
"gpuSeconds": 8.5,
|
| 20 |
-
"calls": 2,
|
| 21 |
-
"overlap": 0.5,
|
| 22 |
-
"started": "2026-10-16T03:28:08Z",
|
| 23 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 9,758 patches of 128\u00d7128 px, overlap 0.5 \u00b7 8.5 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 24 |
-
},
|
| 25 |
-
"w025": {
|
| 26 |
-
"tex": "data/gpu/w025.jpg",
|
| 27 |
-
"receipt": "data/gpu/receipts/w025.json",
|
| 28 |
-
"hi": "hi/w025_gpu.jpg",
|
| 29 |
-
"patches": 10316,
|
| 30 |
-
"gpuSeconds": 10.1,
|
| 31 |
-
"calls": 2,
|
| 32 |
-
"overlap": 0.5,
|
| 33 |
-
"started": "2026-10-16T03:28:21Z",
|
| 34 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 10,316 patches of 128\u00d7128 px, overlap 0.5 \u00b7 10.1 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 35 |
-
},
|
| 36 |
-
"w026": {
|
| 37 |
-
"tex": "data/gpu/w026.jpg",
|
| 38 |
-
"receipt": "data/gpu/receipts/w026.json",
|
| 39 |
-
"hi": "hi/w026_gpu.jpg",
|
| 40 |
-
"patches": 11741,
|
| 41 |
-
"gpuSeconds": 10.5,
|
| 42 |
-
"calls": 2,
|
| 43 |
-
"overlap": 0.5,
|
| 44 |
-
"started": "2026-10-16T03:28:28Z",
|
| 45 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 11,741 patches of 128\u00d7128 px, overlap 0.5 \u00b7 10.5 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 46 |
-
},
|
| 47 |
-
"w027": {
|
| 48 |
-
"tex": "data/gpu/w027.jpg",
|
| 49 |
-
"receipt": "data/gpu/receipts/w027.json",
|
| 50 |
-
"hi": "hi/w027_gpu.jpg",
|
| 51 |
-
"patches": 11328,
|
| 52 |
-
"gpuSeconds": 10.2,
|
| 53 |
-
"calls": 2,
|
| 54 |
-
"overlap": 0.5,
|
| 55 |
-
"started": "2026-10-16T03:28:12Z",
|
| 56 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 11,328 patches of 128\u00d7128 px, overlap 0.5 \u00b7 10.2 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 57 |
-
},
|
| 58 |
-
"w028": {
|
| 59 |
-
"tex": "data/gpu/w028.jpg",
|
| 60 |
-
"receipt": "data/gpu/receipts/w028.json",
|
| 61 |
-
"hi": "hi/w028_gpu.jpg",
|
| 62 |
-
"patches": 11358,
|
| 63 |
-
"gpuSeconds": 9.1,
|
| 64 |
-
"calls": 2,
|
| 65 |
-
"overlap": 0.5,
|
| 66 |
-
"started": "2026-10-16T03:28:09Z",
|
| 67 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 11,358 patches of 128\u00d7128 px, overlap 0.5 \u00b7 9.1 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 68 |
-
},
|
| 69 |
-
"w029": {
|
| 70 |
-
"tex": "data/gpu/w029.jpg",
|
| 71 |
-
"receipt": "data/gpu/receipts/w029.json",
|
| 72 |
-
"hi": "hi/w029_gpu.jpg",
|
| 73 |
-
"patches": 11356,
|
| 74 |
-
"gpuSeconds": 10.2,
|
| 75 |
-
"calls": 2,
|
| 76 |
-
"overlap": 0.5,
|
| 77 |
-
"started": "2026-10-16T03:28:12Z",
|
| 78 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 11,356 patches of 128\u00d7128 px, overlap 0.5 \u00b7 10.2 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 79 |
-
},
|
| 80 |
-
"w030": {
|
| 81 |
-
"tex": "data/gpu/w030.jpg",
|
| 82 |
-
"receipt": "data/gpu/receipts/w030.json",
|
| 83 |
-
"hi": "hi/w030_gpu.jpg",
|
| 84 |
-
"patches": 11412,
|
| 85 |
-
"gpuSeconds": 10.4,
|
| 86 |
-
"calls": 2,
|
| 87 |
-
"overlap": 0.5,
|
| 88 |
-
"started": "2026-10-16T03:28:26Z",
|
| 89 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 11,412 patches of 128\u00d7128 px, overlap 0.5 \u00b7 10.4 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 90 |
-
},
|
| 91 |
-
"w031": {
|
| 92 |
-
"tex": "data/gpu/w031.jpg",
|
| 93 |
-
"receipt": "data/gpu/receipts/w031.json",
|
| 94 |
-
"hi": "hi/w031_gpu.jpg",
|
| 95 |
-
"patches": 11436,
|
| 96 |
-
"gpuSeconds": 12.5,
|
| 97 |
-
"calls": 2,
|
| 98 |
-
"overlap": 0.5,
|
| 99 |
-
"started": "2026-10-16T03:29:25Z",
|
| 100 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 11,436 patches of 128\u00d7128 px, overlap 0.5 \u00b7 12.5 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 101 |
-
},
|
| 102 |
-
"w032": {
|
| 103 |
-
"tex": "data/gpu/w032.jpg",
|
| 104 |
-
"receipt": "data/gpu/receipts/w032.json",
|
| 105 |
-
"hi": "hi/w032_gpu.jpg",
|
| 106 |
-
"patches": 11640,
|
| 107 |
-
"gpuSeconds": 10.3,
|
| 108 |
-
"calls": 2,
|
| 109 |
-
"overlap": 0.5,
|
| 110 |
-
"started": "2026-10-16T03:29:31Z",
|
| 111 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 11,640 patches of 128\u00d7128 px, overlap 0.5 \u00b7 10.3 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 112 |
-
},
|
| 113 |
-
"w033": {
|
| 114 |
-
"tex": "data/gpu/w033.jpg",
|
| 115 |
-
"receipt": "data/gpu/receipts/w033.json",
|
| 116 |
-
"hi": "hi/w033_gpu.jpg",
|
| 117 |
-
"patches": 11803,
|
| 118 |
-
"gpuSeconds": 10.1,
|
| 119 |
-
"calls": 2,
|
| 120 |
-
"overlap": 0.5,
|
| 121 |
-
"started": "2026-10-16T03:30:17Z",
|
| 122 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 11,803 patches of 128\u00d7128 px, overlap 0.5 \u00b7 10.1 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 123 |
-
},
|
| 124 |
-
"w034": {
|
| 125 |
-
"tex": "data/gpu/w034.jpg",
|
| 126 |
-
"receipt": "data/gpu/receipts/w034.json",
|
| 127 |
-
"hi": "hi/w034_gpu.jpg",
|
| 128 |
-
"patches": 11785,
|
| 129 |
-
"gpuSeconds": 9.3,
|
| 130 |
-
"calls": 2,
|
| 131 |
-
"overlap": 0.5,
|
| 132 |
-
"started": "2026-10-16T03:29:44Z",
|
| 133 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 11,785 patches of 128\u00d7128 px, overlap 0.5 \u00b7 9.3 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 134 |
-
},
|
| 135 |
-
"w035": {
|
| 136 |
-
"tex": "data/gpu/w035.jpg",
|
| 137 |
-
"receipt": "data/gpu/receipts/w035.json",
|
| 138 |
-
"hi": "hi/w035_gpu.jpg",
|
| 139 |
-
"patches": 6456,
|
| 140 |
-
"gpuSeconds": 5.6,
|
| 141 |
-
"calls": 1,
|
| 142 |
-
"overlap": 0.5,
|
| 143 |
-
"started": "2026-10-16T03:29:27Z",
|
| 144 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 6,456 patches of 128\u00d7128 px, overlap 0.5 \u00b7 5.6 s of GPU in 1 call on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 145 |
-
},
|
| 146 |
-
"w036": {
|
| 147 |
-
"tex": "data/gpu/w036.jpg",
|
| 148 |
-
"receipt": "data/gpu/receipts/w036.json",
|
| 149 |
-
"hi": "hi/w036_gpu.jpg",
|
| 150 |
-
"patches": 8923,
|
| 151 |
-
"gpuSeconds": 8.5,
|
| 152 |
-
"calls": 2,
|
| 153 |
-
"overlap": 0.5,
|
| 154 |
-
"started": "2026-10-16T03:30:09Z",
|
| 155 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 8,923 patches of 128\u00d7128 px, overlap 0.5 \u00b7 8.5 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 156 |
-
},
|
| 157 |
-
"w037": {
|
| 158 |
-
"tex": "data/gpu/w037.jpg",
|
| 159 |
-
"receipt": "data/gpu/receipts/w037.json",
|
| 160 |
-
"hi": "hi/w037_gpu.jpg",
|
| 161 |
-
"patches": 9501,
|
| 162 |
-
"gpuSeconds": 9.5,
|
| 163 |
-
"calls": 2,
|
| 164 |
-
"overlap": 0.5,
|
| 165 |
-
"started": "2026-10-16T03:30:14Z",
|
| 166 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 9,501 patches of 128\u00d7128 px, overlap 0.5 \u00b7 9.5 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 167 |
-
},
|
| 168 |
-
"w038": {
|
| 169 |
-
"tex": "data/gpu/w038.jpg",
|
| 170 |
-
"receipt": "data/gpu/receipts/w038.json",
|
| 171 |
-
"hi": "hi/w038_gpu.jpg",
|
| 172 |
-
"patches": 9970,
|
| 173 |
-
"gpuSeconds": 9.2,
|
| 174 |
-
"calls": 2,
|
| 175 |
-
"overlap": 0.5,
|
| 176 |
-
"started": "2026-10-16T03:30:16Z",
|
| 177 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 9,970 patches of 128\u00d7128 px, overlap 0.5 \u00b7 9.2 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 178 |
-
},
|
| 179 |
-
"w039": {
|
| 180 |
-
"tex": "data/gpu/w039.jpg",
|
| 181 |
-
"receipt": "data/gpu/receipts/w039.json",
|
| 182 |
-
"hi": "hi/w039_gpu.jpg",
|
| 183 |
-
"patches": 10514,
|
| 184 |
-
"gpuSeconds": 8.7,
|
| 185 |
-
"calls": 2,
|
| 186 |
-
"overlap": 0.5,
|
| 187 |
-
"started": "2026-10-16T03:30:33Z",
|
| 188 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 10,514 patches of 128\u00d7128 px, overlap 0.5 \u00b7 8.7 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 189 |
-
},
|
| 190 |
-
"w040": {
|
| 191 |
-
"tex": "data/gpu/w040.jpg",
|
| 192 |
-
"receipt": "data/gpu/receipts/w040.json",
|
| 193 |
-
"hi": "hi/w040_gpu.jpg",
|
| 194 |
-
"patches": 11076,
|
| 195 |
-
"gpuSeconds": 9.0,
|
| 196 |
-
"calls": 2,
|
| 197 |
-
"overlap": 0.5,
|
| 198 |
-
"started": "2026-10-16T03:31:00Z",
|
| 199 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 11,076 patches of 128\u00d7128 px, overlap 0.5 \u00b7 9.0 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 200 |
-
},
|
| 201 |
-
"w041": {
|
| 202 |
-
"tex": "data/gpu/w041.jpg",
|
| 203 |
-
"receipt": "data/gpu/receipts/w041.json",
|
| 204 |
-
"hi": "hi/w041_gpu.jpg",
|
| 205 |
-
"patches": 10920,
|
| 206 |
-
"gpuSeconds": 9.4,
|
| 207 |
-
"calls": 2,
|
| 208 |
-
"overlap": 0.5,
|
| 209 |
-
"started": "2026-10-16T03:31:02Z",
|
| 210 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 10,920 patches of 128\u00d7128 px, overlap 0.5 \u00b7 9.4 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 211 |
-
},
|
| 212 |
-
"w042": {
|
| 213 |
-
"tex": "data/gpu/w042.jpg",
|
| 214 |
-
"receipt": "data/gpu/receipts/w042.json",
|
| 215 |
-
"hi": "hi/w042_gpu.jpg",
|
| 216 |
-
"patches": 10440,
|
| 217 |
-
"gpuSeconds": 8.4,
|
| 218 |
-
"calls": 2,
|
| 219 |
-
"overlap": 0.5,
|
| 220 |
-
"started": "2026-10-16T03:31:11Z",
|
| 221 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 10,440 patches of 128\u00d7128 px, overlap 0.5 \u00b7 8.4 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 222 |
-
},
|
| 223 |
-
"w043": {
|
| 224 |
-
"tex": "data/gpu/w043.jpg",
|
| 225 |
-
"receipt": "data/gpu/receipts/w043.json",
|
| 226 |
-
"hi": "hi/w043_gpu.jpg",
|
| 227 |
-
"patches": 11006,
|
| 228 |
-
"gpuSeconds": 9.9,
|
| 229 |
-
"calls": 2,
|
| 230 |
-
"overlap": 0.5,
|
| 231 |
-
"started": "2026-10-16T03:31:44Z",
|
| 232 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 11,006 patches of 128\u00d7128 px, overlap 0.5 \u00b7 9.9 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 233 |
-
},
|
| 234 |
-
"w044": {
|
| 235 |
-
"tex": "data/gpu/w044.jpg",
|
| 236 |
-
"receipt": "data/gpu/receipts/w044.json",
|
| 237 |
-
"hi": "hi/w044_gpu.jpg",
|
| 238 |
-
"patches": 11022,
|
| 239 |
-
"gpuSeconds": 11.4,
|
| 240 |
-
"calls": 2,
|
| 241 |
-
"overlap": 0.5,
|
| 242 |
-
"started": "2026-10-16T03:31:54Z",
|
| 243 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 11,022 patches of 128\u00d7128 px, overlap 0.5 \u00b7 11.4 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 244 |
-
},
|
| 245 |
-
"w045": {
|
| 246 |
-
"tex": "data/gpu/w045.jpg",
|
| 247 |
-
"receipt": "data/gpu/receipts/w045.json",
|
| 248 |
-
"hi": "hi/w045_gpu.jpg",
|
| 249 |
-
"patches": 10950,
|
| 250 |
-
"gpuSeconds": 9.7,
|
| 251 |
-
"calls": 2,
|
| 252 |
-
"overlap": 0.5,
|
| 253 |
-
"started": "2026-10-16T03:31:58Z",
|
| 254 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 10,950 patches of 128\u00d7128 px, overlap 0.5 \u00b7 9.7 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 255 |
-
},
|
| 256 |
"w046": {
|
| 257 |
"tex": "data/gpu/w046.jpg",
|
| 258 |
"receipt": "data/gpu/receipts/w046.json",
|
|
@@ -262,7 +9,7 @@
|
|
| 262 |
"calls": 1,
|
| 263 |
"overlap": 0.25,
|
| 264 |
"started": "2026-10-16T00:22:15Z",
|
| 265 |
-
"meta": "
|
| 266 |
},
|
| 267 |
"w047": {
|
| 268 |
"tex": "data/gpu/w047.jpg",
|
|
@@ -273,7 +20,7 @@
|
|
| 273 |
"calls": 3,
|
| 274 |
"overlap": 0.5,
|
| 275 |
"started": "2026-10-15T20:09:32Z",
|
| 276 |
-
"meta": "
|
| 277 |
},
|
| 278 |
"w048": {
|
| 279 |
"tex": "data/gpu/w048.jpg",
|
|
@@ -284,7 +31,7 @@
|
|
| 284 |
"calls": 1,
|
| 285 |
"overlap": 0.25,
|
| 286 |
"started": "2026-10-16T00:23:44Z",
|
| 287 |
-
"meta": "
|
| 288 |
},
|
| 289 |
"w049": {
|
| 290 |
"tex": "data/gpu/w049.jpg",
|
|
@@ -295,7 +42,7 @@
|
|
| 295 |
"calls": 1,
|
| 296 |
"overlap": 0.25,
|
| 297 |
"started": "2026-10-16T00:25:23Z",
|
| 298 |
-
"meta": "
|
| 299 |
},
|
| 300 |
"w050": {
|
| 301 |
"tex": "data/gpu/w050.jpg",
|
|
@@ -306,7 +53,7 @@
|
|
| 306 |
"calls": 1,
|
| 307 |
"overlap": 0.5,
|
| 308 |
"started": "2026-10-16T00:26:27Z",
|
| 309 |
-
"meta": "
|
| 310 |
},
|
| 311 |
"w051": {
|
| 312 |
"tex": "data/gpu/w051.jpg",
|
|
@@ -317,103 +64,15 @@
|
|
| 317 |
"calls": 1,
|
| 318 |
"overlap": 0.2,
|
| 319 |
"started": "2026-10-16T00:28:35Z",
|
| 320 |
-
"meta": "
|
| 321 |
-
},
|
| 322 |
-
"w052": {
|
| 323 |
-
"tex": "data/gpu/w052.jpg",
|
| 324 |
-
"receipt": "data/gpu/receipts/w052.json",
|
| 325 |
-
"hi": "hi/w052_gpu.jpg",
|
| 326 |
-
"patches": 16763,
|
| 327 |
-
"gpuSeconds": 13.6,
|
| 328 |
-
"calls": 3,
|
| 329 |
-
"overlap": 0.5,
|
| 330 |
-
"started": "2026-10-16T03:32:06Z",
|
| 331 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 16,763 patches of 128\u00d7128 px, overlap 0.5 \u00b7 13.6 s of GPU in 3 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 332 |
-
},
|
| 333 |
-
"w053": {
|
| 334 |
-
"tex": "data/gpu/w053.jpg",
|
| 335 |
-
"receipt": "data/gpu/receipts/w053.json",
|
| 336 |
-
"hi": "hi/w053_gpu.jpg",
|
| 337 |
-
"patches": 16590,
|
| 338 |
-
"gpuSeconds": 20.9,
|
| 339 |
-
"calls": 3,
|
| 340 |
-
"overlap": 0.5,
|
| 341 |
-
"started": "2026-10-16T03:32:19Z",
|
| 342 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 16,590 patches of 128\u00d7128 px, overlap 0.5 \u00b7 20.9 s of GPU in 3 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 343 |
-
},
|
| 344 |
-
"w054": {
|
| 345 |
-
"tex": "data/gpu/w054.jpg",
|
| 346 |
-
"receipt": "data/gpu/receipts/w054.json",
|
| 347 |
-
"hi": "hi/w054_gpu.jpg",
|
| 348 |
-
"patches": 18048,
|
| 349 |
-
"gpuSeconds": 14.3,
|
| 350 |
-
"calls": 3,
|
| 351 |
-
"overlap": 0.5,
|
| 352 |
-
"started": "2026-10-16T03:32:53Z",
|
| 353 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 18,048 patches of 128\u00d7128 px, overlap 0.5 \u00b7 14.3 s of GPU in 3 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 354 |
-
},
|
| 355 |
-
"w055": {
|
| 356 |
-
"tex": "data/gpu/w055.jpg",
|
| 357 |
-
"receipt": "data/gpu/receipts/w055.json",
|
| 358 |
-
"hi": "hi/w055_gpu.jpg",
|
| 359 |
-
"patches": 17156,
|
| 360 |
-
"gpuSeconds": 13.1,
|
| 361 |
-
"calls": 3,
|
| 362 |
-
"overlap": 0.5,
|
| 363 |
-
"started": "2026-10-16T03:32:47Z",
|
| 364 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 17,156 patches of 128\u00d7128 px, overlap 0.5 \u00b7 13.1 s of GPU in 3 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 365 |
-
},
|
| 366 |
-
"w056": {
|
| 367 |
-
"tex": "data/gpu/w056.jpg",
|
| 368 |
-
"receipt": "data/gpu/receipts/w056.json",
|
| 369 |
-
"hi": "hi/w056_gpu.jpg",
|
| 370 |
-
"patches": 15243,
|
| 371 |
-
"gpuSeconds": 11.9,
|
| 372 |
-
"calls": 2,
|
| 373 |
-
"overlap": 0.5,
|
| 374 |
-
"started": "2026-10-16T03:32:46Z",
|
| 375 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 15,243 patches of 128\u00d7128 px, overlap 0.5 \u00b7 11.9 s of GPU in 2 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 376 |
-
},
|
| 377 |
-
"w057": {
|
| 378 |
-
"tex": "data/gpu/w057.jpg",
|
| 379 |
-
"receipt": "data/gpu/receipts/w057.json",
|
| 380 |
-
"hi": "hi/w057_gpu.jpg",
|
| 381 |
-
"patches": 16223,
|
| 382 |
-
"gpuSeconds": 14.8,
|
| 383 |
-
"calls": 3,
|
| 384 |
-
"overlap": 0.5,
|
| 385 |
-
"started": "2026-10-16T03:33:53Z",
|
| 386 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 16,223 patches of 128\u00d7128 px, overlap 0.5 \u00b7 14.8 s of GPU in 3 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 387 |
-
},
|
| 388 |
-
"w058": {
|
| 389 |
-
"tex": "data/gpu/w058.jpg",
|
| 390 |
-
"receipt": "data/gpu/receipts/w058.json",
|
| 391 |
-
"hi": "hi/w058_gpu.jpg",
|
| 392 |
-
"patches": 15924,
|
| 393 |
-
"gpuSeconds": 15.0,
|
| 394 |
-
"calls": 3,
|
| 395 |
-
"overlap": 0.5,
|
| 396 |
-
"started": "2026-10-16T03:34:22Z",
|
| 397 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 15,924 patches of 128\u00d7128 px, overlap 0.5 \u00b7 15.0 s of GPU in 3 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 398 |
-
},
|
| 399 |
-
"w059": {
|
| 400 |
-
"tex": "data/gpu/w059.jpg",
|
| 401 |
-
"receipt": "data/gpu/receipts/w059.json",
|
| 402 |
-
"hi": "hi/w059_gpu.jpg",
|
| 403 |
-
"patches": 15294,
|
| 404 |
-
"gpuSeconds": 13.1,
|
| 405 |
-
"calls": 3,
|
| 406 |
-
"overlap": 0.5,
|
| 407 |
-
"started": "2026-10-16T03:34:29Z",
|
| 408 |
-
"meta": "ink_9um (9 \u00b5m): scrollprize/ink_9um (seed 42, step 75000) on the 9.4 \u00b5m scan (9.362 \u00b5m voxels) \u00b7 15,294 patches of 128\u00d7128 px, overlap 0.5 \u00b7 13.1 s of GPU in 3 calls on Modal \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 409 |
}
|
| 410 |
},
|
| 411 |
"totals": {
|
| 412 |
-
"windings":
|
| 413 |
-
"patches":
|
| 414 |
-
"gpuSeconds":
|
| 415 |
-
"calls":
|
| 416 |
"device": "NVIDIA RTX PRO 6000 Blackwell Server Edition MIG 2g.48gb"
|
| 417 |
},
|
| 418 |
-
"about": "<b>
|
| 419 |
-
}
|
|
|
|
| 1 |
{
|
| 2 |
"windings": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
"w046": {
|
| 4 |
"tex": "data/gpu/w046.jpg",
|
| 5 |
"receipt": "data/gpu/receipts/w046.json",
|
|
|
|
| 9 |
"calls": 1,
|
| 10 |
"overlap": 0.25,
|
| 11 |
"started": "2026-10-16T00:22:15Z",
|
| 12 |
+
"meta": "ZeroGPU run: scrollprize/ink_9um (seed 42, step 75000) on the 9.362 \u00b5m scan \u00b7 4,891 patches of 128\u00d7128 px, overlap 0.25 (patch seams visible) \u00b7 7.5 s of GPU in 1 call \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 13 |
},
|
| 14 |
"w047": {
|
| 15 |
"tex": "data/gpu/w047.jpg",
|
|
|
|
| 20 |
"calls": 3,
|
| 21 |
"overlap": 0.5,
|
| 22 |
"started": "2026-10-15T20:09:32Z",
|
| 23 |
+
"meta": "ZeroGPU run: scrollprize/ink_9um (seed 42, step 75000) on the 9.362 \u00b5m scan \u00b7 10,950 patches of 128\u00d7128 px, overlap 0.5 \u00b7 18.4 s of GPU in 3 calls \u00b7 2026-10-15 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 24 |
},
|
| 25 |
"w048": {
|
| 26 |
"tex": "data/gpu/w048.jpg",
|
|
|
|
| 31 |
"calls": 1,
|
| 32 |
"overlap": 0.25,
|
| 33 |
"started": "2026-10-16T00:23:44Z",
|
| 34 |
+
"meta": "ZeroGPU run: scrollprize/ink_9um (seed 42, step 75000) on the 9.362 \u00b5m scan \u00b7 4,885 patches of 128\u00d7128 px, overlap 0.25 (patch seams visible) \u00b7 7.6 s of GPU in 1 call \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 35 |
},
|
| 36 |
"w049": {
|
| 37 |
"tex": "data/gpu/w049.jpg",
|
|
|
|
| 42 |
"calls": 1,
|
| 43 |
"overlap": 0.25,
|
| 44 |
"started": "2026-10-16T00:25:23Z",
|
| 45 |
+
"meta": "ZeroGPU run: scrollprize/ink_9um (seed 42, step 75000) on the 9.362 \u00b5m scan \u00b7 5,306 patches of 128\u00d7128 px, overlap 0.25 (patch seams visible) \u00b7 8.2 s of GPU in 1 call \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 46 |
},
|
| 47 |
"w050": {
|
| 48 |
"tex": "data/gpu/w050.jpg",
|
|
|
|
| 53 |
"calls": 1,
|
| 54 |
"overlap": 0.5,
|
| 55 |
"started": "2026-10-16T00:26:27Z",
|
| 56 |
+
"meta": "ZeroGPU run: scrollprize/ink_9um (seed 42, step 75000) on the 9.362 \u00b5m scan \u00b7 4,629 patches of 128\u00d7128 px, overlap 0.5 \u00b7 6.4 s of GPU in 1 call \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
| 57 |
},
|
| 58 |
"w051": {
|
| 59 |
"tex": "data/gpu/w051.jpg",
|
|
|
|
| 64 |
"calls": 1,
|
| 65 |
"overlap": 0.2,
|
| 66 |
"started": "2026-10-16T00:28:35Z",
|
| 67 |
+
"meta": "ZeroGPU run: scrollprize/ink_9um (seed 42, step 75000) on the 9.362 \u00b5m scan \u00b7 6,480 patches of 128\u00d7128 px, overlap 0.2 (patch seams visible) \u00b7 9.1 s of GPU in 1 call \u00b7 2026-10-16 \u00b7 drag to pan, scroll or pinch to zoom, double-click to fit"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
}
|
| 69 |
},
|
| 70 |
"totals": {
|
| 71 |
+
"windings": 6,
|
| 72 |
+
"patches": 37141,
|
| 73 |
+
"gpuSeconds": 57.2,
|
| 74 |
+
"calls": 8,
|
| 75 |
"device": "NVIDIA RTX PRO 6000 Blackwell Server Edition MIG 2g.48gb"
|
| 76 |
},
|
| 77 |
+
"about": "<b>The cyan band</b> is ours. We used all 8 of a free Hugging Face account's daily ZeroGPU runs (the limit is 8; most of the daily GPU-time quota was left over) to run the public <a href=\"https://huggingface.co/scrollprize/ink_9um\" target=\"_blank\" rel=\"noopener\">scrollprize/ink_9um</a> model over windings w046 to w051: 37,141 patches in 57 s of measured GPU time on half an NVIDIA RTX PRO 6000 Blackwell. It reads the 9.362 \u00b5m scan, about four times coarser than the 2.4 \u00b5m scan behind the amber layer, so its ink map is noisier. w047 and w050 ran with patch overlap 0.5; w046, w048, w049 and w051 ran with less overlap, so their 128 px patch grid shows as faint square tiles. That grid is a stitching artifact, not ink. Click a cyan winding to compare the two maps."
|
| 78 |
+
}
|
index.html
CHANGED
|
@@ -4,78 +4,26 @@
|
|
| 4 |
<meta charset="utf-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
| 6 |
<title>Unroll · PHerc. 139</title>
|
| 7 |
-
<meta name="description" content="Unroll a carbonized Herculaneum scroll in your browser: 37 windings traced in X-ray CT
|
| 8 |
<meta property="og:title" content="Unroll · a Herculaneum scroll, in your browser">
|
| 9 |
-
<meta property="og:description" content="37 windings of PHerc. 139, traced in X-ray CT.
|
| 10 |
<meta property="og:image" content="https://huggingface.co/spaces/SharpMeow/unroll/resolve/main/og.jpg">
|
| 11 |
-
<meta property="og:image:width" content="1200">
|
| 12 |
-
<meta property="og:image:height" content="630">
|
| 13 |
-
<meta property="og:image:alt" content="Carbonized papyrus sheets of PHerc. 139 laid flat in 3D, with amber ink-layer glow">
|
| 14 |
-
<meta property="og:type" content="website">
|
| 15 |
-
<meta property="og:site_name" content="Unroll">
|
| 16 |
-
<meta property="og:url" content="https://huggingface.co/spaces/SharpMeow/unroll">
|
| 17 |
-
<link rel="canonical" href="https://huggingface.co/spaces/SharpMeow/unroll">
|
| 18 |
-
<meta name="twitter:title" content="Unroll · a Herculaneum scroll, in your browser">
|
| 19 |
-
<meta name="twitter:description" content="37 windings of PHerc. 139, traced in X-ray CT. Ink layers show where models detect ink; letters visible here are candidates for expert reading.">
|
| 20 |
-
<meta name="twitter:image" content="https://huggingface.co/spaces/SharpMeow/unroll/resolve/main/og.jpg">
|
| 21 |
-
<meta name="twitter:image:alt" content="Carbonized papyrus sheets of PHerc. 139 laid flat in 3D, with amber ink-layer glow">
|
| 22 |
-
<meta name="theme-color" content="#060505">
|
| 23 |
-
<meta name="color-scheme" content="dark">
|
| 24 |
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23060505'/%3E%3Cpath d='M16 6a10 10 0 1 0 0.01 0M16 10a6 6 0 1 1-0.01 0M16 14a2 2 0 1 0 0.01 0' fill='none' stroke='%23ffb347' stroke-width='1.6'/%3E%3C/svg%3E">
|
| 25 |
-
<link rel="manifest" href="manifest.webmanifest">
|
| 26 |
-
<link rel="apple-touch-icon" href="icons/apple-touch-icon.png">
|
| 27 |
-
<meta name="apple-mobile-web-app-capable" content="yes">
|
| 28 |
-
<meta name="mobile-web-app-capable" content="yes">
|
| 29 |
-
<meta name="apple-mobile-web-app-title" content="Unroll">
|
| 30 |
-
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
| 31 |
<meta name="twitter:card" content="summary_large_image">
|
| 32 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 33 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 34 |
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
| 35 |
-
<link rel="stylesheet" href="style.css
|
| 36 |
-
<link rel="stylesheet" href="bench.css?v=20261016vol3">
|
| 37 |
-
<link rel="stylesheet" href="ui.css?v=20261016vol3">
|
| 38 |
-
<link rel="alternate" type="application/json+oembed" href="https://sharpmeow-unroll.static.hf.space/oembed.json" title="Unroll">
|
| 39 |
-
<script>
|
| 40 |
-
// embed mode is decided before the first paint so the compact layout never flashes
|
| 41 |
-
(function () { var q = location.search; if (/[?&]embed=(1|true|yes)\b/.test(q)) { document.documentElement.classList.add('embed'); if (/[?&]controls=(0|false|no)\b/.test(q)) document.documentElement.classList.add('embed-nocontrols'); } })();
|
| 42 |
-
</script>
|
| 43 |
-
<!-- start the big downloads and the 3D library in parallel with the page instead of one after another -->
|
| 44 |
-
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js" crossorigin>
|
| 45 |
-
<script>
|
| 46 |
-
// the default scroll's big files, fetched while three.js loads; skipped when the URL opens another scroll (#s=...)
|
| 47 |
-
if (!/(^|[#&])s=/.test(location.hash)) ['scrolls/index.json', 'scrolls/PHerc0139/manifest.json', 'data/scroll.json', 'data/scroll.i16p.gz', 'data/atlas_ink.webp', 'data/atlas_ct.webp'].forEach(function (h) {
|
| 48 |
-
var l = document.createElement('link'); l.rel = 'preload'; l.as = 'fetch'; l.crossOrigin = 'anonymous'; l.href = h; document.head.appendChild(l);
|
| 49 |
-
});
|
| 50 |
-
</script>
|
| 51 |
<script type="importmap">
|
| 52 |
{ "imports": {
|
| 53 |
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
|
| 54 |
-
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
|
| 55 |
-
"./lib/ctsource.js": "./lib/ctsource.js?v=20261016vol3",
|
| 56 |
-
"./lib/scrolls.js": "./lib/scrolls.js?v=20261016vol3",
|
| 57 |
-
"./lib/manifest.js": "./lib/manifest.js?v=20261016vol3",
|
| 58 |
-
"./lib/openown.js": "./lib/openown.js?v=20261016vol3",
|
| 59 |
-
"./lib/formats.js": "./lib/formats.js?v=20261016vol3",
|
| 60 |
-
"./lib/example.js": "./lib/example.js?v=20261016vol3",
|
| 61 |
-
"./lib/bench-core.js": "./lib/bench-core.js?v=20261016vol3",
|
| 62 |
-
"./lib/api.js": "./lib/api.js?v=20261016vol3",
|
| 63 |
-
"./lib/apispec.js": "./lib/apispec.js?v=20261016vol3",
|
| 64 |
-
"./lib/embed.js": "./lib/embed.js?v=20261016vol3",
|
| 65 |
-
"./lib/version.js": "./lib/version.js?v=20261016vol3",
|
| 66 |
-
"./tear.js": "./tear.js?v=20261016vol3nan",
|
| 67 |
-
"./damage.js": "./damage.js?v=20261016vol3"
|
| 68 |
} }
|
| 69 |
</script>
|
| 70 |
</head>
|
| 71 |
<body>
|
| 72 |
-
<canvas id="gl"
|
| 73 |
-
<button id="embedGate" class="embed-gate" hidden data-testid="embed-activate" data-api="embed.activate()" aria-label="Activate the 3D viewer: after this, dragging, pinching and the mouse wheel control the scroll"><span class="mono">Click to interact</span></button>
|
| 74 |
-
<a id="embedFull" class="embed-full mono" href="./" target="_blank" rel="noopener" data-testid="embed-open-full" data-api="deepLink()">Open full viewer ↗</a>
|
| 75 |
-
<p id="viewDesc" class="sr-only" data-testid="view-description">Loading the scroll.</p>
|
| 76 |
-
<div id="liveState" class="sr-only" role="status" aria-live="polite" aria-atomic="true" data-testid="live-state"></div>
|
| 77 |
<div class="scrim top" aria-hidden="true"></div><div class="scrim bot" aria-hidden="true"></div>
|
| 78 |
-
<div class="vignette" aria-hidden="true"></div>
|
| 79 |
<div class="grain" aria-hidden="true"></div>
|
| 80 |
|
| 81 |
<div id="loader">
|
|
@@ -84,66 +32,24 @@ if (!/(^|[#&])s=/.test(location.hash)) ['scrolls/index.json', 'scrolls/PHerc0139
|
|
| 84 |
<div class="loader-title">Reconstructing the scroll</div>
|
| 85 |
<div class="bar"><i id="loadbar"></i></div>
|
| 86 |
<div class="mono dim" id="loadtxt">0 MB</div>
|
| 87 |
-
<div class="load-err" id="loadErr" hidden>
|
| 88 |
-
<p>The scroll could not be loaded. <span id="loadErrWhy"></span></p>
|
| 89 |
-
<button class="chip" onclick="location.reload()" data-testid="load-retry" data-api="reload()">Try again</button>
|
| 90 |
-
</div>
|
| 91 |
</div>
|
| 92 |
</div>
|
| 93 |
-
<script>
|
| 94 |
-
// empty and error states: if the scroll data or 3D graphics fail, say so instead of loading forever
|
| 95 |
-
(function () {
|
| 96 |
-
function fail(e) {
|
| 97 |
-
var box = document.getElementById('loadErr');
|
| 98 |
-
if (!box || !document.getElementById('loader')) return;
|
| 99 |
-
var msg = String((e && (e.reason || e.error || e.message)) || '');
|
| 100 |
-
var why = window.__unrollWhy ? window.__unrollWhy : /webgl|context/i.test(msg) ? 'Your browser could not start 3D graphics (WebGL). Try another browser or turn on hardware acceleration.'
|
| 101 |
-
: 'A data file did not arrive. Check your connection and try again.';
|
| 102 |
-
document.getElementById('loadErrWhy').textContent = why;
|
| 103 |
-
box.hidden = false;
|
| 104 |
-
document.getElementById('loader').classList.add('failed');
|
| 105 |
-
}
|
| 106 |
-
addEventListener('unhandledrejection', fail);
|
| 107 |
-
addEventListener('error', function (e) { if (e.filename && /app\.js|tear\.js|lib\/|three/.test(e.filename)) fail(e); });
|
| 108 |
-
})();
|
| 109 |
-
</script>
|
| 110 |
|
| 111 |
<header class="hud tl">
|
| 112 |
<div class="mono dim kicker">PHERC. 0139 <span class="sep">/</span> HERCULANEUM <span class="sep">/</span> BURIED 79 CE</div>
|
| 113 |
<h1>Unroll a scroll nobody has opened in <em>1,947 years.</em></h1>
|
| 114 |
-
<p class="lede">37 of its windings have been traced through X
|
| 115 |
-
<div class="scroll-pick" id="scrollPick" hidden><label class="mono dim" for="scrollSel">SCROLL</label> <select id="scrollSel" aria-label="Choose a scroll" data-testid="scroll-picker" data-api="openScroll()"></select> <a class="prize-badge" id="prizeBadge" data-testid="prize-badge" data-api="links()" hidden href="https://scrollprize.org/prizes" target="_blank" rel="noopener" title="An open Vesuvius Challenge prize can be won on this scroll. See the prizes page">🏆 prize eligible</a> <button class="chip prize-only" id="prizeOnly" data-testid="prize-only" data-api="prizeOnly()" hidden aria-pressed="false" title="Show only scrolls marked prize eligible on scrollprize.org">🏆 prize eligible only</button></div>
|
| 116 |
</header>
|
| 117 |
|
| 118 |
<nav class="hud tr" aria-label="Layers">
|
| 119 |
-
<div class="seg" role="tablist"
|
| 120 |
-
<button class="chip" data-layer="xray" role="tab"
|
| 121 |
-
<button class="chip on" data-layer="ink" role="tab"
|
| 122 |
-
<button class="chip gpu" data-layer="gpu" role="tab"
|
| 123 |
-
</div>
|
| 124 |
-
<p class="layer-hint" id="layerHint" aria-live="polite">Published ink (2.4 µm): Vesuvius Challenge detection, 2.4 µm scan, all windings.</p>
|
| 125 |
-
<details class="layer-why" id="layerWhy"><summary data-testid="layer-why" data-api="panel('why')">Why two ink layers?</summary><p>Published ink is the Vesuvius Challenge result on the fine 2.4 µm scan. ink_9um (9 µm) comes from the public scrollprize/ink_9um model, which the Vesuvius Challenge trained; Chase Hendrick ran it on a scan about 4x coarser, so it is blurrier. It shows what an open model can recover from cheaper scans, as a baseline you can check.</p></details>
|
| 126 |
-
<div class="seg tear-seg">
|
| 127 |
-
<button class="chip" id="btnTear" data-testid="destroy-mode" data-api="destroy.mode()" aria-pressed="false" title="Destroy: tear, crush, burn or soak the scroll, rolled up or unrolled. Damage stays as you roll and unroll. Just for fun, not a real unrolling. (T)">Destroy</button>
|
| 128 |
-
<button class="chip tool" id="toolTear" data-tool="tear" data-testid="destroy-tool-tear" data-api="destroy.tool('tear')" hidden aria-pressed="false" title="Drag across the papyrus to rip it (X)">Tear</button>
|
| 129 |
-
<button class="chip tool" id="btnCrush" data-tool="crush" data-testid="destroy-tool-crush" data-api="destroy.tool('crush')" hidden aria-pressed="false" title="Press to crumble it; hold to press harder and go deeper into the roll (C)">Crush</button>
|
| 130 |
-
<button class="chip tool" id="toolBurn" data-tool="burn" data-testid="destroy-tool-burn" data-api="destroy.tool('burn')" hidden aria-pressed="false" title="Click to ignite: embers creep slowly along the fibres (B)">Burn</button>
|
| 131 |
-
<button class="chip tool" id="toolWater" data-tool="water" data-testid="destroy-tool-water" data-api="destroy.tool('water')" hidden aria-pressed="false" title="Press or hold to drip and pour water: it darkens, softens, weakens and slowly dissolves the papyrus (W)">Water</button>
|
| 132 |
-
<button class="chip" id="btnReset" data-testid="destroy-reset" data-api="destroy.reset()" hidden title="Undo all damage and clear the debris">Reset</button>
|
| 133 |
-
<button class="chip" id="btnCrumble" data-testid="destroy-crumble" data-api="destroy.crumble()" hidden title="Crumble the whole scroll: it breaks up from the edges and falls into a heap of dust. Reset restores it. (K)">Crumble</button>
|
| 134 |
-
<button class="chip" id="btnSandbox" data-testid="destroy-sandbox" data-api="destroy.sandbox()" hidden aria-pressed="false" title="Lay the strip flat with full fracture physics: grab to rip, Crush to crumble">Sandbox</button>
|
| 135 |
-
<button class="chip" id="btnShake" data-testid="destroy-shake" data-api="destroy.shake()" hidden title="Knock the table: loosens random bits">Shake</button>
|
| 136 |
-
<button class="chip" id="btnSound" data-testid="destroy-sound" data-api="sound()" hidden aria-pressed="false" title="Soft crackle when it breaks">Sound off</button>
|
| 137 |
</div>
|
| 138 |
<div class="tools">
|
| 139 |
-
<button class="icon" id="
|
| 140 |
-
<button class="icon" id="
|
| 141 |
-
<button class="icon" id="btnInfo" data-testid="about" data-api="panel('about')" title="About this page" aria-label="About">i</button>
|
| 142 |
-
<button class="icon" id="btnEmbed" data-testid="embed" data-api="panel('embed')" title="Embed this viewer on your site" aria-label="Embed this viewer"></></button>
|
| 143 |
-
<button class="icon" id="btnFull" data-testid="fullscreen" data-api="fullscreen()" title="Fullscreen" aria-label="Fullscreen" aria-pressed="false">
|
| 144 |
-
<svg viewBox="0 0 24 24" width="15" height="15"><path d="M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>
|
| 145 |
-
</button>
|
| 146 |
-
<button class="icon" id="btnShot" data-testid="screenshot" data-api="screenshot()" title="Save a picture" aria-label="Save a picture">
|
| 147 |
<svg viewBox="0 0 24 24" width="15" height="15"><path d="M4 8h3l2-3h6l2 3h3v11H4z" fill="none" stroke="currentColor" stroke-width="1.5"/><circle cx="12" cy="13" r="3.5" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>
|
| 148 |
</button>
|
| 149 |
</div>
|
|
@@ -153,17 +59,16 @@ if (!/(^|[#&])s=/.test(location.hash)) ['scrolls/index.json', 'scrolls/PHerc0139
|
|
| 153 |
<div><span class="dim">WINDING</span> <b id="rWind">w059</b></div>
|
| 154 |
<div><span class="dim">RADIUS</span> <b id="rRad">13.3 mm</b></div>
|
| 155 |
<div><span class="dim">STRIP</span> <b id="rLen">0.00 m</b></div>
|
| 156 |
-
<div id="rGpuRow" hidden><span class="dim">
|
| 157 |
-
<button class="chip read" id="btnRead"
|
| 158 |
</aside>
|
| 159 |
|
| 160 |
<footer class="hud bottom">
|
| 161 |
-
<button id="play"
|
| 162 |
<svg viewBox="0 0 24 24" width="16" height="16" id="playIcon"><path d="M7 5l12 7-12 7z" fill="currentColor"/></svg>
|
| 163 |
</button>
|
| 164 |
-
<
|
| 165 |
-
|
| 166 |
-
<input type="range" id="scrubIn" class="scrub-in" min="0" max="1000" step="1" value="0" aria-label="Unroll position" data-testid="unroll-slider" data-api="setUnroll()">
|
| 167 |
<div class="ticks" id="ticks"></div>
|
| 168 |
<div class="track"><i id="fill"></i></div>
|
| 169 |
<div class="knob" id="knob"></div>
|
|
@@ -175,88 +80,35 @@ if (!/(^|[#&])s=/.test(location.hash)) ['scrolls/index.json', 'scrolls/PHerc0139
|
|
| 175 |
|
| 176 |
<div id="tip" class="tip mono" hidden></div>
|
| 177 |
|
| 178 |
-
<section id="focus" class="sheet"
|
| 179 |
<div class="sheet-head">
|
| 180 |
<div>
|
| 181 |
<div class="mono dim" id="fKicker">WINDING</div>
|
| 182 |
<h2 id="fTitle">w046</h2>
|
| 183 |
</div>
|
| 184 |
<div class="sheet-tools">
|
| 185 |
-
<div class="seg small" id="fSeg"
|
| 186 |
-
<button class="icon" id="fClose"
|
| 187 |
</div>
|
| 188 |
</div>
|
| 189 |
-
<div class="viewer" id="viewer"
|
| 190 |
-
<div class="swipe-line" id="fSwipeLine" hidden></div>
|
| 191 |
-
<div class="scalebar mono" id="fScale" aria-hidden="true"><i></i><span>10 mm</span></div>
|
| 192 |
-
<div class="img-err mono dim" id="fErr" hidden>This image could not be loaded.</div>
|
| 193 |
-
<label class="swipe mono" id="fSwipeWrap" hidden data-testid="compare-slider-label" data-api="compare(true, pos)"><span id="fSwipeA">Published ink (2.4 <span class="mu">µ</span>m)</span> <input type="range" id="fSwipe" data-testid="compare-slider" data-api="compare(true, pos)" min="0" max="100" value="50" aria-label="Swipe between Published ink (2.4 µm) and ink_9um (9 µm)"> <span id="fSwipeB">ink_9um (9 <span class="mu">µ</span>m)</span></label>
|
| 194 |
-
</div>
|
| 195 |
<div class="sheet-foot mono dim" id="fMeta"></div>
|
| 196 |
</section>
|
| 197 |
|
| 198 |
-
<section id="
|
| 199 |
-
<div class="sheet-head">
|
| 200 |
-
<div><div class="mono dim">OPEN YOUR OWN</div><h2 id="oTitle">Unroll your own scroll</h2></div>
|
| 201 |
-
<button class="icon" id="oClose" data-testid="open-close" data-api="panel('open', false)" aria-label="Close">✕</button>
|
| 202 |
-
</div>
|
| 203 |
-
<div class="about-body">
|
| 204 |
-
<p class="o-private"><b>Nothing is uploaded.</b> Your files are read by this page inside your browser and kept on this device (in browser storage) until you remove them. No server sees them.</p>
|
| 205 |
-
<div class="o-drop" id="oDrop" tabindex="0" role="button" data-testid="open-drop" data-api="open.fromFiles()" aria-label="Drop files here or press Enter to pick files">
|
| 206 |
-
<div class="o-drop-text">Drop a <b>zip</b>, or a <b>manifest.json</b> with its meshes and images, or a folder</div>
|
| 207 |
-
<div class="o-drop-btns">
|
| 208 |
-
<label class="chip" data-testid="open-pick-files-label" data-api="open.fromFiles()">Pick files<input type="file" id="oPick" aria-label="Pick files" data-testid="open-pick-files" data-api="open.fromFiles()" multiple accept=".zip,.json,.obj,.ply,.png,.jpg,.jpeg,.tif,.tiff" hidden></label>
|
| 209 |
-
<label class="chip" data-testid="open-pick-folder-label" data-api="open.fromFiles()">Pick a folder<input type="file" id="oPickDir" aria-label="Pick a folder" data-testid="open-pick-folder" data-api="open.fromFiles()" webkitdirectory multiple hidden></label>
|
| 210 |
-
</div>
|
| 211 |
-
</div>
|
| 212 |
-
<form class="o-url" id="oURLForm"><input type="url" id="oURL" placeholder="https://example.org/my-scroll/manifest.json or .zip" aria-label="URL of a manifest or zip" data-testid="open-url" data-api="open.fromURL()"><button class="chip" type="submit" data-testid="open-url-submit" data-api="open.fromURL()">Open URL</button></form>
|
| 213 |
-
<p class="mono dim small">A URL is fetched by your browser straight from that server, which must allow cross-origin requests (CORS).</p>
|
| 214 |
-
<div class="o-status mono" id="oStatus" role="status" aria-live="polite" hidden></div>
|
| 215 |
-
<div id="oSaved" hidden><div class="mono dim">IN THIS BROWSER</div><ul class="o-list" id="oSavedList"></ul></div>
|
| 216 |
-
<p>Meshes: OBJ or PLY with texture coordinates (UVs), one per winding. Layers: PNG, JPG or TIFF images laid out on those UVs: an X-ray layer, an ink layer and an optional comparison layer. The manifest says which file is which, the voxel size, and who made the data.</p>
|
| 217 |
-
<p><button class="chip" id="oExample" data-testid="open-example" data-api="open.example()">Download example package</button> <a href="https://huggingface.co/spaces/SharpMeow/unroll/blob/main/docs/ADDING_A_SCROLL.md#open-your-own" target="_blank" rel="noopener">How to make one</a></p>
|
| 218 |
-
<p class="mono dim small" id="oLimits"></p>
|
| 219 |
-
</div>
|
| 220 |
-
</section>
|
| 221 |
-
|
| 222 |
-
<section id="about" class="sheet about" role="dialog" aria-modal="false" aria-labelledby="aTitle" hidden data-testid="about-panel">
|
| 223 |
<div class="sheet-head">
|
| 224 |
-
<div><div class="mono dim">ABOUT</div><h2
|
| 225 |
-
<button class="icon" id="aClose"
|
| 226 |
</div>
|
| 227 |
<div class="about-body">
|
| 228 |
<p>PHerc. 139 is one of the papyrus rolls carbonized when Vesuvius buried Herculaneum in 79 CE. It is too fragile to open, so it was scanned with X‑rays at the ESRF synchrotron in Grenoble. It is held by the Biblioteca Nazionale di Napoli.</p>
|
| 229 |
-
<p>Each sheet in the 3D view is one winding of the roll, traced through the CT volume by the <a href="https://scrollprize.org" target="_blank" rel="noopener">Vesuvius Challenge</a>. The shapes are the real ones, from the public 9.362 µm meshes. The <b>X‑ray</b> layer is the CT intensity on each surface. The <b>
|
| 230 |
<p id="aboutGpu" hidden></p>
|
| 231 |
<p>Unrolling lays the windings end to end, outermost first, the way a reader would open the roll. Each traced winding runs a little past one full turn, so roughly a tenth of the 2.8 m strip repeats surface from its neighbour.</p>
|
| 232 |
-
<p class="mono dim small">Sources: open data bucket <code>PHerc0139/segments/*/mesh/*9.362um.tifxyz</code>, <code>surface-volumes/9.362um…zarr</code> and <code>ink-detection/downsampled</code>. Data: 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. <i>Vesuvius Challenge - CT Scans of Herculaneum Papyri.</i> Vesuvius Challenge. Licensed <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" rel="noopener">CC BY‑NC 4.0</a>. The page's code (HTML, CSS, JS and <code>tools/</code>) is
|
| 233 |
-
<p class="mono dim small made-by">Made by Chase Hendrick.</p>
|
| 234 |
-
</div>
|
| 235 |
-
<div class="sheet-foot mono dim small about-foot"><span>For agents: <a href="llms.txt" target="_blank" rel="noopener">llms.txt</a> · <a href="AGENTS.md" target="_blank" rel="noopener">AGENTS.md</a> · <a href="api.json" target="_blank" rel="noopener">api.json</a></span> <span id="aVersion" class="version" data-testid="version" title="Version and build (CHANGELOG.md)"></span></div>
|
| 236 |
-
</section>
|
| 237 |
-
|
| 238 |
-
<section id="embedSheet" class="sheet about" role="dialog" aria-modal="false" aria-labelledby="eTitle" hidden data-testid="embed-panel">
|
| 239 |
-
<div class="sheet-head">
|
| 240 |
-
<div><div class="mono dim">EMBED</div><h2 id="eTitle">Put this viewer on your site</h2></div>
|
| 241 |
-
<button class="icon" id="eClose" data-testid="embed-close" data-api="panel('embed', false)" aria-label="Close">✕</button>
|
| 242 |
-
</div>
|
| 243 |
-
<div class="about-body">
|
| 244 |
-
<p>Paste this into your page. Visitors can unroll, scrub, switch layers and use every tool inside the frame; a click hands the mouse wheel to the viewer, so your page still scrolls normally.</p>
|
| 245 |
-
<label class="mono small" data-testid="embed-option-state-label" data-api="embed.snippet({ state })"><input type="checkbox" id="embedState" checked data-testid="embed-option-state" data-api="embed.snippet({ state })"> Start at the current view</label>
|
| 246 |
-
<label class="mono small" data-testid="embed-option-autoplay-label" data-api="embed.snippet({ autoplay })"><input type="checkbox" id="embedAutoplay" data-testid="embed-option-autoplay" data-api="embed.snippet({ autoplay })"> Autoplay the unroll</label>
|
| 247 |
-
<label class="mono small" data-testid="embed-option-nocontrols-label" data-api="embed.snippet({ controls })"><input type="checkbox" id="embedNoControls" data-testid="embed-option-nocontrols" data-api="embed.snippet({ controls })"> Hide the controls</label>
|
| 248 |
-
<textarea id="embedCode" class="embed-code mono" rows="5" readonly aria-label="Embed code" data-testid="embed-code" data-api="embed.snippet()"></textarea>
|
| 249 |
-
<p><button class="chip" id="embedCopy" data-testid="embed-copy" data-api="embed.snippet()">Copy</button> <a id="embedPreviewURL" href="./" target="_blank" rel="noopener" data-testid="embed-preview" data-api="embed.snippet()">Preview the embed</a></p>
|
| 250 |
-
<p class="mono dim small">oEmbed: <a href="oembed.json" target="_blank" rel="noopener">/oembed.json</a>. Host pages can drive the viewer with postMessage; see <a href="docs/EMBED.md" target="_blank" rel="noopener">EMBED.md</a>.</p>
|
| 251 |
</div>
|
| 252 |
</section>
|
| 253 |
|
| 254 |
-
<script type="module" src="app.js
|
| 255 |
-
<script type="module" src="polish.js?v=20261016vol3"></script>
|
| 256 |
-
<script type="module" src="lib/bench.js?v=20261016vol3"></script>
|
| 257 |
-
<script type="module" src="ui.js?v=20261016vol3"></script>
|
| 258 |
-
<script type="module" src="lib/ctvolume.js?v=20261016vol3"></script>
|
| 259 |
-
<script type="module" src="lib/ctwl.js?v=20261016vol3"></script>
|
| 260 |
-
<script type="module" src="lib/dragres.js?v=20261016vol3"></script>
|
| 261 |
</body>
|
| 262 |
</html>
|
|
|
|
| 4 |
<meta charset="utf-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
| 6 |
<title>Unroll · PHerc. 139</title>
|
| 7 |
+
<meta name="description" content="Unroll a carbonized Herculaneum scroll in your browser: 37 windings traced in X-ray CT, with the ink found by neural networks.">
|
| 8 |
<meta property="og:title" content="Unroll · a Herculaneum scroll, in your browser">
|
| 9 |
+
<meta property="og:description" content="37 windings of PHerc. 139, traced in X-ray CT. Scrub to unroll it and watch the ink appear.">
|
| 10 |
<meta property="og:image" content="https://huggingface.co/spaces/SharpMeow/unroll/resolve/main/og.jpg">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23060505'/%3E%3Cpath d='M16 6a10 10 0 1 0 0.01 0M16 10a6 6 0 1 1-0.01 0M16 14a2 2 0 1 0 0.01 0' fill='none' stroke='%23ffb347' stroke-width='1.6'/%3E%3C/svg%3E">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
<meta name="twitter:card" content="summary_large_image">
|
| 13 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 14 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 15 |
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
| 16 |
+
<link rel="stylesheet" href="style.css">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
<script type="importmap">
|
| 18 |
{ "imports": {
|
| 19 |
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
|
| 20 |
+
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
} }
|
| 22 |
</script>
|
| 23 |
</head>
|
| 24 |
<body>
|
| 25 |
+
<canvas id="gl"></canvas>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
<div class="scrim top" aria-hidden="true"></div><div class="scrim bot" aria-hidden="true"></div>
|
|
|
|
| 27 |
<div class="grain" aria-hidden="true"></div>
|
| 28 |
|
| 29 |
<div id="loader">
|
|
|
|
| 32 |
<div class="loader-title">Reconstructing the scroll</div>
|
| 33 |
<div class="bar"><i id="loadbar"></i></div>
|
| 34 |
<div class="mono dim" id="loadtxt">0 MB</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
</div>
|
| 36 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
<header class="hud tl">
|
| 39 |
<div class="mono dim kicker">PHERC. 0139 <span class="sep">/</span> HERCULANEUM <span class="sep">/</span> BURIED 79 CE</div>
|
| 40 |
<h1>Unroll a scroll nobody has opened in <em>1,947 years.</em></h1>
|
| 41 |
+
<p class="lede">37 of its windings have been traced through X‑ray CT by the Vesuvius Challenge, and neural networks found the ink on them. Drag to look around. Scrub to unroll.</p>
|
|
|
|
| 42 |
</header>
|
| 43 |
|
| 44 |
<nav class="hud tr" aria-label="Layers">
|
| 45 |
+
<div class="seg" role="tablist">
|
| 46 |
+
<button class="chip" data-layer="xray" role="tab">X‑ray</button>
|
| 47 |
+
<button class="chip on" data-layer="ink" role="tab">Ink</button>
|
| 48 |
+
<button class="chip gpu" data-layer="gpu" role="tab" id="gpuChip" hidden><i class="dot"></i>ZeroGPU</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
</div>
|
| 50 |
<div class="tools">
|
| 51 |
+
<button class="icon" id="btnInfo" title="About this page" aria-label="About">i</button>
|
| 52 |
+
<button class="icon" id="btnShot" title="Save a picture" aria-label="Save a picture">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
<svg viewBox="0 0 24 24" width="15" height="15"><path d="M4 8h3l2-3h6l2 3h3v11H4z" fill="none" stroke="currentColor" stroke-width="1.5"/><circle cx="12" cy="13" r="3.5" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>
|
| 54 |
</button>
|
| 55 |
</div>
|
|
|
|
| 59 |
<div><span class="dim">WINDING</span> <b id="rWind">w059</b></div>
|
| 60 |
<div><span class="dim">RADIUS</span> <b id="rRad">13.3 mm</b></div>
|
| 61 |
<div><span class="dim">STRIP</span> <b id="rLen">0.00 m</b></div>
|
| 62 |
+
<div id="rGpuRow" hidden><span class="dim">ZEROGPU</span> <b id="rGpu">·</b></div>
|
| 63 |
+
<button class="chip read" id="btnRead">Read w059</button>
|
| 64 |
</aside>
|
| 65 |
|
| 66 |
<footer class="hud bottom">
|
| 67 |
+
<button id="play" class="play" aria-label="Unroll">
|
| 68 |
<svg viewBox="0 0 24 24" width="16" height="16" id="playIcon"><path d="M7 5l12 7-12 7z" fill="currentColor"/></svg>
|
| 69 |
</button>
|
| 70 |
+
<div class="scrub" id="scrub">
|
| 71 |
+
<input type="range" id="scrubIn" class="scrub-in" min="0" max="1000" step="1" value="0" aria-label="Unroll position">
|
|
|
|
| 72 |
<div class="ticks" id="ticks"></div>
|
| 73 |
<div class="track"><i id="fill"></i></div>
|
| 74 |
<div class="knob" id="knob"></div>
|
|
|
|
| 80 |
|
| 81 |
<div id="tip" class="tip mono" hidden></div>
|
| 82 |
|
| 83 |
+
<section id="focus" class="sheet" hidden>
|
| 84 |
<div class="sheet-head">
|
| 85 |
<div>
|
| 86 |
<div class="mono dim" id="fKicker">WINDING</div>
|
| 87 |
<h2 id="fTitle">w046</h2>
|
| 88 |
</div>
|
| 89 |
<div class="sheet-tools">
|
| 90 |
+
<div class="seg small" id="fSeg"></div>
|
| 91 |
+
<button class="icon" id="fClose" aria-label="Close">✕</button>
|
| 92 |
</div>
|
| 93 |
</div>
|
| 94 |
+
<div class="viewer" id="viewer"><img id="fImg" alt="Ink detection of this winding, flattened" draggable="false"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
<div class="sheet-foot mono dim" id="fMeta"></div>
|
| 96 |
</section>
|
| 97 |
|
| 98 |
+
<section id="about" class="sheet about" hidden>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
<div class="sheet-head">
|
| 100 |
+
<div><div class="mono dim">ABOUT</div><h2>What you are looking at</h2></div>
|
| 101 |
+
<button class="icon" id="aClose" aria-label="Close">✕</button>
|
| 102 |
</div>
|
| 103 |
<div class="about-body">
|
| 104 |
<p>PHerc. 139 is one of the papyrus rolls carbonized when Vesuvius buried Herculaneum in 79 CE. It is too fragile to open, so it was scanned with X‑rays at the ESRF synchrotron in Grenoble. It is held by the Biblioteca Nazionale di Napoli.</p>
|
| 105 |
+
<p>Each sheet in the 3D view is one winding of the roll, traced through the CT volume by the <a href="https://scrollprize.org" target="_blank" rel="noopener">Vesuvius Challenge</a>. The shapes are the real ones, from the public 9.362 µm meshes. The <b>X‑ray</b> layer is the CT intensity on each surface. The <b>Ink</b> layer is the Vesuvius Challenge's public ink detection on the 2.4 µm scan. In 3D it is drawn through a local‑contrast filter so letter‑sized strokes stand out; the close‑up view shows the published preview unfiltered, downscaled to 2,400 px.</p>
|
| 106 |
<p id="aboutGpu" hidden></p>
|
| 107 |
<p>Unrolling lays the windings end to end, outermost first, the way a reader would open the roll. Each traced winding runs a little past one full turn, so roughly a tenth of the 2.8 m strip repeats surface from its neighbour.</p>
|
| 108 |
+
<p class="mono dim small">Sources: open data bucket <code>PHerc0139/segments/*/mesh/*9.362um.tifxyz</code>, <code>surface-volumes/9.362um…zarr</code> and <code>ink-detection/downsampled</code>. Data: 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. <i>Vesuvius Challenge - CT Scans of Herculaneum Papyri.</i> Vesuvius Challenge. Licensed <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" rel="noopener">CC BY‑NC 4.0</a>. The page's code (HTML, CSS, JS and <code>tools/</code>) is MIT; see the Space's README.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
</div>
|
| 110 |
</section>
|
| 111 |
|
| 112 |
+
<script type="module" src="app.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
</body>
|
| 114 |
</html>
|
og.jpg
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
style.css
CHANGED
|
@@ -171,120 +171,3 @@ button:focus-visible, a:focus-visible { outline: 2px solid var(--fg); outline-of
|
|
| 171 |
}
|
| 172 |
|
| 173 |
.credit { text-shadow: 0 0 8px #000, 0 0 2px #000; }
|
| 174 |
-
|
| 175 |
-
.tr { flex-wrap: wrap; justify-content: flex-end; }
|
| 176 |
-
.layer-hint { order: 3; flex-basis: 100%; margin: 6px 2px 0; font-size: 10.5px; line-height: 1.35; color: var(--dim); max-width: 300px; text-align: right; }
|
| 177 |
-
.layer-why { order: 4; flex-basis: 100%; max-width: 300px; margin-left: auto; font-size: 10.5px; line-height: 1.35; color: var(--dim); text-align: right; }
|
| 178 |
-
.layer-why summary { cursor: pointer; }
|
| 179 |
-
.layer-why p { margin: 4px 0 0; }
|
| 180 |
-
@media (max-width: 700px) { .layer-hint, .layer-why { display: none; } }
|
| 181 |
-
.tear-seg { order: 5; margin-left: auto; margin-top: 6px; }
|
| 182 |
-
body.tearing .bottom, body.tearing #readout { opacity: .35; pointer-events: none; }
|
| 183 |
-
.load-err { margin-top: 14px; max-width: 340px; line-height: 1.5; color: var(--fg); }
|
| 184 |
-
.load-err p { margin: 0 0 10px; }
|
| 185 |
-
#loader.failed .bar { opacity: .3; }
|
| 186 |
-
.viewer img.cmp { pointer-events: none; }
|
| 187 |
-
.swipe-line { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--gpu); box-shadow: 0 0 6px var(--gpu); pointer-events: none; }
|
| 188 |
-
.swipe { position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%); display: flex; gap: 10px; align-items: center; font-size: 11px; padding: 6px 12px; background: rgba(0,0,0,.6); border: 1px solid var(--line); color: var(--fg); }
|
| 189 |
-
.swipe input { width: 180px; }
|
| 190 |
-
.scalebar { position: absolute; left: 16px; bottom: 16px; font-size: 10.5px; color: var(--fg); pointer-events: none; text-shadow: 0 0 4px #000; }
|
| 191 |
-
.scalebar i { display: block; height: 4px; border: 1px solid var(--fg); border-top: 0; margin-bottom: 4px; }
|
| 192 |
-
.img-err { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
|
| 193 |
-
.layer-why summary { display: block; text-align: right; }
|
| 194 |
-
[hidden] { display: none !important; }
|
| 195 |
-
|
| 196 |
-
/* polish: vignette over the 3D view, under the HUD */
|
| 197 |
-
.vignette { position: fixed; inset: 0; z-index: 1; pointer-events: none; background: radial-gradient(ellipse 85% 80% at 55% 52%, rgba(0,0,0,0) 55%, rgba(0,0,0,.55) 100%); }
|
| 198 |
-
body.shot .vignette { display: none; }
|
| 199 |
-
|
| 200 |
-
/* polish: keyboard help */
|
| 201 |
-
.help { inset: auto auto var(--gut) var(--gut); width: min(440px, calc(100vw - 2 * var(--gut))); max-height: calc(100vh - 2 * var(--gut)); }
|
| 202 |
-
.keys { display: grid; grid-template-columns: auto 1fr; gap: 9px 16px; margin: 0; padding: 16px 20px 6px; overflow: auto; font-size: 11.5px; color: #c9c1b5; }
|
| 203 |
-
.keys dt { white-space: nowrap; color: var(--dim); }
|
| 204 |
-
.keys dd { margin: 0; }
|
| 205 |
-
kbd { display: inline-block; min-width: 20px; padding: 2px 6px; border: 1px solid var(--line-strong); border-bottom-width: 2px; font: inherit; color: var(--fg); text-align: center; }
|
| 206 |
-
.help-foot { padding: 4px 20px 16px; margin: 0; }
|
| 207 |
-
@media (pointer: coarse) { #btnHelp { display: none; } }
|
| 208 |
-
|
| 209 |
-
/* polish: phones, keep the layer switch, the read button and the scrubber from overlapping */
|
| 210 |
-
@media (max-width: 720px) {
|
| 211 |
-
.tr { left: var(--gut); justify-content: space-between; row-gap: 8px; }
|
| 212 |
-
.tr > .seg[role=tablist] { flex: 1 1 100%; }
|
| 213 |
-
.tr > .seg[role=tablist] .chip { flex: 1 1 0; min-width: 0; padding-left: 6px; padding-right: 6px; font-size: 10px; letter-spacing: .06em; line-height: 1.25; }
|
| 214 |
-
.tear-seg { margin-top: 0; order: 2; }
|
| 215 |
-
.tools { order: 1; }
|
| 216 |
-
.bl { bottom: calc(var(--gut) + 64px + 44px + 8px + 44px + 14px); }
|
| 217 |
-
.tear-seg { flex-wrap: wrap; justify-content: flex-end; }
|
| 218 |
-
}
|
| 219 |
-
/* polish: a flex-wrap row sizes itself to all its items side by side, which pushed the layer switch over the title */
|
| 220 |
-
@media (min-width: 721px) { .tr { max-width: 600px; } }
|
| 221 |
-
/* scroll picker and "Open your own" */
|
| 222 |
-
.scroll-pick { display: flex; align-items: center; gap: 8px; margin-top: 12px; pointer-events: auto; }
|
| 223 |
-
.scroll-pick select { background: rgba(12,10,9,0.8); color: #ece6db; border: 1px solid rgba(255,255,255,0.18); border-radius: 999px; padding: 5px 10px; font: inherit; font-size: 12px; max-width: 240px; }
|
| 224 |
-
.local-tag { color: #7fd8ff; }
|
| 225 |
-
.open-own .o-private { border-left: 2px solid #7fd8ff; padding-left: 10px; }
|
| 226 |
-
.o-drop { border: 1.5px dashed rgba(255,255,255,0.28); border-radius: 12px; padding: 22px 16px; text-align: center; cursor: pointer; margin: 10px 0; transition: background .2s, border-color .2s; }
|
| 227 |
-
.o-drop.over, .o-drop:focus-visible { background: rgba(255,179,71,0.08); border-color: #ffb347; outline: none; }
|
| 228 |
-
.o-drop-btns { display: flex; gap: 8px; justify-content: center; margin-top: 12px; flex-wrap: wrap; }
|
| 229 |
-
.o-drop-btns label { cursor: pointer; }
|
| 230 |
-
.o-url { display: flex; gap: 8px; margin-top: 8px; }
|
| 231 |
-
.o-url input { flex: 1; min-width: 0; background: rgba(0,0,0,0.4); color: inherit; border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; padding: 7px 10px; font: inherit; font-size: 13px; }
|
| 232 |
-
.o-status { padding: 10px 12px; border-radius: 8px; font-size: 12px; margin: 8px 0; background: rgba(255,255,255,0.05); }
|
| 233 |
-
.o-status.err { background: rgba(255,80,60,0.12); color: #ffb4a8; }
|
| 234 |
-
.o-status.ok { background: rgba(80,200,255,0.1); color: #a8e6ff; }
|
| 235 |
-
.o-status ul { margin: 6px 0 0; padding-left: 18px; }
|
| 236 |
-
.o-list { list-style: none; padding: 0; margin: 6px 0 12px; }
|
| 237 |
-
.o-list li { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,0.08); }
|
| 238 |
-
.o-list li > span:last-child { display: flex; gap: 6px; }
|
| 239 |
-
.mu { text-transform: none; }
|
| 240 |
-
@media (min-width: 721px) { .hud.tr .seg[role=tablist] .chip { letter-spacing: .07em; padding: 8px 11px; } }
|
| 241 |
-
|
| 242 |
-
/* Destroy mode: tool cursors (a fist for Crush, squeezed while pressed; a flame for Burn; a droplet for Water) */
|
| 243 |
-
body.destroy.tool-tear #gl { cursor: crosshair; }
|
| 244 |
-
body.destroy.tool-crush #gl { cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cg fill='%23f3e6cf' stroke='%231a120c' stroke-width='1.6' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M6 13c0-2 1.4-3.2 3-3.2s2.8 1.1 2.8 2.6c0-1.9 1.3-3 2.9-3s2.8 1.1 2.8 2.8c.1-1.6 1.4-2.6 2.9-2.6 1.6 0 2.8 1.2 2.8 2.9v.9c.4-.7 1.2-1.1 2-1.1 1.4 0 2.3 1 2.3 2.5v5.2c0 4.7-3.7 8.1-8.6 8.1h-3.4C10 28.1 6 24.6 6 20.4z'/%3E%3Cpath d='M11.8 12.4v3.6M17.5 12.2v3.6M23.2 12.5v3.4M6.4 17.4c2.3-1.6 5.4-1.6 7.4.4' fill='none'/%3E%3C/g%3E%3C/svg%3E") 16 11, pointer; }
|
| 245 |
-
body.destroy.tool-crush.pressing #gl, body.destroy.tool-crush #gl:active { cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='-1 -4 34 38'%3E%3Cg fill='%23f3e6cf' stroke='%231a120c' stroke-width='1.6' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M6 13c0-2 1.4-3.2 3-3.2s2.8 1.1 2.8 2.6c0-1.9 1.3-3 2.9-3s2.8 1.1 2.8 2.8c.1-1.6 1.4-2.6 2.9-2.6 1.6 0 2.8 1.2 2.8 2.9v.9c.4-.7 1.2-1.1 2-1.1 1.4 0 2.3 1 2.3 2.5v5.2c0 4.7-3.7 8.1-8.6 8.1h-3.4C10 28.1 6 24.6 6 20.4z'/%3E%3Cpath d='M11.8 12.4v3.6M17.5 12.2v3.6M23.2 12.5v3.4M6.4 17.4c2.3-1.6 5.4-1.6 7.4.4' fill='none'/%3E%3C/g%3E%3C/svg%3E") 16 12, pointer; }
|
| 246 |
-
body.destroy.tool-burn #gl { cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath d='M16 3c1 5 7.5 8 7.5 15.5a7.5 7.5 0 0 1-15 0c0-4.2 2-6.5 3.2-8.6.4 3 1.8 4.3 3 4.4-1-4.2 0-8.3 1.3-11.3z' fill='%23ff8a2a' stroke='%231a120c' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M16 15c.6 3 3.6 4.4 3.6 7.6a3.6 3.6 0 0 1-7.2 0c0-2.2 2-4.4 3.6-7.6z' fill='%23ffd56a'/%3E%3C/svg%3E") 16 4, pointer; }
|
| 247 |
-
body.destroy.tool-water #gl { cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath d='M16 3C12.4 9.6 8 13.8 8 19.4a8 8 0 0 0 16 0C24 13.8 19.6 9.6 16 3z' fill='%2378b8e8' stroke='%230c1620' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M12.4 19.6a3.8 3.8 0 0 0 3 3.6' fill='none' stroke='%23e8f6ff' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E") 16 27, pointer; }
|
| 248 |
-
.touch-pop { position: fixed; z-index: 8; width: 44px; height: 44px; margin: -22px 0 0 -22px; pointer-events: none; background: center / contain no-repeat; animation: pop .65s ease-out forwards; }
|
| 249 |
-
.touch-pop.crush { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='-1 -4 34 38'%3E%3Cg fill='%23f3e6cf' stroke='%231a120c' stroke-width='1.6' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M6 13c0-2 1.4-3.2 3-3.2s2.8 1.1 2.8 2.6c0-1.9 1.3-3 2.9-3s2.8 1.1 2.8 2.8c.1-1.6 1.4-2.6 2.9-2.6 1.6 0 2.8 1.2 2.8 2.9v.9c.4-.7 1.2-1.1 2-1.1 1.4 0 2.3 1 2.3 2.5v5.2c0 4.7-3.7 8.1-8.6 8.1h-3.4C10 28.1 6 24.6 6 20.4z'/%3E%3Cpath d='M11.8 12.4v3.6M17.5 12.2v3.6M23.2 12.5v3.4M6.4 17.4c2.3-1.6 5.4-1.6 7.4.4' fill='none'/%3E%3C/g%3E%3C/svg%3E"); }
|
| 250 |
-
.touch-pop.burn { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath d='M16 3c1 5 7.5 8 7.5 15.5a7.5 7.5 0 0 1-15 0c0-4.2 2-6.5 3.2-8.6.4 3 1.8 4.3 3 4.4-1-4.2 0-8.3 1.3-11.3z' fill='%23ff8a2a' stroke='%231a120c' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M16 15c.6 3 3.6 4.4 3.6 7.6a3.6 3.6 0 0 1-7.2 0c0-2.2 2-4.4 3.6-7.6z' fill='%23ffd56a'/%3E%3C/svg%3E"); }
|
| 251 |
-
.touch-pop.water { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath d='M16 3C12.4 9.6 8 13.8 8 19.4a8 8 0 0 0 16 0C24 13.8 19.6 9.6 16 3z' fill='%2378b8e8' stroke='%230c1620' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M12.4 19.6a3.8 3.8 0 0 0 3 3.6' fill='none' stroke='%23e8f6ff' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E"); }
|
| 252 |
-
.touch-pop.tear { display: none; }
|
| 253 |
-
@keyframes pop { 0% { transform: scale(.6); opacity: 0 } 25% { transform: scale(1.05); opacity: 1 } 100% { transform: scale(.9); opacity: 0 } }
|
| 254 |
-
.chip.tool.on { background: var(--fg); color: var(--bg); }
|
| 255 |
-
.chip:disabled { opacity: .35; cursor: default; }
|
| 256 |
-
.roll-up { flex: none; border: 1px solid var(--line-strong); background: var(--panel); color: var(--fg); min-height: 34px; }
|
| 257 |
-
.roll-up.on { background: var(--ink); color: var(--bg); border-color: var(--ink); }
|
| 258 |
-
@media (max-width: 720px) { .roll-up { padding: 8px; font-size: 10px; } }
|
| 259 |
-
@media (max-width: 720px) { body.destroy #readout { display: none; } }
|
| 260 |
-
|
| 261 |
-
.prize-badge { font-size: 11px; color: #f2c66d; border: 1px solid rgba(242,198,109,0.45); border-radius: 999px; padding: 3px 9px; text-decoration: none; white-space: nowrap; }
|
| 262 |
-
.prize-badge:hover { background: rgba(242,198,109,0.12); }
|
| 263 |
-
.scroll-pick .prize-only { font-size: 11px; padding: 3px 9px; }
|
| 264 |
-
.scroll-pick .prize-only.on { border-color: rgba(242,198,109,0.7); color: #f2c66d; }
|
| 265 |
-
.scroll-pick { flex-wrap: wrap; }
|
| 266 |
-
.sr-only { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
|
| 267 |
-
#gl:focus-visible, .viewer:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }
|
| 268 |
-
.about-foot { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; opacity: .75; }
|
| 269 |
-
.about-foot a { color: inherit; }
|
| 270 |
-
.about-foot .version { letter-spacing: .02em; }
|
| 271 |
-
|
| 272 |
-
/* ---------------------------------------------------------------- embed mode (?embed=1) */
|
| 273 |
-
#btnFull, .embed-full, .embed-gate { display: none; }
|
| 274 |
-
.embed #btnFull { display: inline-flex; }
|
| 275 |
-
.embed .embed-full { display: block; position: fixed; left: 14px; top: 56px; z-index: 30; font-size: 11px; color: var(--dim, #9a8f80); text-decoration: none; padding: 4px 8px; border-radius: 999px; background: rgba(6, 5, 5, .6); }
|
| 276 |
-
.embed .embed-full:hover, .embed .embed-full:focus-visible { color: #fff; }
|
| 277 |
-
.embed .embed-gate:not([hidden]) { display: flex; position: fixed; inset: 0; z-index: 5; align-items: center; justify-content: center; border: 0; background: transparent; color: inherit; cursor: pointer; touch-action: pan-x pan-y; }
|
| 278 |
-
.embed .embed-gate span { padding: 8px 14px; border-radius: 999px; background: rgba(6, 5, 5, .72); border: 1px solid rgba(255, 255, 255, .18); font-size: 12px; letter-spacing: .06em; }
|
| 279 |
-
.embed .embed-gate:hover span, .embed .embed-gate:focus-visible span { border-color: rgba(255, 255, 255, .5); }
|
| 280 |
-
.embed .hud.tl .kicker, .embed .hud.tl .lede, .embed .vignette { display: none; }
|
| 281 |
-
.embed .hud.tl h1 { font-size: clamp(16px, 4.2vw, 24px); line-height: 1.1; max-width: 60vw; }
|
| 282 |
-
.embed .layer-hint, .embed .layer-why { display: none; }
|
| 283 |
-
.embed .readout { display: none; }
|
| 284 |
-
.embed-nocontrols .hud, .embed-nocontrols .scrub, .embed-nocontrols .dock { display: none !important; }
|
| 285 |
-
@media (max-height: 420px) { .embed .hud.tl h1 { display: none; } }
|
| 286 |
-
.embed-code { width: 100%; box-sizing: border-box; background: rgba(255, 255, 255, .05); color: inherit; border: 1px solid rgba(255, 255, 255, .15); border-radius: 8px; padding: 8px; font-size: 11px; resize: vertical; }
|
| 287 |
-
#embedSheet label { display: block; margin: 4px 0; }
|
| 288 |
-
|
| 289 |
-
.toast { position: fixed; left: 50%; bottom: 84px; transform: translateX(-50%); z-index: 60; max-width: min(560px, 90vw); padding: 8px 14px; border-radius: 10px; background: rgba(6, 5, 5, .88); border: 1px solid rgba(255, 255, 255, .18); font-size: 12px; line-height: 1.4; }
|
| 290 |
-
.toast[hidden] { display: none; }
|
|
|
|
| 171 |
}
|
| 172 |
|
| 173 |
.credit { text-shadow: 0 0 8px #000, 0 0 2px #000; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tools/build.py
CHANGED
|
@@ -8,12 +8,6 @@ Outputs:
|
|
| 8 |
scroll.json manifest: per-winding grid sizes, byte offsets, physical sizes, atlas rects, sources
|
| 9 |
atlas_ink.jpg, atlas_ct.jpg every winding packed at one physical scale (grayscale)
|
| 10 |
hi/wNNN.jpg per-winding Vesuvius Challenge ink preview for the close-up view
|
| 11 |
-
|
| 12 |
-
Coarseness: UNROLL_STEP sets the grid step of a full build (default 3). A lighter copy of a built mesh, for phones
|
| 13 |
-
and low-power devices, needs no raw downloads:
|
| 14 |
-
python tools/build.py --coarsen 2 data/PHerc0172 writes scroll_lite.bin, scroll_lite.json, scroll_lite.i16p.gz
|
| 15 |
-
It keeps every 2nd sampled row and column (plus the last ones), so the UV mapping (rowIdx, colIdx over the same
|
| 16 |
-
gridH, gridW), the atlases, the ink layers and the flat layout are unchanged.
|
| 17 |
"""
|
| 18 |
import glob
|
| 19 |
import http.client
|
|
@@ -34,15 +28,11 @@ from PIL import Image
|
|
| 34 |
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 35 |
RAW = os.environ.get("UNROLL_RAW", f"{ROOT}/raw")
|
| 36 |
OUT = os.environ.get("UNROLL_OUT", f"{ROOT}/data")
|
| 37 |
-
B = "https://vesuvius-challenge-open-data.s3.amazonaws.com"
|
| 38 |
-
|
| 39 |
-
VOX_MM = float(os.environ.get("UNROLL_VOX_MM", "0.009362")) # scan voxel of the meshes (9.362 um for PHerc0139)
|
| 40 |
CELL_MM = 20 * VOX_MM # tifxyz scale 0.05: one grid cell is 20 voxels
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
RENUMBER = os.environ.get("UNROLL_RENUMBER") == "1" # name the windings w001.. by radius, innermost first
|
| 44 |
-
STEP = int(os.environ.get("UNROLL_STEP", "3")) # grid decimation for the browser mesh (larger is coarser)
|
| 45 |
-
PPMM = float(os.environ.get("UNROLL_PPMM", "7.5")) # atlas pixels per mm (lowered for long scrolls)
|
| 46 |
ATLAS = 4096
|
| 47 |
PAD = 6
|
| 48 |
HI_MAX = 2400
|
|
@@ -53,26 +43,13 @@ Image.MAX_IMAGE_PIXELS = None
|
|
| 53 |
|
| 54 |
def fetch_ct(w, src, shape_hw):
|
| 55 |
"""Level-3 X-ray texture (mean of the middle layers) of a winding's 9.362 um surface volume."""
|
| 56 |
-
cache = f"{RAW}/{
|
| 57 |
if os.path.exists(cache):
|
| 58 |
return np.load(cache)
|
| 59 |
seg = src["seg"]
|
| 60 |
-
sv = [s for s in src["sv"] if s.startswith(
|
| 61 |
-
|
| 62 |
-
# the level nearest the grid's size (level 3 for 9 um scans; finer scans need a coarser level)
|
| 63 |
-
lvl = 3
|
| 64 |
-
for lv in range(3, 7):
|
| 65 |
-
try:
|
| 66 |
-
mt = json.loads(urllib.request.urlopen(f"{root}/{lv}/.zarray").read())
|
| 67 |
-
except urllib.error.HTTPError:
|
| 68 |
-
break
|
| 69 |
-
lvl = lv
|
| 70 |
-
if mt["shape"][1] <= shape_hw[0] * 8:
|
| 71 |
-
break
|
| 72 |
-
base = f"{root}/{lvl}"
|
| 73 |
meta = json.loads(urllib.request.urlopen(f"{base}/.zarray").read())
|
| 74 |
-
comp = meta.get("compressor")
|
| 75 |
-
assert comp is None or comp.get("id") == "blosc", f"compressed zarr not handled: {comp}"
|
| 76 |
D, H, W = meta["shape"]
|
| 77 |
cz, ch, cw = meta["chunks"]
|
| 78 |
sep = meta.get("dimension_separator") or "."
|
|
@@ -88,9 +65,6 @@ def fetch_ct(w, src, shape_hw):
|
|
| 88 |
body = r.stdout[:-3]
|
| 89 |
if code in (b"403", b"404"):
|
| 90 |
return ij, None
|
| 91 |
-
if code == b"200" and comp:
|
| 92 |
-
import imagecodecs
|
| 93 |
-
body = imagecodecs.blosc_decode(body)
|
| 94 |
if code == b"200" and len(body) == cz * ch * cw:
|
| 95 |
raw = body
|
| 96 |
break
|
|
@@ -98,8 +72,7 @@ def fetch_ct(w, src, shape_hw):
|
|
| 98 |
else:
|
| 99 |
raise OSError(f"could not fetch {url}")
|
| 100 |
a = np.frombuffer(raw, np.uint8).reshape(cz, ch, cw)
|
| 101 |
-
|
| 102 |
-
return ij, a[lo:lo + (CT_LAYERS[1] - CT_LAYERS[0])].astype(np.float32).mean(0)
|
| 103 |
|
| 104 |
with ThreadPoolExecutor(4) as ex:
|
| 105 |
for (i, j), tile in ex.map(one, jobs):
|
|
@@ -154,7 +127,7 @@ def shelf_pack(sizes, width):
|
|
| 154 |
|
| 155 |
def main():
|
| 156 |
os.makedirs(f"{OUT}/hi", exist_ok=True)
|
| 157 |
-
dirs = sorted(glob.glob(f"{RAW}/
|
| 158 |
names = [os.path.basename(d) for d in dirs]
|
| 159 |
grids, srcs, metas = [], [], []
|
| 160 |
for d in dirs:
|
|
@@ -163,33 +136,7 @@ def main():
|
|
| 163 |
grids.append((x, y, z, v))
|
| 164 |
srcs.append(json.load(open(f"{d}/source.json")))
|
| 165 |
metas.append(json.load(open(f"{d}/meta.json")))
|
| 166 |
-
if RENUMBER:
|
| 167 |
-
# order by median distance from a first axis fit through all windings: the innermost becomes w001
|
| 168 |
-
allg = [np.concatenate([g[i][g[3]] for g in grids]) for i in range(3)]
|
| 169 |
-
ax0, ay0 = np.polyfit(allg[2], allg[0], 1), np.polyfit(allg[2], allg[1], 1)
|
| 170 |
-
rad = [float(np.median(np.hypot(g[0][g[3]] - np.polyval(ax0, g[2][g[3]]), g[1][g[3]] - np.polyval(ay0, g[2][g[3]]))))
|
| 171 |
-
for g in grids]
|
| 172 |
-
order = np.argsort(rad)
|
| 173 |
-
grids, srcs, metas, dirs = ([L[i] for i in order] for L in (grids, srcs, metas, dirs))
|
| 174 |
-
names = [f"w{k + 1:03d}" for k in range(len(dirs))]
|
| 175 |
-
print("renumbered by radius:", [(n, srcs[k]["seg"], round(rad[order[k]])) for k, n in enumerate(names)])
|
| 176 |
ax, ay = axis_fit(grids)
|
| 177 |
-
rad2 = [float(np.median(np.hypot(g[0][g[3]] - np.polyval(ax, g[2][g[3]]), g[1][g[3]] - np.polyval(ay, g[2][g[3]])))) * VOX_MM
|
| 178 |
-
for g in grids]
|
| 179 |
-
if RENUMBER:
|
| 180 |
-
# second pass on the refined axis, so the ids follow the radius exactly
|
| 181 |
-
order = np.argsort(rad2)
|
| 182 |
-
grids, srcs, metas, dirs = ([L[i] for i in order] for L in (grids, srcs, metas, dirs))
|
| 183 |
-
rad2 = [rad2[i] for i in order]
|
| 184 |
-
ax, ay = axis_fit(grids)
|
| 185 |
-
print("winding order check (id, median radius mm):", [(n, round(r, 1)) for n, r in zip(names, rad2)])
|
| 186 |
-
bad = sum(1 for a, b in zip(rad2, rad2[1:]) if b < a)
|
| 187 |
-
print("radius decreases between consecutive ids:", bad)
|
| 188 |
-
global PPMM
|
| 189 |
-
if "UNROLL_PPMM" not in os.environ:
|
| 190 |
-
cells = sum(g[3].shape[0] * g[3].shape[1] for g in grids) * CELL_MM * CELL_MM
|
| 191 |
-
PPMM = min(PPMM, float(np.sqrt(0.62 * ATLAS * ATLAS / cells)))
|
| 192 |
-
print("PPMM", round(PPMM, 3))
|
| 193 |
allz = np.concatenate([g[2][g[3]] for g in grids])
|
| 194 |
zc = float((allz.min() + allz.max()) / 2)
|
| 195 |
cx0, cy0 = float(np.polyval(ax, zc)), float(np.polyval(ay, zc))
|
|
@@ -201,21 +148,11 @@ def main():
|
|
| 201 |
H, W = v.shape
|
| 202 |
tw, th = int(round(W * CELL_MM * PPMM)), int(round(H * CELL_MM * PPMM))
|
| 203 |
sizes[w] = (tw, th)
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
hi.save(f"{OUT}/hi/{w}.jpg", quality=84, optimize=True, progressive=True)
|
| 210 |
-
if SCROLL != "PHerc0139" and os.path.exists(f"{d}/ink1um_ds8.jpg"):
|
| 211 |
-
# second published ink layer (1.129 um scan): a per-winding overlay texture plus a close-up
|
| 212 |
-
os.makedirs(f"{OUT}/ink1", exist_ok=True)
|
| 213 |
-
i1 = Image.open(f"{d}/ink1um_ds8.jpg").convert("L")
|
| 214 |
-
h1 = i1.copy()
|
| 215 |
-
h1.thumbnail((HI_MAX, HI_MAX), Image.LANCZOS)
|
| 216 |
-
h1.save(f"{OUT}/hi/{w}_1um.jpg", quality=84, optimize=True, progressive=True)
|
| 217 |
-
i1.thumbnail((1200, 1200), Image.LANCZOS)
|
| 218 |
-
i1.save(f"{OUT}/ink1/{w}.jpg", quality=80, optimize=True, progressive=True)
|
| 219 |
ct = fetch_ct(w, src, (H, W))
|
| 220 |
valid = ct > 0
|
| 221 |
lo, hi_ = np.percentile(ct[valid], [1.5, 99.5]) if valid.any() else (0, 1)
|
|
@@ -227,15 +164,12 @@ def main():
|
|
| 227 |
if used > ATLAS:
|
| 228 |
sys.exit(f"atlas overflow: {used} px > {ATLAS}; lower PPMM")
|
| 229 |
atlas_h = 1 << int(np.ceil(np.log2(used)))
|
| 230 |
-
atlases = {"ct": Image.new("L", (ATLAS, atlas_h), 0)}
|
| 231 |
-
if not NO_INK:
|
| 232 |
-
atlases["ink"] = Image.new("L", (ATLAS, atlas_h), 0)
|
| 233 |
for w in names:
|
| 234 |
-
|
| 235 |
-
|
| 236 |
for k, im in atlases.items():
|
| 237 |
im.save(f"{OUT}/atlas_{k}.jpg", quality=88, optimize=True, progressive=True)
|
| 238 |
-
im.save(f"{OUT}/atlas_{k}.webp", quality=82, method=6)
|
| 239 |
print("atlas", ATLAS, atlas_h, "used rows", used)
|
| 240 |
|
| 241 |
# meshes
|
|
@@ -253,9 +187,7 @@ def main():
|
|
| 253 |
px = np.where(vs, xs - cx0, 0)
|
| 254 |
py = np.where(vs, ys - cy0, 0)
|
| 255 |
pz = np.where(vs, zs - zc, 0)
|
| 256 |
-
|
| 257 |
-
assert np.abs(st).max() < 32767, "coordinates overflow int16: use a coarser mesh"
|
| 258 |
-
arr = st.astype(np.int16)
|
| 259 |
arr[~vs] = -32768
|
| 260 |
b = arr.tobytes()
|
| 261 |
blobs.append(b)
|
|
@@ -270,16 +202,17 @@ def main():
|
|
| 270 |
"gridH": int(H), "gridW": int(W),
|
| 271 |
"offset": offset, "bytes": len(b),
|
| 272 |
"widthMM": round(W * CELL_MM, 2), "heightMM": round(H * CELL_MM, 2),
|
| 273 |
-
"areaCM2": round(
|
| 274 |
"radiusMM": round(float(np.median(r)), 2),
|
| 275 |
"atlas": [x0 / ATLAS, y0 / atlas_h, (x0 + tw) / ATLAS, (y0 + th) / atlas_h],
|
| 276 |
-
|
|
|
|
| 277 |
"mesh_source": src["mesh"],
|
| 278 |
})
|
| 279 |
offset += len(b)
|
| 280 |
open(f"{OUT}/scroll.bin", "wb").write(b"".join(blobs))
|
| 281 |
manifest = {
|
| 282 |
-
"scroll":
|
| 283 |
"voxelMM": VOX_MM, "cellMM": CELL_MM,
|
| 284 |
"axis": {"centre": [cx0, cy0, zc], "dxdz": ax[0], "dydz": ay[0]},
|
| 285 |
"atlas": {"w": ATLAS, "h": atlas_h, "ppmm": PPMM},
|
|
@@ -287,41 +220,11 @@ def main():
|
|
| 287 |
"totalWidthMM": round(sum(wi["widthMM"] for wi in wins), 1),
|
| 288 |
"totalAreaCM2": round(sum(wi["areaCM2"] for wi in wins), 1),
|
| 289 |
"vertices": int(offset // 6),
|
| 290 |
-
"step": STEP,
|
| 291 |
}
|
| 292 |
json.dump(manifest, open(f"{OUT}/scroll.json", "w"), separators=(",", ":"))
|
| 293 |
print("vertices", manifest["vertices"], "bin MB", offset / 1e6, "strip m", manifest["totalWidthMM"] / 1000,
|
| 294 |
"area cm2", manifest["totalAreaCM2"])
|
| 295 |
|
| 296 |
|
| 297 |
-
def coarsen(folder, k, name="scroll_lite"):
|
| 298 |
-
"""Keep every k-th sampled row and column of each winding (always the first and last) of a built mesh."""
|
| 299 |
-
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 300 |
-
from pack_bin import pack
|
| 301 |
-
man = json.load(open(f"{folder}/scroll.json"))
|
| 302 |
-
raw = np.fromfile(f"{folder}/scroll.bin", "<i2")
|
| 303 |
-
blobs, offset, wins = [], 0, []
|
| 304 |
-
for w in man["windings"]:
|
| 305 |
-
R, C = w["rows"], w["cols"]
|
| 306 |
-
a = raw[w["offset"] // 2:(w["offset"] + w["bytes"]) // 2].reshape(R, C, 3)
|
| 307 |
-
ri = sorted(set(range(0, R, k)) | {R - 1})
|
| 308 |
-
ci = sorted(set(range(0, C, k)) | {C - 1})
|
| 309 |
-
b = np.ascontiguousarray(a[np.ix_(ri, ci)]).astype("<i2").tobytes()
|
| 310 |
-
blobs.append(b)
|
| 311 |
-
wins.append({**w, "rows": len(ri), "cols": len(ci), "rowIdx": [w["rowIdx"][i] for i in ri],
|
| 312 |
-
"colIdx": [w["colIdx"][i] for i in ci], "offset": offset, "bytes": len(b)})
|
| 313 |
-
offset += len(b)
|
| 314 |
-
data = b"".join(blobs)
|
| 315 |
-
out = {**man, "windings": wins, "vertices": int(offset // 6), "step": man.get("step", 3) * k}
|
| 316 |
-
open(f"{folder}/{name}.bin", "wb").write(data)
|
| 317 |
-
open(f"{folder}/{name}.i16p.gz", "wb").write(pack(data))
|
| 318 |
-
json.dump(out, open(f"{folder}/{name}.json", "w"), separators=(",", ":"))
|
| 319 |
-
print("vertices", man["vertices"], "->", out["vertices"], "bin bytes", len(raw) * 2, "->", len(data),
|
| 320 |
-
"packed", os.path.getsize(f"{folder}/{name}.i16p.gz"))
|
| 321 |
-
|
| 322 |
-
|
| 323 |
if __name__ == "__main__":
|
| 324 |
-
|
| 325 |
-
coarsen(sys.argv[3].rstrip("/"), int(sys.argv[2]))
|
| 326 |
-
else:
|
| 327 |
-
main()
|
|
|
|
| 8 |
scroll.json manifest: per-winding grid sizes, byte offsets, physical sizes, atlas rects, sources
|
| 9 |
atlas_ink.jpg, atlas_ct.jpg every winding packed at one physical scale (grayscale)
|
| 10 |
hi/wNNN.jpg per-winding Vesuvius Challenge ink preview for the close-up view
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
"""
|
| 12 |
import glob
|
| 13 |
import http.client
|
|
|
|
| 28 |
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 29 |
RAW = os.environ.get("UNROLL_RAW", f"{ROOT}/raw")
|
| 30 |
OUT = os.environ.get("UNROLL_OUT", f"{ROOT}/data")
|
| 31 |
+
B = "https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com"
|
| 32 |
+
VOX_MM = 0.009362 # 9.362 um scan voxel
|
|
|
|
| 33 |
CELL_MM = 20 * VOX_MM # tifxyz scale 0.05: one grid cell is 20 voxels
|
| 34 |
+
STEP = 3 # grid decimation for the browser mesh
|
| 35 |
+
PPMM = 7.5 # atlas pixels per mm
|
|
|
|
|
|
|
|
|
|
| 36 |
ATLAS = 4096
|
| 37 |
PAD = 6
|
| 38 |
HI_MAX = 2400
|
|
|
|
| 43 |
|
| 44 |
def fetch_ct(w, src, shape_hw):
|
| 45 |
"""Level-3 X-ray texture (mean of the middle layers) of a winding's 9.362 um surface volume."""
|
| 46 |
+
cache = f"{RAW}/{w}/ct_l3.npy"
|
| 47 |
if os.path.exists(cache):
|
| 48 |
return np.load(cache)
|
| 49 |
seg = src["seg"]
|
| 50 |
+
sv = [s for s in src["sv"] if s.startswith("9.362um")][0]
|
| 51 |
+
base = f"{B}/PHerc0139/segments/{seg}/surface-volumes/{sv}/3"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
meta = json.loads(urllib.request.urlopen(f"{base}/.zarray").read())
|
|
|
|
|
|
|
| 53 |
D, H, W = meta["shape"]
|
| 54 |
cz, ch, cw = meta["chunks"]
|
| 55 |
sep = meta.get("dimension_separator") or "."
|
|
|
|
| 65 |
body = r.stdout[:-3]
|
| 66 |
if code in (b"403", b"404"):
|
| 67 |
return ij, None
|
|
|
|
|
|
|
|
|
|
| 68 |
if code == b"200" and len(body) == cz * ch * cw:
|
| 69 |
raw = body
|
| 70 |
break
|
|
|
|
| 72 |
else:
|
| 73 |
raise OSError(f"could not fetch {url}")
|
| 74 |
a = np.frombuffer(raw, np.uint8).reshape(cz, ch, cw)
|
| 75 |
+
return ij, a[CT_LAYERS[0]:CT_LAYERS[1]].astype(np.float32).mean(0)
|
|
|
|
| 76 |
|
| 77 |
with ThreadPoolExecutor(4) as ex:
|
| 78 |
for (i, j), tile in ex.map(one, jobs):
|
|
|
|
| 127 |
|
| 128 |
def main():
|
| 129 |
os.makedirs(f"{OUT}/hi", exist_ok=True)
|
| 130 |
+
dirs = sorted(glob.glob(f"{RAW}/w*"))
|
| 131 |
names = [os.path.basename(d) for d in dirs]
|
| 132 |
grids, srcs, metas = [], [], []
|
| 133 |
for d in dirs:
|
|
|
|
| 136 |
grids.append((x, y, z, v))
|
| 137 |
srcs.append(json.load(open(f"{d}/source.json")))
|
| 138 |
metas.append(json.load(open(f"{d}/meta.json")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
ax, ay = axis_fit(grids)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
allz = np.concatenate([g[2][g[3]] for g in grids])
|
| 141 |
zc = float((allz.min() + allz.max()) / 2)
|
| 142 |
cx0, cy0 = float(np.polyval(ax, zc)), float(np.polyval(ay, zc))
|
|
|
|
| 148 |
H, W = v.shape
|
| 149 |
tw, th = int(round(W * CELL_MM * PPMM)), int(round(H * CELL_MM * PPMM))
|
| 150 |
sizes[w] = (tw, th)
|
| 151 |
+
ink = Image.open(f"{d}/ink2um_ds8.jpg").convert("L")
|
| 152 |
+
ink_tiles[w] = local_contrast(ink.resize((tw, th), Image.LANCZOS), 2.2 * PPMM)
|
| 153 |
+
hi = ink.copy()
|
| 154 |
+
hi.thumbnail((HI_MAX, HI_MAX), Image.LANCZOS)
|
| 155 |
+
hi.save(f"{OUT}/hi/{w}.jpg", quality=84, optimize=True, progressive=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
ct = fetch_ct(w, src, (H, W))
|
| 157 |
valid = ct > 0
|
| 158 |
lo, hi_ = np.percentile(ct[valid], [1.5, 99.5]) if valid.any() else (0, 1)
|
|
|
|
| 164 |
if used > ATLAS:
|
| 165 |
sys.exit(f"atlas overflow: {used} px > {ATLAS}; lower PPMM")
|
| 166 |
atlas_h = 1 << int(np.ceil(np.log2(used)))
|
| 167 |
+
atlases = {"ink": Image.new("L", (ATLAS, atlas_h), 0), "ct": Image.new("L", (ATLAS, atlas_h), 0)}
|
|
|
|
|
|
|
| 168 |
for w in names:
|
| 169 |
+
atlases["ink"].paste(ink_tiles[w], pos[w])
|
| 170 |
+
atlases["ct"].paste(ct_tiles[w], pos[w])
|
| 171 |
for k, im in atlases.items():
|
| 172 |
im.save(f"{OUT}/atlas_{k}.jpg", quality=88, optimize=True, progressive=True)
|
|
|
|
| 173 |
print("atlas", ATLAS, atlas_h, "used rows", used)
|
| 174 |
|
| 175 |
# meshes
|
|
|
|
| 187 |
px = np.where(vs, xs - cx0, 0)
|
| 188 |
py = np.where(vs, ys - cy0, 0)
|
| 189 |
pz = np.where(vs, zs - zc, 0)
|
| 190 |
+
arr = np.stack([px, py, pz], -1).round().astype(np.int16)
|
|
|
|
|
|
|
| 191 |
arr[~vs] = -32768
|
| 192 |
b = arr.tobytes()
|
| 193 |
blobs.append(b)
|
|
|
|
| 202 |
"gridH": int(H), "gridW": int(W),
|
| 203 |
"offset": offset, "bytes": len(b),
|
| 204 |
"widthMM": round(W * CELL_MM, 2), "heightMM": round(H * CELL_MM, 2),
|
| 205 |
+
"areaCM2": round(meta["area_vx2"] * VOX_MM * VOX_MM / 100, 2),
|
| 206 |
"radiusMM": round(float(np.median(r)), 2),
|
| 207 |
"atlas": [x0 / ATLAS, y0 / atlas_h, (x0 + tw) / ATLAS, (y0 + th) / atlas_h],
|
| 208 |
+
"hi": f"hi/{w}.jpg",
|
| 209 |
+
"ink_source": [f for f, _ in src["ink"] if "2.399um" in f][0],
|
| 210 |
"mesh_source": src["mesh"],
|
| 211 |
})
|
| 212 |
offset += len(b)
|
| 213 |
open(f"{OUT}/scroll.bin", "wb").write(b"".join(blobs))
|
| 214 |
manifest = {
|
| 215 |
+
"scroll": "PHerc0139",
|
| 216 |
"voxelMM": VOX_MM, "cellMM": CELL_MM,
|
| 217 |
"axis": {"centre": [cx0, cy0, zc], "dxdz": ax[0], "dydz": ay[0]},
|
| 218 |
"atlas": {"w": ATLAS, "h": atlas_h, "ppmm": PPMM},
|
|
|
|
| 220 |
"totalWidthMM": round(sum(wi["widthMM"] for wi in wins), 1),
|
| 221 |
"totalAreaCM2": round(sum(wi["areaCM2"] for wi in wins), 1),
|
| 222 |
"vertices": int(offset // 6),
|
|
|
|
| 223 |
}
|
| 224 |
json.dump(manifest, open(f"{OUT}/scroll.json", "w"), separators=(",", ":"))
|
| 225 |
print("vertices", manifest["vertices"], "bin MB", offset / 1e6, "strip m", manifest["totalWidthMM"] / 1000,
|
| 226 |
"area cm2", manifest["totalAreaCM2"])
|
| 227 |
|
| 228 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
if __name__ == "__main__":
|
| 230 |
+
main()
|
|
|
|
|
|
|
|
|
tools/fetch.py
CHANGED
|
@@ -14,14 +14,10 @@ import urllib.parse
|
|
| 14 |
import urllib.request
|
| 15 |
from concurrent.futures import ThreadPoolExecutor
|
| 16 |
|
| 17 |
-
B = "https://vesuvius-challenge-open-data.s3.amazonaws.com"
|
| 18 |
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 19 |
RAW = os.environ.get("UNROLL_RAW", f"{ROOT}/raw")
|
| 20 |
-
SCROLL =
|
| 21 |
-
MESH_RES = os.environ.get("UNROLL_MESH_RES", "9.362um") # which tifxyz to take (the coarse scan keeps int16 coordinates in range)
|
| 22 |
-
MESH_VOL = os.environ.get("UNROLL_MESH_VOL", "") # optional volume id, when a segment has two meshes at one resolution
|
| 23 |
-
INK_RES = os.environ.get("UNROLL_INK_RES", "") # which ink preview to call ink2um_ds8.jpg ("" = the non-1.129 um one, newest model)
|
| 24 |
-
NO_INK = os.environ.get("UNROLL_NO_INK") == "1" # X-ray only: fetch no ink at all
|
| 25 |
|
| 26 |
|
| 27 |
def get(url, tries=5):
|
|
@@ -77,36 +73,21 @@ def main():
|
|
| 77 |
inv = list(ex.map(segment, segs))
|
| 78 |
json.dump(inv, open(f"{RAW}/inv_{SCROLL}.json", "w"), indent=1)
|
| 79 |
jobs = []
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
if use_w and not m:
|
| 85 |
continue
|
| 86 |
-
|
| 87 |
-
if not meshes:
|
| 88 |
-
continue
|
| 89 |
-
inks = sorted(s.get("ink_ds", []))
|
| 90 |
-
main_ink = [f for f in inks if (INK_RES in f if INK_RES else "1.129um" not in f)]
|
| 91 |
-
if not NO_INK and not main_ink:
|
| 92 |
-
print("skip", s["seg"], "(no ink preview)")
|
| 93 |
-
continue
|
| 94 |
-
# wNNN when the segment names carry unique winding numbers; otherwise the segment timestamp (build renumbers by radius)
|
| 95 |
-
w = m.group(1) if use_w else "s" + s["seg"].split("-")[0]
|
| 96 |
-
mesh = meshes[0]
|
| 97 |
d = f"{RAW}/{w}"
|
| 98 |
os.makedirs(d, exist_ok=True)
|
| 99 |
base = f"{B}/{SCROLL}/segments/{s['seg']}"
|
| 100 |
for f in ("x.tif", "y.tif", "z.tif", "meta.json"):
|
| 101 |
jobs.append((f"{base}/mesh/{mesh}/{f}", f"{d}/{f}"))
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
if "1.129um" in f:
|
| 107 |
-
jobs.append((f"{base}/ink-detection/downsampled/{f}", f"{d}/ink1um_ds8.jpg"))
|
| 108 |
-
json.dump({"seg": s["seg"], "w": w, "mesh": mesh, "sv": s.get("sv"), "ink": s.get("ink"),
|
| 109 |
-
"ink_ds": None if NO_INK else main_ink[-1]},
|
| 110 |
open(f"{d}/source.json", "w"), indent=1)
|
| 111 |
|
| 112 |
def fetch(job):
|
|
|
|
| 14 |
import urllib.request
|
| 15 |
from concurrent.futures import ThreadPoolExecutor
|
| 16 |
|
| 17 |
+
B = "https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com"
|
| 18 |
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 19 |
RAW = os.environ.get("UNROLL_RAW", f"{ROOT}/raw")
|
| 20 |
+
SCROLL = "PHerc0139"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
|
| 23 |
def get(url, tries=5):
|
|
|
|
| 73 |
inv = list(ex.map(segment, segs))
|
| 74 |
json.dump(inv, open(f"{RAW}/inv_{SCROLL}.json", "w"), indent=1)
|
| 75 |
jobs = []
|
| 76 |
+
for s in inv:
|
| 77 |
+
m = re.search(r"-(w\d{3})_", s["seg"])
|
| 78 |
+
meshes = [x for x in s.get("mesh", []) if "9.362um" in x]
|
| 79 |
+
if not m or not meshes:
|
|
|
|
| 80 |
continue
|
| 81 |
+
w, mesh = m.group(1), meshes[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
d = f"{RAW}/{w}"
|
| 83 |
os.makedirs(d, exist_ok=True)
|
| 84 |
base = f"{B}/{SCROLL}/segments/{s['seg']}"
|
| 85 |
for f in ("x.tif", "y.tif", "z.tif", "meta.json"):
|
| 86 |
jobs.append((f"{base}/mesh/{mesh}/{f}", f"{d}/{f}"))
|
| 87 |
+
for f in s.get("ink_ds", []):
|
| 88 |
+
tag = "ink1um" if "1.129um" in f else "ink2um"
|
| 89 |
+
jobs.append((f"{base}/ink-detection/downsampled/{f}", f"{d}/{tag}_ds8.jpg"))
|
| 90 |
+
json.dump({"seg": s["seg"], "w": w, "mesh": mesh, "sv": s.get("sv"), "ink": s.get("ink")},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
open(f"{d}/source.json", "w"), indent=1)
|
| 92 |
|
| 93 |
def fetch(job):
|
tools/gpu_layer.py
CHANGED
|
@@ -78,7 +78,7 @@ def main():
|
|
| 78 |
"calls": g["calls"],
|
| 79 |
"overlap": args["overlap"],
|
| 80 |
"started": receipt["calls"][0]["started"],
|
| 81 |
-
"meta": (f"
|
| 82 |
f"{p:,} patches of 128×128 px, overlap {args['overlap']}"
|
| 83 |
f"{' (patch seams visible)' if args['overlap'] < 0.5 else ''} · {g['measured']:.1f} s of GPU in "
|
| 84 |
f"{g['calls']} call{'s' if g['calls'] > 1 else ''} · {receipt['calls'][0]['started'][:10]} · "
|
|
@@ -92,12 +92,12 @@ def main():
|
|
| 92 |
"windings": wins,
|
| 93 |
"totals": {"windings": len(wins), "patches": tot_patches, "gpuSeconds": round(tot_measured, 1),
|
| 94 |
"calls": tot_calls, "device": device},
|
| 95 |
-
"about": (f"<b>
|
|
|
|
| 96 |
f"<a href=\"https://huggingface.co/scrollprize/ink_9um\" target=\"_blank\" rel=\"noopener\">"
|
| 97 |
-
f"scrollprize/ink_9um</a> model
|
| 98 |
-
f"and processed the output for this viewer: {tot_patches:,} patches in "
|
| 99 |
f"{tot_measured:.0f} s of measured GPU time on half an NVIDIA RTX PRO 6000 Blackwell. It reads the "
|
| 100 |
-
f"9.
|
| 101 |
f"is noisier. {say(full)} ran with patch overlap 0.5; {say(low)} ran with less overlap, so "
|
| 102 |
f"their 128 px patch grid shows as faint square tiles. That grid is a stitching artifact, not ink. "
|
| 103 |
f"Click a cyan winding to compare the two maps."),
|
|
|
|
| 78 |
"calls": g["calls"],
|
| 79 |
"overlap": args["overlap"],
|
| 80 |
"started": receipt["calls"][0]["started"],
|
| 81 |
+
"meta": (f"ZeroGPU run: scrollprize/ink_9um (seed 42, step 75000) on the 9.362 µm scan · "
|
| 82 |
f"{p:,} patches of 128×128 px, overlap {args['overlap']}"
|
| 83 |
f"{' (patch seams visible)' if args['overlap'] < 0.5 else ''} · {g['measured']:.1f} s of GPU in "
|
| 84 |
f"{g['calls']} call{'s' if g['calls'] > 1 else ''} · {receipt['calls'][0]['started'][:10]} · "
|
|
|
|
| 92 |
"windings": wins,
|
| 93 |
"totals": {"windings": len(wins), "patches": tot_patches, "gpuSeconds": round(tot_measured, 1),
|
| 94 |
"calls": tot_calls, "device": device},
|
| 95 |
+
"about": (f"<b>The cyan band</b> is ours. We used all {tot_calls} of a free Hugging Face account's daily "
|
| 96 |
+
f"ZeroGPU runs (the limit is 8; most of the daily GPU-time quota was left over) to run the public "
|
| 97 |
f"<a href=\"https://huggingface.co/scrollprize/ink_9um\" target=\"_blank\" rel=\"noopener\">"
|
| 98 |
+
f"scrollprize/ink_9um</a> model over windings {ids[0]} to {ids[-1]}: {tot_patches:,} patches in "
|
|
|
|
| 99 |
f"{tot_measured:.0f} s of measured GPU time on half an NVIDIA RTX PRO 6000 Blackwell. It reads the "
|
| 100 |
+
f"9.362 µm scan, about four times coarser than the 2.4 µm scan behind the amber layer, so its ink map "
|
| 101 |
f"is noisier. {say(full)} ran with patch overlap 0.5; {say(low)} ran with less overlap, so "
|
| 102 |
f"their 128 px patch grid shows as faint square tiles. That grid is a stitching artifact, not ink. "
|
| 103 |
f"Click a cyan winding to compare the two maps."),
|