SplatAtlas / methods /wrapper_gspull.py
KCBtheone's picture
Upload SplatAtlas benchmark pipeline code
23e73f9 verified
Raw
History Blame Contribute Delete
10.1 kB
import os
import sys
import random
import torch
import numpy as np
import torch.nn.functional as F
from argparse import ArgumentParser
from core.registry import register_method
from core.base_method import BaseMethod
GS_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../GS-Pull'))
# 强隔离:清空残留路径并确保 GS-Pull 绝对优先
if GS_ROOT not in sys.path:
sys.path.insert(0, GS_ROOT)
# GS-Pull's gaussian_splatting is a vanilla 3DGS copy — its scene/__init__.py
# does `from utils.system_utils import ...` expecting the gaussian_splatting/
# subdir to be on sys.path.
_GS_INNER = os.path.join(GS_ROOT, 'gaussian_splatting')
if _GS_INNER not in sys.path:
sys.path.insert(0, _GS_INNER)
from gaussian_splatting.scene import Scene, GaussianModel
from gaussian_splatting.arguments import ModelParams, PipelineParams, OptimizationParams as GSOptParams
from gaussian_splatting.gaussian_renderer import render as vanilla_render
from sugar_scene.sugar_model import SuGaR
from sugar_scene.sugar_optimizer import OptimizationParams as SuGaROptParams, SuGaROptimizer
from sugar_scene.sugar_densifier import SuGaRDensifier
from sugar_utils.loss_utils import ssim, l1_loss
from np_utils.train import Runner
@register_method("gspull")
class GSPullWrapper(BaseMethod):
def __init__(self, dataset_config, hyperparams):
self.source_path = dataset_config["source_path"]
self.model_path = dataset_config["model_path"]
self.resolution = dataset_config.get("resolution", 1)
self.track_decoupling = hyperparams.get("track_decoupling", False)
parser = ArgumentParser()
lp = ModelParams(parser)
op = GSOptParams(parser)
pp = PipelineParams(parser)
args = parser.parse_args([])
args.source_path = self.source_path
args.model_path = self.model_path
args.resolution = self.resolution
args.eval = True
self.dataset = lp.extract(args)
self.gs_opt = op.extract(args)
self.pipe = pp.extract(args)
# 防呆逻辑:兼容某些变体改名为 shfeature_lr 的情况
if not hasattr(self.gs_opt, 'shfeature_lr') and hasattr(self.gs_opt, 'feature_lr'):
setattr(self.gs_opt, 'shfeature_lr', getattr(self.gs_opt, 'feature_lr'))
self.gaussians = GaussianModel(self.dataset.sh_degree)
# INJECTED_RES_FIX begin
import sys as _sys
_scene, _explicit_res = None, None
for _i, _a in enumerate(_sys.argv[:-1]):
_v = _sys.argv[_i + 1]
if _a == "--scene": _scene = _v
elif _a == "--source_path": _scene = _v.rstrip("/").split("/")[-1]
elif _a == "--resolution":
try: _explicit_res = int(_v)
except: pass
_OUTDOOR_360 = {"bicycle", "flowers", "garden", "stump", "treehill"}
if _explicit_res is not None and _explicit_res > 0:
_res = _explicit_res
elif _scene is not None:
_res = 4 if _scene in _OUTDOOR_360 else 2
else:
_res = None
try:
if _res is not None:
self.dataset.resolution = _res
print("[res-fix] scene=%s explicit=%s -> res=%s (%s)" % (_scene, _explicit_res, _res, __file__))
except Exception as _e:
print("[res-fix] FAILED:", _e)
# INJECTED_RES_FIX end
self.scene = Scene(self.dataset, self.gaussians)
self.gaussians.training_setup(self.gs_opt)
self.bg_color = torch.tensor([1, 1, 1] if self.dataset.white_background else [0, 0, 0], dtype=torch.float32, device="cuda")
self.train_cameras = self.scene.getTrainCameras().copy()
self.viewpoint_stack = self.train_cameras.copy()
self.sugar = None
self.optimizer = None
self.densifier = None
self._iter = 0
self.last_n_gaussians = len(self.gaussians.get_xyz)
def _init_sugar_stage(self):
self.sugar = SuGaR(
nerfmodel=None,
points=self.gaussians.get_xyz.detach(),
colors=torch.zeros_like(self.gaussians.get_xyz),
initialize=False,
sh_levels=self.gaussians.active_sh_degree + 1,
learnable_positions=True,
triangle_scale=1.0,
keep_track_of_knn=True,
knn_to_track=16,
beta_mode="average",
freeze_gaussians=False,
surface_mesh_to_bind=None,
)
with torch.no_grad():
self.sugar._points.copy_(self.gaussians._xyz)
self.sugar._scales.copy_(self.gaussians._scaling)
self.sugar._quaternions.copy_(self.gaussians._rotation)
self.sugar.all_densities.copy_(self.gaussians._opacity)
self.sugar._sh_coordinates_dc.copy_(self.gaussians._features_dc)
self.sugar._sh_coordinates_rest.copy_(self.gaussians._features_rest)
self.sugar.part_num = 1
self.sugar.neus = Runner(self.model_path, None, part_num=self.sugar.part_num)
spatial_lr_scale = self.scene.cameras_extent
opt_p = SuGaROptParams(
iterations=15000, position_lr_init=0.00016, position_lr_final=0.0000016,
position_lr_max_steps=30000, scaling_lr=0.005, opacity_lr=0.05,
feature_lr=0.0025, rotation_lr=0.001
)
self.optimizer = SuGaROptimizer(self.sugar, opt_p, spatial_lr_scale=spatial_lr_scale)
self.densifier = SuGaRDensifier(self.sugar, self.optimizer, 0.0002, 0.005, 20, spatial_lr_scale, 0.01)
def train_iteration(self, step):
self._iter += 1
metrics = {}
histograms = {}
if self._iter <= 7000:
if not self.viewpoint_stack:
self.viewpoint_stack = self.train_cameras.copy()
viewpoint_cam = self.viewpoint_stack.pop(random.randint(0, len(self.viewpoint_stack) - 1))
render_pkg = vanilla_render(viewpoint_cam, self.gaussians, self.pipe, self.bg_color)
image, viewspace_point_tensor, visibility_filter, radii = render_pkg["render"], render_pkg["viewspace_points"], render_pkg["visibility_filter"], render_pkg["radii"]
gt_image = viewpoint_cam.original_image.cuda()
loss = 0.8 * l1_loss(image, gt_image) + 0.2 * (1.0 - ssim(image, gt_image))
loss.backward()
with torch.no_grad():
self.gaussians.max_radii2D[visibility_filter] = torch.max(self.gaussians.max_radii2D[visibility_filter], radii[visibility_filter])
self.gaussians.add_densification_stats(viewspace_point_tensor, visibility_filter)
if self._iter > 500 and self._iter % 100 == 0:
self.gaussians.densify_and_prune(self.gs_opt.densify_grad_threshold, 0.005, self.scene.cameras_extent, 20)
if self._iter % 3000 == 0:
self.gaussians.reset_opacity()
self.gaussians.optimizer.step()
self.gaussians.optimizer.zero_grad(set_to_none=True)
num_gaussians = len(self.gaussians.get_xyz)
metrics = {
"loss": float(loss), "num_gaussians": int(num_gaussians),
"peak_vram_GB": float(torch.cuda.max_memory_allocated() / (1024 ** 3))
}
self.last_n_gaussians = num_gaussians
return metrics, histograms
if self._iter == 7001:
self._init_sugar_stage()
is_pulling = self._iter > 9000
if self._iter == 9001:
prune_mask = (self.sugar.strengths < 0.5).squeeze()
self.densifier.prune_points(prune_mask)
self.sugar.reset_neighbors()
self.sugar.neus.reset_datasets(self.model_path, self.sugar.points.detach().cpu().numpy(), iteration=9000, scene_name="scene")
if not self.viewpoint_stack:
self.viewpoint_stack = self.train_cameras.copy()
cam = self.viewpoint_stack.pop(random.randint(0, len(self.viewpoint_stack) - 1))
outputs = self.sugar.render_image_gaussian_rasterizer(
camera_indices=self.train_cameras.index(cam), bg_color=self.bg_color,
sh_deg=self.sugar.sh_levels-1, compute_covariance_in_rasterizer=True, return_2d_radii=True
)
pred_rgb = outputs['image'].view(-1, cam.image_height, cam.image_width, 3).transpose(-1, -2).transpose(-2, -3)
gt_rgb = cam.original_image.cuda().unsqueeze(0)
loss = 0.8 * l1_loss(pred_rgb, gt_rgb) + 0.2 * (1.0 - ssim(pred_rgb, gt_rgb))
loss.backward()
self.optimizer.step()
self.optimizer.zero_grad(set_to_none=True)
metrics = {"loss": float(loss), "num_gaussians": len(self.sugar.points)}
return metrics, {}
def render(self, camera):
with torch.no_grad():
if self._iter <= 7000:
return {"image": vanilla_render(camera, self.gaussians, self.pipe, self.bg_color)["render"], "depth": None}
idx = 0
for i, c in enumerate(self.train_cameras):
if c.uid == camera.uid: idx = i; break
outputs = self.sugar.render_image_gaussian_rasterizer(camera_indices=idx)
return {"image": outputs["image"], "depth": None}
def save(self, save_dir, step):
if self.sugar:
self.sugar.save_model(path=os.path.join(save_dir, f"{step}.pt"), iteration=step)
else:
self.gaussians.save_ply(os.path.join(save_dir, "point_cloud", f"iteration_{step}", "point_cloud.ply"))
def load(self, model_path, iteration):
pass
def get_spatial_centers(self):
return self.sugar.points if self.sugar else self.gaussians.get_xyz
def compute_physical_metrics(self, cameras=None):
return {"num_gaussians": float(len(self.get_spatial_centers()))}
def evaluate_spatial_field(self, query_points: torch.Tensor, cameras=None) -> torch.Tensor:
return torch.zeros(len(query_points), device="cuda")