import {
esc,
icon,
external,
section,
table,
empty,
fact,
tier,
status,
thumbnail,
enhance,
} from "./ui.js";
import {
previewHTML,
figuresHTML,
videoHTML,
bindPreview,
bindVideo,
} from "./preview.js";
export const defaults = {
q: "",
source: "",
method: "",
tier: "",
status: "",
route: "",
preview: false,
sort: "name",
};
const tabs = [
["overview", "概况"],
["annotation", "采集与标注"],
["access", "数据访问"],
["conversion", "SOMA转换"],
];
function options(list, value, label) {
return `${list.map((item) => ``).join("")}`;
}
export function filterDatasets(context, filters) {
const q = filters.q.trim().toLowerCase();
return context.data.datasets
.filter((d) => {
if (
["source", "method", "tier", "status", "route"].some(
(key) => filters[key] && d[key] !== filters[key],
)
)
return false;
if (filters.preview && !context.previews[d.id]?.length) return false;
const haystack = [
d.name,
d.camera,
d.gt,
d.fills,
d.scale,
d.subjects,
...(d.tags || []),
]
.join(" ")
.toLowerCase();
return !q || haystack.includes(q);
})
.sort((a, b) => {
if (filters.sort === "year")
return (
(Number(b.year?.match(/\d{4}/)?.[0]) || 0) -
(Number(a.year?.match(/\d{4}/)?.[0]) || 0) ||
a.name.localeCompare(b.name)
);
if (filters.sort === "status")
return (
context.meta.status[a.status].order -
context.meta.status[b.status].order || a.name.localeCompare(b.name)
);
return a.name.localeCompare(b.name, "en", { sensitivity: "base" });
});
}
export function catalogView(context, filters) {
return `数据集 ${context.data.datasets.length}
`;
}
export function bindCatalog(context, filters) {
function update() {
const datasets = filterDatasets(context, filters);
document.getElementById("result-count").textContent =
`${datasets.length} / ${context.data.datasets.length}`;
document.getElementById("dataset-results").innerHTML = datasets.length
? datasets
.map(
(d) => `
${thumbnail(d, context)}
${esc(d.name)}
${esc(d.year)}
${esc(context.meta.source[d.source].name)}${esc(context.meta.method[d.method].short)}
${esc(d.gt)}${tier(d.tier, context.meta)}
${esc(d.scale || "—")}${esc(d.subjects)}
${status(d.status, context.meta)}
`,
)
.join("")
: empty("没有匹配的数据集", true);
const params = new URLSearchParams();
Object.entries(filters).forEach(([k, v]) => {
if (v && v !== defaults[k]) params.set(k, String(v));
});
history.replaceState(
null,
"",
`#catalog${params.size ? "?" + params : ""}`,
);
document.getElementById("dataset-sort").value = filters.sort;
enhance();
document.querySelectorAll("[data-clear-filters]").forEach((button) => {
button.onclick = reset;
});
}
function reset() {
Object.assign(filters, defaults);
document.getElementById("dataset-search").value = "";
document.querySelectorAll("[data-filter]").forEach((el) => {
if (el.type === "checkbox") el.checked = false;
else el.value = "";
});
update();
}
document.getElementById("catalog-filters").onsubmit = (event) =>
event.preventDefault();
document.getElementById("dataset-search").oninput = (event) => {
filters.q = event.target.value;
update();
};
document.querySelectorAll("[data-filter]").forEach((el) => {
el.onchange = () => {
filters[el.dataset.filter] =
el.type === "checkbox" ? el.checked : el.value;
update();
};
});
document.getElementById("dataset-sort").onchange = (event) => {
filters.sort = event.target.value;
update();
};
update();
}
export function overviewView(context) {
const { data, meta } = context;
const matrix = data.meta.methods.map(
(method) =>
`
| ${esc(method.short)} | ${data.meta.sources
.map((source) => {
const items = data.datasets.filter(
(d) => d.method === method.id && d.source === source.id,
);
return `${items.map((d) => `${esc(d.name)}`).join("") || '—'} | `;
})
.join("")}
`,
);
return `
${section("来源 × 标注", table(["标注方法", ...data.meta.sources.map((s) => s.name)], matrix, "matrix"))}
${section("Shape真值", `${data.meta.tiers.map((t) => ``).join("")}
`)}
${section(
"优先接入",
``,
)}
`;
}
export function detailView(id, tab, context) {
const dataset = context.byId[id];
if (!dataset)
return empty("未找到这个数据集") + '返回数据集';
const { meta } = context,
method = meta.method[dataset.method],
route = meta.route[dataset.route];
const links = [...(dataset.links || []), ...(dataset.team || [])];
const body =
tab === "annotation"
? `${section(method.name, `${method.steps.map((s) => `- ${esc(s)}
`).join("")}
${fact("可信度", esc(method.trust))}${fact("常见问题", esc(method.issues))}
相关数据集`)}${figuresHTML(id, context)}`
: tab === "access"
? section(
"来源",
`${links.map((l) => `- ${external(l.label, l.url)}
`).join("")}
${!links.length ? '暂无已登记的访问链接。
' : ""}`,
)
: tab === "conversion"
? `${section(route.name, `${fact("原始标注", esc(route.from))}${fact("方法", esc(route.how))}${fact("Shape监督", route.shape ? "可用;仍须核对来源与拟合残差" : "不使用")}${fact("层级", tier(dataset.tier, meta) + " " + esc(meta.tier[dataset.tier].name))}
全部转换路线`)}${id === "egorenderer" ? '拟合MANO/SOMA是辅助标注,不参与原生手部渲染。
' : ""}`
: `${previewHTML(dataset, context)}${section("数据规格", `${fact("视角与相机", esc(dataset.camera))}${fact("受试者", esc(dataset.subjects))}${fact("规模", esc(dataset.scale))}${fact("原始标注", esc(dataset.gt))}${fact("Shape来源", tier(dataset.tier, meta) + " " + esc(meta.tier[dataset.tier].name))}
${dataset.risk ? `${esc(dataset.risk)}
` : ""}`)}${videoHTML(id, context)}${!context.previews[id]?.length ? figuresHTML(id, context) : ""}`;
return `数据集
${id === "egorenderer" ? '' : ""}
${body}
`;
}
export function bindDetail(id, context) {
if (!context.byId[id]) return;
bindPreview(context.byId[id], context);
bindVideo(id, context);
}