File size: 7,137 Bytes
23e73f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#!/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()