paris4-ink-map / index.html
SharpMeow's picture
Paris 4 Ink Map 1.2: 3D scroll, neighboring turns, VC3D project, saved views
d3405a2 verified
Raw History Blame Contribute Delete
44.9 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Paris 4 Ink Map</title>
<meta name="description" content="The Vesuvius Challenge organizers' whole-scroll ink map of PHerc. Paris 4: zoom, windings, the spiral over live CT, downloads of any size, an OME-Zarr stream, an HTTP API and an MCP server.">
<meta name="theme-color" content="#05070a">
<meta name="color-scheme" content="dark">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%2305070a'/%3E%3Cpath d='M16 16m-2 0a2 2 0 1 0 4 0a4 4 0 1 0-8 0a6 6 0 1 0 12 0a8 8 0 1 0-16 0a10 10 0 1 0 20 0' fill='none' stroke='%23f4b860' stroke-width='1.6'/%3E%3C/svg%3E">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="static/style.css?v=4">
<script src="https://cdnjs.cloudflare.com/ajax/libs/openseadragon/4.1.0/openseadragon.min.js"></script>
<!-- space-onescroll:start -->
<!-- one page scroll bar: written by python -m kit spaceconnect render; do not edit -->
<style>iframe[srcdoc]{display:block;max-height:none!important}</style>
<script>
(function () {
if (window.__oneScroll) return;
window.__oneScroll = true;
var CSS = 'html,body{height:auto!important;min-height:0!important}html{overflow:hidden!important}';
function size(f) {
var d, st = f.__os;
try { d = f.contentDocument; } catch (e) { return; }
if (!d || !d.body || st.off) return;
var de = d.documentElement, r = Math.ceil(de.getBoundingClientRect().height), over = de.scrollHeight;
var ch = f.clientHeight, t = r;
if (over > ch + 1) t = Math.max(r, over);
else if (r >= st.r) t = Math.max(r, ch);
st.r = r;
if (Math.abs(t - ch) < 2) return;
var now = Date.now();
st.n = now - st.t0 < 4000 ? st.n + 1 : 1;
if (st.n === 1) st.t0 = now;
if (st.n > 40) { st.off = true; f.removeAttribute('scrolling'); var g = d.getElementById('space-onescroll'); if (g) g.remove(); return; }
f.style.setProperty('height', t + 'px', 'important');
}
function attach(f) {
var d, st = f.__os;
try { d = f.contentDocument; } catch (e) { return; }
if (!d || !d.documentElement || st.doc === d) return;
st.doc = d; st.r = Infinity; st.n = 0; st.off = false;
if (!d.getElementById('space-onescroll')) {
var s = d.createElement('style'); s.id = 'space-onescroll'; s.textContent = CSS;
(d.head || d.documentElement).appendChild(s);
}
f.setAttribute('scrolling', 'no');
f.style.setProperty('min-height', '0', 'important');
f.style.setProperty('max-height', 'none', 'important');
var w = d.defaultView, go = function () { size(f); };
if (w && w.ResizeObserver) { var ro = new w.ResizeObserver(go); ro.observe(d.documentElement); if (d.body) ro.observe(d.body); }
d.addEventListener('load', go, true);
go();
}
function bind(f) {
if (f.__os || f.getAttribute('data-scroll') === 'inner') return;
try { if (!f.contentDocument) return; } catch (e) { return; }
f.__os = {t0: 0, n: 0, r: 0};
f.addEventListener('load', function () { attach(f); });
if (f.contentDocument.readyState !== 'loading') attach(f);
else f.contentDocument.addEventListener('DOMContentLoaded', function () { attach(f); });
}
function scan() { var fs = document.getElementsByTagName('iframe'); for (var i = 0; i < fs.length; i++) bind(fs[i]); }
function start() {
scan();
new MutationObserver(scan).observe(document.documentElement, {childList: true, subtree: true});
window.addEventListener('resize', function () {
var fs = document.getElementsByTagName('iframe');
for (var i = 0; i < fs.length; i++) if (fs[i].__os) size(fs[i]);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); else start();
})();
</script>
<!-- space-onescroll:end -->
</head>
<body>
<header class="top">
<div class="top-in">
<a class="brand" href="#map">
<svg viewBox="0 0 32 32" aria-hidden="true"><path d="M16 16m-2 0a2 2 0 1 0 4 0a4 4 0 1 0-8 0a6 6 0 1 0 12 0a8 8 0 1 0-16 0a10 10 0 1 0 20 0" fill="none" stroke="currentColor" stroke-width="1.6"/></svg>
<span>Paris 4 Ink Map</span>
</a>
<nav class="nav" aria-label="Sections">
<a href="#map">Map</a><a href="#scroll3d">3D</a><a href="#neighbors">Neighbors</a><a href="#downloads">Download</a><a href="#stream">Stream</a><a href="#api">API &amp; MCP</a><a href="#connect">Connect</a><a href="#about">About</a>
</nav>
<span id="status" class="pill">Starting</span>
<button id="help-btn" class="btn icon" type="button" title="Keys and tips (?)">?</button>
</div>
</header>
<div class="hero">
<div class="hero-in">
<p class="eyebrow">PHerc. Paris 4 路 Vesuvius Challenge open data</p>
<h1>The whole scroll in one sheet</h1>
<p class="lede">The organizers' ink map of PHerc. Paris 4, rendered on their spiral fit of the full scroll: 10.9 m of papyrus, streamed at full resolution. Zoom in, follow a winding, see where the sheet sits in the CT, and download or stream any part of it.</p>
<dl class="stats">
<div><dt>Sheet</dt><dd>10.9 m</dd></div>
<div><dt>Pixels</dt><dd>142,283 脳 2,390</dd></div>
<div><dt>Resolution</dt><dd>0.0768 mm</dd></div>
<div><dt>Windings</dt><dd>10 to 135</dd></div>
<div><dt>Scan Height</dt><dd>38.4 to 170.4 mm</dd></div>
</dl>
</div>
</div>
<div id="loading" class="loading">
<div class="spiral"></div>
<div>
<div id="load-phase" class="load-phase">Reading the map from the open data</div>
<div class="bar"><i id="load-bar"></i></div>
<div id="load-detail" class="dim mono"></div>
</div>
</div>
<main id="app" hidden>
<section id="map" class="overview card">
<div class="card-h">
<h2>Whole Scroll</h2>
<span class="dim">Left: outer end, winding 135. Right: inner end, winding 10. Click or drag to move the view.</span>
<span class="grow"></span>
<label class="mini">Rows <select id="ov-rows"><option>2</option><option selected>3</option><option>4</option><option>6</option><option>8</option></select></label>
</div>
<div id="ov-wrap" class="ov-wrap"><img id="ov-img" alt="The whole ink map, wrapped into rows"><canvas id="ov-canvas"></canvas></div>
</section>
<section class="workspace">
<div class="viewer-col card">
<div class="card-h">
<h2>Ink Map</h2>
<span id="where" class="dim mono"></span>
<span class="grow"></span>
<div class="seg" role="group" aria-label="Tools">
<button type="button" data-tool="pan" class="on" title="Pan and zoom">Pan</button>
<button type="button" data-tool="measure" title="Measure a distance (M)">Measure</button>
</div>
<button id="save-view" type="button" class="btn small" title="Save this view as a PNG with a scale bar (S)">Save View</button>
<button id="full-btn" type="button" class="btn small" title="Fullscreen (F)">Fullscreen</button>
</div>
<div id="osd" class="osd"><canvas id="osd-overlay"></canvas>
<div class="scalebar"><i id="sb-bar"></i><span id="sb-label"></span></div>
<div id="hud" class="hud mono"></div>
</div>
<div class="controls">
<div class="ctl-group">
<label>Colors <select id="cmap"><option value="gray">Gray</option><option value="ember">Ember</option><option value="ice">Ice</option><option value="viridis">Viridis</option></select></label>
<label class="chk"><input id="invert" type="checkbox"> Invert</label>
</div>
<div class="ctl-group">
<label>Gamma <input id="gamma" type="range" min="0.4" max="2.5" step="0.05" value="1"><output id="gamma-v">1.00</output></label>
<label>Contrast <input id="contrast" type="range" min="0.5" max="3" step="0.05" value="1"><output id="contrast-v">1.00</output></label>
<label>Brightness <input id="bright" type="range" min="0.4" max="2" step="0.05" value="1"><output id="bright-v">1.00</output></label>
</div>
<div class="ctl-group">
<label class="chk"><input id="wind-on" type="checkbox"> Windings</label>
<label>Opacity <input id="wind-op" type="range" min="0.1" max="1" step="0.05" value="0.45"></label>
<label class="chk"><input id="grid-on" type="checkbox"> Grid (mm)</label>
<button id="reset-view" type="button" class="btn small">Whole Map</button>
</div>
</div>
</div>
<aside class="side">
<div class="card">
<div class="card-h"><h2>Scroll Map</h2><span class="dim">The spiral at one height, over the CT</span></div>
<div class="xs-wrap"><canvas id="xs" aria-label="Cross-section of the scroll"></canvas><div id="xs-msg" class="xs-msg"></div></div>
<div class="xs-ctl">
<label class="grow">Height <input id="xs-z" type="range" min="39" max="170" step="0.1" value="105"><output id="xs-z-v" class="mono">105.0 mm</output></label>
<label class="chk" title="The scroll map follows the pointer on the ink map (L)"><input id="xs-follow" type="checkbox" checked> Follow</label>
</div>
<p class="dim tiny">Bright: the papyrus in the ink view. Ring: the pointer. Click the spiral to go there; wheel to zoom, drag to pan, double-click to reset. CT: 2.4 碌m 78 keV scan, level 4 (38.4 碌m), read live from the open data.</p>
</div>
<div class="card">
<div class="card-h"><h2>Under the Pointer</h2><span class="dim">Click the map to pin</span></div>
<dl id="readout" class="readout mono"></dl>
<div class="row-btns">
<button id="copy-xyz" type="button" class="btn small">Copy XYZ</button>
<a id="ng-link" class="btn small" target="_blank" rel="noopener">Open CT in Neuroglancer</a>
<button id="copy-link" type="button" class="btn small">Copy View Link</button>
</div>
</div>
<div class="card">
<div class="card-h"><h2>Go To</h2></div>
<div class="goto">
<label>Winding <input id="go-w" type="number" min="10" max="135" step="1" placeholder="10 to 135"></label>
<button id="go-w-btn" type="button" class="btn small">Go</button>
<label>Millimeters from outer end <input id="go-mm" type="number" min="0" step="1" placeholder="0"></label>
<button id="go-mm-btn" type="button" class="btn small">Go</button>
</div>
<div class="card-h sub-h"><h3>Bookmarks</h3><span class="dim">Kept in this browser</span><span class="grow"></span><button id="bm-add" type="button" class="btn small">Add This View (B)</button></div>
<ol id="bm-list" class="bm-list"></ol>
<div class="row-btns bm-io">
<button id="bm-export" type="button" class="btn small" title="Save your bookmarks as a JSON file">Export</button>
<label class="btn small" title="Load bookmarks from a JSON file">Import<input id="bm-import" type="file" accept="application/json,.json" hidden></label>
<button id="bm-share" type="button" class="btn small" title="Copy a link that holds every bookmark">Share as Link</button>
</div>
</div>
</aside>
</section>
<section id="scroll3d" class="card">
<div class="sec-h">
<h2>3D Scroll</h2>
<p class="dim">The organizers' spiral fit as it sits in the scan, colored by this ink map. Drag Unroll to open the roll into the flat sheet, outer end first; drag to turn it, wheel to zoom, click any point to go there on the ink map. The ring follows your pointer on the map.</p>
</div>
<div id="scroll3d-box" class="s3d-box"><p class="dim s3d-wait">The 3D view loads when you scroll here.</p></div>
</section>
<section id="neighbors" class="card">
<div class="sec-h">
<h2>Neighboring Turns</h2>
<p class="dim">A rolled sheet lies against itself. For the pinned place, this finds the papyrus just outside and just inside it at the same scan height and sets the three side by side, with how alike their ink is. Neighboring turns are always somewhat alike; where a neighbor is far more alike than it is elsewhere along the same two turns, the ink here may come from that turn, or from where the fit meets it, rather than from this one.</p>
</div>
<div class="nb-bar">
<button id="nb-run" type="button" class="btn primary small">Check the Pinned Place (N)</button>
<label class="mini">Window <select id="nb-half"><option value="6">12 mm</option><option value="12" selected>24 mm</option><option value="20">40 mm</option></select></label>
<span id="nb-msg" class="dim"></span>
</div>
<div class="nb-grid">
<figure class="nb" id="nb-outer"><div class="nb-img"><img alt="The turn just outside"></div><figcaption><b>Outer Turn</b><span class="nb-meta mono"></span><span class="nb-score"><i></i></span><button type="button" class="btn small nb-go">Go There</button></figcaption></figure>
<figure class="nb here" id="nb-here"><div class="nb-img"><img alt="This place"></div><figcaption><b>This Place</b><span class="nb-meta mono"></span></figcaption></figure>
<figure class="nb" id="nb-inner"><div class="nb-img"><img alt="The turn just inside"></div><figcaption><b>Inner Turn</b><span class="nb-meta mono"></span><span class="nb-score"><i></i></span><button type="button" class="btn small nb-go">Go There</button></figcaption></figure>
</div>
<p id="nb-null" class="dim tiny"></p>
</section>
<section id="downloads" class="card">
<div class="sec-h">
<h2>Download</h2>
<p class="dim">Cut any region at full or reduced resolution, take the whole map, or go straight to the organizers' originals. Nothing is copied here: the Space reads the open data.</p>
</div>
<div class="tri">
<div class="panel">
<h3>This View</h3>
<p class="dim">The region in the ink map now.</p>
<label class="field">Resolution <select id="dl-scale"><option value="1">Full, 0.0768 mm/px</option><option value="2">1/2, 0.154 mm/px</option><option value="4">1/4, 0.307 mm/px</option><option value="8">1/8, 0.614 mm/px</option><option value="16">1/16, 1.23 mm/px</option></select></label>
<p id="dl-size" class="mono size"></p>
<div class="row-btns">
<a id="dl-png" class="btn primary">PNG</a>
<a id="dl-tif" class="btn">TIFF</a>
</div>
<p id="dl-note" class="dim tiny"></p>
</div>
<div class="panel">
<h3>Whole Map</h3>
<p class="dim">The full sheet as one tiled, deflated TIFF, cut by this Space.</p>
<ul class="dl-list">
<li><a class="btn" href="api/crop?x0=0&amp;y0=0&amp;x1=142283&amp;y1=2390&amp;scale=1&amp;fmt=tif">Full Resolution</a><span class="dim mono">142,283 脳 2,390</span></li>
<li><a class="btn" href="api/crop?x0=0&amp;y0=0&amp;x1=142283&amp;y1=2390&amp;scale=2&amp;fmt=tif">Half</a><span class="dim mono">71,141 脳 1,195</span></li>
<li><a class="btn" href="api/crop?x0=0&amp;y0=0&amp;x1=142283&amp;y1=2390&amp;scale=4&amp;fmt=tif">Quarter</a><span class="dim mono">35,570 脳 597</span></li>
<li><a class="btn" href="api/overview.png?rows=3&amp;width=4000">Preview PNG</a><span class="dim mono">3 rows, 4,000 px wide</span></li>
</ul>
</div>
<div class="panel">
<h3>Original</h3>
<p class="dim">The organizers' published file, from the Vesuvius Challenge open data.</p>
<a id="dl-orig" class="btn" target="_blank" rel="noopener" href="https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHercParis4/segments/20260926044226-spiral-output-generation-19/ink-detection/PHercParis4-20260926044226-2.4um-0.22m-78keV-volume-20260411134726-ink3d-20260926084000-student-v12A-25000-c7766be9-l128-accmax4-max-52-76-scale0.0625-nearest.tif">BigTIFF, 236 MB</a>
<p class="dim tiny">uint8, deflate, 64 px tiles. Every source file, with its size and SHA-256, is listed below.</p>
</div>
</div>
<h3 class="tbl-h">Source Files</h3>
<table class="files"><thead><tr><th>File</th><th>Size</th><th>SHA-256</th></tr></thead><tbody id="files"></tbody></table>
</section>
<section id="stream" class="card">
<div class="sec-h">
<h2>Stream</h2>
<p class="dim">Read the map from your own code or viewer, a tile at a time, without downloading it. Open to every origin (CORS), no account.</p>
</div>
<div class="quad stream">
<div class="panel">
<h3>OME-Zarr</h3>
<p class="dim">OME-NGFF 0.4 on zarr v2: 9 levels, 512 px uint8 chunks, zlib. Works with zarr, ome-zarr-py, napari and Neuroglancer.</p>
<div class="url"><code>https://sharpmeow-paris4-ink-map.hf.space/zarr/</code><button type="button" class="icon-btn" data-copy="https://sharpmeow-paris4-ink-map.hf.space/zarr/" data-what="Zarr URL copied">Copy</button></div>
<a class="btn small" id="ng-zarr" target="_blank" rel="noopener">Open in Neuroglancer</a>
</div>
<div class="panel">
<h3>Python</h3>
<pre class="code"><code>import fsspec, zarr
url = "https://sharpmeow-paris4-ink-map.hf.space/zarr/"
g = zarr.open_consolidated(fsspec.get_mapper(url))
full = g["0"] # 2,390 x 142,283, uint8
strip = full[:, 70000:72000]</code></pre>
<p class="dim tiny">Needs <span class="mono">zarr</span> 2, <span class="mono">fsspec</span> and <span class="mono">aiohttp</span>. Level <span class="mono">k</span> is 2<sup>k</sup> times coarser.</p>
</div>
<div class="panel">
<h3>Deep Zoom</h3>
<p class="dim">The tiles this page uses: 256 px JPEG, for OpenSeadragon and other Deep Zoom viewers.</p>
<div class="url"><code>https://sharpmeow-paris4-ink-map.hf.space/tiles/ink.dzi</code><button type="button" class="icon-btn" data-copy="https://sharpmeow-paris4-ink-map.hf.space/tiles/ink.dzi" data-what="DZI URL copied">Copy</button></div>
<p class="dim tiny">Tiles: <span class="mono">/tiles/ink_files/{level}/{col}_{row}.jpg</span>, with <span class="mono">?cmap=</span> and <span class="mono">&amp;gamma=</span>.</p>
</div>
<div class="panel" id="vc3d">
<h3>Open in VC3D</h3>
<p class="dim">A ready project for villa's VC3D: the 2.4 碌m CT streams from the open data, and the organizers' spiral fit comes as a segment in that scan's voxels, so the ink map's sheet sits in the scan.</p>
<a class="btn small primary" href="api/vc3d.zip" download>VC3D Project (zip, 117 MB)</a>
<ol class="steps">
<li>Unzip it anywhere.</li>
<li>In VC3D: File, Open Project, then pick <span class="mono">paris4-spiral-output-generation-19.volpkg.json</span>.</li>
</ol>
<p class="dim tiny">Or by hand: File, Attach Remote Zarr with the CT URL below, then File, Attach Segments with the zip's <span class="mono">segments</span> folder. Built from VC3D's project format in villa's source; not yet opened in a VC3D build here.</p>
<div class="url"><code>https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHercParis4/volumes/20260411134726-2.400um-0.2m-78keV-masked.zarr/</code><button type="button" class="icon-btn" data-copy="https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHercParis4/volumes/20260411134726-2.400um-0.2m-78keV-masked.zarr/" data-what="CT URL copied">Copy</button></div>
</div>
</div>
</section>
<section id="api" class="card">
<div class="sec-h">
<h2>API &amp; MCP</h2>
<p class="dim">An HTTP API for scripts and an MCP server for agents, both public. Every endpoint is described in the <a href="https://sharpmeow-paris4-ink-map.hf.space/docs" target="_blank" rel="noopener">interactive API docs</a>.</p>
</div>
<div class="duo">
<div class="panel">
<h3>HTTP API</h3>
<table class="api">
<tbody>
<tr><td><a class="mono" href="api/info" target="_blank">/api/info</a></td><td>Size, scale, orientation, windings, stream URLs, sources with SHA-256</td></tr>
<tr><td><a class="mono" href="api/point?x=71000&amp;y=1200" target="_blank">/api/point</a></td><td>One map pixel: winding, distance from each end, scan height, voxel</td></tr>
<tr><td><a class="mono" href="api/winding/60" target="_blank">/api/winding/{n}</a></td><td>One winding's pixel range</td></tr>
<tr><td><a class="mono" href="api/crop?x0=70000&amp;y0=600&amp;x1=72000&amp;y1=1800&amp;scale=1&amp;fmt=tif">/api/crop</a></td><td>A region: PNG up to 60 Mpx, TIFF of any size</td></tr>
<tr><td><a class="mono" href="api/neighbors?x=120000&amp;y=1800" target="_blank">/api/neighbors</a></td><td>The turns just outside and inside a place: where, how far, how alike</td></tr>
<tr><td><a class="mono" href="api/vc3d.zip">/api/vc3d.zip</a></td><td>A VC3D project: the CT streamed from the open data, the fit as a segment</td></tr>
<tr><td><a class="mono" href="api/mesh3d.json?stride=8" target="_blank">/api/mesh3d.json</a></td><td>The fit's mesh as a point cloud for 3D: layout of <span class="mono">/api/mesh3d.bin</span></td></tr>
<tr><td><a class="mono" href="api/section?z_mm=105&amp;step=2" target="_blank">/api/section</a></td><td>Where the sheet crosses one scan height</td></tr>
<tr><td><a class="mono" href="api/ct.png?z_mm=105" target="_blank">/api/ct.png</a></td><td>The CT slice at one height, level 4, cropped to the scroll</td></tr>
<tr><td><a class="mono" href="api/overview.png?rows=3&amp;width=1600" target="_blank">/api/overview.png</a></td><td>The whole map wrapped into rows</td></tr>
<tr><td><a class="mono" href="api" target="_blank">/api</a></td><td>Every endpoint with an example, as JSON</td></tr>
</tbody>
</table>
</div>
<div class="panel">
<h3>MCP Server</h3>
<p class="dim">Streamable HTTP, JSON replies, no session. Seven tools: <span class="mono">get_info</span>, <span class="mono">find_winding</span>, <span class="mono">point_info</span>, <span class="mono">crop_image</span> (returns a PNG), <span class="mono">cross_section</span>, <span class="mono">neighbor_turns</span> and <span class="mono">view_link</span>.</p>
<div class="url"><code>https://sharpmeow-paris4-ink-map.hf.space/mcp</code><button type="button" class="icon-btn" data-copy="https://sharpmeow-paris4-ink-map.hf.space/mcp" data-what="MCP URL copied">Copy</button></div>
<p class="lbl">Claude Code</p>
<div class="url"><code>claude mcp add --transport http paris4-ink-map https://sharpmeow-paris4-ink-map.hf.space/mcp</code><button type="button" class="icon-btn" data-copy="claude mcp add --transport http paris4-ink-map https://sharpmeow-paris4-ink-map.hf.space/mcp" data-what="Command copied">Copy</button></div>
<p class="lbl">Other clients (JSON config)</p>
<pre class="code"><code>{"mcpServers": {"paris4-ink-map": {
"type": "http",
"url": "https://sharpmeow-paris4-ink-map.hf.space/mcp"}}}</code></pre>
</div>
</div>
</section>
<section class="card connect-card">
<!-- space-connect:start -->
<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}
.scc h2{font-size:1.15em;margin:.2em 0 .3em}.scc h3{font-size:1em;margin:0 0 .2em}
.scc p{margin:.3em 0}.scc a{color:var(--a)}
.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)}
.scc .scc-meta{font-size:.85em;opacity:.8}
.scc details{border-top:1px solid color-mix(in srgb,currentColor 12%,transparent);padding:.35em 0}
.scc summary{cursor:pointer;font-weight:600;list-style-position:inside}
.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}
.scc .scc-b.ok{color:#3fb37f}.scc .scc-b.src{color:var(--a)}.scc .scc-b.warn{color:#e0a030}
.scc .scc-code{position:relative;margin:.4em 0 .2em}
.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}
.scc code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
.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}
.scc button.scc-copy:hover{border-color:var(--a)}
.scc .scc-url{position:relative}.scc .scc-url pre{padding-right:4.5em;white-space:pre-wrap;word-break:break-all}
.scc .scc-go{display:inline-block;margin:.3em 0;padding:.25em .8em;border-radius:6px;border:1px solid var(--a);text-decoration:none}
.scc footer{font-size:.8em;opacity:.75;margin-top:.6em}</style>
<h2>Connect your tool</h2>
<p>The map on this page is the organizers&#x27; published ink map of PHerc. Paris 4; this Space streams it and reads the published mesh and CT, so every file below is linked at the Vesuvius Challenge open data, never copied here. The page&#x27;s own API and its MCP server turn a map pixel into a winding and scan coordinates, cut crops, draw the spiral at one height, compare a place with the turns beside it and build a VC3D project. The Space is public and needs no account.</p>
<div class="scc-src" id="connect-mcp"><h3>MCP Server for Agents</h3>
<p class="scc-meta">Vesuvius Challenge open data, CC BY-NC 4.0; the map and fit are the organizers&#x27; 路 Streamable HTTP, no session, JSON replies. Seven tools: get_info, find_winding, point_info, crop_image (returns a PNG), cross_section (optionally with the CT slice), neighbor_turns (optionally with the three windows) and view_link.</p>
<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-paris4-ink-map.hf.space/mcp</code></pre></div>
<details><summary>Links and embed<span class="scc-b ok">tested 2026-10-24</span></summary>
<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>MCP endpoint (Streamable HTTP, POST): https://sharpmeow-paris4-ink-map.hf.space/mcp</code></pre></div></details>
</div>
<div class="scc-src" id="connect-api"><h3>The Page&#x27;s API</h3>
<p class="scc-meta">Vesuvius Challenge open data, CC BY-NC 4.0; the map and fit are the organizers&#x27; 路 Map pixels: x 0 to 142,282 from the outer end, y 0 to 2,389 from the top, 0.0768 mm each.</p>
<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-paris4-ink-map.hf.space/api/info</code></pre></div>
<details><summary>Links and embed<span class="scc-b ok">tested 2026-10-24</span></summary>
<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>Size, scale, orientation, windings and every source with its SHA-256 (JSON): https://sharpmeow-paris4-ink-map.hf.space/api/info
One map pixel: winding, distance from each end, scan height, voxel (JSON): https://sharpmeow-paris4-ink-map.hf.space/api/point?x=71000&amp;y=1200
One winding&#x27;s pixel range (JSON): https://sharpmeow-paris4-ink-map.hf.space/api/winding/60
A region as a raw TIFF of any size (scale 1, 2, 4, 8 or 16; fmt png adds colors, up to 60 Mpx): https://sharpmeow-paris4-ink-map.hf.space/api/crop?x0=70000&amp;y0=600&amp;x1=72000&amp;y1=1800&amp;scale=1&amp;fmt=tif
Where the sheet crosses one scan height (JSON): https://sharpmeow-paris4-ink-map.hf.space/api/section?z_mm=105&amp;step=2
The turns just outside and inside one place: where, how far, how alike, against a baseline (JSON): https://sharpmeow-paris4-ink-map.hf.space/api/neighbors?x=120000&amp;y=1800
A VC3D project: the 2.4 um CT streamed from the open data, the spiral fit as a tifxyz segment (zip): https://sharpmeow-paris4-ink-map.hf.space/api/vc3d.zip
The fit&#x27;s mesh as a point cloud for 3D views: layout and counts (JSON); binary at /api/mesh3d.bin: https://sharpmeow-paris4-ink-map.hf.space/api/mesh3d.json?stride=8
The map as a streamable OME-Zarr 0.4 (zarr v2, 9 levels, 512 px zlib chunks): https://sharpmeow-paris4-ink-map.hf.space/zarr/.zattrs
Every endpoint with an example (JSON); interactive docs at /docs: https://sharpmeow-paris4-ink-map.hf.space/api
Deep-zoom descriptor of the map (DZI, 256 px JPEG tiles): https://sharpmeow-paris4-ink-map.hf.space/tiles/ink.dzi
The whole map wrapped into rows (PNG): https://sharpmeow-paris4-ink-map.hf.space/api/overview.png?rows=4&amp;width=1600</code></pre></div></details>
</div>
<div class="scc-src" id="connect-ink"><h3>Ink Map, Full Resolution</h3>
<p class="scc-meta">Vesuvius Challenge open data, CC BY-NC 4.0 路 BigTIFF, 142,283 by 2,390 pixels, uint8, deflate, 64 px tiles, no overviews, 236 MB. tifffile reads it.</p>
<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/PHercParis4/segments/20260926044226-spiral-output-generation-19/ink-detection/PHercParis4-20260926044226-2.4um-0.22m-78keV-volume-20260411134726-ink3d-20260926084000-student-v12A-25000-c7766be9-l128-accmax4-max-52-76-scale0.0625-nearest.tif</code></pre></div>
<details><summary>Python<span class="scc-b ok">tested 2026-10-24</span></summary>
<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>import urllib.request
raw = urllib.request.urlopen(&quot;https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHercParis4/segments/20260926044226-spiral-output-generation-19/ink-detection/PHercParis4-20260926044226-2.4um-0.22m-78keV-volume-20260411134726-ink3d-20260926084000-student-v12A-25000-c7766be9-l128-accmax4-max-52-76-scale0.0625-nearest.tif&quot;).read()</code></pre></div></details>
<details><summary>curl<span class="scc-b ok">tested 2026-10-24</span></summary>
<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://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHercParis4/segments/20260926044226-spiral-output-generation-19/ink-detection/PHercParis4-20260926044226-2.4um-0.22m-78keV-volume-20260411134726-ink3d-20260926084000-student-v12A-25000-c7766be9-l128-accmax4-max-52-76-scale0.0625-nearest.tif</code></pre></div></details>
</div>
<div class="scc-src" id="connect-segment"><h3>The Whole Segment Folder: Ink, Mesh, Spiral Fit</h3>
<p class="scc-meta">Vesuvius Challenge open data, CC BY-NC 4.0 路 <a href="https://vesuvius-challenge-open-data.s3.amazonaws.com/index.html#PHercParis4/segments/20260926044226-spiral-output-generation-19/" target="_blank" rel="noopener">browse</a></p>
<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>s3://vesuvius-challenge-open-data/PHercParis4/segments/20260926044226-spiral-output-generation-19/</code></pre></div>
<details><summary>Python (list the folder)<span class="scc-b ok">tested 2026-10-24</span></summary>
<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>import re, urllib.request
B = &quot;https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com&quot;
xml = urllib.request.urlopen(B + &quot;/?list-type=2&amp;delimiter=/&amp;prefix=PHercParis4/segments/20260926044226-spiral-output-generation-19/&quot;).read().decode()
print(re.findall(r&quot;&lt;Prefix&gt;([^&lt;]+)&lt;/Prefix&gt;&quot;, xml)) # subfolders; &lt;Key&gt; gives files</code></pre></div></details>
</div>
<div class="scc-src" id="connect-mesh"><h3>The Fit&#x27;s Mesh (tifxyz, 9.6 um Voxels of Scan 20260411134726)</h3>
<p class="scc-meta">Vesuvius Challenge open data, CC BY-NC 4.0 路 956 by 56,913 nodes at 0.192 mm. This grid is already the fit rotated 180 degrees, as the map is (grid_transform in meta.json): map x = 2.5 times the column, map y = 2.5 times the row. Multiply the coordinates by 4 for 2.4 um voxels.</p>
<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/PHercParis4/segments/20260926044226-spiral-output-generation-19/mesh/intermediate/tifxyz_normalized/</code></pre></div>
<details><summary>Python (numpy, tifffile)<span class="scc-b ok">tested 2026-10-24</span></summary>
<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
import io, json, urllib.request
import numpy as np, tifffile
T = &quot;https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHercParis4/segments/20260926044226-spiral-output-generation-19/mesh/intermediate/tifxyz_normalized&quot;
meta = json.load(urllib.request.urlopen(T + &quot;/meta.json&quot;)) # scale, bbox, format
get = lambda c: tifffile.imread(io.BytesIO(urllib.request.urlopen(f&quot;{T}/{c}.tif&quot;).read()))
xyz = np.stack([get(c) for c in &quot;xyz&quot;], -1) # xyz[i, j] = (x, y, z) in scan voxels; -1 = no surface</code></pre></div></details>
</div>
<div class="scc-src" id="connect-ct"><h3>CT, 2.4 um 78 keV Scan 20260411134726</h3>
<p class="scc-meta">Vesuvius Challenge open data, CC BY-NC 4.0 路 Uncompressed, 128 cubed chunks; level 4 is 38.4 um, the level the scroll map on this page reads.</p>
<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/PHercParis4/volumes/20260411134726-2.400um-0.2m-78keV-masked.zarr/</code></pre></div>
<details><summary>Python (zarr)<span class="scc-b ok">tested 2026-10-24</span></summary>
<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 &quot;zarr&gt;=3&quot; fsspec aiohttp
import zarr
root = zarr.open_group(&quot;https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHercParis4/volumes/20260411134726-2.400um-0.2m-78keV-masked.zarr/&quot;, mode=&quot;r&quot;) # OME-Zarr, zarr v2
a = root[&quot;4&quot;] # level 0 is full resolution; level L is 2**L times coarser
print(a.shape, a.dtype, a.chunks)
block = a[2688:2752, 896:960, 896:960] # (z, y, x); reads only the chunks it touches</code></pre></div></details>
<details><summary>villa&#x27;s vesuvius package<span class="scc-b ok">tested 2026-10-24</span></summary>
<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 &quot;vesuvius[volume-only]&quot;
from vesuvius import Volume
v = Volume(type=&quot;zarr&quot;, path=&quot;https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHercParis4/volumes/20260411134726-2.400um-0.2m-78keV-masked.zarr/4&quot;) # one pyramid level
block = v[2688:2752, 896:960, 896:960]</code></pre></div></details>
<details><summary>VC3D<span class="scc-b src">from villa&#x27;s source</span></summary>
<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&#x27;s volume-cartographer), open or create a project, then
File &gt; Attach Remote Zarr... and paste:
https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHercParis4/volumes/20260411134726-2.400um-0.2m-78keV-masked.zarr/
It accepts http, https and s3 OME-Zarr roots.</code></pre></div></details>
<details><summary>neuroglancer<span class="scc-b ok">tested 2026-10-24</span></summary>
<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%2FPHercParis4%2Fvolumes%2F20260411134726-2.400um-0.2m-78keV-masked.zarr%2F%22%2C%22name%22%3A%22CT%2C%202.4%20um%2078%20keV%20Scan%2020260411134726%22%7D%5D%2C%22layout%22%3A%224panel%22%7D" target="_blank" rel="noopener">Open in neuroglancer</a>
<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%2FPHercParis4%2Fvolumes%2F20260411134726-2.400um-0.2m-78keV-masked.zarr%2F%22%2C%22name%22%3A%22CT%2C%202.4%20um%2078%20keV%20Scan%2020260411134726%22%7D%5D%2C%22layout%22%3A%224panel%22%7D</code></pre></div></details>
<details><summary>curl<span class="scc-b ok">tested 2026-10-24</span></summary>
<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/PHercParis4/volumes/20260411134726-2.400um-0.2m-78keV-masked.zarr/.zattrs # OME multiscales metadata
curl -s https://vesuvius-challenge-open-data.s3.us-east-1.amazonaws.com/PHercParis4/volumes/20260411134726-2.400um-0.2m-78keV-masked.zarr/4/.zarray # one level: shape, chunks, dtype, codec</code></pre></div></details>
</div>
<footer>Machine-readable: <a href="https://huggingface.co/spaces/SharpMeow/paris4-ink-map/blob/main/connect.json" target="_blank" rel="noopener">connect.json</a> and <a href="https://huggingface.co/spaces/SharpMeow/paris4-ink-map/blob/main/CONNECT.md" target="_blank" rel="noopener">CONNECT.md</a>. Checked 2026-10-29. "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>
<!-- space-connect:end -->
</section>
<section id="about" class="card about">
<div class="sec-h"><h2>About This Map</h2></div>
<div class="duo">
<div>
<p>This is the Vesuvius Challenge organizers' ink map of the whole of PHerc. Paris 4, rendered on their spiral fit of the full scroll (run in VC3D's spiral workspace for 41,295 steps, notes in the segment's <span class="mono">FIT_NOTES.md</span>; windings 10 to 135; the map is 10.9 m long). The ink map was uploaded to the open-data bucket on 15 October 2026.</p>
<p>The surface is a fit: where a fit strays from the sheet, a map can repeat papyrus or miss it. The fit covers scan heights 38.4 to 170.4 mm only, so papyrus above or below that, including the top line of the columns, is not in this map. An ink map is model output, not a reading.</p>
<p>Map pixels are 0.0768 mm. The map is drawn as published: rotated 180 degrees from the fit, so the outer end is on the left and high scan z is up. Windings come from the published <span class="mono">windings.tif</span>, a conservative reconstruction: gray marks nodes inside two winding boxes at once.</p>
</div>
<div>
<h3>Keys</h3>
<ul class="keys">
<li><kbd>?</kbd> Keys and tips</li><li><kbd>W</kbd> Windings on or off</li>
<li><kbd>G</kbd> Millimeter grid</li><li><kbd>M</kbd> Measure</li>
<li><kbd>I</kbd> Invert</li><li><kbd>[</kbd> <kbd>]</kbd> Gamma</li>
<li><kbd>B</kbd> Bookmark this view</li><li><kbd>H</kbd> Whole map</li>
<li><kbd>L</kbd> Scroll map follows pointer</li><li><kbd>N</kbd> Neighboring turns</li>
<li><kbd>S</kbd> Save view as PNG</li><li><kbd>F</kbd> Fullscreen</li>
<li><kbd>+</kbd> <kbd>-</kbd> <kbd>Arrows</kbd> Zoom, pan</li>
</ul>
<h3>Credits</h3>
<p class="dim">Ink map, spiral fit, mesh and CT: the Vesuvius Challenge organizers, from the <a href="https://scrollprize.org/data" target="_blank" rel="noopener">Vesuvius Challenge open data</a> under <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" rel="noopener">CC BY-NC 4.0</a>. Page code Apache 2.0.</p>
</div>
</div>
</section>
</main>
<footer class="foot">Paris 4 Ink Map 路 Data: Vesuvius Challenge open data, CC BY-NC 4.0 路 An ink map is model output, not a reading.</footer>
<dialog id="help">
<h2>Paris 4 Ink Map</h2>
<p>Wheel or pinch to zoom, drag to pan. Click the whole-scroll strip to jump; click the scroll map's spiral to go to that papyrus. Click the ink map to pin the readout; click again to unpin.</p>
<ul class="keys">
<li><kbd>W</kbd> Windings overlay</li><li><kbd>G</kbd> Millimeter grid</li><li><kbd>M</kbd> Measure (click two points; Esc clears)</li><li><kbd>I</kbd> Invert</li>
<li><kbd>[</kbd> <kbd>]</kbd> Gamma down, up</li><li><kbd>B</kbd> Bookmark</li><li><kbd>H</kbd> Whole map</li><li><kbd>L</kbd> Scroll map follows the pointer</li>
<li><kbd>N</kbd> Neighboring turns at the pin</li><li><kbd>S</kbd> Save view as PNG</li><li><kbd>F</kbd> Fullscreen</li>
</ul>
<form method="dialog"><button class="btn">Close</button></form>
</dialog>
<div id="toast" class="toast" role="status"></div>
<script src="static/scroll3d.js?v=4"></script>
<script src="static/app.js?v=4"></script>
</body>
</html>