Any-to-Any
Bagel
Safetensors
bagel
HuggingSelf commited on
Commit
8d58519
·
verified ·
1 Parent(s): c58f857

Add Unified-Bench old protocol bundle: unified_bench_old_protocol_0p8346/scripts/run_scoring.py

Browse files
unified_bench_old_protocol_0p8346/scripts/run_scoring.py ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Unified-Bench scoring — computes similarity between reference images and the
4
+ round-trip-generated images produced by run_generation.py.
5
+
6
+ For each enabled scorer (clip / dinov2 / dinov3 / longclip), computes per-image
7
+ cosine similarity and aggregates min / max / average. Output JSON format
8
+ mirrors model/UAE/Unified-Bench/results/example.json for direct comparison
9
+ with the UAE paper numbers.
10
+
11
+ Called via subprocess from cli/unified_bench.py.
12
+
13
+ Usage:
14
+ python eval/generation/unified_bench/run_scoring.py --config <config.yaml>
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import json
20
+ import re
21
+ import sys
22
+ import traceback
23
+ from pathlib import Path
24
+ from typing import Any, Dict, List, Optional, Tuple
25
+
26
+ from tqdm import tqdm
27
+
28
+ from umm.core.config import load_config
29
+
30
+ # Make scorers importable when run as a script.
31
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
32
+ from scorers import load_scorer # noqa: E402
33
+
34
+
35
+ def _resolve_path(path_str: str, repo_root: Path) -> Path:
36
+ path = Path(path_str).expanduser()
37
+ return path if path.is_absolute() else repo_root / path
38
+
39
+
40
+ def _natural_key(path: Path) -> Tuple[int, str]:
41
+ stem = path.stem
42
+ m = re.match(r"^(\d+)$", stem)
43
+ if m:
44
+ return (0, f"{int(m.group(1)):012d}")
45
+ return (1, stem)
46
+
47
+
48
+ def _build_pairs(ref_dir: Path, gen_dir: Path) -> List[Tuple[str, Path, Path]]:
49
+ """Match each reference image with a generated image by stem.
50
+
51
+ Ref files may have arbitrary extensions (jpg/png); generated images are
52
+ always .png (saved by run_generation.py). Pairs with a missing gen image
53
+ are skipped and logged.
54
+ """
55
+ exts = {".jpg", ".jpeg", ".png", ".webp", ".bmp"}
56
+ ref_files = sorted(
57
+ [p for p in ref_dir.iterdir() if p.is_file() and p.suffix.lower() in exts],
58
+ key=_natural_key,
59
+ )
60
+
61
+ pairs: List[Tuple[str, Path, Path]] = []
62
+ missing: List[str] = []
63
+ for ref in ref_files:
64
+ gen = gen_dir / f"{ref.stem}.png"
65
+ if not gen.is_file():
66
+ # Try alternative extensions in case the backbone saved .jpg etc.
67
+ alt = None
68
+ for ext in (".jpg", ".jpeg", ".webp"):
69
+ cand = gen_dir / f"{ref.stem}{ext}"
70
+ if cand.is_file():
71
+ alt = cand
72
+ break
73
+ if alt is None:
74
+ missing.append(ref.stem)
75
+ continue
76
+ gen = alt
77
+ pairs.append((ref.stem, ref, gen))
78
+
79
+ if missing:
80
+ print(f"[unified_bench score] WARNING: {len(missing)} gen images missing: {missing[:10]}...")
81
+ return pairs
82
+
83
+
84
+ def _score_with(scorer, pairs: List[Tuple[str, Path, Path]], name: str) -> Tuple[Dict[str, float], Dict[str, float]]:
85
+ per_image: Dict[str, float] = {}
86
+ first_logged = False
87
+ for item_id, ref, gen in tqdm(pairs, desc=f"[{name}]"):
88
+ try:
89
+ sim = scorer.score_pair(str(ref), str(gen))
90
+ sim_f = float(sim)
91
+ per_image[f"{gen.name}"] = sim_f
92
+ if not first_logged:
93
+ print(f"[{name}] first pair sample — id={item_id}, sim={sim_f:.4f}")
94
+ first_logged = True
95
+ except Exception as exc:
96
+ print(f"[{name}] id={item_id} scoring error: {exc}")
97
+ if not first_logged:
98
+ # Show full traceback for the first failure only, to pinpoint root cause.
99
+ traceback.print_exc()
100
+ first_logged = True
101
+ per_image[f"{gen.name}"] = float("nan")
102
+
103
+ valid = [v for v in per_image.values() if v == v] # filter NaN
104
+ n_errors = len(pairs) - len(valid)
105
+ if n_errors:
106
+ print(f"[{name}] WARNING: {n_errors}/{len(pairs)} pairs failed scoring (NaN)")
107
+ stats = {
108
+ "total_images": len(pairs),
109
+ "valid_pairs": len(valid),
110
+ "average_similarity": float(sum(valid) / len(valid)) if valid else 0.0,
111
+ "min_similarity": float(min(valid)) if valid else 0.0,
112
+ "max_similarity": float(max(valid)) if valid else 0.0,
113
+ }
114
+ return per_image, stats
115
+
116
+
117
+ def _load_eval_cfg(config_path: str) -> Tuple[Dict[str, Any], Dict[str, Any]]:
118
+ raw_cfg = load_config(config_path)
119
+ bench_cfg = raw_cfg.get("unified_bench", {}) if isinstance(raw_cfg.get("unified_bench"), dict) else {}
120
+ inference_cfg = raw_cfg.get("inference", {}) if isinstance(raw_cfg.get("inference"), dict) else {}
121
+ return bench_cfg, inference_cfg
122
+
123
+
124
+ def main() -> int:
125
+ parser = argparse.ArgumentParser(description="Unified-Bench scoring")
126
+ parser.add_argument("--config", required=True, help="Path to eval config YAML")
127
+ args = parser.parse_args()
128
+
129
+ bench_cfg, inference_cfg = _load_eval_cfg(args.config)
130
+ repo_root = Path(__file__).resolve().parents[3]
131
+
132
+ backbone = str(inference_cfg.get("backbone", "unknown")).strip()
133
+
134
+ ref_dir = _resolve_path(
135
+ str(bench_cfg.get("ref_dir", "/datasets/unified_bench/Image")), repo_root
136
+ )
137
+ out_dir = _resolve_path(
138
+ str(bench_cfg.get("out_dir", f"output/unified_bench/{backbone}")), repo_root
139
+ )
140
+ gen_dir = out_dir / "images"
141
+
142
+ score_cfg = bench_cfg.get("scoring", {}) or {}
143
+ if not isinstance(score_cfg, dict):
144
+ score_cfg = {}
145
+ model_names: List[str] = list(score_cfg.get("models", ["clip", "dinov2", "dinov3", "longclip"]))
146
+ model_paths: Dict[str, str] = dict(score_cfg.get("model_paths", {}) or {})
147
+ device = str(score_cfg.get("device", "cuda"))
148
+
149
+ score_output_path_raw = bench_cfg.get("score_output_path")
150
+ if score_output_path_raw:
151
+ score_output_path = _resolve_path(str(score_output_path_raw), repo_root)
152
+ else:
153
+ score_output_path = out_dir / "summary.json"
154
+ score_output_path.parent.mkdir(parents=True, exist_ok=True)
155
+
156
+ print(f"[unified_bench score] backbone={backbone}, ref_dir={ref_dir}, gen_dir={gen_dir}")
157
+ print(f"[unified_bench score] scorers={model_names}")
158
+
159
+ if not gen_dir.is_dir():
160
+ raise FileNotFoundError(f"Generated images dir not found: {gen_dir}")
161
+ if not ref_dir.is_dir():
162
+ raise FileNotFoundError(f"Reference images dir not found: {ref_dir}")
163
+
164
+ pairs = _build_pairs(ref_dir, gen_dir)
165
+ if not pairs:
166
+ raise RuntimeError(
167
+ f"No (ref, gen) pairs matched. ref_dir={ref_dir}, gen_dir={gen_dir}"
168
+ )
169
+ print(f"[unified_bench score] matched {len(pairs)} pairs")
170
+
171
+ similarities_by_model: Dict[str, Dict[str, float]] = {}
172
+ statistics_by_model: Dict[str, Dict[str, float]] = {}
173
+ skipped_models: Dict[str, str] = {}
174
+
175
+ for name in model_names:
176
+ path = model_paths.get(name)
177
+ if not path:
178
+ skipped_models[name] = "no model_path configured"
179
+ print(f"[unified_bench score] SKIP {name}: no model_path in config")
180
+ continue
181
+ try:
182
+ scorer = load_scorer(name, model_path=path, device=device)
183
+ except Exception as exc:
184
+ skipped_models[name] = f"load failed: {exc}"
185
+ print(f"[unified_bench score] SKIP {name}: {exc}")
186
+ traceback.print_exc()
187
+ continue
188
+
189
+ per_image, stats = _score_with(scorer, pairs, name)
190
+ similarities_by_model[name] = per_image
191
+ statistics_by_model[name] = stats
192
+ # Free GPU memory between scorers.
193
+ try:
194
+ import torch
195
+ del scorer
196
+ if torch.cuda.is_available():
197
+ torch.cuda.empty_cache()
198
+ except Exception:
199
+ pass
200
+
201
+ summary_averages = {
202
+ name: stats.get("average_similarity", 0.0)
203
+ for name, stats in statistics_by_model.items()
204
+ }
205
+ if summary_averages:
206
+ best = max(summary_averages.items(), key=lambda kv: kv[1])
207
+ worst = min(summary_averages.items(), key=lambda kv: kv[1])
208
+ overall_avg = sum(summary_averages.values()) / len(summary_averages)
209
+ else:
210
+ best = worst = ("none", 0.0)
211
+ overall_avg = 0.0
212
+
213
+ output = {
214
+ "backbone": backbone,
215
+ "ref_dir": str(ref_dir),
216
+ "gen_dir": str(gen_dir),
217
+ "model_types": list(similarities_by_model.keys()),
218
+ "similarities_by_model": similarities_by_model,
219
+ "statistics_by_model": statistics_by_model,
220
+ "summary": {
221
+ "overall_average": float(overall_avg),
222
+ "best_model": [best[0], float(best[1])],
223
+ "worst_model": [worst[0], float(worst[1])],
224
+ "averages_by_model": summary_averages,
225
+ },
226
+ "skipped_models": skipped_models,
227
+ }
228
+
229
+ score_output_path.write_text(json.dumps(output, indent=2, ensure_ascii=False), encoding="utf-8")
230
+ print(f"[unified_bench score] wrote {score_output_path}")
231
+ print(f"[unified_bench score] averages: {summary_averages}")
232
+ print(f"[unified_bench score] skipped: {skipped_models}")
233
+ return 0
234
+
235
+
236
+ if __name__ == "__main__":
237
+ sys.exit(main())