Initial upload: BPN deblur pipeline code (scripts, triangle-splatting, BAGS, EVSSM forks)
c75b162 verified | #!/usr/bin/env python3 | |
| """Evaluate the 4-scene GT-pose retraining campaign (BAGS + TriSplat, | |
| GT pose + GT depth, depth_loss_alpha=0.1, 15K iters) on the NIMA-koniq | |
| 'sharp frame' subset selected by select_sharp_i2slam.py (top-15% NIMA | |
| scoring of raw pre-EVSSM frames). | |
| Unlike the COLMAP-pose 50K runs (metrics_i2slam_sharp_4scenes_50k.py), | |
| these GT-pose scenes register 100% of frames in sequential evssm_idx | |
| order, so the train/test (--eval --llffhold 8) split and each frame's | |
| render_idx within its split are exactly recoverable from evssm_idx: | |
| - evssm_idx % 8 == 0 -> test split, render_idx = evssm_idx // 8 | |
| - else -> train split, render_idx = evssm_idx - evssm_idx//8 - 1 | |
| (verified against actual train/test counts for all 4 scenes). | |
| Computes PSNR / SSIM / LPIPS / NIMA, comparing renders against the | |
| EVSSM-deblurred frames (the same 'sharp-frame' ground truth definition | |
| used by the COLMAP-pose evaluation, for direct comparability).""" | |
| import os, json, numpy as np | |
| from PIL import Image | |
| import torch, pyiqa, lpips as lpips_lib | |
| from skimage.metrics import structural_similarity as ssim_fn | |
| from skimage.metrics import peak_signal_noise_ratio as psnr_fn | |
| BASE = "/home/szha0669/storage/blur_slam_exp" | |
| ITER = 15000 | |
| HOLD = 8 | |
| SELECTION = json.load(open(f"{BASE}/outputs/logs/i2slam_sharp_frame_selection.json")) | |
| SCENES = ["scene0024_01", "scene0031_00", "scene0736_00", "tum_fr2_xyz"] | |
| device = torch.device("cuda") | |
| nima_fn = pyiqa.create_metric("nima-koniq", device=device) | |
| lpips_fn = lpips_lib.LPIPS(net='vgg').to(device) | |
| def t(img): | |
| return (torch.from_numpy(np.array(img).astype(np.float32) / 255.) | |
| .permute(2, 0, 1).unsqueeze(0).to(device) * 2 - 1) | |
| def split_and_render_idx(evssm_idx): | |
| if evssm_idx % HOLD == 0: | |
| return "test", evssm_idx // HOLD | |
| return "train", evssm_idx - evssm_idx // HOLD - 1 | |
| def eval_scene(out_root, scene, frames): | |
| rows = [] | |
| gt_dir = f"{BASE}/data/evssm_deblurred_i2slam/{scene}" | |
| for fr in frames: | |
| ei = fr["evssm_idx"] | |
| split, ri = split_and_render_idx(ei) | |
| pred_p = os.path.join(out_root, scene, split, f"ours_{ITER}", "test_preds_2", f"{ri:05d}.png") | |
| gt_p = os.path.join(gt_dir, f"{ei:06d}.png") | |
| if not (os.path.exists(pred_p) and os.path.exists(gt_p)): | |
| continue | |
| pred = Image.open(pred_p).convert('RGB') | |
| gt = Image.open(gt_p).convert('RGB').resize(pred.size, Image.LANCZOS) | |
| pa, ga = np.array(pred), np.array(gt) | |
| with torch.no_grad(): | |
| lp = float(lpips_fn(t(pred), t(gt)).item()) | |
| rows.append(dict( | |
| psnr=psnr_fn(ga, pa, data_range=255), | |
| ssim=ssim_fn(ga, pa, channel_axis=2, data_range=255), | |
| lpips=lp, | |
| nima=float(nima_fn(pred_p)), | |
| )) | |
| if not rows: | |
| return None, 0 | |
| return {k: float(np.mean([x[k] for x in rows])) for k in rows[0]}, len(rows) | |
| def collect(out_root): | |
| return {scene: eval_scene(out_root, scene, SELECTION[scene]) for scene in SCENES} | |
| def print_table(label, per_scene): | |
| print(f"\n=== {label} (iteration {ITER}, GT pose+depth, alpha=0.1, sharp-frame test set) ===") | |
| print(f"{'scene':>14} {'n':>4} {'PSNR':>7} {'SSIM':>7} {'LPIPS':>7} {'NIMA':>7}") | |
| print("-" * 56) | |
| valid = [] | |
| for scene, (r, n) in per_scene.items(): | |
| if r is None: | |
| print(f"{scene:>14} {'N/A':>4}") | |
| continue | |
| valid.append(r) | |
| print(f"{scene:>14} {n:>4} {r['psnr']:7.3f} {r['ssim']:7.4f} {r['lpips']:7.4f} {r['nima']:7.4f}") | |
| if valid: | |
| avg = {k: float(np.mean([x[k] for x in valid])) for k in valid[0]} | |
| print("-" * 56) | |
| print(f"{'AVERAGE':>14} {'':>4} {avg['psnr']:7.3f} {avg['ssim']:7.4f} {avg['lpips']:7.4f} {avg['nima']:7.4f}") | |
| return per_scene | |
| bags_results = collect(f"{BASE}/outputs/bags_i2slam_gtall") | |
| tri_results = collect(f"{BASE}/outputs/trigsplat_i2slam_gtall") | |
| print_table("BAGS / MipSplat (GT pose+depth)", bags_results) | |
| print_table("TriSplat + BPN (GT pose+depth)", tri_results) | |
| out_json = { | |
| "bags_gtpose": {s: r for s, (r, n) in bags_results.items()}, | |
| "trisplat_gtpose": {s: r for s, (r, n) in tri_results.items()}, | |
| } | |
| out_path = f"{BASE}/outputs/logs/metrics_4scenes_gtpose_sharp.json" | |
| json.dump(out_json, open(out_path, "w"), indent=2) | |
| print(f"\nSaved -> {out_path}") | |