{html.escape(stem)}
{html.escape(h["id"])}
失败用例({len(fails)})
') parts.append('| 人脸 | 发型 | 耗时(ms) | 原因 | ' '
|---|---|---|---|
| {html.escape(r["stem"])} | ' f'{html.escape(r["hair_cn"])} | ' f'{r["ms"]} | ' f'{html.escape(r["error"] or "")} |
"""根据 results.json 生成发际线生发对比报告 HTML。 报告布局(原图 vs 结果对比): - 顶部:总览统计(成功/失败数、耗时、参数) - 按脸分组,每张脸一个区块: - 左:原图 - 右:5 发型 × {高清, 非高清} 网格(共 10 张),失败的格子标注原因 - 失败用例汇总表 输出:static/report_hairline_v2/index.html """ import html import json import os from urllib.parse import quote OUT_DIR = "/home/xsl/hair/static/report_hairline_v2" RESULTS = os.path.join(OUT_DIR, "results.json") TARGET = os.path.join(OUT_DIR, "index.html") def url(path): """把相对路径里的中文做 URL 编码,分隔符 '/' 保留。 Starlette StaticFiles 对未编码中文路径返回 400,编码后所有浏览器稳定可加载。 """ return "/".join(quote(seg) for seg in path.split("/")) def main(): with open(RESULTS, encoding="utf-8") as fh: data = json.load(fh) meta = data["meta"] results = data["results"] hairstyles = meta["hairstyles"] # [{id, cn}] faces = meta["faces"] # [stem, ...] hr_opts = meta["hr_options"] # [{is_hr, tag}] # 索引:(stem, hair_id, hr_tag) -> result idx = {} for r in results: idx[(r["stem"], r["hair_id"], r["hr_tag"])] = r # 统计 ok = sum(1 for r in results if r["ok"]) fail = len(results) - ok parts = [] parts.append(f"""
| 人脸 | 发型 | 耗时(ms) | 原因 | ' '
|---|---|---|---|
| {html.escape(r["stem"])} | ' f'{html.escape(r["hair_cn"])} | ' f'{r["ms"]} | ' f'{html.escape(r["error"] or "")} |