Download src/preview.js from DarthSoluke/EgoNetDatasetResearch: direct link, hf CLI and curl.
- Browser
- Download file 9.32 kB
-
https://huggingface.co/spaces/DarthSoluke/EgoNetDatasetResearch/resolve/main/src/preview.js
- Command line
-
hf download hf://spaces/DarthSoluke/EgoNetDatasetResearch/src/preview.js
-
curl -L -o preview.js https://huggingface.co/spaces/DarthSoluke/EgoNetDatasetResearch/resolve/main/src/preview.js
9.32 kB
| import { esc, icon, external, section } from "./ui.js"; | |
| export function previewHTML(dataset, context) { | |
| const previews = context.previews[dataset.id] || []; | |
| if (!previews.length) return ""; | |
| return `<section class="preview" id="dataset-preview" aria-label="${esc(dataset.name)}样例"> | |
| <div class="preview-toolbar">${icon("chevron-left", "上一个样例", "data-prev")} | |
| <select data-sample aria-label="样例">${previews.map((p, i) => `<option value="${i}">${i + 1} · ${esc(p.caption)}</option>`).join("")}</select> | |
| ${icon("chevron-right", "下一个样例", "data-next")} | |
| <select data-mode aria-label="标注类型"></select> | |
| </div> | |
| <div class="preview-pair"> | |
| <figure><figcaption data-rgb-label>原始图像</figcaption><svg data-left role="img" aria-label="原始图像" preserveAspectRatio="xMidYMid meet"><image data-rgb/></svg></figure> | |
| <figure><figcaption><span data-gt-label>来源标注</span><span data-loading role="status" aria-live="polite">加载中</span></figcaption><svg data-right role="img" aria-label="来源标注叠加" preserveAspectRatio="xMidYMid meet"><image data-base/><image data-overlay/></svg></figure> | |
| </div> | |
| <div class="preview-adjustments"> | |
| <label>透明度<input data-alpha aria-label="叠加透明度" type="range" min="0" max="100" value="100"><output data-alpha-value>100%</output></label> | |
| <label>缩放<input data-zoom aria-label="同步缩放" type="range" min="1" max="8" step="0.1" value="1"><output data-zoom-value>1×</output></label> | |
| <div class="button-group">${icon("scan", "定位手部", "data-hand")}${icon("maximize", "显示全图", "data-reset")}</div> | |
| </div> | |
| <div class="preview-thumbs" aria-label="选择样例">${previews.map((p, i) => `<button type="button" data-preview="${i}" aria-label="样例${i + 1}"><img src="${esc(p.rgb)}" alt="" loading="lazy"><span>${i + 1}</span></button>`).join("")}</div> | |
| <details class="provenance"><summary>来源与抽样</summary><div data-source class="source-content"></div>${samplingHTML(dataset.id, context.sampling)}</details> | |
| </section>`; | |
| } | |
| export function samplingHTML(id, sampling) { | |
| const sample = sampling.datasets?.[id]; | |
| if (!sample) return ""; | |
| return `<dl class="facts compact">${Object.entries(sample.categories) | |
| .map( | |
| ([dimension, groups]) => | |
| `<div><dt>${esc(dimension)}</dt><dd>${Object.entries(groups) | |
| .map(([name, n]) => `${esc(name)} ${n.selected}/${n.pool}`) | |
| .join(" · ")}</dd></div>`, | |
| ) | |
| .join( | |
| "", | |
| )}</dl><p class="source-note">${esc(sampling.strategy)} ${esc(sampling.dimensions_note)}</p>`; | |
| } | |
| export function bindPreview(dataset, context) { | |
| const root = document.getElementById("dataset-preview"); | |
| if (!root) return; | |
| const previews = context.previews[dataset.id]; | |
| const find = (name) => root.querySelector(`[data-${name}]`); | |
| let index = 0, | |
| box, | |
| version = 0; | |
| function viewport() { | |
| const zoom = Number(find("zoom").value); | |
| const w = box[2] / zoom, | |
| h = box[3] / zoom; | |
| const viewBox = [ | |
| box[0] + (box[2] - w) / 2, | |
| box[1] + (box[3] - h) / 2, | |
| w, | |
| h, | |
| ].join(" "); | |
| find("left").setAttribute("viewBox", viewBox); | |
| find("right").setAttribute("viewBox", viewBox); | |
| find("zoom-value").textContent = `${Number(zoom.toFixed(1))}×`; | |
| } | |
| async function annotation() { | |
| const sample = previews[index], | |
| mode = sample.modes[Number(find("mode").value)]; | |
| const own = ++version, | |
| pane = find("right"), | |
| loading = find("loading"); | |
| find("gt-label").textContent = | |
| mode.label + (dataset.method === "pseudo" ? " · 预测标注" : ""); | |
| loading.hidden = false; | |
| loading.textContent = "加载中"; | |
| pane.setAttribute("aria-busy", "true"); | |
| const images = []; | |
| try { | |
| await Promise.all( | |
| [ | |
| ["base", sample.rgb], | |
| ["overlay", mode.src], | |
| ].map( | |
| ([name, url]) => | |
| new Promise((resolve, reject) => { | |
| const image = document.createElementNS( | |
| "http://www.w3.org/2000/svg", | |
| "image", | |
| ); | |
| image.dataset[name] = ""; | |
| image.setAttribute("width", sample.width); | |
| image.setAttribute("height", sample.height); | |
| image.style.visibility = "hidden"; | |
| if (name === "overlay") | |
| image.style.opacity = Number(find("alpha").value) / 100; | |
| image.onload = resolve; | |
| image.onerror = reject; | |
| find(name).replaceWith(image); | |
| images.push(image); | |
| image.setAttribute("href", url); | |
| }), | |
| ), | |
| ); | |
| if (own !== version || !root.isConnected) return; | |
| images.forEach((image) => { | |
| image.style.visibility = "visible"; | |
| }); | |
| loading.hidden = true; | |
| } catch (_) { | |
| if (own !== version || !root.isConnected) return; | |
| loading.textContent = "图像加载失败"; | |
| } finally { | |
| if (own === version && root.isConnected) | |
| pane.setAttribute("aria-busy", "false"); | |
| } | |
| } | |
| function show() { | |
| const sample = previews[index]; | |
| find("sample").value = index; | |
| for (const [name, value] of [ | |
| ["width", sample.width], | |
| ["height", sample.height], | |
| ["href", sample.rgb], | |
| ]) | |
| find("rgb").setAttribute(name, value); | |
| find("rgb-label").textContent = sample.rgb_label; | |
| find("mode").innerHTML = sample.modes | |
| .map((m, i) => `<option value="${i}">${esc(m.label)}</option>`) | |
| .join(""); | |
| annotation(); | |
| box = [0, 0, sample.width, sample.height]; | |
| find("zoom").value = 1; | |
| viewport(); | |
| find("hand").hidden = !sample.crop; | |
| find("source").innerHTML = | |
| `<p>${esc(sample.source)}</p>${sample.note ? `<p>${esc(sample.note)}</p>` : ""}<p>${Object.entries( | |
| sample.strata || {}, | |
| ) | |
| .map(([k, v]) => `${esc(k)}:${esc(v)}`) | |
| .join( | |
| " · ", | |
| )}</p><div class="link-list">${sample.source_page ? external("来源图册", sample.source_page) : ""}${sample.evidence_url ? external("素材记录", sample.evidence_url) : ""}${external("原图", sample.rgb)}</div>${sample.license ? `<p>${esc(sample.license)}</p>` : ""}`; | |
| root | |
| .querySelectorAll("[data-preview]") | |
| .forEach((button) => | |
| button.setAttribute( | |
| "aria-pressed", | |
| Number(button.dataset.preview) === index, | |
| ), | |
| ); | |
| } | |
| find("prev").onclick = () => { | |
| index = (index + previews.length - 1) % previews.length; | |
| show(); | |
| }; | |
| find("next").onclick = () => { | |
| index = (index + 1) % previews.length; | |
| show(); | |
| }; | |
| find("sample").onchange = (event) => { | |
| index = Number(event.target.value); | |
| show(); | |
| }; | |
| find("mode").onchange = annotation; | |
| find("alpha").oninput = (event) => { | |
| find("overlay").style.opacity = event.target.value / 100; | |
| find("alpha-value").textContent = `${event.target.value}%`; | |
| }; | |
| find("zoom").oninput = viewport; | |
| find("hand").onclick = () => { | |
| box = previews[index].crop; | |
| find("zoom").value = 1; | |
| viewport(); | |
| }; | |
| find("reset").onclick = () => { | |
| box = [0, 0, previews[index].width, previews[index].height]; | |
| find("zoom").value = 1; | |
| viewport(); | |
| }; | |
| root.querySelectorAll("[data-preview]").forEach((button) => { | |
| button.onclick = () => { | |
| index = Number(button.dataset.preview); | |
| show(); | |
| }; | |
| }); | |
| show(); | |
| } | |
| export function figuresHTML(id, context) { | |
| const entry = context.figures[id]; | |
| if (!entry?.figures?.length) return ""; | |
| return section( | |
| "采集与标注", | |
| `${entry.note ? `<p class="source-note">${esc(entry.note)}</p>` : ""}<div class="official-figures">${entry.figures.map((figure) => `<figure><h3>${esc(figure.title)}</h3><div class="figure-images">${figure.images.map((img) => `<a href="${esc(img.src)}" target="_blank" rel="noopener"><img src="${esc(img.src)}" alt="${esc(figure.title)}" width="${img.width}" height="${img.height}" loading="lazy"></a>`).join("")}</div><figcaption>${esc(figure.caption)} ${external(figure.source_label, figure.source_url)}</figcaption><details><summary>原文与署名</summary><p>${esc(figure.original_caption)}</p><p>${esc(figure.attribution)}</p></details></figure>`).join("")}</div>`, | |
| ); | |
| } | |
| export function videoHTML(id, context) { | |
| const list = context.videos[id] || []; | |
| if (!list.length) return ""; | |
| return section( | |
| "动作序列", | |
| `<select id="clip-select" aria-label="视频序列">${list.map((v, i) => `<option value="${i}">${esc(v.recording_id)} · ${esc(v.source_tag)}</option>`).join("")}</select><video id="clip-player" controls playsinline preload="none"></video><p id="clip-note" class="source-note"></p>`, | |
| ); | |
| } | |
| export function bindVideo(id, context) { | |
| const select = document.getElementById("clip-select"); | |
| if (!select) return; | |
| const player = document.getElementById("clip-player"); | |
| const update = () => { | |
| const video = context.videos[id][Number(select.value)]; | |
| player.pause(); | |
| player.src = video.url; | |
| document.getElementById("clip-note").textContent = | |
| `${video.source_tag} · ${video.mano_source} · 来源帧 ${video.source_first_frame}–${video.source_last_frame} · ${video.duration_s}秒`; | |
| }; | |
| select.onchange = update; | |
| update(); | |
| } | |