File size: 6,502 Bytes
1a36f9f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
904b30e
 
 
 
 
1a36f9f
 
 
 
 
 
 
 
 
 
 
904b30e
 
1a36f9f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/usr/bin/env python3
"""在存放数据集的服务器上运行:批量生成样例并同步到 Hugging Face。

做法与团队 nine-source Space 一致:图片放进一个独立的 HF Dataset 仓库,
Space 只登记固定版本的 URL,网页不依赖 SSH、本机服务或临时隧道。

    export HF_TOKEN=hf_xxx          # 或先 hf auth login
    python tools/server_sync.py manifest.jsonl \
        --media-repo DarthSoluke/EgoNetDatasetResearch-media \
        --space DarthSoluke/EgoNetDatasetResearch

manifest.jsonl 每行一个样例,两种写法任选:

  已经渲染好的叠加图
    {"dataset": "hograspnet", "rgb": "/SSD/.../0042.png", "gt": "/SSD/.../0042_overlay.png",
     "caption": "受试者 01,侧捏", "source": "官方 MANO"}

  只有原图和网格,由本脚本渲染(网格可以是 MANO,也可以是转换后的 SOMA)
    {"dataset": "hograspnet", "rgb": "/SSD/.../0042.png", "mesh": "/SSD/.../0042_soma.npz",
     "caption": "受试者 01,侧捏", "source": "官方 MANO → R1 → SOMA"}
    mesh.npz 的格式见 render_overlay.py(verts、faces、K,可选 joints)

可选字段:name(样例名,默认取 rgb 文件名)。

参数:
  --replace     先清掉这些数据集里以前由本脚本同步的样例,再写入新的
  --local-only  只写到本地目录 --out(默认 ./sync_out),不上传,用于先检查
"""
import argparse
import json
import os
import re
import shutil
import sys
import tempfile
from pathlib import Path

from PIL import Image

sys.path.insert(0, str(Path(__file__).resolve().parent))
from render_overlay import overlay  # noqa: E402

ORIGIN = "server_sync"


def to_jpg(img, dst, max_side):
    img = img.convert("RGB")
    s = max_side / max(img.size)
    if s < 1:
        img = img.resize((round(img.width * s), round(img.height * s)), Image.LANCZOS)
    dst.parent.mkdir(parents=True, exist_ok=True)
    img.save(dst, "JPEG", quality=86, optimize=True)


def build(manifest, stage, max_side):
    import numpy as np
    rows = [json.loads(l) for l in Path(manifest).read_text(encoding="utf-8").splitlines() if l.strip()]
    items = {}
    for i, r in enumerate(rows, 1):
        ds = r["dataset"]
        name = re.sub(r"[^\w\-]", "_", r.get("name") or Path(r["rgb"]).stem.removesuffix("_rgb"))
        rgb = Image.open(r["rgb"])
        if r.get("gt"):
            gt = Image.open(r["gt"])
        elif r.get("mesh"):
            m = np.load(r["mesh"])
            K = np.array(r["K"]) if r.get("K") else m["K"]
            gt = overlay(rgb, m["verts"], m["faces"], K, joints=m["joints"] if "joints" in m.files else None)
        else:
            sys.exit(f"第 {i} 行既没有 gt 也没有 mesh")
        rel_rgb, rel_gt = f"media/{ds}/{name}_rgb.jpg", f"media/{ds}/{name}_gt.jpg"
        to_jpg(rgb, stage / rel_rgb, max_side)
        to_jpg(gt, stage / rel_gt, max_side)
        size = Image.open(stage / rel_rgb).size
        items.setdefault(ds, []).append({"id":name,"rgb":rel_rgb,"gt":rel_gt,"modes":[{"label":r.get("source", ""),"src":rel_gt}],
                                         "width":size[0],"height":size[1],"caption":r.get("caption", ""),"source":r.get("source", ""),
                                         "rgb_label":"原始 RGB","strata":r.get("strata",{}),"source_page":r.get("source_page",""),
                                         "evidence_url":r.get("evidence_url",""),"license":r.get("license",""),"note":"","origin":ORIGIN})
        print(f"[{i}/{len(rows)}] {ds}/{name}")
    return items


def merge(prev, items, prefix, replace):
    for ds, new in items.items():
        old = prev.get(ds, [])
        if replace:
            old = [p for p in old if p.get("origin") != ORIGIN]
        new_paths = {prefix + n["rgb"] for n in new}
        old = [p for p in old if p.get("rgb") not in new_paths]
        prev[ds] = [dict(n, rgb=prefix+n["rgb"], gt=prefix+n["gt"],
                         modes=[{"label":m["label"],"src":prefix+m["src"]} for m in n["modes"]]) for n in new] + old
    return prev


def main():
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("manifest")
    ap.add_argument("--media-repo", default="DarthSoluke/EgoNetDatasetResearch-media")
    ap.add_argument("--space", default="DarthSoluke/EgoNetDatasetResearch")
    ap.add_argument("--max-side", type=int, default=960)
    ap.add_argument("--replace", action="store_true")
    ap.add_argument("--local-only", action="store_true")
    ap.add_argument("--out", default="sync_out")
    a = ap.parse_args()

    if a.local_only:
        out = Path(a.out)
        items = build(a.manifest, out, a.max_side)
        pf = out / "data/previews.json"
        prev = json.loads(pf.read_text(encoding="utf-8")) if pf.exists() else {}
        pf.parent.mkdir(parents=True, exist_ok=True)
        pf.write_text(json.dumps(merge(prev, items, "", a.replace), ensure_ascii=False, indent=1) + "\n", encoding="utf-8")
        print(f"已写到 {out}/,检查无误后去掉 --local-only 重新运行")
        return

    from huggingface_hub import HfApi, hf_hub_download
    api = HfApi(token=os.environ.get("HF_TOKEN"))
    stage = Path(tempfile.mkdtemp(prefix="soma_sync_"))
    try:
        items = build(a.manifest, stage, a.max_side)
        api.create_repo(a.media_repo, repo_type="dataset", exist_ok=True)
        info = api.upload_folder(repo_id=a.media_repo, repo_type="dataset", folder_path=str(stage),
                                 commit_message=f"同步 {sum(map(len, items.values()))} 对样例:{', '.join(sorted(items))}")
        rev = info.oid
        prefix = f"https://huggingface.co/datasets/{a.media_repo}/resolve/{rev}/"

        cur = hf_hub_download(a.space, "data/previews.json", repo_type="space", token=api.token)
        prev = json.loads(Path(cur).read_text(encoding="utf-8"))
        merged = merge(prev, items, prefix, a.replace)
        tmp = stage / "previews.json"
        tmp.write_text(json.dumps(merged, ensure_ascii=False, indent=1) + "\n", encoding="utf-8")
        api.upload_file(path_or_fileobj=str(tmp), path_in_repo="data/previews.json", repo_id=a.space, repo_type="space",
                        commit_message=f"登记样例(媒体版本 {rev[:8]})")
        print(f"完成:媒体仓库版本 {rev},已更新 {a.space} 的 data/previews.json")
    finally:
        shutil.rmtree(stage, ignore_errors=True)


if __name__ == "__main__":
    main()