#!/usr/bin/env python """Round-3 probe — pinpoint whether Native/Ours 挑的是同一批图.""" import os, sys, inspect, json import numpy as np from PIL import Image sys.path.insert(0, '/root/autodl-tmp/3dgsAtlas_official') DATASET_ROOT = "/root/autodl-tmp/dataset/tnt" OUTPUT_ROOT = "/root/autodl-tmp/SplatAtlas/outputs" SCENES = [("truck", "PASS"), ("lighthouse", "FAIL")] THUMB = 256 # reverse-lookup 降采样尺寸 def sec(title): print("\n" + "=" * 70) print(f" {title}") print("=" * 70) def build_args(source_path, img_dir): from scene.dataset_readers import readColmapSceneInfo sig = inspect.signature(readColmapSceneInfo) args = [] for i, (k, p) in enumerate(sig.parameters.items()): if i == 0: args.append(source_path) elif i == 1: args.append(img_dir) elif k == "eval": args.append(True) elif k == "train_test_exp": args.append(False) else: args.append(p.default if p.default != inspect.Parameter.empty else "") return args def load_thumb(path): img = Image.open(path).convert('RGB').resize((THUMB, THUMB), Image.LANCZOS) return np.asarray(img, dtype=np.float32) / 255.0 # ---------- Probe 11: Reverse lookup Native GT → images_2 ---------- def probe_reverse_lookup(scene): sec(f"PROBE 11 — Reverse-lookup Native GT in full images_2 [{scene}]") cell = os.path.join(OUTPUT_ROOT, f"vanilla_3dgs_{scene}") gt_dir = os.path.join(cell, "gt_test_30000") img_dir = os.path.join(DATASET_ROOT, scene, "images_2") native_gts = sorted([f for f in os.listdir(gt_dir) if f.lower().endswith(('.png','.jpg'))]) i2_files = sorted([f for f in os.listdir(img_dir) if f.lower().endswith(('.png','.jpg','.jpeg'))]) print(f"Native GT count : {len(native_gts)}") print(f"images_2 count : {len(i2_files)}") print(f"Building {THUMB}x{THUMB} thumb cache for vectorized PSNR match...") i2_arr = np.stack([load_thumb(os.path.join(img_dir, f)) for f in i2_files]) # (N, H, W, 3) print(f" cache shape: {i2_arr.shape}") print(f"\n{'idx':>3} {'native_gt':>12} -> {'best_match':>15} {'best_psnr':>10} {'margin':>8}") print("-" * 60) matches = [] for idx, ng in enumerate(native_gts): ng_arr = load_thumb(os.path.join(gt_dir, ng)) diff = i2_arr - ng_arr[None] mse = (diff * diff).mean(axis=(1, 2, 3)) psnrs = 10 * np.log10(1.0 / np.maximum(mse, 1e-10)) order = np.argsort(-psnrs) best_idx, best_psnr = order[0], psnrs[order[0]] margin = best_psnr - psnrs[order[1]] best_name = i2_files[best_idx] matches.append((ng, best_name, float(best_psnr), float(margin))) if idx < 10: flag = "" if best_psnr > 30 else " [LOW]" print(f"{idx:>3} {ng:>12} -> {best_name:>15} {best_psnr:>10.2f} {margin:>8.2f}{flag}") if len(matches) > 10: print(f"... (showing first 10 of {len(matches)})") match_names = [m[1] for m in matches] match_set = set(match_names) low_quality = [m for m in matches if m[2] < 30] print(f"\n--- Reverse-lookup summary ---") print(f"Unique matches : {len(match_set)} / {len(matches)} Native GT") print(f"Low-quality (<30 dB) matches: {len(low_quality)} " f"→ {'[WARN] Native GT 可能来自一个 Ours 看不到的图源' if low_quality else '[OK]'}") # 对比 Ours split from scene.dataset_readers import readColmapSceneInfo scene_info = readColmapSceneInfo(*build_args( os.path.join(DATASET_ROOT, scene), "images_2")) ours_set = set(c.image_name for c in scene_info.test_cameras) overlap = match_set & ours_set n_only = match_set - ours_set o_only = ours_set - match_set print(f"\n--- Compare to Ours eval=True test split ---") print(f"Native真挑 ∩ Ours挑 : {len(overlap):>3}") print(f"Native only : {len(n_only):>3} e.g. {sorted(n_only)[:5]}") print(f"Ours only : {len(o_only):>3} e.g. {sorted(o_only)[:5]}") if not o_only and not n_only: print(f"\n[VERDICT] 两边挑的是完全同一批图 → 纯粹 enumerate 顺序不同") # 检查是否 ordering 差异 our_test_names = [c.image_name for c in scene_info.test_cameras] if list(match_names) == our_test_names: print(f" 且顺序也完全一致 — baseline 差异不来自 GT 端") else: print(f" 但 enumerate 顺序不同 — 会影响 render↔GT 配对:") print(f" Native order (first 5): {match_names[:5]}") print(f" Ours order (first 5): {our_test_names[:5]}") elif len(overlap) == 0: print(f"\n[VERDICT] 两边完全不相交 → Native 训练时的 dataset_readers 和我们用的不是同一个版本") else: pct = 100 * len(overlap) / max(len(ours_set), 1) print(f"\n[VERDICT] 部分重合 ({pct:.0f}%) → split 逻辑在某维度上漂移") return matches, scene_info # ---------- Probe 12: Baseline JSON 内容 ---------- def probe_baseline_json(scene): sec(f"PROBE 12 — Native baseline JSON content [{scene}]") cell = os.path.join(OUTPUT_ROOT, f"vanilla_3dgs_{scene}") for name in ["metrics_test_iter30000.json", "results.json", "cfg_args"]: p = os.path.join(cell, name) if os.path.exists(p): print(f"\n <{name}>") with open(p) as f: content = f.read() print(content[:800]) if len(content) > 800: print(f" ... ({len(content)} bytes total)") # ---------- Probe 13: enumerate 顺序与 LLFF hold 对齐 ---------- def probe_enumerate_order(scene, matches, scene_info): sec(f"PROBE 13 — enumerate alignment: Native GT idx i <-> which images_2 file [{scene}]") our_names = [c.image_name for c in scene_info.test_cameras] print(f"{'idx':>3} {'Native→真图':>18} {'Ours test_cam[i]':>18} {'same?':>6}") print("-" * 55) n_align = 0 for i, (ng, bn, bp, _) in enumerate(matches[:min(15, len(matches))]): on = our_names[i] if i < len(our_names) else "—" same = (bn == on) if same: n_align += 1 print(f"{i:>3} {bn:>18} {on:>18} {'Y' if same else 'N':>6}") # full sweep full_align = sum(1 for i, (_, bn, _, _) in enumerate(matches) if i < len(our_names) and bn == our_names[i]) print(f"\nFull enumerate alignment: {full_align} / {len(matches)} positions match") if full_align == len(matches): print("[OK] Native 和 Ours 的 enumerate 顺序完全一致 — 问题不在 GT/split 层") else: print(f"[!!] Enumerate 顺序不同 — 我们的 render[i] 对的是 Native GT[j],i≠j") def main(): for scene, label in SCENES: print(f"\n\n{'#'*70}\n# SCENE: {scene} [{label}]\n{'#'*70}") matches, scene_info = probe_reverse_lookup(scene) probe_baseline_json(scene) probe_enumerate_order(scene, matches, scene_info) if __name__ == "__main__": main()