File size: 4,301 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
import argparse
import os
import torch
import torchvision
import numpy as np
from tqdm import tqdm
import sys
import concurrent.futures

sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from core.registry import METHOD_REGISTRY
import methods
from ufd_evalkit.geometric import depth_to_normal

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("--method", type=str, required=True)
    parser.add_argument("--source_path", type=str, required=True)
    parser.add_argument("--model_path", type=str, required=True)
    parser.add_argument("--iteration", type=int, default=30000)
    parser.add_argument("--resolution", type=int, default=-1)
    return parser.parse_args()

def save_np(data, path): np.save(path, data)

def main():
    args = parse_args()
    
    # 动态从注册表获取模型,拒绝写死的 import
    model_class = methods.load_method(args.method)
    
    dataset_config = {"source_path": args.source_path, "model_path": args.model_path, "resolution": args.resolution}
    model = model_class(dataset_config, hyperparams={})
    
    print(f"📥 [SplatAtlas] 正在请求变体自行加载资产... (Iteration: {args.iteration})")
    try:
        # 严格遵守契约:由 Wrapper 决定如何加载 .ply 或 MLP 权重
        model.load(args.model_path, args.iteration)
    except Exception as e:
        print(f"❌ 资产加载失败,请检查 [{args.method}] 的 load() 方法实现: {e}")
        return
    
    print(f"📷 [SplatAtlas] 启动离线资产渲染 (异步 I/O 开启)")
    
    executor = concurrent.futures.ThreadPoolExecutor(max_workers=16)
    futures = []
    
    test_render_dir = os.path.join(args.model_path, f"renders_test_{args.iteration}")
    test_depth_dir = os.path.join(args.model_path, f"depths_test_{args.iteration}")
    test_normal_dir = os.path.join(args.model_path, f"normals_test_{args.iteration}")
    test_gt_dir = os.path.join(args.model_path, f"gt_test_{args.iteration}")
    for d in [test_render_dir, test_depth_dir, test_normal_dir, test_gt_dir]: os.makedirs(d, exist_ok=True)
    
    train_render_dir = os.path.join(args.model_path, f"renders_train_{args.iteration}")
    for d in [train_render_dir]: os.makedirs(d, exist_ok=True)

    with torch.no_grad():
        test_cams = model.scene.getTestCameras()
        if test_cams and isinstance(test_cams[0], list):
            flat_cams = [c for sub in test_cams for c in sub]
        else:
            flat_cams = test_cams
        for idx, view in enumerate(tqdm(flat_cams, desc="Rendering Test")):
            render_pkg = model.render(view)
            rendering = render_pkg.get("render", render_pkg.get("image")).detach().cpu()
            depth = render_pkg.get("depth", None)
            
            futures.append(executor.submit(torchvision.utils.save_image, rendering, os.path.join(test_render_dir, f'{idx:05d}.png')))
            futures.append(executor.submit(torchvision.utils.save_image, view.original_image.cpu(), os.path.join(test_gt_dir, f'{idx:05d}.png')))
            
            if depth is not None:
                depth_cpu = depth.detach().cpu()
                normal = render_pkg.get("normal", depth_to_normal(depth)).detach().cpu()
                futures.append(executor.submit(save_np, depth_cpu.numpy(), os.path.join(test_depth_dir, f'{idx:05d}.npy')))
                futures.append(executor.submit(torchvision.utils.save_image, normal, os.path.join(test_normal_dir, f'{idx:05d}.png')))
                
        for idx, view in enumerate(tqdm(model.scene.getTrainCameras(), desc="Rendering Train")):
            render_pkg = model.render(view)
            rendering = render_pkg.get("render", render_pkg.get("image")).detach().cpu()
            futures.append(executor.submit(torchvision.utils.save_image, rendering, os.path.join(train_render_dir, f'{idx:05d}.png')))

    print("⏳ [Async I/O] GPU 渲染完毕!正在等待 CPU 将所有数据落盘...")
    concurrent.futures.wait(futures)
    executor.shutdown()
    
    flag_path = os.path.join(args.model_path, f"render_complete_{args.iteration}.flag")
    with open(flag_path, "w") as f: f.write("Completed by Async Engine")
    print("✅ 完美落盘,标记成功!")

if __name__ == "__main__":
    main()