Spaces:
Running
Running
Snapped Labels viewer v1
Browse files- CONNECT.md +119 -0
- LICENSE +201 -0
- NOTICE +12 -0
- README.md +29 -3
- app.js +276 -0
- connect.html +87 -0
- connect.json +31 -0
- index.html +185 -17
- style.css +76 -25
CONNECT.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Connect your tool: Snapped Labels
|
| 2 |
+
|
| 3 |
+
The snapped meshes, their records and the CT they sit on read straight from these URLs, logged out. Every segment is a tifxyz folder, so VC3D, villa and anything that reads tifxyz opens it as it is.
|
| 4 |
+
|
| 5 |
+
Checked 2026-10-27. Generated from `connect.json` by `kit spaceconnect` in [ChaseHendrick/Scrolls](https://github.com/ChaseHendrick/Scrolls).
|
| 6 |
+
|
| 7 |
+
## Snapped labels: every segment, with index.json and checksums
|
| 8 |
+
|
| 9 |
+
`https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels`
|
| 10 |
+
|
| 11 |
+
License: CC BY-NC 4.0 (derived from Vesuvius Challenge open data). One folder per segment, SCROLL/SEGMENT/: snapped.tifxyz, abstain.tif, confidence.tif, reason.tif, source.tif (finesnap), release_mask.tif and release.json (source URLs, sha256 of every file, summary). index.json lists every segment; viewer/ holds the page's cross sections.
|
| 12 |
+
|
| 13 |
+
### Python (huggingface_hub) (tested 2026-10-24)
|
| 14 |
+
|
| 15 |
+
```python
|
| 16 |
+
# pip install huggingface_hub
|
| 17 |
+
from huggingface_hub import hf_hub_download, snapshot_download
|
| 18 |
+
|
| 19 |
+
path = hf_hub_download("SharpMeow/vesuvius-snapped-labels", "index.json", repo_type="dataset") # one file, cached
|
| 20 |
+
folder = snapshot_download("SharpMeow/vesuvius-snapped-labels", repo_type="dataset")
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
### Command line (hf) (tested 2026-10-24)
|
| 24 |
+
|
| 25 |
+
```bash
|
| 26 |
+
pip install -U huggingface_hub
|
| 27 |
+
hf download SharpMeow/vesuvius-snapped-labels index.json --repo-type dataset --local-dir vesuvius-snapped-labels
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
### curl (tested 2026-10-24)
|
| 31 |
+
|
| 32 |
+
```bash
|
| 33 |
+
curl -L -O https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/index.json
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
## One snapped mesh (tifxyz): PHerc0841 w00
|
| 37 |
+
|
| 38 |
+
`https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/PHerc0841/20260220213127-w00/snapped.tifxyz`
|
| 39 |
+
|
| 40 |
+
License: CC BY-NC 4.0 (derived from Vesuvius Challenge open data). Same grid and voxel frame as the published mesh it came from (release.json names it), so the two line up vertex for vertex; -1 where there is no vertex or it was held back.
|
| 41 |
+
|
| 42 |
+
### Python (numpy, tifffile) (tested 2026-10-24)
|
| 43 |
+
|
| 44 |
+
```python
|
| 45 |
+
# pip install numpy tifffile
|
| 46 |
+
import io, json, urllib.request
|
| 47 |
+
import numpy as np, tifffile
|
| 48 |
+
|
| 49 |
+
T = "https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/PHerc0841/20260220213127-w00/snapped.tifxyz"
|
| 50 |
+
meta = json.load(urllib.request.urlopen(T + "/meta.json")) # scale, bbox, format
|
| 51 |
+
get = lambda c: tifffile.imread(io.BytesIO(urllib.request.urlopen(f"{T}/{c}.tif").read()))
|
| 52 |
+
xyz = np.stack([get(c) for c in "xyz"], -1) # xyz[i, j] = (x, y, z) in scan voxels; -1 = no surface
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
## The CT that mesh is in, PHerc0841 9.366 um
|
| 56 |
+
|
| 57 |
+
`https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr`
|
| 58 |
+
|
| 59 |
+
License: CC BY-NC 4.0 (Vesuvius Challenge open data). Read live from the open-data bucket, never copied here. Mesh coordinates are level-0 voxels of this scan.
|
| 60 |
+
|
| 61 |
+
### Python (zarr) (tested 2026-10-24)
|
| 62 |
+
|
| 63 |
+
```python
|
| 64 |
+
# pip install "zarr>=3" fsspec aiohttp
|
| 65 |
+
import zarr
|
| 66 |
+
|
| 67 |
+
root = zarr.open_group("https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr", mode="r") # OME-Zarr, zarr v2
|
| 68 |
+
a = root["2"] # level 0 is full resolution; level L is 2**L times coarser
|
| 69 |
+
print(a.shape, a.dtype, a.chunks)
|
| 70 |
+
block = a[3600:3664, 600:664, 500:564] # (z, y, x); reads only the chunks it touches
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
### villa's vesuvius package (tested 2026-10-24)
|
| 74 |
+
|
| 75 |
+
```python
|
| 76 |
+
# pip install "vesuvius[volume-only]"
|
| 77 |
+
from vesuvius import Volume
|
| 78 |
+
|
| 79 |
+
v = Volume(type="zarr", path="https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr/2") # one pyramid level
|
| 80 |
+
block = v[3600:3664, 600:664, 500:564]
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
### VC3D (from villa's source)
|
| 84 |
+
|
| 85 |
+
```text
|
| 86 |
+
In VC3D (villa's volume-cartographer), open or create a project, then
|
| 87 |
+
File > Attach Remote Zarr... and paste:
|
| 88 |
+
|
| 89 |
+
https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr
|
| 90 |
+
|
| 91 |
+
It accepts http, https and s3 OME-Zarr roots.
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
### neuroglancer (tested 2026-10-24)
|
| 95 |
+
|
| 96 |
+
```text
|
| 97 |
+
https://neuroglancer-demo.appspot.com/#!%7B%22layers%22%3A%5B%7B%22type%22%3A%22image%22%2C%22source%22%3A%22zarr%3A%2F%2Fhttps%3A%2F%2Fvesuvius-challenge-open-data.s3.us-east-1.amazonaws.com%2FPHerc0841%2Fvolumes%2F20250821151531-9.366um-1.2m-113keV-masked.zarr%2F%22%2C%22name%22%3A%22The%20CT%20that%20mesh%20is%20in%2C%20PHerc0841%209.366%20um%22%7D%5D%2C%22layout%22%3A%224panel%22%7D
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
### curl (tested 2026-10-24)
|
| 101 |
+
|
| 102 |
+
```bash
|
| 103 |
+
curl -s https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr/.zattrs # OME multiscales metadata
|
| 104 |
+
curl -s https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr/2/.zarray # one level: shape, chunks, dtype, codec
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
## This page's own files
|
| 108 |
+
|
| 109 |
+
`https://sharpmeow-snapped-labels.static.hf.space/`
|
| 110 |
+
|
| 111 |
+
License: Page code Apache 2.0; data as above. In the browser console, window.snappedLabels.segments() lists the index and window.snappedLabels.open(SEGMENT, Z) opens a segment at a plane; ?seg=SEGMENT&z=Z links to a view.
|
| 112 |
+
|
| 113 |
+
### Links and embed (tested 2026-10-24)
|
| 114 |
+
|
| 115 |
+
```text
|
| 116 |
+
The dataset card: https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels
|
| 117 |
+
A segment's cross sections for the viewer (float32 rows, see the .json beside it): https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/viewer/PHerc0841/20260220213127-w00.json
|
| 118 |
+
Page source: https://github.com/ChaseHendrick/Scrolls/tree/main/spaces/snapped-labels
|
| 119 |
+
```
|
LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright [yyyy] [name of copyright owner]
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
NOTICE
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Snapped Labels
|
| 2 |
+
Copyright 2026 Chase Hendrick
|
| 3 |
+
|
| 4 |
+
The page's code (index.html, app.js and style.css) is licensed under the Apache License, Version 2.0 (LICENSE).
|
| 5 |
+
|
| 6 |
+
Data
|
| 7 |
+
- Every mesh, record and cross section the page shows is read at run time from the dataset
|
| 8 |
+
SharpMeow/vesuvius-snapped-labels: published surface labels of the Vesuvius Challenge open data moved onto the
|
| 9 |
+
papyrus surface, derived from the open data and shared under CC BY-NC 4.0, like the meshes and scans they come from.
|
| 10 |
+
- CT is not stored here. Visitors' browsers read it from the Vesuvius Challenge open-data bucket
|
| 11 |
+
(https://scrollprize.org/data), CC BY-NC 4.0.
|
| 12 |
+
The Apache licence of the code does not apply to the data.
|
README.md
CHANGED
|
@@ -1,10 +1,36 @@
|
|
| 1 |
---
|
| 2 |
title: Snapped Labels
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: yellow
|
| 5 |
-
colorTo:
|
| 6 |
sdk: static
|
|
|
|
| 7 |
pinned: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: Snapped Labels
|
| 3 |
+
emoji: 🧲
|
| 4 |
colorFrom: yellow
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: static
|
| 7 |
+
app_file: index.html
|
| 8 |
pinned: false
|
| 9 |
+
license: apache-2.0
|
| 10 |
+
short_description: Vesuvius surface labels moved onto the papyrus, on live CT
|
| 11 |
+
tags:
|
| 12 |
+
- vesuvius-challenge
|
| 13 |
+
- herculaneum
|
| 14 |
+
- computed-tomography
|
| 15 |
+
- tifxyz
|
| 16 |
+
- segmentation
|
| 17 |
+
datasets:
|
| 18 |
+
- SharpMeow/vesuvius-snapped-labels
|
| 19 |
---
|
| 20 |
|
| 21 |
+
# Snapped Labels
|
| 22 |
+
|
| 23 |
+
Published Vesuvius Challenge surface labels of PHerc0139, PHerc0814, PHerc0841 and PHerc0172, each vertex moved
|
| 24 |
+
along its mesh normal onto the papyrus recto face, drawn over X-ray CT that your browser streams from the open data.
|
| 25 |
+
On fresh test sites, 91% of snapped PHerc0139 vertices sit within one voxel of the face seen in an independent
|
| 26 |
+
1.129 µm scan, against 24% for the published labels.
|
| 27 |
+
|
| 28 |
+
- **Viewer**: pick a segment and a z plane; orange is the published label, blue the snapped one, grey strokes join
|
| 29 |
+
matching points. Arrow keys step along the label and through z; `?seg=SEGMENT&z=Z` links to a view.
|
| 30 |
+
- **All segments**: every segment with its share snapped, median move and held-back count.
|
| 31 |
+
- **How it works**: the two methods, how they were measured, the files in each segment folder, and the limits.
|
| 32 |
+
- **Connect your tool**: tested snippets for the dataset, a mesh and the CT.
|
| 33 |
+
|
| 34 |
+
Data: [SharpMeow/vesuvius-snapped-labels](https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels)
|
| 35 |
+
(CC BY-NC 4.0, like the open data it comes from). Page source: `spaces/snapped-labels` in
|
| 36 |
+
[ChaseHendrick/Scrolls](https://github.com/ChaseHendrick/Scrolls). Made by Chase Hendrick.
|
app.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Snapped Labels: the snapped-label dataset on live X-ray CT. Everything is read in the browser: the dataset's index
|
| 2 |
+
// and per-segment cross sections from Hugging Face, the CT slice from the Vesuvius Challenge open-data bucket by HTTP
|
| 3 |
+
// range reads (uint8, 128^3 chunks, no compressor). ?data=URL reads another copy of the dataset; ?seg=ID&z=Z opens a
|
| 4 |
+
// segment at a plane.
|
| 5 |
+
'use strict';
|
| 6 |
+
|
| 7 |
+
const params = new URLSearchParams(location.search);
|
| 8 |
+
const DATA = (params.get('data') || 'https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main').replace(/\/+$/, '');
|
| 9 |
+
const $ = (id) => document.getElementById(id);
|
| 10 |
+
const fmt = (n) => (n == null ? 'n/a' : Number(n).toLocaleString('en-US'));
|
| 11 |
+
const pct = (a, b) => (b ? `${(100 * a / b).toFixed(1)}%` : 'n/a');
|
| 12 |
+
|
| 13 |
+
const S = { index: [], seg: null, view: null, rows: null, plane: 0, cx: 0, cy: 0, win: 192, order: [], at: 0, abort: null };
|
| 14 |
+
|
| 15 |
+
async function getJSON(url) { const r = await fetch(url, { credentials: 'omit' }); if (!r.ok) throw new Error(`${r.status} ${url}`); return r.json(); }
|
| 16 |
+
|
| 17 |
+
// ---------------------------------------------------------------- CT slices
|
| 18 |
+
|
| 19 |
+
const zarrays = new Map();
|
| 20 |
+
function zarray(vol, L) { const k = `${vol}/${L}`; if (!zarrays.has(k)) zarrays.set(k, getJSON(`${k}/.zarray`)); return zarrays.get(k); }
|
| 21 |
+
|
| 22 |
+
async function rangeGet(url, off, len, signal) {
|
| 23 |
+
const r = await fetch(url, { headers: { Range: `bytes=${off}-${off + len - 1}` }, signal, credentials: 'omit' });
|
| 24 |
+
if (r.status === 404 || r.status === 403) return null; // a chunk the masked scan leaves out
|
| 25 |
+
if (!r.ok) throw new Error(`${r.status} ${url}`);
|
| 26 |
+
return new Uint8Array(await r.arrayBuffer());
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
// A level-0 window [y0, y0+n) x [x0, x0+n) of plane z, as a Uint8Array of n*n (0 outside the scan).
|
| 30 |
+
async function ctWindow(vol, z, y0, x0, n, signal) {
|
| 31 |
+
const m = await zarray(vol, 0);
|
| 32 |
+
const [Z, Y, X] = m.shape, [cz, cy, cx] = m.chunks;
|
| 33 |
+
if (m.compressor) throw new Error('compressed scan: the viewer reads uncompressed chunks only');
|
| 34 |
+
const out = new Uint8Array(n * n);
|
| 35 |
+
if (z < 0 || z >= Z) return out;
|
| 36 |
+
const jobs = [];
|
| 37 |
+
for (let by = Math.floor(Math.max(0, y0) / cy); by * cy < Math.min(Y, y0 + n); by++) {
|
| 38 |
+
for (let bx = Math.floor(Math.max(0, x0) / cx); bx * cx < Math.min(X, x0 + n); bx++) {
|
| 39 |
+
const url = `${vol}/0/${Math.floor(z / cz)}/${by}/${bx}`;
|
| 40 |
+
jobs.push(rangeGet(url, (z % cz) * cy * cx, cy * cx, signal).then((b) => {
|
| 41 |
+
if (!b) return;
|
| 42 |
+
for (let j = 0; j < cy; j++) {
|
| 43 |
+
const yy = by * cy + j - y0; if (yy < 0 || yy >= n) continue;
|
| 44 |
+
for (let i = 0; i < cx; i++) { const xx = bx * cx + i - x0; if (xx >= 0 && xx < n) out[yy * n + xx] = b[j * cx + i]; }
|
| 45 |
+
}
|
| 46 |
+
}));
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
await Promise.all(jobs);
|
| 50 |
+
return out;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
// ---------------------------------------------------------------- drawing
|
| 54 |
+
|
| 55 |
+
// A plane's rows are line pieces, float32: published ax ay bx by, then snapped ax ay bx by.
|
| 56 |
+
const R = 8;
|
| 57 |
+
const cv = $('cv'), cx2d = cv.getContext('2d');
|
| 58 |
+
let ctImage = null; // {key, img}
|
| 59 |
+
|
| 60 |
+
function planeRows() {
|
| 61 |
+
const p = S.view.planes[S.plane];
|
| 62 |
+
return p ? S.rows.subarray(p.first * R, (p.first + p.count) * R) : new Float32Array(0);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
function ringOrder() {
|
| 66 |
+
// the plane's line pieces in order around their centroid: Next and Previous walk along the label
|
| 67 |
+
const r = planeRows(), n = r.length / R;
|
| 68 |
+
if (!n) { S.order = []; return; }
|
| 69 |
+
let mx = 0, my = 0;
|
| 70 |
+
for (let i = 0; i < n; i++) { mx += r[R * i]; my += r[R * i + 1]; }
|
| 71 |
+
mx /= n; my /= n;
|
| 72 |
+
const idx = Array.from({ length: n }, (_, i) => i);
|
| 73 |
+
idx.sort((a, b) => Math.atan2(r[R * a + 1] - my, r[R * a] - mx) - Math.atan2(r[R * b + 1] - my, r[R * b] - mx));
|
| 74 |
+
S.order = idx;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
function centreOn(k) {
|
| 78 |
+
const r = planeRows();
|
| 79 |
+
if (!S.order.length) return;
|
| 80 |
+
S.at = ((k % S.order.length) + S.order.length) % S.order.length;
|
| 81 |
+
const i = S.order[S.at];
|
| 82 |
+
S.cx = r[R * i]; S.cy = r[R * i + 1];
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
function middle() {
|
| 86 |
+
// the position in S.order of the piece nearest the plane's centroid: a start on the label, away from its ends
|
| 87 |
+
const r = planeRows(), n = r.length / R;
|
| 88 |
+
if (!n) return 0;
|
| 89 |
+
let mx = 0, my = 0;
|
| 90 |
+
for (let i = 0; i < n; i++) { mx += r[R * i]; my += r[R * i + 1]; }
|
| 91 |
+
mx /= n; my /= n;
|
| 92 |
+
let best = Infinity, bi = 0;
|
| 93 |
+
for (let i = 0; i < n; i++) { const d = (r[R * i] - mx) ** 2 + (r[R * i + 1] - my) ** 2; if (d < best) { best = d; bi = i; } }
|
| 94 |
+
return S.order.indexOf(bi);
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
async function draw() {
|
| 98 |
+
if (!S.view) return;
|
| 99 |
+
const p = S.view.planes[S.plane];
|
| 100 |
+
$('z-out').textContent = p ? p.z : '';
|
| 101 |
+
const n = S.win, x0 = Math.round(S.cx - n / 2), y0 = Math.round(S.cy - n / 2), z = p ? p.z : 0;
|
| 102 |
+
const key = `${S.view.volume}|${z}|${y0}|${x0}|${n}`;
|
| 103 |
+
const W = cv.width, k = W / n;
|
| 104 |
+
if (!ctImage || ctImage.key !== key) {
|
| 105 |
+
if (S.abort) S.abort.abort();
|
| 106 |
+
S.abort = new AbortController();
|
| 107 |
+
msg('Reading CT…');
|
| 108 |
+
let buf;
|
| 109 |
+
try { buf = await ctWindow(S.view.volume, z, y0, x0, n, S.abort.signal); } catch (e) { if (e.name === 'AbortError') return; msg(`CT read failed: ${e.message}`); return; }
|
| 110 |
+
// contrast: 1st to 99.5th percentile of the window's nonzero voxels
|
| 111 |
+
const h = new Uint32Array(256); let c = 0;
|
| 112 |
+
for (const v of buf) if (v) { h[v]++; c++; }
|
| 113 |
+
let lo = 0, hi = 255, acc = 0;
|
| 114 |
+
for (let v = 0; v < 256; v++) { acc += h[v]; if (acc >= 0.01 * c) { lo = v; break; } }
|
| 115 |
+
acc = 0;
|
| 116 |
+
for (let v = 0; v < 256; v++) { acc += h[v]; if (acc >= 0.995 * c) { hi = v; break; } }
|
| 117 |
+
const img = new ImageData(n, n), s = 255 / Math.max(1, hi - lo);
|
| 118 |
+
for (let i = 0; i < n * n; i++) { const g = Math.max(0, Math.min(255, (buf[i] - lo) * s)); img.data[4 * i] = img.data[4 * i + 1] = img.data[4 * i + 2] = g; img.data[4 * i + 3] = 255; }
|
| 119 |
+
const off = document.createElement('canvas'); off.width = n; off.height = n; off.getContext('2d').putImageData(img, 0, 0);
|
| 120 |
+
ctImage = { key, img: off };
|
| 121 |
+
msg('');
|
| 122 |
+
}
|
| 123 |
+
cx2d.imageSmoothingEnabled = false;
|
| 124 |
+
cx2d.drawImage(ctImage.img, 0, 0, W, W);
|
| 125 |
+
const r = planeRows(), m = r.length / R;
|
| 126 |
+
const X = (x) => (x - x0 + 0.5) * k, Y = (y) => (y - y0 + 0.5) * k;
|
| 127 |
+
const css = getComputedStyle(document.documentElement);
|
| 128 |
+
const lw = Math.max(1.5, Math.min(3, k * 0.4));
|
| 129 |
+
if ($('l-move').checked) {
|
| 130 |
+
// grey strokes from the published line to the snapped one, at each piece's midpoint
|
| 131 |
+
cx2d.strokeStyle = css.getPropertyValue('--move').trim() || 'rgba(200,200,200,.6)';
|
| 132 |
+
cx2d.lineWidth = Math.max(1, lw * 0.45); cx2d.beginPath();
|
| 133 |
+
for (let i = 0; i < m; i++) {
|
| 134 |
+
const a = R * i, px = (r[a] + r[a + 2]) / 2, py = (r[a + 1] + r[a + 3]) / 2, qx = (r[a + 4] + r[a + 6]) / 2, qy = (r[a + 5] + r[a + 7]) / 2;
|
| 135 |
+
if (Math.abs(px - qx) + Math.abs(py - qy) < 0.05) continue;
|
| 136 |
+
cx2d.moveTo(X(px), Y(py)); cx2d.lineTo(X(qx), Y(qy));
|
| 137 |
+
}
|
| 138 |
+
cx2d.stroke();
|
| 139 |
+
}
|
| 140 |
+
const line = (o, colour) => {
|
| 141 |
+
cx2d.strokeStyle = colour; cx2d.lineWidth = lw; cx2d.lineCap = 'round'; cx2d.beginPath();
|
| 142 |
+
for (let i = 0; i < m; i++) { const a = R * i + o; cx2d.moveTo(X(r[a]), Y(r[a + 1])); cx2d.lineTo(X(r[a + 2]), Y(r[a + 3])); }
|
| 143 |
+
cx2d.stroke();
|
| 144 |
+
};
|
| 145 |
+
if ($('l-pub').checked) line(0, css.getPropertyValue('--pub').trim() || '#f08a24');
|
| 146 |
+
if ($('l-snap').checked) line(4, css.getPropertyValue('--snap').trim() || '#2f7bf6');
|
| 147 |
+
// scale bar: 10 voxels
|
| 148 |
+
const um = S.view.voxel_um || null;
|
| 149 |
+
cx2d.fillStyle = '#fff'; cx2d.fillRect(16, W - 22, 10 * k, 4);
|
| 150 |
+
cx2d.font = '13px system-ui, sans-serif'; cx2d.fillText(um ? `10 voxels (${Math.round(10 * um)} µm)` : '10 voxels', 16, W - 30);
|
| 151 |
+
history.replaceState(null, '', `?${new URLSearchParams({ ...(params.get('data') ? { data: params.get('data') } : {}), seg: S.seg.segment, z: String(z) })}`);
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
function msg(t) { const e = $('cv-msg'); e.textContent = t; e.hidden = !t; }
|
| 155 |
+
|
| 156 |
+
// ---------------------------------------------------------------- segments
|
| 157 |
+
|
| 158 |
+
function card(seg) {
|
| 159 |
+
const v = seg.vertices_released, sn = seg.snapped, ab = seg.abstained, held = seg.vertices_held_back;
|
| 160 |
+
const pb = seg.placed_by;
|
| 161 |
+
const mv = seg.move_voxels || {};
|
| 162 |
+
const fine = pb ? `<div><b>${pct(pb.finer_scan, v)}</b><span>placed in the 2.4 µm scan</span></div>` : '';
|
| 163 |
+
const base = `${DATA}/${seg.path}`;
|
| 164 |
+
$('seg-card').innerHTML = `
|
| 165 |
+
<div class="row">
|
| 166 |
+
<div><b>${fmt(v)}</b><span>released vertices</span></div>
|
| 167 |
+
<div><b>${pct(sn, v)}</b><span>snapped onto a face</span></div>
|
| 168 |
+
${fine}
|
| 169 |
+
<div><b>${mv.median != null ? mv.median.toFixed(2) : 'n/a'} vx</b><span>median move (90th pct ${mv.p90 != null ? mv.p90.toFixed(1) : 'n/a'})</span></div>
|
| 170 |
+
<div><b>${fmt(held)}</b><span>held back for evaluation</span></div>
|
| 171 |
+
</div>
|
| 172 |
+
<div class="meter"><i style="width:${100 * sn / Math.max(1, v + held)}%;background:var(--snap)"></i><i style="width:${100 * ab / Math.max(1, v + held)}%;background:var(--pub)"></i><i style="width:${100 * held / Math.max(1, v + held)}%;background:var(--held)"></i></div>
|
| 173 |
+
<div class="legend"><span><i style="background:var(--snap)"></i>snapped</span><span><i style="background:var(--pub)"></i>kept at the published position</span><span><i style="background:var(--held)"></i>held back</span></div>
|
| 174 |
+
<pre class="cmd"><code>hf download SharpMeow/vesuvius-snapped-labels --repo-type dataset --include "${seg.path}/*" --local-dir snapped-labels</code></pre>
|
| 175 |
+
<p class="hint">Mesh: <a href="${base}/snapped.tifxyz/meta.json" target="_blank" rel="noopener">${seg.path}/snapped.tifxyz</a> · record: <a href="${base}/release.json" target="_blank" rel="noopener">release.json</a> · CT: <a href="${S.view ? S.view.volume : '#'}/.zattrs" target="_blank" rel="noopener">${S.view ? S.view.volume.split('/').pop() : ''}</a></p>`;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
async function openSeg(seg, z) {
|
| 179 |
+
S.seg = seg;
|
| 180 |
+
document.querySelectorAll('#seg-list li').forEach((li) => li.classList.toggle('on', li.dataset.id === seg.segment));
|
| 181 |
+
$('v-title').textContent = `${seg.scroll} · ${seg.segment} · ${seg.method}`;
|
| 182 |
+
msg('Loading cross sections…');
|
| 183 |
+
const base = `${DATA}/viewer/${seg.scroll}/${seg.segment}`;
|
| 184 |
+
const [view, bin] = await Promise.all([getJSON(`${base}.json`), fetch(`${base}.bin`, { credentials: 'omit' }).then((r) => { if (!r.ok) throw new Error(`${r.status}`); return r.arrayBuffer(); })]);
|
| 185 |
+
S.view = view; S.rows = new Float32Array(bin);
|
| 186 |
+
const zr = $('z-range'); zr.max = String(Math.max(0, view.planes.length - 1));
|
| 187 |
+
let pi = Math.floor(view.planes.length / 2);
|
| 188 |
+
if (z != null) { let best = Infinity; view.planes.forEach((p, i) => { const d = Math.abs(p.z - z); if (d < best) { best = d; pi = i; } }); }
|
| 189 |
+
// start on the busiest plane near the middle, so the first view shows the label
|
| 190 |
+
if (z == null) { let best = -1; for (let i = Math.floor(pi * 0.6); i <= Math.min(view.planes.length - 1, Math.ceil(pi * 1.4)); i++) if (view.planes[i].count > best) { best = view.planes[i].count; pi = i; } }
|
| 191 |
+
S.plane = pi; zr.value = String(pi);
|
| 192 |
+
ringOrder(); centreOn(middle());
|
| 193 |
+
card(seg);
|
| 194 |
+
ctImage = null;
|
| 195 |
+
await draw();
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
function listSegs() {
|
| 199 |
+
const sc = $('f-scroll').value, q = $('f-find').value.trim().toLowerCase();
|
| 200 |
+
const ul = $('seg-list'); ul.innerHTML = '';
|
| 201 |
+
for (const s of S.index) {
|
| 202 |
+
if (sc && s.scroll !== sc) continue;
|
| 203 |
+
if (q && !s.segment.toLowerCase().includes(q)) continue;
|
| 204 |
+
const li = document.createElement('li'); li.dataset.id = s.segment;
|
| 205 |
+
const nm = s.segment.replace(/^\d{14}-/, '');
|
| 206 |
+
li.innerHTML = `<span>${nm}</span><small>${pct(s.snapped, s.vertices_released)}</small>`;
|
| 207 |
+
li.title = s.segment;
|
| 208 |
+
li.onclick = () => openSeg(s).catch((e) => msg(e.message));
|
| 209 |
+
if (S.seg && S.seg.segment === s.segment) li.classList.add('on');
|
| 210 |
+
ul.appendChild(li);
|
| 211 |
+
}
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
function table() {
|
| 215 |
+
const tb = document.querySelector('#tbl tbody'); tb.innerHTML = '';
|
| 216 |
+
for (const s of S.index) {
|
| 217 |
+
const tr = document.createElement('tr');
|
| 218 |
+
tr.innerHTML = `<td>${s.scroll}</td><td>${s.segment}</td><td>${s.method}</td><td class="n">${fmt(s.vertices_released)}</td><td class="n">${pct(s.snapped, s.vertices_released)}</td><td class="n">${s.move_voxels && s.move_voxels.median != null ? s.move_voxels.median.toFixed(2) : 'n/a'}</td><td class="n">${fmt(s.vertices_held_back)}</td>`;
|
| 219 |
+
tr.onclick = () => { tab('view'); openSeg(s).catch((e) => msg(e.message)); };
|
| 220 |
+
tb.appendChild(tr);
|
| 221 |
+
}
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
function tab(name) {
|
| 225 |
+
document.querySelectorAll('.tabs button').forEach((b) => b.setAttribute('aria-selected', String(b.dataset.tab === name)));
|
| 226 |
+
document.querySelectorAll('[data-pane]').forEach((p) => { p.hidden = p.dataset.pane !== name; });
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
// ---------------------------------------------------------------- wiring
|
| 230 |
+
|
| 231 |
+
document.querySelectorAll('.tabs button').forEach((b) => { b.onclick = () => tab(b.dataset.tab); });
|
| 232 |
+
$('f-scroll').onchange = listSegs;
|
| 233 |
+
$('f-find').oninput = listSegs;
|
| 234 |
+
['l-pub', 'l-snap', 'l-move'].forEach((id) => { $(id).onchange = () => draw(); });
|
| 235 |
+
$('z-range').oninput = () => { S.plane = Number($('z-range').value); ringOrder(); const r = planeRows(); if (r.length) { let best = Infinity, bi = 0; for (let i = 0; i < r.length / R; i++) { const d = (r[R * i] - S.cx) ** 2 + (r[R * i + 1] - S.cy) ** 2; if (d < best) { best = d; bi = i; } } S.at = Math.max(0, S.order.indexOf(bi)); } draw(); };
|
| 236 |
+
$('win').onchange = () => { S.win = Number($('win').value); draw(); };
|
| 237 |
+
$('b-next').onclick = () => { centreOn(S.at + Math.max(1, Math.round(S.order.length / 24))); draw(); };
|
| 238 |
+
$('b-prev').onclick = () => { centreOn(S.at - Math.max(1, Math.round(S.order.length / 24))); draw(); };
|
| 239 |
+
document.addEventListener('keydown', (e) => {
|
| 240 |
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') return;
|
| 241 |
+
if (e.key === 'ArrowRight') $('b-next').click();
|
| 242 |
+
if (e.key === 'ArrowLeft') $('b-prev').click();
|
| 243 |
+
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { e.preventDefault(); const zr = $('z-range'); zr.value = String(Number(zr.value) + (e.key === 'ArrowUp' ? 1 : -1)); zr.oninput(); }
|
| 244 |
+
});
|
| 245 |
+
cv.addEventListener('click', (e) => {
|
| 246 |
+
const b = cv.getBoundingClientRect(), n = S.win;
|
| 247 |
+
S.cx += ((e.clientX - b.left) / b.width - 0.5) * n; S.cy += ((e.clientY - b.top) / b.height - 0.5) * n; draw();
|
| 248 |
+
});
|
| 249 |
+
|
| 250 |
+
// window.snappedLabels: the same data for scripts and headless browsers
|
| 251 |
+
window.snappedLabels = {
|
| 252 |
+
data: DATA,
|
| 253 |
+
segments: () => S.index.slice(),
|
| 254 |
+
open: (segment, z) => { const s = S.index.find((x) => x.segment === segment); if (!s) throw new Error(`no segment ${segment}`); return openSeg(s, z); },
|
| 255 |
+
view: () => (S.view ? { ...S.view, plane: S.view.planes[S.plane], centre: [S.cx, S.cy] } : null),
|
| 256 |
+
};
|
| 257 |
+
|
| 258 |
+
(async function init() {
|
| 259 |
+
try {
|
| 260 |
+
const idx = await getJSON(`${DATA}/index.json`);
|
| 261 |
+
S.index = idx.segments;
|
| 262 |
+
$('st-count').textContent = String(S.index.length);
|
| 263 |
+
const scrolls = [...new Set(S.index.map((s) => s.scroll))];
|
| 264 |
+
$('f-scroll').innerHTML = `<option value="">All scrolls</option>` + scrolls.map((s) => `<option>${s}</option>`).join('');
|
| 265 |
+
table();
|
| 266 |
+
const want = params.get('seg');
|
| 267 |
+
const big = (sc) => S.index.filter((s) => s.scroll === sc).sort((a, b) => b.vertices_released - a.vertices_released)[0];
|
| 268 |
+
const first = S.index.find((s) => s.segment === want) || big('PHerc0139') || S.index[0]; // default: the largest PHerc0139 segment
|
| 269 |
+
$('f-scroll').value = first.scroll;
|
| 270 |
+
listSegs();
|
| 271 |
+
await openSeg(first, params.get('z') != null ? Number(params.get('z')) : null);
|
| 272 |
+
} catch (e) {
|
| 273 |
+
$('v-title').textContent = 'Could not load the dataset';
|
| 274 |
+
msg(`${e.message}. The dataset is ${DATA}`);
|
| 275 |
+
}
|
| 276 |
+
})();
|
connect.html
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<section class="scc" id="connect" aria-label="Connect your tool"><style>.scc{--a:var(--scc-accent,#4ea8de);color:inherit;font:inherit;line-height:1.5;max-width:60rem}
|
| 2 |
+
.scc h2{font-size:1.15em;margin:.2em 0 .3em}.scc h3{font-size:1em;margin:0 0 .2em}
|
| 3 |
+
.scc p{margin:.3em 0}.scc a{color:var(--a)}
|
| 4 |
+
.scc .scc-src{border:1px solid color-mix(in srgb,currentColor 18%,transparent);border-radius:10px;padding:.7em .9em;margin:.7em 0;background:color-mix(in srgb,currentColor 4%,transparent)}
|
| 5 |
+
.scc .scc-meta{font-size:.85em;opacity:.8}
|
| 6 |
+
.scc details{border-top:1px solid color-mix(in srgb,currentColor 12%,transparent);padding:.35em 0}
|
| 7 |
+
.scc summary{cursor:pointer;font-weight:600;list-style-position:inside}
|
| 8 |
+
.scc .scc-b{font-weight:400;font-size:.75em;padding:.05em .5em;border-radius:99px;margin-left:.5em;border:1px solid currentColor;opacity:.75;white-space:nowrap}
|
| 9 |
+
.scc .scc-b.ok{color:#3fb37f}.scc .scc-b.src{color:var(--a)}.scc .scc-b.warn{color:#e0a030}
|
| 10 |
+
.scc .scc-code{position:relative;margin:.4em 0 .2em}
|
| 11 |
+
.scc pre{margin:0;padding:.7em .8em;border-radius:8px;overflow:auto;font-size:.82em;background:color-mix(in srgb,currentColor 9%,transparent);white-space:pre}
|
| 12 |
+
.scc code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
| 13 |
+
.scc button.scc-copy{all:unset;box-sizing:border-box;position:absolute;top:.35em;right:.35em;font-size:.75em;line-height:1.4;padding:.1em .6em;border-radius:6px;border:1px solid color-mix(in srgb,currentColor 30%,transparent);background:color-mix(in srgb,currentColor 8%,transparent);color:inherit;cursor:pointer}
|
| 14 |
+
.scc button.scc-copy:hover{border-color:var(--a)}
|
| 15 |
+
.scc .scc-url{position:relative}.scc .scc-url pre{padding-right:4.5em;white-space:pre-wrap;word-break:break-all}
|
| 16 |
+
.scc .scc-go{display:inline-block;margin:.3em 0;padding:.25em .8em;border-radius:6px;border:1px solid var(--a);text-decoration:none}
|
| 17 |
+
.scc footer{font-size:.8em;opacity:.75;margin-top:.6em}</style>
|
| 18 |
+
<h2>Connect your tool</h2>
|
| 19 |
+
<p>The snapped meshes, their records and the CT they sit on read straight from these URLs, logged out. Every segment is a tifxyz folder, so VC3D, villa and anything that reads tifxyz opens it as it is.</p>
|
| 20 |
+
<div class="scc-src" id="connect-data"><h3>Snapped labels: every segment, with index.json and checksums</h3>
|
| 21 |
+
<p class="scc-meta">CC BY-NC 4.0 (derived from Vesuvius Challenge open data) · One folder per segment, SCROLL/SEGMENT/: snapped.tifxyz, abstain.tif, confidence.tif, reason.tif, source.tif (finesnap), release_mask.tif and release.json (source URLs, sha256 of every file, summary). index.json lists every segment; viewer/ holds the page's cross sections.</p>
|
| 22 |
+
<div class="scc-url"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels</code></pre></div>
|
| 23 |
+
<details><summary>Python (huggingface_hub)<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 24 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code># pip install huggingface_hub
|
| 25 |
+
from huggingface_hub import hf_hub_download, snapshot_download
|
| 26 |
+
|
| 27 |
+
path = hf_hub_download("SharpMeow/vesuvius-snapped-labels", "index.json", repo_type="dataset") # one file, cached
|
| 28 |
+
folder = snapshot_download("SharpMeow/vesuvius-snapped-labels", repo_type="dataset")</code></pre></div></details>
|
| 29 |
+
<details><summary>Command line (hf)<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 30 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>pip install -U huggingface_hub
|
| 31 |
+
hf download SharpMeow/vesuvius-snapped-labels index.json --repo-type dataset --local-dir vesuvius-snapped-labels</code></pre></div></details>
|
| 32 |
+
<details><summary>curl<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 33 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>curl -L -O https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/index.json</code></pre></div></details>
|
| 34 |
+
</div>
|
| 35 |
+
<div class="scc-src" id="connect-mesh"><h3>One snapped mesh (tifxyz): PHerc0841 w00</h3>
|
| 36 |
+
<p class="scc-meta">CC BY-NC 4.0 (derived from Vesuvius Challenge open data) · Same grid and voxel frame as the published mesh it came from (release.json names it), so the two line up vertex for vertex; -1 where there is no vertex or it was held back.</p>
|
| 37 |
+
<div class="scc-url"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/PHerc0841/20260220213127-w00/snapped.tifxyz</code></pre></div>
|
| 38 |
+
<details><summary>Python (numpy, tifffile)<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 39 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code># pip install numpy tifffile
|
| 40 |
+
import io, json, urllib.request
|
| 41 |
+
import numpy as np, tifffile
|
| 42 |
+
|
| 43 |
+
T = "https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/PHerc0841/20260220213127-w00/snapped.tifxyz"
|
| 44 |
+
meta = json.load(urllib.request.urlopen(T + "/meta.json")) # scale, bbox, format
|
| 45 |
+
get = lambda c: tifffile.imread(io.BytesIO(urllib.request.urlopen(f"{T}/{c}.tif").read()))
|
| 46 |
+
xyz = np.stack([get(c) for c in "xyz"], -1) # xyz[i, j] = (x, y, z) in scan voxels; -1 = no surface</code></pre></div></details>
|
| 47 |
+
</div>
|
| 48 |
+
<div class="scc-src" id="connect-ct"><h3>The CT that mesh is in, PHerc0841 9.366 um</h3>
|
| 49 |
+
<p class="scc-meta">CC BY-NC 4.0 (Vesuvius Challenge open data) · Read live from the open-data bucket, never copied here. Mesh coordinates are level-0 voxels of this scan. · <a href="https://scrollprize.org/data_browser/PHerc0841" target="_blank" rel="noopener">browse</a></p>
|
| 50 |
+
<div class="scc-url"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr</code></pre></div>
|
| 51 |
+
<details><summary>Python (zarr)<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 52 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code># pip install "zarr>=3" fsspec aiohttp
|
| 53 |
+
import zarr
|
| 54 |
+
|
| 55 |
+
root = zarr.open_group("https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr", mode="r") # OME-Zarr, zarr v2
|
| 56 |
+
a = root["2"] # level 0 is full resolution; level L is 2**L times coarser
|
| 57 |
+
print(a.shape, a.dtype, a.chunks)
|
| 58 |
+
block = a[3600:3664, 600:664, 500:564] # (z, y, x); reads only the chunks it touches</code></pre></div></details>
|
| 59 |
+
<details><summary>villa's vesuvius package<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 60 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code># pip install "vesuvius[volume-only]"
|
| 61 |
+
from vesuvius import Volume
|
| 62 |
+
|
| 63 |
+
v = Volume(type="zarr", path="https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr/2") # one pyramid level
|
| 64 |
+
block = v[3600:3664, 600:664, 500:564]</code></pre></div></details>
|
| 65 |
+
<details><summary>VC3D<span class="scc-b src">from villa's source</span></summary>
|
| 66 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>In VC3D (villa's volume-cartographer), open or create a project, then
|
| 67 |
+
File > Attach Remote Zarr... and paste:
|
| 68 |
+
|
| 69 |
+
https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr
|
| 70 |
+
|
| 71 |
+
It accepts http, https and s3 OME-Zarr roots.</code></pre></div></details>
|
| 72 |
+
<details><summary>neuroglancer<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 73 |
+
<a class="scc-go" href="https://neuroglancer-demo.appspot.com/#!%7B%22layers%22%3A%5B%7B%22type%22%3A%22image%22%2C%22source%22%3A%22zarr%3A%2F%2Fhttps%3A%2F%2Fvesuvius-challenge-open-data.s3.us-east-1.amazonaws.com%2FPHerc0841%2Fvolumes%2F20250821151531-9.366um-1.2m-113keV-masked.zarr%2F%22%2C%22name%22%3A%22The%20CT%20that%20mesh%20is%20in%2C%20PHerc0841%209.366%20um%22%7D%5D%2C%22layout%22%3A%224panel%22%7D" target="_blank" rel="noopener">Open in neuroglancer</a>
|
| 74 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>https://neuroglancer-demo.appspot.com/#!%7B%22layers%22%3A%5B%7B%22type%22%3A%22image%22%2C%22source%22%3A%22zarr%3A%2F%2Fhttps%3A%2F%2Fvesuvius-challenge-open-data.s3.us-east-1.amazonaws.com%2FPHerc0841%2Fvolumes%2F20250821151531-9.366um-1.2m-113keV-masked.zarr%2F%22%2C%22name%22%3A%22The%20CT%20that%20mesh%20is%20in%2C%20PHerc0841%209.366%20um%22%7D%5D%2C%22layout%22%3A%224panel%22%7D</code></pre></div></details>
|
| 75 |
+
<details><summary>curl<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 76 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>curl -s https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr/.zattrs # OME multiscales metadata
|
| 77 |
+
curl -s https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr/2/.zarray # one level: shape, chunks, dtype, codec</code></pre></div></details>
|
| 78 |
+
</div>
|
| 79 |
+
<div class="scc-src" id="connect-page"><h3>This page's own files</h3>
|
| 80 |
+
<p class="scc-meta">Page code Apache 2.0; data as above · In the browser console, window.snappedLabels.segments() lists the index and window.snappedLabels.open(SEGMENT, Z) opens a segment at a plane; ?seg=SEGMENT&z=Z links to a view.</p>
|
| 81 |
+
<div class="scc-url"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>https://sharpmeow-snapped-labels.static.hf.space/</code></pre></div>
|
| 82 |
+
<details><summary>Links and embed<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 83 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>The dataset card: https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels
|
| 84 |
+
A segment's cross sections for the viewer (float32 rows, see the .json beside it): https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/viewer/PHerc0841/20260220213127-w00.json
|
| 85 |
+
Page source: https://github.com/ChaseHendrick/Scrolls/tree/main/spaces/snapped-labels</code></pre></div></details>
|
| 86 |
+
</div>
|
| 87 |
+
<footer>Machine-readable: <a href="https://huggingface.co/spaces/SharpMeow/snapped-labels/blob/main/connect.json" target="_blank" rel="noopener">connect.json</a> and <a href="https://huggingface.co/spaces/SharpMeow/snapped-labels/blob/main/CONNECT.md" target="_blank" rel="noopener">CONNECT.md</a>. Checked 2026-10-27. "Tested" means the snippet ran against this public data on that date. Scan data keeps its own license (the Vesuvius Challenge open data is CC BY-NC 4.0).</footer></section>
|
connect.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema": "space-connect/1",
|
| 3 |
+
"space": "SharpMeow/snapped-labels",
|
| 4 |
+
"title": "Snapped Labels",
|
| 5 |
+
"checked": "2026-10-27",
|
| 6 |
+
"intro": "The snapped meshes, their records and the CT they sit on read straight from these URLs, logged out. Every segment is a tifxyz folder, so VC3D, villa and anything that reads tifxyz opens it as it is.",
|
| 7 |
+
"sources": [
|
| 8 |
+
{"id": "data", "title": "Snapped labels: every segment, with index.json and checksums", "kind": "hf-dataset",
|
| 9 |
+
"url": "https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels",
|
| 10 |
+
"repo": "SharpMeow/vesuvius-snapped-labels", "file": "index.json",
|
| 11 |
+
"license": "CC BY-NC 4.0 (derived from Vesuvius Challenge open data)",
|
| 12 |
+
"note": "One folder per segment, SCROLL/SEGMENT/: snapped.tifxyz, abstain.tif, confidence.tif, reason.tif, source.tif (finesnap), release_mask.tif and release.json (source URLs, sha256 of every file, summary). index.json lists every segment; viewer/ holds the page's cross sections."},
|
| 13 |
+
{"id": "mesh", "title": "One snapped mesh (tifxyz): PHerc0841 w00", "kind": "tifxyz",
|
| 14 |
+
"url": "https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/PHerc0841/20260220213127-w00/snapped.tifxyz",
|
| 15 |
+
"license": "CC BY-NC 4.0 (derived from Vesuvius Challenge open data)",
|
| 16 |
+
"note": "Same grid and voxel frame as the published mesh it came from (release.json names it), so the two line up vertex for vertex; -1 where there is no vertex or it was held back."},
|
| 17 |
+
{"id": "ct", "title": "The CT that mesh is in, PHerc0841 9.366 um", "kind": "ome-zarr",
|
| 18 |
+
"url": "https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr",
|
| 19 |
+
"zarr_format": 2, "example": {"level": "2", "start": [3600, 600, 500]},
|
| 20 |
+
"license": "CC BY-NC 4.0 (Vesuvius Challenge open data)",
|
| 21 |
+
"note": "Read live from the open-data bucket, never copied here. Mesh coordinates are level-0 voxels of this scan.",
|
| 22 |
+
"browse": "https://scrollprize.org/data_browser/PHerc0841"},
|
| 23 |
+
{"id": "page", "title": "This page's own files", "kind": "page-api",
|
| 24 |
+
"url": "https://sharpmeow-snapped-labels.static.hf.space/",
|
| 25 |
+
"links": [{"label": "The dataset card", "url": "https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels"},
|
| 26 |
+
{"label": "A segment's cross sections for the viewer (float32 rows, see the .json beside it)", "url": "https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/viewer/PHerc0841/20260220213127-w00.json"},
|
| 27 |
+
{"label": "Page source", "url": "https://github.com/ChaseHendrick/Scrolls/tree/main/spaces/snapped-labels"}],
|
| 28 |
+
"license": "Page code Apache 2.0; data as above",
|
| 29 |
+
"note": "In the browser console, window.snappedLabels.segments() lists the index and window.snappedLabels.open(SEGMENT, Z) opens a segment at a plane; ?seg=SEGMENT&z=Z links to a view."}
|
| 30 |
+
]
|
| 31 |
+
}
|
index.html
CHANGED
|
@@ -1,19 +1,187 @@
|
|
| 1 |
<!doctype html>
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
+
<title>Snapped Labels</title>
|
| 7 |
+
<meta name="description" content="Published Vesuvius Challenge surface labels moved onto the papyrus surface, shown on live X-ray CT.">
|
| 8 |
+
<link rel="stylesheet" href="style.css">
|
| 9 |
+
</head>
|
| 10 |
+
<body>
|
| 11 |
+
<header class="top">
|
| 12 |
+
<div class="brand">
|
| 13 |
+
<h1>Snapped Labels</h1>
|
| 14 |
+
<p class="sub">Published surface labels of four Herculaneum scrolls, moved along their normals onto the papyrus surface in the CT. Orange is the published label, blue is where it lands.</p>
|
| 15 |
+
</div>
|
| 16 |
+
<nav class="tabs" role="tablist">
|
| 17 |
+
<button data-tab="view" role="tab" aria-selected="true">Viewer</button>
|
| 18 |
+
<button data-tab="all" role="tab">All segments</button>
|
| 19 |
+
<button data-tab="how" role="tab">How it works</button>
|
| 20 |
+
<button data-tab="connect" role="tab">Connect your tool</button>
|
| 21 |
+
</nav>
|
| 22 |
+
</header>
|
| 23 |
+
|
| 24 |
+
<section class="stats" aria-label="Measured accuracy">
|
| 25 |
+
<div class="stat"><b>91%</b><span>of vertices within 1 voxel of the recto face in a 1.129 µm scan, on 60 fresh PHerc0139 test sites. Published labels: 24%.</span></div>
|
| 26 |
+
<div class="stat"><b>71%</b><span>within 1 voxel on 60 fresh PHerc0814 test sites with the single-scan snap. Published labels: 24%.</span></div>
|
| 27 |
+
<div class="stat"><b>+0.61 voxels</b><span>closer to the surface: a 3D surface model trained on snapped labels against the same model trained on published ones, scored on a scroll neither saw.</span></div>
|
| 28 |
+
<div class="stat"><b id="st-count">108</b><span>segments on PHerc0139, PHerc0814, PHerc0841 and PHerc0172, as tifxyz meshes you can drop into VC3D or villa.</span></div>
|
| 29 |
+
</section>
|
| 30 |
+
|
| 31 |
+
<main>
|
| 32 |
+
<section data-pane="view" class="viewer">
|
| 33 |
+
<aside class="side">
|
| 34 |
+
<label class="lbl" for="f-scroll">Scroll</label>
|
| 35 |
+
<select id="f-scroll"></select>
|
| 36 |
+
<label class="lbl" for="f-find">Segment</label>
|
| 37 |
+
<input id="f-find" type="search" placeholder="filter, e.g. w025" autocomplete="off">
|
| 38 |
+
<ul id="seg-list" class="seglist" aria-label="Segments"></ul>
|
| 39 |
+
</aside>
|
| 40 |
+
<div class="stage">
|
| 41 |
+
<div class="bar">
|
| 42 |
+
<span id="v-title" class="vt">Loading…</span>
|
| 43 |
+
<span class="sp"></span>
|
| 44 |
+
<label class="tog"><input type="checkbox" id="l-pub" checked><i class="dot pub"></i>Published</label>
|
| 45 |
+
<label class="tog"><input type="checkbox" id="l-snap" checked><i class="dot snap"></i>Snapped</label>
|
| 46 |
+
<label class="tog"><input type="checkbox" id="l-move" checked><i class="dot move"></i>Moves</label>
|
| 47 |
+
</div>
|
| 48 |
+
<div class="canvaswrap">
|
| 49 |
+
<canvas id="cv" width="720" height="720" aria-label="CT slice with the published and snapped labels"></canvas>
|
| 50 |
+
<div id="cv-msg" class="cvmsg" hidden></div>
|
| 51 |
+
</div>
|
| 52 |
+
<div class="ctl">
|
| 53 |
+
<button id="b-prev" title="Previous stretch of the label (left arrow)">◀</button>
|
| 54 |
+
<button id="b-next" title="Next stretch of the label (right arrow)">▶</button>
|
| 55 |
+
<label class="zl">z <input id="z-range" type="range" min="0" max="0" value="0"> <output id="z-out">0</output></label>
|
| 56 |
+
<label class="zl">Window <select id="win"><option value="96">96 voxels</option><option value="192" selected>192 voxels</option><option value="384">384 voxels</option></select></label>
|
| 57 |
+
</div>
|
| 58 |
+
<p class="hint">Click the image to centre it. The lines are where each mesh crosses this z plane: orange for the published label, blue for the snapped one, with grey strokes joining matching points. Where the snap left a vertex in place, the two lines coincide.</p>
|
| 59 |
+
<div id="seg-card" class="card"></div>
|
| 60 |
+
</div>
|
| 61 |
+
</section>
|
| 62 |
+
|
| 63 |
+
<section data-pane="all" hidden>
|
| 64 |
+
<div class="tablewrap"><table id="tbl"><thead><tr>
|
| 65 |
+
<th>Scroll</th><th>Segment</th><th>Method</th><th class="n">Vertices</th><th class="n">Snapped</th><th class="n">Median move</th><th class="n">Held back</th>
|
| 66 |
+
</tr></thead><tbody></tbody></table></div>
|
| 67 |
+
</section>
|
| 68 |
+
|
| 69 |
+
<section data-pane="how" hidden class="prose">
|
| 70 |
+
<figure class="fig"><img src="https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/snapped-0139.gif" width="600" height="600" alt="PHerc0139 segment w054 on CT at z 8048: the published label, then the snapped label, then both with the moves, at four stops along the label" loading="lazy"><figcaption>PHerc0139 w054, z 8048: published (orange), snapped (blue), both.</figcaption></figure>
|
| 71 |
+
<h2>What a snap does</h2>
|
| 72 |
+
<p>A surface label is a mesh (tifxyz: three float grids, x, y and z in scan voxels) that follows one wrap of papyrus. Traced labels sit near the sheet but rarely on it: on fresh test sites only about a quarter of published label vertices were within one voxel of the sheet's recto face. A snap moves each vertex along its mesh normal onto the face of the bright band nearest it, on the side toward the scroll's centre line.</p>
|
| 73 |
+
<h2>Two methods</h2>
|
| 74 |
+
<ul>
|
| 75 |
+
<li><b>finesnap</b> (PHerc0139, PHerc0814, PHerc0841): the label's own 9.4 µm scan is registered, vertex by vertex, to a 2.4 µm scan of the same scroll, and the face is found there. Where that fails, the label scan's own face is used (<code>source.tif</code> says which).</li>
|
| 76 |
+
<li><b>labelsnap</b> (PHerc0172): the face is found in the label's own 7.9 µm scan.</li>
|
| 77 |
+
</ul>
|
| 78 |
+
<h2>How it was measured</h2>
|
| 79 |
+
<figure class="fig"><img src="https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/accuracy.png" width="1920" height="576" alt="Bar chart: vertices within 1 voxel of the recto face. PHerc0139: published 23.7%, labelsnap 79.4%, finesnap 91.2%. PHerc0814: published 24.2%, labelsnap 71.0%." loading="lazy"></figure>
|
| 80 |
+
<p>Truth is the recto face seen in an independent 1.129 µm scan, at test sites drawn after the method was fixed and never used before. PHerc0139, 60 sites: within 1 voxel 23.7% for the published labels, 79.4% for labelsnap, 91.2% for finesnap. PHerc0814, 60 sites: 24.2% for published labels, 71.0% for labelsnap. A 3D surface model trained on snapped PHerc0139 labels placed the surface 0.61 voxels closer on PHerc0814 than the same model trained on published labels (95% interval 0.39 to 0.84; all 9 seed pairings).</p>
|
| 81 |
+
<h2>What is in each segment folder</h2>
|
| 82 |
+
<ul>
|
| 83 |
+
<li><code>snapped.tifxyz/</code>: the moved mesh, same grid as the source; <code>-1</code> where there is no vertex or it was held back.</li>
|
| 84 |
+
<li><code>abstain.tif</code>: 0 snapped, 1 left at its published position, 255 no vertex.</li>
|
| 85 |
+
<li><code>confidence.tif</code> (0 to 1), <code>reason.tif</code> (why a vertex abstained), <code>source.tif</code> (finesnap: 1 fine scan, 2 label scan).</li>
|
| 86 |
+
<li><code>release_mask.tif</code>: 0 released, 1 held back for evaluation, 255 not in the source mesh. <code>release.json</code>: source URLs, checksums and the summary.</li>
|
| 87 |
+
</ul>
|
| 88 |
+
<h2>Limits</h2>
|
| 89 |
+
<p>A snap moves along the normal only; it does not fix a label that follows the wrong wrap. Where two sheets touch, the face it finds can belong to the neighbour. Small regions are held back for evaluation and five segments of PHerc0139 and one of PHerc0841 are not included. The meshes are geometry: they say nothing about ink or text.</p>
|
| 90 |
+
<p class="credit">Scans and source meshes: Vesuvius Challenge open data, CC BY-NC 4.0; the snapped meshes are shared under the same terms. Page code Apache 2.0. Made by Chase Hendrick.</p>
|
| 91 |
+
</section>
|
| 92 |
+
|
| 93 |
+
<section data-pane="connect" hidden>
|
| 94 |
+
<!-- space-connect:start -->
|
| 95 |
+
<section class="scc" id="connect" aria-label="Connect your tool"><style>.scc{--a:var(--scc-accent,#4ea8de);color:inherit;font:inherit;line-height:1.5;max-width:60rem}
|
| 96 |
+
.scc h2{font-size:1.15em;margin:.2em 0 .3em}.scc h3{font-size:1em;margin:0 0 .2em}
|
| 97 |
+
.scc p{margin:.3em 0}.scc a{color:var(--a)}
|
| 98 |
+
.scc .scc-src{border:1px solid color-mix(in srgb,currentColor 18%,transparent);border-radius:10px;padding:.7em .9em;margin:.7em 0;background:color-mix(in srgb,currentColor 4%,transparent)}
|
| 99 |
+
.scc .scc-meta{font-size:.85em;opacity:.8}
|
| 100 |
+
.scc details{border-top:1px solid color-mix(in srgb,currentColor 12%,transparent);padding:.35em 0}
|
| 101 |
+
.scc summary{cursor:pointer;font-weight:600;list-style-position:inside}
|
| 102 |
+
.scc .scc-b{font-weight:400;font-size:.75em;padding:.05em .5em;border-radius:99px;margin-left:.5em;border:1px solid currentColor;opacity:.75;white-space:nowrap}
|
| 103 |
+
.scc .scc-b.ok{color:#3fb37f}.scc .scc-b.src{color:var(--a)}.scc .scc-b.warn{color:#e0a030}
|
| 104 |
+
.scc .scc-code{position:relative;margin:.4em 0 .2em}
|
| 105 |
+
.scc pre{margin:0;padding:.7em .8em;border-radius:8px;overflow:auto;font-size:.82em;background:color-mix(in srgb,currentColor 9%,transparent);white-space:pre}
|
| 106 |
+
.scc code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
| 107 |
+
.scc button.scc-copy{all:unset;box-sizing:border-box;position:absolute;top:.35em;right:.35em;font-size:.75em;line-height:1.4;padding:.1em .6em;border-radius:6px;border:1px solid color-mix(in srgb,currentColor 30%,transparent);background:color-mix(in srgb,currentColor 8%,transparent);color:inherit;cursor:pointer}
|
| 108 |
+
.scc button.scc-copy:hover{border-color:var(--a)}
|
| 109 |
+
.scc .scc-url{position:relative}.scc .scc-url pre{padding-right:4.5em;white-space:pre-wrap;word-break:break-all}
|
| 110 |
+
.scc .scc-go{display:inline-block;margin:.3em 0;padding:.25em .8em;border-radius:6px;border:1px solid var(--a);text-decoration:none}
|
| 111 |
+
.scc footer{font-size:.8em;opacity:.75;margin-top:.6em}</style>
|
| 112 |
+
<h2>Connect your tool</h2>
|
| 113 |
+
<p>The snapped meshes, their records and the CT they sit on read straight from these URLs, logged out. Every segment is a tifxyz folder, so VC3D, villa and anything that reads tifxyz opens it as it is.</p>
|
| 114 |
+
<div class="scc-src" id="connect-data"><h3>Snapped labels: every segment, with index.json and checksums</h3>
|
| 115 |
+
<p class="scc-meta">CC BY-NC 4.0 (derived from Vesuvius Challenge open data) · One folder per segment, SCROLL/SEGMENT/: snapped.tifxyz, abstain.tif, confidence.tif, reason.tif, source.tif (finesnap), release_mask.tif and release.json (source URLs, sha256 of every file, summary). index.json lists every segment; viewer/ holds the page's cross sections.</p>
|
| 116 |
+
<div class="scc-url"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels</code></pre></div>
|
| 117 |
+
<details><summary>Python (huggingface_hub)<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 118 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code># pip install huggingface_hub
|
| 119 |
+
from huggingface_hub import hf_hub_download, snapshot_download
|
| 120 |
+
|
| 121 |
+
path = hf_hub_download("SharpMeow/vesuvius-snapped-labels", "index.json", repo_type="dataset") # one file, cached
|
| 122 |
+
folder = snapshot_download("SharpMeow/vesuvius-snapped-labels", repo_type="dataset")</code></pre></div></details>
|
| 123 |
+
<details><summary>Command line (hf)<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 124 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>pip install -U huggingface_hub
|
| 125 |
+
hf download SharpMeow/vesuvius-snapped-labels index.json --repo-type dataset --local-dir vesuvius-snapped-labels</code></pre></div></details>
|
| 126 |
+
<details><summary>curl<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 127 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>curl -L -O https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/index.json</code></pre></div></details>
|
| 128 |
+
</div>
|
| 129 |
+
<div class="scc-src" id="connect-mesh"><h3>One snapped mesh (tifxyz): PHerc0841 w00</h3>
|
| 130 |
+
<p class="scc-meta">CC BY-NC 4.0 (derived from Vesuvius Challenge open data) · Same grid and voxel frame as the published mesh it came from (release.json names it), so the two line up vertex for vertex; -1 where there is no vertex or it was held back.</p>
|
| 131 |
+
<div class="scc-url"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/PHerc0841/20260220213127-w00/snapped.tifxyz</code></pre></div>
|
| 132 |
+
<details><summary>Python (numpy, tifffile)<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 133 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code># pip install numpy tifffile
|
| 134 |
+
import io, json, urllib.request
|
| 135 |
+
import numpy as np, tifffile
|
| 136 |
+
|
| 137 |
+
T = "https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/PHerc0841/20260220213127-w00/snapped.tifxyz"
|
| 138 |
+
meta = json.load(urllib.request.urlopen(T + "/meta.json")) # scale, bbox, format
|
| 139 |
+
get = lambda c: tifffile.imread(io.BytesIO(urllib.request.urlopen(f"{T}/{c}.tif").read()))
|
| 140 |
+
xyz = np.stack([get(c) for c in "xyz"], -1) # xyz[i, j] = (x, y, z) in scan voxels; -1 = no surface</code></pre></div></details>
|
| 141 |
+
</div>
|
| 142 |
+
<div class="scc-src" id="connect-ct"><h3>The CT that mesh is in, PHerc0841 9.366 um</h3>
|
| 143 |
+
<p class="scc-meta">CC BY-NC 4.0 (Vesuvius Challenge open data) · Read live from the open-data bucket, never copied here. Mesh coordinates are level-0 voxels of this scan. · <a href="https://scrollprize.org/data_browser/PHerc0841" target="_blank" rel="noopener">browse</a></p>
|
| 144 |
+
<div class="scc-url"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr</code></pre></div>
|
| 145 |
+
<details><summary>Python (zarr)<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 146 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code># pip install "zarr>=3" fsspec aiohttp
|
| 147 |
+
import zarr
|
| 148 |
+
|
| 149 |
+
root = zarr.open_group("https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr", mode="r") # OME-Zarr, zarr v2
|
| 150 |
+
a = root["2"] # level 0 is full resolution; level L is 2**L times coarser
|
| 151 |
+
print(a.shape, a.dtype, a.chunks)
|
| 152 |
+
block = a[3600:3664, 600:664, 500:564] # (z, y, x); reads only the chunks it touches</code></pre></div></details>
|
| 153 |
+
<details><summary>villa's vesuvius package<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 154 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code># pip install "vesuvius[volume-only]"
|
| 155 |
+
from vesuvius import Volume
|
| 156 |
+
|
| 157 |
+
v = Volume(type="zarr", path="https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr/2") # one pyramid level
|
| 158 |
+
block = v[3600:3664, 600:664, 500:564]</code></pre></div></details>
|
| 159 |
+
<details><summary>VC3D<span class="scc-b src">from villa's source</span></summary>
|
| 160 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>In VC3D (villa's volume-cartographer), open or create a project, then
|
| 161 |
+
File > Attach Remote Zarr... and paste:
|
| 162 |
+
|
| 163 |
+
https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr
|
| 164 |
+
|
| 165 |
+
It accepts http, https and s3 OME-Zarr roots.</code></pre></div></details>
|
| 166 |
+
<details><summary>neuroglancer<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 167 |
+
<a class="scc-go" href="https://neuroglancer-demo.appspot.com/#!%7B%22layers%22%3A%5B%7B%22type%22%3A%22image%22%2C%22source%22%3A%22zarr%3A%2F%2Fhttps%3A%2F%2Fvesuvius-challenge-open-data.s3.us-east-1.amazonaws.com%2FPHerc0841%2Fvolumes%2F20250821151531-9.366um-1.2m-113keV-masked.zarr%2F%22%2C%22name%22%3A%22The%20CT%20that%20mesh%20is%20in%2C%20PHerc0841%209.366%20um%22%7D%5D%2C%22layout%22%3A%224panel%22%7D" target="_blank" rel="noopener">Open in neuroglancer</a>
|
| 168 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>https://neuroglancer-demo.appspot.com/#!%7B%22layers%22%3A%5B%7B%22type%22%3A%22image%22%2C%22source%22%3A%22zarr%3A%2F%2Fhttps%3A%2F%2Fvesuvius-challenge-open-data.s3.us-east-1.amazonaws.com%2FPHerc0841%2Fvolumes%2F20250821151531-9.366um-1.2m-113keV-masked.zarr%2F%22%2C%22name%22%3A%22The%20CT%20that%20mesh%20is%20in%2C%20PHerc0841%209.366%20um%22%7D%5D%2C%22layout%22%3A%224panel%22%7D</code></pre></div></details>
|
| 169 |
+
<details><summary>curl<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 170 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>curl -s https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr/.zattrs # OME multiscales metadata
|
| 171 |
+
curl -s https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHerc0841/volumes/20250821151531-9.366um-1.2m-113keV-masked.zarr/2/.zarray # one level: shape, chunks, dtype, codec</code></pre></div></details>
|
| 172 |
+
</div>
|
| 173 |
+
<div class="scc-src" id="connect-page"><h3>This page's own files</h3>
|
| 174 |
+
<p class="scc-meta">Page code Apache 2.0; data as above · In the browser console, window.snappedLabels.segments() lists the index and window.snappedLabels.open(SEGMENT, Z) opens a segment at a plane; ?seg=SEGMENT&z=Z links to a view.</p>
|
| 175 |
+
<div class="scc-url"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>https://sharpmeow-snapped-labels.static.hf.space/</code></pre></div>
|
| 176 |
+
<details><summary>Links and embed<span class="scc-b ok">tested 2026-10-24</span></summary>
|
| 177 |
+
<div class="scc-code"><button type="button" class="scc-copy" data-testid="connect-copy" onclick="navigator.clipboard&&navigator.clipboard.writeText(this.parentNode.querySelector('code').innerText).then(()=>{this.textContent='Copied';setTimeout(()=>this.textContent='Copy',1200)})">Copy</button><pre><code>The dataset card: https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels
|
| 178 |
+
A segment's cross sections for the viewer (float32 rows, see the .json beside it): https://huggingface.co/datasets/SharpMeow/vesuvius-snapped-labels/resolve/main/viewer/PHerc0841/20260220213127-w00.json
|
| 179 |
+
Page source: https://github.com/ChaseHendrick/Scrolls/tree/main/spaces/snapped-labels</code></pre></div></details>
|
| 180 |
+
</div>
|
| 181 |
+
<footer>Machine-readable: <a href="https://huggingface.co/spaces/SharpMeow/snapped-labels/blob/main/connect.json" target="_blank" rel="noopener">connect.json</a> and <a href="https://huggingface.co/spaces/SharpMeow/snapped-labels/blob/main/CONNECT.md" target="_blank" rel="noopener">CONNECT.md</a>. Checked 2026-10-27. "Tested" means the snippet ran against this public data on that date. Scan data keeps its own license (the Vesuvius Challenge open data is CC BY-NC 4.0).</footer></section>
|
| 182 |
+
<!-- space-connect:end -->
|
| 183 |
+
</section>
|
| 184 |
+
</main>
|
| 185 |
+
<script src="app.js"></script>
|
| 186 |
+
</body>
|
| 187 |
</html>
|
style.css
CHANGED
|
@@ -1,28 +1,79 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
| 4 |
}
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
margin-top: 0;
|
| 9 |
}
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
.
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
}
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--bg: #f7f7fb; --panel: #ffffff; --ink: #1b1d29; --mute: #5d6378; --line: #dfe1ea;
|
| 3 |
+
--pub: #f08a24; --snap: #2f7bf6; --move: rgba(120, 124, 140, .75); --accent: #7c5cff; --held: #e5487f;
|
| 4 |
+
--code: #eef0f7;
|
| 5 |
}
|
| 6 |
+
@media (prefers-color-scheme: dark) {
|
| 7 |
+
:root { --bg: #11131c; --panel: #191c28; --ink: #e7e9f2; --mute: #9aa1b8; --line: #2b3042; --code: #222738;
|
| 8 |
+
--move: rgba(200, 204, 220, .6); }
|
|
|
|
| 9 |
}
|
| 10 |
+
* { box-sizing: border-box; }
|
| 11 |
+
[hidden] { display: none !important; }
|
| 12 |
+
body { margin: 0; background: var(--bg); color: var(--ink); font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
|
| 13 |
+
a { color: var(--accent); }
|
| 14 |
+
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .88em; background: var(--code); padding: .05em .3em; border-radius: 4px; }
|
| 15 |
+
.top { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px 24px; padding: 20px 24px 8px; }
|
| 16 |
+
.brand { flex: 1 1 420px; }
|
| 17 |
+
h1 { margin: 0; font-size: 1.7em; letter-spacing: -.01em;
|
| 18 |
+
background: linear-gradient(90deg, var(--pub), var(--accent) 55%, var(--snap)); -webkit-background-clip: text; background-clip: text; color: transparent; }
|
| 19 |
+
.sub { margin: 4px 0 0; color: var(--mute); max-width: 60em; }
|
| 20 |
+
.tabs { display: flex; gap: 4px; flex-wrap: wrap; }
|
| 21 |
+
.tabs button { border: 1px solid var(--line); background: var(--panel); color: var(--ink); padding: 6px 14px; border-radius: 99px; cursor: pointer; font: inherit; }
|
| 22 |
+
.tabs button[aria-selected="true"] { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
|
| 23 |
+
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 10px; padding: 8px 24px 4px; }
|
| 24 |
+
.stat { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 10px 14px; }
|
| 25 |
+
.stat b { display: block; font-size: 1.45em; }
|
| 26 |
+
.stat:nth-child(1) b { color: var(--snap); } .stat:nth-child(2) b { color: var(--accent); }
|
| 27 |
+
.stat:nth-child(3) b { color: var(--pub); } .stat:nth-child(4) b { color: var(--held); }
|
| 28 |
+
.stat span { color: var(--mute); font-size: .88em; }
|
| 29 |
+
main { padding: 12px 24px 32px; }
|
| 30 |
+
.viewer { display: grid; grid-template-columns: 260px 1fr; gap: 16px; }
|
| 31 |
+
.side { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 12px; display: flex; flex-direction: column; gap: 6px; max-height: 960px; }
|
| 32 |
+
.lbl { font-size: .8em; color: var(--mute); text-transform: uppercase; letter-spacing: .05em; }
|
| 33 |
+
select, input[type="search"] { font: inherit; color: var(--ink); background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 6px 8px; }
|
| 34 |
+
.seglist { list-style: none; margin: 4px 0 0; padding: 0; overflow: auto; flex: 1; min-height: 160px; }
|
| 35 |
+
.seglist li { padding: 6px 8px; border-radius: 8px; cursor: pointer; display: flex; justify-content: space-between; gap: 8px; }
|
| 36 |
+
.seglist li:hover { background: var(--bg); }
|
| 37 |
+
.seglist li.on { background: color-mix(in srgb, var(--accent) 16%, transparent); }
|
| 38 |
+
.seglist small { color: var(--mute); white-space: nowrap; }
|
| 39 |
+
.stage { min-width: 0; }
|
| 40 |
+
.bar { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 8px; }
|
| 41 |
+
.vt { font-weight: 600; }
|
| 42 |
+
.sp { flex: 1; }
|
| 43 |
+
.tog { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; color: var(--mute); }
|
| 44 |
+
.dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
|
| 45 |
+
.dot.pub { background: var(--pub); } .dot.snap { background: var(--snap); } .dot.move { background: var(--move); border-radius: 2px; height: 3px; width: 14px; }
|
| 46 |
+
.canvaswrap { position: relative; background: #000; border-radius: 12px; overflow: hidden; aspect-ratio: 1 / 1; max-width: 760px; }
|
| 47 |
+
canvas { width: 100%; height: 100%; display: block; cursor: crosshair; image-rendering: pixelated; }
|
| 48 |
+
.cvmsg { position: absolute; inset: auto 0 0 0; padding: 8px 12px; background: rgba(0, 0, 0, .6); color: #fff; font-size: .9em; }
|
| 49 |
+
.ctl { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 10px; max-width: 760px; }
|
| 50 |
+
.ctl button { font: inherit; border: 1px solid var(--line); background: var(--panel); color: var(--ink); border-radius: 8px; padding: 4px 12px; cursor: pointer; }
|
| 51 |
+
.zl { display: inline-flex; align-items: center; gap: 6px; color: var(--mute); }
|
| 52 |
+
.zl input[type="range"] { width: 260px; accent-color: var(--accent); }
|
| 53 |
+
.hint { color: var(--mute); font-size: .85em; max-width: 760px; }
|
| 54 |
+
.card { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; max-width: 760px; }
|
| 55 |
+
.card .row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; margin: 6px 0 10px; }
|
| 56 |
+
.card .row div b { display: block; font-size: 1.15em; }
|
| 57 |
+
.card .row div span { color: var(--mute); font-size: .8em; }
|
| 58 |
+
.meter { height: 10px; border-radius: 99px; background: var(--line); overflow: hidden; display: flex; margin: 6px 0; }
|
| 59 |
+
.meter i { display: block; height: 100%; }
|
| 60 |
+
.legend { font-size: .8em; color: var(--mute); display: flex; gap: 12px; flex-wrap: wrap; }
|
| 61 |
+
.legend i { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 4px; vertical-align: -1px; }
|
| 62 |
+
pre.cmd { background: var(--code); padding: 8px 10px; border-radius: 8px; white-space: pre-wrap; overflow-wrap: anywhere; font-size: .82em; margin: 6px 0 0; }
|
| 63 |
+
.tablewrap { overflow: auto; background: var(--panel); border: 1px solid var(--line); border-radius: 12px; }
|
| 64 |
+
table { border-collapse: collapse; width: 100%; font-size: .9em; }
|
| 65 |
+
th, td { padding: 6px 10px; border-bottom: 1px solid var(--line); text-align: left; white-space: nowrap; }
|
| 66 |
+
th.n, td.n { text-align: right; font-variant-numeric: tabular-nums; }
|
| 67 |
+
tbody tr { cursor: pointer; } tbody tr:hover { background: var(--bg); }
|
| 68 |
+
.prose { max-width: 52em; background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 8px 20px 16px; }
|
| 69 |
+
.prose h2 { font-size: 1.1em; margin: 1em 0 .3em; }
|
| 70 |
+
.credit { color: var(--mute); font-size: .85em; }
|
| 71 |
+
.fig { margin: 12px 0; }
|
| 72 |
+
.fig img { max-width: 100%; height: auto; border-radius: 10px; display: block; }
|
| 73 |
+
.fig figcaption { color: var(--mute); font-size: .85em; margin-top: 4px; }
|
| 74 |
+
@media (max-width: 760px) {
|
| 75 |
+
.top, .stats, main { padding-left: 16px; padding-right: 16px; }
|
| 76 |
+
.viewer { grid-template-columns: 1fr; }
|
| 77 |
+
.side { max-height: 300px; }
|
| 78 |
+
.zl input[type="range"] { width: 160px; }
|
| 79 |
}
|