Download methods/wrapper_flod.py from KCBtheone/SplatAtlas: direct link, hf CLI and curl.
- Browser
- Download file 16.3 kB
-
https://huggingface.co/KCBtheone/SplatAtlas/resolve/main/methods/wrapper_flod.py
- Command line
-
hf download hf://KCBtheone/SplatAtlas/methods/wrapper_flod.py
-
curl -L -o wrapper_flod.py https://huggingface.co/KCBtheone/SplatAtlas/resolve/main/methods/wrapper_flod.py
16.3 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 | |
| sys.path.insert(0, '/root/autodl-tmp/FLoD_offy') | |
| from utils.loss_utils import l1_loss, ssim | |
| from gaussian_renderer import render as native_render | |
| from scene import Scene, GaussianModel | |
| from arguments import ModelParams, PipelineParams, OptimizationParams | |
| def expand_list(l, size): | |
| if len(l) >= size: | |
| return l[:size] | |
| return l + [l[-1]] * (size - len(l)) | |
| class FLODWrapper(BaseMethod): | |
| def __init__(self, dataset_config, hyperparams): | |
| self.parser = ArgumentParser() | |
| self.lp = ModelParams(self.parser) | |
| self.op = OptimizationParams(self.parser) | |
| self.pp = PipelineParams(self.parser) | |
| self.args = self.parser.parse_args([]) | |
| self.args.source_path = dataset_config["source_path"] | |
| self.args.model_path = dataset_config["model_path"] | |
| self.args.eval = True | |
| self.args.resolution = dataset_config.get("resolution", 1) | |
| self.track_decoupling = hyperparams.get("track_decoupling", False) | |
| self.dataset = self.lp.extract(self.args) | |
| self.opt = self.op.extract(self.args) | |
| self.pipe = self.pp.extract(self.args) | |
| self.gaussians = GaussianModel( | |
| sh_degree=self.dataset.sh_degree, | |
| lod1_scaling_lower_bound=self.dataset.lod1_scaling_lower_bound, | |
| lod_scaling_ratio=self.dataset.lod_scaling_ratio, | |
| increase_lod_num_childs=self.dataset.increase_lod_num_childs, | |
| current_lod=self.opt.lod_min, | |
| max_lod=self.opt.lod_max, | |
| use_voxel_sampling=self.dataset.use_voxel_sampling, | |
| voxel_sampling_size=self.dataset.voxel_sampling_size | |
| ) | |
| # 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.opt) | |
| bg_color = [1, 1, 1] if self.dataset.white_background else [0, 0, 0] | |
| self.background = torch.tensor(bg_color, dtype=torch.float32, device="cuda") | |
| self.viewpoint_stack = self.scene.getTrainCameras().copy() | |
| self.last_n_gaussians = len(self.gaussians.get_xyz) | |
| self.current_lod_idx = 0 | |
| lods = self.opt.lod_max - self.opt.lod_min + 1 | |
| self.densify_grad_thresholds = expand_list(self.opt.densify_grad_threshold, lods) | |
| self.densification_intervals = expand_list(self.opt.densification_interval, lods) | |
| self.densify_from_iters = expand_list(self.opt.densify_from_iter, lods) | |
| self.densify_until_iters = expand_list(self.opt.densify_until_iter, lods) | |
| self.prune_opacity_thresholds = expand_list(self.opt.prune_opacity_threshold, lods) | |
| self.pruning_intervals = expand_list(self.opt.pruning_interval, lods) | |
| self.prune_from_iters = expand_list(self.opt.prune_from_iter, lods) | |
| self.prune_until_iters = expand_list(self.opt.prune_until_iter, lods) | |
| self.prune_overlap_thresholds = expand_list(self.opt.prune_overlap_threshold, lods) | |
| self.pruning_overlap_intervals = expand_list(self.opt.pruning_overlap_interval, lods) | |
| self.opacity_reset_intervals = expand_list(self.opt.opacity_reset_interval, lods) | |
| self.lambda_dssims = expand_list(self.opt.lambda_dssim, lods) | |
| def train_iteration(self, step): | |
| target_idx = 0 | |
| local_step = step | |
| for idx, iters in enumerate(self.opt.lod_iterations): | |
| if local_step <= iters: | |
| target_idx = idx | |
| break | |
| local_step -= iters | |
| if target_idx > self.current_lod_idx: | |
| self.gaussians.increase_lod() | |
| self.gaussians.training_setup(self.opt) | |
| self.current_lod_idx = target_idx | |
| self.gaussians.update_learning_rate(local_step) | |
| if local_step % 1000 == 0: | |
| self.gaussians.oneupSHdegree() | |
| if not self.viewpoint_stack: | |
| self.viewpoint_stack = self.scene.getTrainCameras().copy() | |
| viewpoint_cam = self.viewpoint_stack.pop(random.randint(0, len(self.viewpoint_stack) - 1)) | |
| render_pkg = native_render(viewpoint_cam, self.gaussians, self.pipe, self.background) | |
| image = render_pkg["render"] | |
| viewspace_point_tensor = render_pkg["viewspace_points"] | |
| visibility_filter = render_pkg["visibility_filter"] | |
| radii = render_pkg["radii"] | |
| gt_image = viewpoint_cam.original_image.cuda() | |
| lambda_dssim = self.lambda_dssims[self.current_lod_idx] | |
| Ll1 = l1_loss(image, gt_image) | |
| ssim_value = ssim(image, gt_image) | |
| loss_target = (1.0 - lambda_dssim) * Ll1 | |
| loss_parasitic = lambda_dssim * (1.0 - ssim_value) | |
| loss = loss_target + loss_parasitic | |
| grad_cos_sim = 0.0 | |
| parasitic_ratio = 0.0 | |
| stats = {} | |
| if self.track_decoupling and local_step % 100 == 0: | |
| self.gaussians.optimizer.zero_grad(set_to_none=True) | |
| loss_target.backward(retain_graph=True) | |
| grad_target = self.gaussians._xyz.grad.clone() if self.gaussians._xyz.grad is not None else torch.zeros_like(self.gaussians._xyz) | |
| self.gaussians.optimizer.zero_grad(set_to_none=True) | |
| loss_parasitic.backward(retain_graph=True) | |
| grad_parasitic = self.gaussians._xyz.grad.clone() if self.gaussians._xyz.grad is not None else torch.zeros_like(self.gaussians._xyz) | |
| valid_mask = (torch.norm(grad_target, dim=1) > 0) & (torch.norm(grad_parasitic, dim=1) > 0) | |
| if valid_mask.any(): | |
| grad_cos_sim = float(F.cosine_similarity(grad_target[valid_mask], grad_parasitic[valid_mask], dim=1).mean()) | |
| parasitic_ratio = float(torch.norm(grad_parasitic, dim=1).mean() / (torch.norm(grad_target, dim=1).mean() + 1e-7)) | |
| param_groups_map = { | |
| "spatial": [self.gaussians._xyz], | |
| "geometry": [self.gaussians._scaling, self.gaussians._rotation], | |
| "opacity": [self.gaussians._opacity], | |
| "appearance": [self.gaussians._features_dc, self.gaussians._features_rest], | |
| } | |
| def get_effective_steps(loss_comp): | |
| self.gaussians.optimizer.zero_grad(set_to_none=True) | |
| loss_comp.backward(retain_graph=True) | |
| grads_eff = {} | |
| for group_name, params in param_groups_map.items(): | |
| grad_list = [] | |
| for p in params: | |
| if p.grad is not None: | |
| state = self.gaussians.optimizer.state.get(p, {}) | |
| v_t = state.get("exp_avg_sq", torch.zeros_like(p)) | |
| group_lr = 0.0 | |
| for pg in self.gaussians.optimizer.param_groups: | |
| if id(p) in [id(pgp) for pgp in pg['params']]: | |
| group_lr = pg['lr'] | |
| break | |
| u = (group_lr / (torch.sqrt(v_t) + 1e-8)) * p.grad.clone() | |
| grad_list.append(u.reshape(-1)) | |
| if grad_list: | |
| grads_eff[group_name] = torch.cat(grad_list) | |
| return grads_eff | |
| grads_target_eff = get_effective_steps(loss_target) | |
| grads_parasitic_eff = get_effective_steps(loss_parasitic) | |
| for group_name in param_groups_map: | |
| gt = grads_target_eff.get(group_name) | |
| gp = grads_parasitic_eff.get(group_name) | |
| if gt is not None and gp is not None and gt.norm() > 0 and gp.norm() > 0: | |
| cos = float(F.cosine_similarity(gt.unsqueeze(0), gp.unsqueeze(0))) | |
| r = float(gp.norm() / (gt.norm() + gp.norm() + 1e-7)) | |
| ti = r * max(0.0, -cos) | |
| else: | |
| ti = 0.0 | |
| stats[f"sti_{group_name}"] = ti | |
| self.gaussians.optimizer.zero_grad(set_to_none=True) | |
| loss.backward() | |
| else: | |
| loss.backward() | |
| with torch.no_grad(): | |
| densify_grad_threshold = self.densify_grad_thresholds[self.current_lod_idx] | |
| densification_interval = self.densification_intervals[self.current_lod_idx] | |
| densify_from_iter = self.densify_from_iters[self.current_lod_idx] | |
| densify_until_iter = self.densify_until_iters[self.current_lod_idx] | |
| prune_opacity_threshold = self.prune_opacity_thresholds[self.current_lod_idx] | |
| pruning_interval = self.pruning_intervals[self.current_lod_idx] | |
| prune_from_iter = self.prune_from_iters[self.current_lod_idx] | |
| prune_until_iter = self.prune_until_iters[self.current_lod_idx] | |
| prune_overlap_threshold = self.prune_overlap_thresholds[self.current_lod_idx] | |
| pruning_overlap_interval = self.pruning_overlap_intervals[self.current_lod_idx] | |
| opacity_reset_interval = self.opacity_reset_intervals[self.current_lod_idx] | |
| if local_step < densify_until_iter: | |
| 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 local_step > densify_from_iter and local_step % densification_interval == 0: | |
| self.gaussians.densify(densify_grad_threshold, extent=self.scene.cameras_extent) | |
| if local_step < prune_until_iter: | |
| if local_step > prune_from_iter and local_step % pruning_interval == 0: | |
| size_threshold = 20 if (local_step > opacity_reset_interval and (self.opt.lod_min + self.current_lod_idx) >= self.opt.prune_max_radii2D_lod) else None | |
| if local_step % pruning_overlap_interval == 0: | |
| self.gaussians.prune(prune_opacity_threshold, self.scene.cameras_extent, size_threshold, prune_overlap_threshold) | |
| else: | |
| self.gaussians.prune(prune_opacity_threshold, self.scene.cameras_extent, size_threshold, 0.0) | |
| if local_step < prune_until_iter: | |
| if local_step % opacity_reset_interval == 0 or (self.dataset.white_background and local_step == densify_from_iter): | |
| self.gaussians.reset_opacity() | |
| self.gaussians.optimizer.step() | |
| self.gaussians.optimizer.zero_grad(set_to_none=True) | |
| num_gaussians = self.gaussians.get_xyz.shape[0] | |
| metrics = { | |
| "loss": float(loss), "loss_l1": float(loss_target), "loss_ssim": float(loss_parasitic), | |
| "num_gaussians": int(num_gaussians), "delta_N": int(num_gaussians - self.last_n_gaussians), | |
| "peak_vram_GB": float(torch.cuda.max_memory_allocated() / (1024 ** 3)), | |
| "grad_cos_sim": float(grad_cos_sim), "parasitic_ratio": float(parasitic_ratio), | |
| "current_lod": int(self.opt.lod_min + self.current_lod_idx), | |
| "local_step": int(local_step) | |
| } | |
| metrics.update(stats) | |
| self.last_n_gaussians = num_gaussians | |
| histograms = {} | |
| if step % 1000 == 0: | |
| histograms["opacity"] = torch.sigmoid(self.gaussians._opacity).clone().detach() | |
| scales = self.gaussians.get_scaling.clone().detach() | |
| histograms["scaling"] = scales | |
| scales_2d = scales[:, :2] if scales.shape[1] >= 2 else scales | |
| gamma = scales_2d.max(dim=-1)[0] / (scales_2d.min(dim=-1)[0] + 1e-7) | |
| histograms["anisotropy"] = gamma | |
| histograms["sh_dc_mag"] = self.gaussians._features_dc.detach().norm(dim=-1) | |
| return metrics, histograms | |
| def render(self, camera): | |
| with torch.no_grad(): | |
| render_pkg = native_render(camera, self.gaussians, self.pipe, self.background) | |
| return {"image": render_pkg["render"], "depth": render_pkg.get("depth", None)} | |
| def save(self, save_dir, step): | |
| self.scene.save(step, self.opt.lod_min + self.current_lod_idx) | |
| def load(self, model_path, iteration): | |
| self.gaussians.load(model_path, load_iteration=iteration, load_independent_lvl=False) | |
| def get_spatial_centers(self): | |
| return self.gaussians._xyz | |
| def compute_physical_metrics(self, cameras=None): | |
| metrics = {} | |
| with torch.no_grad(): | |
| scales = self.gaussians.get_scaling | |
| scales_2d = scales[:, :2] if scales.dim() > 1 and scales.shape[1] >= 2 else scales.unsqueeze(-1).expand(-1, 2) | |
| max_S, _ = torch.max(scales_2d, dim=1) | |
| min_S, _ = torch.min(scales_2d, dim=1) | |
| gamma = max_S / (min_S + 1e-7) | |
| metrics["gamma_median"] = float(torch.median(gamma)) | |
| metrics["gamma_90th_percentile"] = float(torch.quantile(gamma, 0.90)) | |
| metrics["scale_mean"] = float(torch.mean(scales_2d)) | |
| metrics["alpha_mean"] = float(torch.mean(torch.sigmoid(self.gaussians._opacity))) | |
| dc, rest = self.gaussians._features_dc, self.gaussians._features_rest | |
| if rest is not None and rest.shape[1] > 0: | |
| metrics["sh_energy_ratio"] = float(rest.norm(dim=-1).mean() / (dc.norm(dim=-1).mean() + 1e-7)) | |
| if cameras is not None and len(cameras) > 0: | |
| view_dirs = [] | |
| for c in cameras: | |
| view_dirs.append(c.world_view_transform[:3, 2].tolist()) | |
| view_dirs = F.normalize(torch.tensor(view_dirs, dtype=torch.float32, device="cuda"), dim=1) | |
| rots = self.gaussians.get_rotation.clone() | |
| w, x, y, z = rots.unbind(dim=-1) | |
| normals = F.normalize(torch.stack([2*(x*z + w*y), 2*(y*z - w*x), 1-2*(x*x + y*y)], dim=-1), dim=1) | |
| max_cos, _ = torch.max(torch.abs(torch.matmul(normals, view_dirs.T)), dim=1) | |
| metrics["billboard_bias_ratio"] = float((max_cos > 0.90).float().mean()) | |
| return metrics | |
| def evaluate_spatial_field(self, query_points: torch.Tensor, cameras=None) -> torch.Tensor: | |
| with torch.no_grad(): | |
| V = query_points.shape[0] | |
| densities = torch.zeros(V, device="cuda") | |
| xyz, opacities = self.gaussians._xyz, torch.sigmoid(self.gaussians._opacity).squeeze() | |
| scales = self.gaussians.get_scaling | |
| sigma_sq = (scales[:, :2].max(dim=1)[0].pow(2)) if scales.shape[1] >= 2 else scales.squeeze().pow(2) | |
| N_gaussians = xyz.shape[0] | |
| chunk_size = max(1, 30_000_000 // (N_gaussians + 1)) | |
| for i in range(0, V, chunk_size): | |
| end = min(i + chunk_size, V) | |
| dist_sq = torch.cdist(query_points[i:end], xyz, p=2).pow(2) | |
| weights = torch.exp(-0.5 * dist_sq / (sigma_sq.unsqueeze(0) + 1e-7)) | |
| densities[i:end] = torch.sum(weights * opacities.unsqueeze(0), dim=1) | |
| return densities | |