Download methods/wrapper_gspull.py from KCBtheone/SplatAtlas: direct link, hf CLI and curl.
- Browser
- Download file 10.1 kB
-
https://huggingface.co/KCBtheone/SplatAtlas/resolve/main/methods/wrapper_gspull.py
- Command line
-
hf download hf://KCBtheone/SplatAtlas/methods/wrapper_gspull.py
-
curl -L -o wrapper_gspull.py https://huggingface.co/KCBtheone/SplatAtlas/resolve/main/methods/wrapper_gspull.py
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 | |
| 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") | |