Download scripts/teaser/canvas_batch.py from teawhite/maliang: direct link, hf CLI and curl.
- Browser
- Download file 23.2 kB
-
https://huggingface.co/teawhite/maliang/resolve/main/scripts/teaser/canvas_batch.py
- Command line
-
hf download hf://teawhite/maliang/scripts/teaser/canvas_batch.py
-
curl -L -o canvas_batch.py https://huggingface.co/teawhite/maliang/resolve/main/scripts/teaser/canvas_batch.py
23.2 kB
| #!/usr/bin/env python3 | |
| """teaser · canvas(白纸作画)量产调度:长地图 × 模板(canvas_edits.TEMPLATES)× 首帧 → 编辑版语义 → DMD 任务(rollout_dmd, | |
| 共享队列 outputs/teaser/edit/canvas/jobs.jsonl)→ 结果目录 cases/<name>/ + index.jsonl → 打分 → showcase。 | |
| 每个 (长地图, 首帧) 的对照: | |
| orig 原版(完整世界,不编辑):live 已经跑过的直接复用(outputs/teaser/edit/live/roll/<地图>__<场景>__orig),没有的新跑; | |
| B0 白纸对照(帧 0 看不见的东西全删 + 1 s 时清空,之后什么都不画):白纸类编辑版(W / R / WV / RV / atk)逐笔效果的参照 | |
| (同首帧同种子同按键 ⇒ 第一笔之前逐帧一致,之后只差这一笔一笔)。 | |
| usage(env:source scripts/teaser/live_env.sh): | |
| $PY scripts/teaser/canvas_batch.py tl --gpus 3,4,5 --procs 12 [--only V] | |
| $PY scripts/teaser/canvas_batch.py jobs # 追加 / 重写 jobs.jsonl(workers 自己会读到新行) | |
| $PY scripts/teaser/canvas_batch.py status | |
| $PY scripts/teaser/canvas_batch.py cases --procs 16 # cases/<name>/ + index.jsonl(mask.npz 在这一步算) | |
| $PY scripts/teaser/canvas_batch.py score --procs 24 # 逐事件效果分 → showcase/scores.json + score_summary.json | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import os | |
| import shutil | |
| import subprocess | |
| import sys | |
| import time | |
| M = "/opt/dlami/nvme/zhiyangdeng/maliang" | |
| HERE = os.path.dirname(os.path.abspath(__file__)) | |
| for _p in (f"{M}/code/gc25d", f"{M}/code/arope", HERE): | |
| if _p not in sys.path: | |
| sys.path.insert(0, _p) | |
| CV = f"{M}/outputs/teaser/edit/canvas" | |
| LV = f"{M}/outputs/teaser/edit/live" | |
| F0 = f"{M}/outputs/teaser/f0" | |
| LF0 = f"{LV}/f0" | |
| LOG = f"{M}/outputs/teaser/logs/edit2" | |
| P8 = "P9C_default_s8944__001056" | |
| MAPS = { # 短名 → 长地图目录 | |
| "patchwork_p9c": f"{LV}/maps_v2/patchwork__{P8}__s10__L181", | |
| "pillarhall_p9c": f"{LV}/maps_v2/pillarhall__{P8}__s20006__L181", | |
| "mountainpass_p9c": f"{LV}/maps_v2/mountainpass__{P8}__s11003__L181", | |
| "farmland_p9c": f"{LV}/maps_v2/farmland__{P8}__s9013__L181", | |
| "patchwork_v10": f"{LV}/maps/patchwork__R10_s1512__001440__s31__L241", | |
| "orchard_v10": f"{LV}/maps/orchard__R10_s2505__001248__s37__L241", | |
| "village_v10": f"{LV}/maps/village__R10_s2505__001248__s37__L241", | |
| } | |
| SCENES = { # 短名 → [(场景, 首帧 png)];第一个 = live 已有原版的那个 | |
| "patchwork_p9c": [("icecave@photoreal", f"{F0}/patchwork__{P8}__s10/icecave@photoreal/qwen21_s0.png"), | |
| ("salt_flats@cartoon", f"{F0}/patchwork__{P8}__s10/salt_flats@cartoon/klein9b_s0.png"), | |
| ("honey_hive@photoreal", f"{F0}/patchwork__{P8}__s10/honey_hive@photoreal/qwen21_s0.png"), | |
| ("greek@voxel", f"{F0}/patchwork__{P8}__s10/greek@voxel/klein9b_s0.png")], | |
| "pillarhall_p9c": [("icecave@cartoon", f"{F0}/pillarhall__{P8}__s20006/icecave@cartoon/qwen21_s0.png")], | |
| "mountainpass_p9c": [("dunes@cartoon", f"{F0}/mountainpass__{P8}__s11003/dunes@cartoon/qwen21_s0.png")], | |
| "farmland_p9c": [("synthwave@cartoon", f"{F0}/farmland__{P8}__s9013/synthwave@cartoon/qwen21_s0.png")], | |
| "patchwork_v10": [("snow_forest@photoreal", f"{LF0}/patchwork__R10_s1512__001440__s31/snow_forest@photoreal/qwen21_s0.png"), | |
| ("alien@photoreal", f"{LF0}/patchwork__R10_s1512__001440__s31/alien@photoreal/qwen21_s1.png"), | |
| ("lava@photoreal", f"{LF0}/patchwork__R10_s1512__001440__s31/lava@photoreal/qwen21_s0.png")], | |
| "orchard_v10": [("icecave@photoreal", f"{LF0}/orchard__R10_s2505__001248__s37/icecave@photoreal/qwen21_s1.png"), | |
| ("alien@photoreal", f"{LF0}/orchard__R10_s2505__001248__s37/alien@photoreal/qwen21_s1.png")], | |
| "village_v10": [("snow_forest@photoreal", f"{LF0}/village__R10_s2505__001248__s37/snow_forest@photoreal/qwen21_s0.png"), | |
| ("dunes@photoreal", f"{LF0}/village__R10_s2505__001248__s37/dunes@photoreal/qwen21_s0.png")], | |
| } | |
| # (地图短名, 模板, blank, attack, 场景下标表) | |
| PLAN = [ | |
| # 1 冒烟:植被地面(不白纸,对照 = 原版) | |
| ("patchwork_p9c", "V", False, False, [0]), | |
| ("pillarhall_p9c", "V", False, False, [0]), | |
| ("patchwork_v10", "V", False, False, [0]), | |
| # 白纸对照 | |
| ("patchwork_p9c", "B0", True, False, [0, 1, 2, 3]), | |
| ("patchwork_v10", "B0", True, False, [0, 1, 2]), | |
| ("pillarhall_p9c", "B0", True, False, [0]), | |
| ("mountainpass_p9c", "B0", True, False, [0]), | |
| ("orchard_v10", "B0", True, False, [0]), | |
| ("village_v10", "B0", True, False, [0]), | |
| # 2 白纸 → 世界;3 同一时间线、多个世界(patchwork_p9c × 4、patchwork_v10 × 3) | |
| ("patchwork_p9c", "W", True, False, [0, 1, 2, 3]), | |
| ("patchwork_v10", "W", True, False, [0, 1, 2]), | |
| ("pillarhall_p9c", "W", True, False, [0]), | |
| ("mountainpass_p9c", "W", True, False, [0]), | |
| ("orchard_v10", "W", True, False, [0]), | |
| ("village_v10", "W", True, False, [0]), | |
| # 擦掉重画 | |
| ("patchwork_p9c", "R", True, False, [0, 1]), | |
| ("patchwork_v10", "R", True, False, [0, 1]), | |
| ("village_v10", "R", True, False, [0]), | |
| ("mountainpass_p9c", "R", True, False, [0]), | |
| # 4 攻击键建造(白纸 + 每次攻击长出一样东西) | |
| ("orchard_v10", "none", True, True, [0]), | |
| ("village_v10", "none", True, True, [0]), | |
| ] | |
| EXTRA = [] # 冒烟之后追加(WV / RV / 新长地图的攻击版),见 canvas_plan2.json | |
| if os.path.exists(f"{CV}/canvas_plan2.json"): | |
| _p2 = json.load(open(f"{CV}/canvas_plan2.json")) | |
| for k, v in _p2.get("maps", {}).items(): | |
| MAPS[k] = v | |
| for k, v in _p2.get("scenes", {}).items(): | |
| SCENES[k] = [tuple(x) for x in v] | |
| EXTRA = [tuple(x) for x in _p2.get("plan", [])] | |
| def tl_name(mshort, tl, blank, attack): | |
| return f"{mshort}__{tl}{'_blank' if blank and tl not in ('B0',) else ''}{'_atk' if attack else ''}" | |
| def tag_of(tl, blank, attack): | |
| return ("atk" if attack else tl) if tl != "none" else "atk" | |
| def roll_name(mshort, scene, tl, blank, attack): | |
| return f"{mshort}__{scene}__{tag_of(tl, blank, attack)}" | |
| def env(): | |
| e = dict(os.environ) | |
| e.update(DIFFSYNTH_SKIP_DOWNLOAD="True", PYTHONPATH=f"{M}/code/gc25d:{M}/code/arope:{HERE}", | |
| HF_HOME="/opt/dlami/nvme/zhiyangdeng/.cache/huggingface", TMPDIR=f"{M}/.tmp", HF_HUB_OFFLINE="1", | |
| PYTHONUNBUFFERED="1", OMP_NUM_THREADS="6", MKL_NUM_THREADS="6", OPENBLAS_NUM_THREADS="6") | |
| return e | |
| def run_pool(cmds, gpus, procs, tag): | |
| os.makedirs(LOG, exist_ok=True) | |
| running, k = [], 0 | |
| todo = list(cmds) | |
| while todo or running: | |
| while todo and len(running) < procs: | |
| name, argv = todo.pop(0) | |
| e = env() | |
| e["CUDA_VISIBLE_DEVICES"] = gpus[k % len(gpus)] | |
| k += 1 | |
| lf = open(os.path.join(LOG, f"canvas_{tag}_{name}.log"), "w") | |
| running.append((name, subprocess.Popen(argv, env=e, stdout=lf, stderr=subprocess.STDOUT), time.time())) | |
| print(f"[start] {name}", flush=True) | |
| for r in list(running): | |
| if r[1].poll() is not None: | |
| running.remove(r) | |
| print(f"[done] {r[0]} rc={r[1].returncode} {time.time() - r[2]:.0f}s", flush=True) | |
| time.sleep(2) | |
| def all_plan(): | |
| return PLAN + EXTRA | |
| def cmd_tl(a): | |
| cmds, seen = [], set() | |
| for mshort, tl, blank, attack, _ in all_plan(): | |
| if a.only and tl not in a.only.split(","): | |
| continue | |
| nm = tl_name(mshort, tl, blank, attack) | |
| if nm in seen: | |
| continue | |
| seen.add(nm) | |
| if os.path.exists(f"{CV}/tl/{nm}/events.json") and not a.redo: | |
| continue | |
| argv = [sys.executable, f"{HERE}/canvas_edits.py", "run", "--map", MAPS[mshort], "--tl", tl, "--name", nm] | |
| if blank: | |
| argv.append("--blank") | |
| if attack: | |
| argv.append("--attack") | |
| cmds.append((nm, argv)) | |
| print(f"{len(cmds)} 条时间线要渲染") | |
| run_pool(cmds, a.gpus.split(","), a.procs, "tl") | |
| def live_orig(mdir, scene): | |
| lname = os.path.basename(mdir) | |
| d = f"{LV}/roll/{lname}__{scene}__orig" | |
| return d if os.path.exists(f"{d}/meta.json") else None | |
| def orig_dir(mshort, scene): | |
| d = live_orig(MAPS[mshort], scene) | |
| return d or f"{CV}/roll/{mshort}__{scene}__orig" | |
| def cmd_jobs(a): | |
| rows, seen = [], set() | |
| for mshort, tl, blank, attack, sidx in all_plan(): | |
| md = MAPS[mshort] | |
| nm = tl_name(mshort, tl, blank, attack) | |
| td = f"{CV}/tl/{nm}" | |
| for si in sidx: | |
| scene, png = SCENES[mshort][si] | |
| if not os.path.exists(png): | |
| print(f"[skip] 没有首帧 {png}") | |
| continue | |
| common = dict(first=png, prompts=f"{md}/prompts.json", actions=f"{md}/actions.npy", out=f"{CV}/roll", seed=0, | |
| labels=dict(scene=scene, map=mshort, long_map=os.path.basename(md))) | |
| on = f"{mshort}__{scene}__orig" | |
| if on not in seen and live_orig(md, scene) is None: | |
| seen.add(on) | |
| rows.append(dict(name=on, ids=f"{md}/ids.npz", prio=2, meta=dict(edit="none", long_map=md, scene=scene), **common)) | |
| if not os.path.exists(f"{td}/events.json"): | |
| continue | |
| ev = json.load(open(f"{td}/events.json")) | |
| rn = roll_name(mshort, scene, tl, blank, attack) | |
| sf = sorted({min(max(e["f"] + d, 0), ev["n_frames"] - 1) for e in ev["events"] for d in (-1, 24)}) | |
| pr = 3 if tl == "V" else (1.5 if tl == "B0" else 1) | |
| rows.append(dict(name=rn, ids=f"{td}/ids.npz", prio=pr, sheet_frames=sf[:16] or None, | |
| meta=dict(edit="canvas", timeline=tl, blank=blank, attack=attack, tl_dir=td, long_map=md, scene=scene, | |
| events=ev["events"], f_first_edit=ev["f_first_edit"]), **common)) | |
| out = f"{CV}/jobs.jsonl" | |
| tmp = out + ".tmp" | |
| with open(tmp, "w") as fh: | |
| for r in rows: | |
| fh.write(json.dumps(r, ensure_ascii=False, default=float) + "\n") | |
| os.replace(tmp, out) | |
| print(f"→ {out}:{len(rows)} 个任务") | |
| def cmd_status(a): | |
| subprocess.run([sys.executable, f"{HERE}/rollout_dmd.py", "--jobs", f"{CV}/jobs.jsonl", "--status"], env=env()) | |
| # ---------------------------------------------------------------------------- | |
| # 结果目录 | |
| # ---------------------------------------------------------------------------- | |
| def pairs(): | |
| """→ [(edited roll dir, orig dir, blank-control dir 或 None, mshort, scene, tl, blank, attack)]。""" | |
| out = [] | |
| for mshort, tl, blank, attack, sidx in all_plan(): | |
| for si in sidx: | |
| scene, _ = SCENES[mshort][si] | |
| ed = f"{CV}/roll/{roll_name(mshort, scene, tl, blank, attack)}" | |
| ctl = f"{CV}/roll/{mshort}__{scene}__B0" if blank and tl != "B0" else None | |
| out.append((ed, orig_dir(mshort, scene), ctl, mshort, scene, tl, blank, attack)) | |
| return out | |
| def link(src, dst): | |
| if os.path.exists(dst): | |
| os.remove(dst) | |
| try: | |
| os.link(src, dst) | |
| except OSError: | |
| shutil.copyfile(src, dst) | |
| EDIT_DESC = { | |
| "V": "veg ground smoke: paint a green path, write MALIANG / 马良 in grass, grow a lawn (world otherwise unchanged)", | |
| "B0": "blank-canvas control: objects not visible at frame 0 removed, visible ones erased at 0.75 s, nothing drawn afterwards", | |
| "W": "blank canvas → world: clear, then houses / trees / towers / wall / stone circle / MALIANG stone letters added step by step", | |
| "R": "erase and redraw: blank canvas → village (houses, wall, tower) → erase everything → forest + stone circle + obelisk + MALIANG", | |
| "WV": "blank canvas → world with vegetation ground painting (green path, MALIANG / 马良 in grass)", | |
| "RV": "erase and redraw with vegetation ground painting", | |
| "atk": "attack-triggered building on a blank canvas: every attack press spawns an object in front of the player", | |
| "WF": "blank canvas → world with grass painting, then a finale burst (one stroke every 1.5 s from 30 s) so the last frames are crowded", | |
| } | |
| def _case_one(args): | |
| import numpy as np | |
| ed, od, ctl, mshort, scene, tl, blank, attack = args | |
| name = os.path.basename(ed) | |
| cd = f"{CV}/cases/{name}" | |
| if not os.path.exists(f"{ed}/meta.json") or not os.path.exists(f"{od}/meta.json"): | |
| return None | |
| os.makedirs(cd, exist_ok=True) | |
| m = json.load(open(f"{ed}/meta.json")) | |
| for fn in ("gen.mp4", "ids.npz", "first.png", "actions.npy", "prompts.json", "sheet.jpg"): | |
| if os.path.exists(f"{ed}/{fn}"): | |
| link(f"{ed}/{fn}", f"{cd}/{fn}") | |
| ids = np.load(f"{ed}/ids.npz")["ids"] | |
| ids_o = np.load(f"{od}/ids.npz")["ids"] | |
| mk = dict(mask=ids != ids_o) | |
| if ctl and os.path.exists(f"{ctl}/ids.npz"): | |
| mk["mask_brush"] = ids != np.load(f"{ctl}/ids.npz")["ids"] | |
| np.savez_compressed(f"{cd}/mask.npz", **mk) | |
| evs = (m.get("meta") or {}).get("events") or [] | |
| tag = tag_of(tl, blank, attack) | |
| edit = dict(edit_type=f"canvas_{tag}", description=EDIT_DESC.get(tag, tag), timeline=tl, blank_canvas=blank, attack_trigger=attack, | |
| trigger="attack key onsets (cell k attack=1, k-1=0; held ⇒ every 3 cells)" if attack else "timeline (fixed frames)", | |
| n_events=len(evs), frames=[e["f"] for e in evs], | |
| events=[dict(type=e["type"], frame=e["f"], t_sec=e["t_sec"], cell=e["cell"], label=e["label"], label_zh=e["label_zh"], | |
| params={k: v for k, v in e.items() if k not in ("items", "type", "f", "t_sec", "cell", "label", "label_zh", | |
| "names")}) for e in evs], | |
| base_source=dict(long_map=(m.get("meta") or {}).get("long_map"), tl_dir=(m.get("meta") or {}).get("tl_dir"), | |
| first_frame=m["job"]["first"]), | |
| paired_original=od, paired_original_case=f"{CV}/cases/{os.path.basename(od)}", | |
| blank_control=ctl, mask_keys=dict(mask="ids != paired original ids", mask_brush="ids != blank-control ids (brush strokes only)" | |
| if "mask_brush" in mk else None)) | |
| json.dump(edit, open(f"{cd}/edit.json", "w"), indent=1, ensure_ascii=False, default=float) | |
| meta = dict(name=name, kind="canvas_edit", model="gc25d v10 causal DMD student (stage3 EMA step 500), 4 steps/cell, sink 3 / window 17", | |
| ckpt=m["ckpt"], seed=m["seed"], fps=16, n_frames=m["n_frames"], cells=m["cells"], res="832x480", decode=m["decode"], | |
| scene=scene, map=mshort, timing=m["timing"], actions="actions.npy uint8 [N,5] = cell 1..N keys (w,a,s,d,attack); cell k = frames 4k-3..4k", | |
| prompts="prompts.json N+1 per-cell strings (cell 0 = cell 1)", roll_dir=ed) | |
| json.dump(meta, open(f"{cd}/meta.json", "w"), indent=1, ensure_ascii=False, default=float) | |
| # 配对原版 / 白纸对照也放成 case(硬链接) | |
| for src, kind in ((od, "original"), (ctl, "blank_control")): | |
| if not src or not os.path.exists(f"{src}/meta.json"): | |
| continue | |
| sd = f"{CV}/cases/{os.path.basename(src)}" | |
| os.makedirs(sd, exist_ok=True) | |
| for fn in ("gen.mp4", "ids.npz", "first.png", "actions.npy", "prompts.json", "sheet.jpg"): | |
| if os.path.exists(f"{src}/{fn}") and not os.path.exists(f"{sd}/{fn}"): | |
| link(f"{src}/{fn}", f"{sd}/{fn}") | |
| mm = json.load(open(f"{src}/meta.json")) | |
| json.dump(dict(edit_type="none" if kind == "original" else "canvas_B0", role=kind, source=src, | |
| description="unedited long-map rollout (full world)" if kind == "original" else EDIT_DESC["B0"]), | |
| open(f"{sd}/edit.json", "w"), indent=1) | |
| json.dump(dict(name=os.path.basename(src), kind=f"canvas_{kind}", ckpt=mm["ckpt"], seed=mm["seed"], fps=16, | |
| n_frames=mm["n_frames"], cells=mm["cells"], scene=scene, map=mshort, roll_dir=src), | |
| open(f"{sd}/meta.json", "w"), indent=1) | |
| return dict(name=name, case_dir=cd, kind=f"canvas_{tag}", map=mshort, scene=scene, timeline=tl, blank=blank, attack=attack, | |
| n_frames=m["n_frames"], sec=round(m["n_frames"] / 16, 1), n_events=len(evs), | |
| events=" → ".join(f"{e['t_sec']:.0f}s {e['label']}" for e in evs), paired_original=os.path.basename(od), | |
| blank_control=os.path.basename(ctl) if ctl else None) | |
| def cmd_cases(a): | |
| from multiprocessing import Pool | |
| todo = [p for p in pairs() if os.path.exists(f"{p[0]}/meta.json")] | |
| with Pool(a.procs) as pool: | |
| rows = [r for r in pool.map(_case_one, todo) if r] | |
| sc = {} | |
| if os.path.exists(f"{CV}/showcase/scores.json"): | |
| sc = json.load(open(f"{CV}/showcase/scores.json")) | |
| seen = set() | |
| with open(f"{CV}/index.jsonl", "w") as fh: | |
| for r in rows: | |
| s = sc.get(r["name"]) | |
| if isinstance(s, list): | |
| v = [x.get("resp@12") for x in s if x.get("resp@12") is not None and x["type"] != "clear"] | |
| r["effect_mean@12"] = round(sum(v) / len(v), 1) if v else None | |
| fh.write(json.dumps(r, ensure_ascii=False) + "\n") | |
| seen.add(r["name"]) | |
| for r in rows: | |
| for k in ("paired_original", "blank_control"): | |
| n = r.get(k) | |
| if n and n not in seen: | |
| seen.add(n) | |
| fh.write(json.dumps(dict(name=n, case_dir=f"{CV}/cases/{n}", kind="canvas_original" if k == "paired_original" | |
| else "canvas_B0_control", map=r["map"], scene=r["scene"], n_frames=r["n_frames"]), | |
| ensure_ascii=False) + "\n") | |
| print(f"{len(rows)} 条编辑版 → {CV}/cases + index.jsonl(共 {len(seen)} 行)") | |
| def self_erase_scores(ed, ev, offs=(12, 48)): | |
| """清空事件的「自身前后」分:R = 帧 f−1 是物体(3..6)、帧 f+o 变成地面(1 / 2)的像素;resp = R 内 f−1 → f+o 的色差 − 其余 | |
| 语义没变处的色差(白纸对照里也清空了、原版又是另一个世界时用)。""" | |
| import cv2 | |
| import numpy as np | |
| import compose as CP | |
| ids = np.load(os.path.join(ed, "ids.npz"))["ids"] | |
| f0 = max(ev["f"] - 1, 0) | |
| want = {f0} | {min(ev["f"] + o, len(ids) - 1) for o in offs} | |
| fr = {} | |
| for k, x in enumerate(CP.iter_video(os.path.join(ed, "gen.mp4"))): | |
| if k in want: | |
| fr[k] = x.astype(np.int16) | |
| if k > max(want): | |
| break | |
| r = dict(type=ev["type"], f=ev["f"], label=ev["label"], metric="self") | |
| for o in offs: | |
| f = min(ev["f"] + o, len(ids) - 1) | |
| R = np.isin(ids[f0], (3, 4, 5, 6)) & np.isin(ids[f], (1, 2)) | |
| R = cv2.erode(R.astype(np.uint8), np.ones((5, 5), np.uint8)) > 0 | |
| d = np.abs(fr[f] - fr[f0]).mean(-1) | |
| out_m = ~cv2.dilate(R.astype(np.uint8), np.ones((15, 15), np.uint8)).astype(bool) & (ids[f] == ids[f0]) | |
| r[f"frac@{o}"] = round(float(R.mean()), 4) | |
| r[f"resp@{o}"] = round(float(d[R].mean() - d[out_m].mean()), 2) if R.sum() > 200 and out_m.any() else None | |
| return r | |
| def _score_one(args): | |
| import live_compose as LC | |
| ed, od, ctl, mshort, scene, tl, blank, attack = args | |
| ref = ctl if ctl and os.path.exists(f"{ctl}/meta.json") else od | |
| try: | |
| evs = [e for e in LC.load_events(ed)[0]] | |
| main = [e for e in evs if e["type"] != "clear"] | |
| sc = {(r["f"], r["type"]): r for r in (LC.event_scores(ref, ed, main) if main else [])} | |
| for e in evs: | |
| if e["type"] != "clear": | |
| continue | |
| if e["f"] <= 24: # 开头的清空:对原版(原版里这些东西还在) | |
| r = LC.event_scores(od, ed, [e])[0] | |
| r["metric"] = "vs_orig" | |
| else: | |
| r = self_erase_scores(ed, e) | |
| sc[(e["f"], e["type"])] = r | |
| out = [sc[(e["f"], e["type"])] for e in evs] | |
| return os.path.basename(ed), dict(ref=os.path.basename(ref), events=out) | |
| except Exception as e: # noqa: BLE001 | |
| import traceback | |
| return os.path.basename(ed), traceback.format_exc() | |
| def cmd_score(a): | |
| """逐事件效果分(live_compose.event_scores:R = 帧 f+o 编辑版 / 参照语义不同、帧 f−1 相同的像素;resp = R 内两版色差 − R 外色差)。 | |
| 参照:白纸类 = 白纸对照 B0(只差这一笔一笔),其余 = 原版。""" | |
| from multiprocessing import Pool | |
| import numpy as np | |
| todo = [p for p in pairs() if os.path.exists(f"{p[0]}/meta.json") and p[5] != "B0"] | |
| with Pool(a.procs) as pool: | |
| res = dict(pool.map(_score_one, todo)) | |
| os.makedirs(f"{CV}/showcase", exist_ok=True) | |
| flat = {k: (v["events"] if isinstance(v, dict) else v) for k, v in res.items()} | |
| json.dump(res, open(f"{CV}/showcase/scores_full.json", "w"), indent=1, ensure_ascii=False) | |
| json.dump(flat, open(f"{CV}/showcase/scores.json", "w"), indent=1, ensure_ascii=False) | |
| by = {} | |
| for k, evs in flat.items(): | |
| if isinstance(evs, str): | |
| continue | |
| for r in evs: | |
| key = r["type"] | |
| if r["type"] in ("text3d", "vtext"): | |
| key += " (cjk)" if "马良" in r["label"] else " (latin)" | |
| if r["type"] == "spawn": | |
| key += f" ({r['label'].split('→')[-1].strip()})" | |
| by.setdefault(key, []).append((r.get("resp@12"), r.get("resp@48"))) | |
| summ = {} | |
| for k, v in sorted(by.items()): | |
| v12 = [x for x, _ in v if x is not None] | |
| v48 = [y for _, y in v if y is not None] | |
| summ[k] = dict(n=len(v), n_scored=len(v12), median12=round(float(np.median(v12)), 1) if v12 else None, | |
| eff_rate12=round(float(np.mean(np.array(v12) >= 15)), 2) if v12 else None, | |
| median48=round(float(np.median(v48)), 1) if v48 else None, | |
| eff_rate48=round(float(np.mean(np.array(v48) >= 15)), 2) if v48 else None) | |
| rank = sorted(((float(np.mean([r["resp@12"] or 0 for r in evs if r["type"] != "clear"] or [0])), k) for k, evs in flat.items() | |
| if not isinstance(evs, str)), reverse=True) | |
| json.dump(dict(per_type=summ, rank=rank), open(f"{CV}/showcase/score_summary.json", "w"), indent=1, ensure_ascii=False) | |
| print(json.dumps(summ, ensure_ascii=False, indent=1)) | |
| for s_, k in rank: | |
| print(f"{s_:6.1f} {k}") | |
| def main(argv=None): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("cmd", choices=["tl", "jobs", "status", "cases", "score"]) | |
| ap.add_argument("--gpus", default="3,4,5") | |
| ap.add_argument("--procs", type=int, default=12) | |
| ap.add_argument("--only", default=None) | |
| ap.add_argument("--redo", action="store_true") | |
| a = ap.parse_args(argv) | |
| dict(tl=cmd_tl, jobs=cmd_jobs, status=cmd_status, cases=cmd_cases, score=cmd_score)[a.cmd](a) | |
| if __name__ == "__main__": | |
| main() | |