Spaces:
Running on Zero
Running on Zero
Download diffusionGS/systems/utils.py from CaiYuanhao/DiffusionGS-Object: direct link, hf CLI and curl.
- Browser
- Download file 32.3 kB
-
https://huggingface.co/spaces/CaiYuanhao/DiffusionGS-Object/resolve/main/diffusionGS/systems/utils.py
- Command line
-
hf download hf://spaces/CaiYuanhao/DiffusionGS-Object/diffusionGS/systems/utils.py
-
curl -L -o utils.py https://huggingface.co/spaces/CaiYuanhao/DiffusionGS-Object/resolve/main/diffusionGS/systems/utils.py
32.3 kB
| import numpy as np | |
| import torch | |
| import torch.nn as nn | |
| import json | |
| from diffusers import DDIMScheduler | |
| from diffusionGS.utils.typing import * | |
| from tqdm import tqdm | |
| import math | |
| import torch.nn.functional as F | |
| from PIL import Image | |
| from torchvision import transforms | |
| from diffusionGS.utils.structure import Mesh | |
| import inspect | |
| from PIL import Image, ImageOps | |
| import cv2 | |
| def _warmup_beta(beta_start, beta_end, num_diffusion_timesteps, warmup_frac): | |
| betas = beta_end * np.ones(num_diffusion_timesteps, dtype=np.float64) | |
| warmup_time = int(num_diffusion_timesteps * warmup_frac) | |
| betas[:warmup_time] = np.linspace(beta_start, beta_end, warmup_time, dtype=np.float64) | |
| return betas | |
| def get_beta_schedule(beta_schedule, *, beta_start, beta_end, num_diffusion_timesteps): | |
| """ | |
| This is the deprecated API for creating beta schedules. | |
| See get_named_beta_schedule() for the new library of schedules. | |
| """ | |
| if beta_schedule == "quad": | |
| betas = ( | |
| np.linspace( | |
| beta_start ** 0.5, | |
| beta_end ** 0.5, | |
| num_diffusion_timesteps, | |
| dtype=np.float64, | |
| ) | |
| ** 2 | |
| ) | |
| elif beta_schedule == "linear": | |
| betas = np.linspace(beta_start, beta_end, num_diffusion_timesteps, dtype=np.float64) | |
| elif beta_schedule == "scaled_linear": | |
| betas = np.linspace(beta_start**0.5, beta_end**0.5, num_diffusion_timesteps, dtype=np.float64)**2 | |
| elif beta_schedule == "warmup10": | |
| betas = _warmup_beta(beta_start, beta_end, num_diffusion_timesteps, 0.1) | |
| elif beta_schedule == "warmup50": | |
| betas = _warmup_beta(beta_start, beta_end, num_diffusion_timesteps, 0.5) | |
| elif beta_schedule == "const": | |
| betas = beta_end * np.ones(num_diffusion_timesteps, dtype=np.float64) | |
| elif beta_schedule == "jsd": # 1/T, 1/(T-1), 1/(T-2), ..., 1 | |
| betas = 1.0 / np.linspace( | |
| num_diffusion_timesteps, 1, num_diffusion_timesteps, dtype=np.float64 | |
| ) | |
| else: | |
| raise NotImplementedError(beta_schedule) | |
| assert betas.shape == (num_diffusion_timesteps,) | |
| return betas | |
| def betas_for_alpha_bar(num_diffusion_timesteps, alpha_bar, max_beta=0.999): | |
| """ | |
| Create a beta schedule that discretizes the given alpha_t_bar function, | |
| which defines the cumulative product of (1-beta) over time from t = [0,1]. | |
| :param num_diffusion_timesteps: the number of betas to produce. | |
| :param alpha_bar: a lambda that takes an argument t from 0 to 1 and | |
| produces the cumulative product of (1-beta) up to that | |
| part of the diffusion process. | |
| :param max_beta: the maximum beta to use; use values lower than 1 to | |
| prevent singularities. | |
| """ | |
| betas = [] | |
| for i in range(num_diffusion_timesteps): | |
| t1 = i / num_diffusion_timesteps | |
| t2 = (i + 1) / num_diffusion_timesteps | |
| betas.append(min(1 - alpha_bar(t2) / alpha_bar(t1), max_beta)) | |
| return np.array(betas) | |
| def get_named_beta_schedule(schedule_name, num_diffusion_timesteps): | |
| """ | |
| Get a pre-defined beta schedule for the given name. | |
| The beta schedule library consists of beta schedules which remain similar | |
| in the limit of num_diffusion_timesteps. | |
| Beta schedules may be added, but should not be removed or changed once | |
| they are committed to maintain backwards compatibility. | |
| """ | |
| if schedule_name == "linear": | |
| # Linear schedule from Ho et al, extended to work for any number of | |
| # diffusion steps. | |
| scale = 1000 / num_diffusion_timesteps | |
| return get_beta_schedule( | |
| "linear", | |
| beta_start=scale * 0.0001, | |
| beta_end=scale * 0.02, | |
| num_diffusion_timesteps=num_diffusion_timesteps, | |
| ) | |
| elif schedule_name == "squaredcos_cap_v2": | |
| return betas_for_alpha_bar( | |
| num_diffusion_timesteps, | |
| lambda t: math.cos((t + 0.008) / 1.008 * math.pi / 2) ** 2, | |
| ) | |
| else: | |
| raise NotImplementedError(f"unknown beta schedule: {schedule_name}") | |
| def normal_kl(mean1, logvar1, mean2, logvar2): | |
| """ | |
| Compute the KL divergence between two gaussians. | |
| Shapes are automatically broadcasted, so batches can be compared to | |
| scalars, among other use cases. | |
| """ | |
| tensor = next( | |
| ( | |
| obj | |
| for obj in (mean1, logvar1, mean2, logvar2) | |
| if isinstance(obj, torch.Tensor) | |
| ), | |
| None, | |
| ) | |
| assert tensor is not None, "at least one argument must be a Tensor" | |
| # Force variances to be Tensors. Broadcasting helps convert scalars to | |
| # Tensors, but it does not work for torch.exp(). | |
| logvar1, logvar2 = [ | |
| x if isinstance(x, torch.Tensor) else torch.tensor(x, device=tensor.device) | |
| for x in (logvar1, logvar2) | |
| ] | |
| return 0.5 * ( | |
| -1.0 | |
| + logvar2 | |
| - logvar1 | |
| + torch.exp(logvar1 - logvar2) | |
| + ((mean1 - mean2) ** 2) * torch.exp(-logvar2) | |
| ) | |
| def mean_flat(tensor): | |
| """ | |
| Take the mean over all non-batch dimensions. | |
| """ | |
| return tensor.mean(dim=list(range(1, len(tensor.shape)))) | |
| def approx_standard_normal_cdf(x): | |
| """ | |
| A fast approximation of the cumulative distribution function of the | |
| standard normal. | |
| """ | |
| return 0.5 * (1.0 + torch.tanh(np.sqrt(2.0 / np.pi) * (x + 0.044715 * torch.pow(x, 3)))) | |
| def discretized_gaussian_log_likelihood(x, *, means, log_scales): | |
| """ | |
| Compute the log-likelihood of a Gaussian distribution discretizing to a | |
| given image. | |
| :param x: the target images. It is assumed that this was uint8 values, | |
| rescaled to the range [-1, 1]. | |
| :param means: the Gaussian mean Tensor. | |
| :param log_scales: the Gaussian log stddev Tensor. | |
| :return: a tensor like x of log probabilities (in nats). | |
| """ | |
| assert x.shape == means.shape == log_scales.shape | |
| centered_x = x - means | |
| inv_stdv = torch.exp(-log_scales) | |
| plus_in = inv_stdv * (centered_x + 1.0 / 255.0) | |
| cdf_plus = approx_standard_normal_cdf(plus_in) | |
| min_in = inv_stdv * (centered_x - 1.0 / 255.0) | |
| cdf_min = approx_standard_normal_cdf(min_in) | |
| log_cdf_plus = torch.log(cdf_plus.clamp(min=1e-12)) | |
| log_one_minus_cdf_min = torch.log((1.0 - cdf_min).clamp(min=1e-12)) | |
| cdf_delta = cdf_plus - cdf_min | |
| log_probs = torch.where( | |
| x < -0.999, | |
| log_cdf_plus, | |
| torch.where(x > 0.999, log_one_minus_cdf_min, torch.log(cdf_delta.clamp(min=1e-12))), | |
| ) | |
| assert log_probs.shape == x.shape | |
| return log_probs | |
| def extract_into_tensor(arr, timesteps, broadcast_shape): | |
| """ | |
| Extract values from a 1-D numpy array for a batch of indices. | |
| :param arr: the 1-D numpy array. | |
| :param timesteps: a tensor of indices into the array to extract. | |
| :param broadcast_shape: a larger shape of K dimensions with the batch | |
| dimension equal to the length of timesteps. | |
| :return: a tensor of shape [batch_size, 1, ...] where the shape has K dims. | |
| """ | |
| res = torch.from_numpy(arr).to(device=timesteps.device)[timesteps].float() | |
| while len(res.shape) < len(broadcast_shape): | |
| res = res[..., None] | |
| return res + torch.zeros(broadcast_shape, device=timesteps.device) | |
| # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps | |
| def retrieve_timesteps( | |
| scheduler, | |
| num_inference_steps: Optional[int] = None, | |
| device: Optional[Union[str, torch.device]] = None, | |
| timesteps: Optional[List[int]] = None, | |
| sigmas: Optional[List[float]] = None, | |
| **kwargs, | |
| ): | |
| r""" | |
| Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles | |
| custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`. | |
| Args: | |
| scheduler (`SchedulerMixin`): | |
| The scheduler to get timesteps from. | |
| num_inference_steps (`int`): | |
| The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps` | |
| must be `None`. | |
| device (`str` or `torch.device`, *optional*): | |
| The device to which the timesteps should be moved to. If `None`, the timesteps are not moved. | |
| timesteps (`List[int]`, *optional*): | |
| Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed, | |
| `num_inference_steps` and `sigmas` must be `None`. | |
| sigmas (`List[float]`, *optional*): | |
| Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed, | |
| `num_inference_steps` and `timesteps` must be `None`. | |
| Returns: | |
| `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the | |
| second element is the number of inference steps. | |
| """ | |
| if timesteps is not None and sigmas is not None: | |
| raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values") | |
| if timesteps is not None: | |
| accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) | |
| if not accepts_timesteps: | |
| raise ValueError( | |
| f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" | |
| f" timestep schedules. Please check whether you are using the correct scheduler." | |
| ) | |
| scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs) | |
| timesteps = scheduler.timesteps | |
| num_inference_steps = len(timesteps) | |
| elif sigmas is not None: | |
| accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) | |
| if not accept_sigmas: | |
| raise ValueError( | |
| f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" | |
| f" sigmas schedules. Please check whether you are using the correct scheduler." | |
| ) | |
| scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs) | |
| timesteps = scheduler.timesteps | |
| num_inference_steps = len(timesteps) | |
| else: | |
| scheduler.set_timesteps(num_inference_steps, device=device, **kwargs) | |
| timesteps = scheduler.timesteps | |
| return timesteps, num_inference_steps | |
| def euler_sample(ddim_scheduler, | |
| diffusion_model: torch.nn.Module, | |
| shape: Union[List[int], Tuple[int]], | |
| cond: torch.FloatTensor, | |
| steps: int, | |
| eta: float = 0.0, | |
| guidance_scale: float = 3.0, | |
| do_classifier_free_guidance: bool = True, | |
| generator: Optional[torch.Generator] = None, | |
| device: torch.device = "cuda:0", | |
| disable_prog: bool = True): | |
| assert steps > 0, f"{steps} must > 0." | |
| # init latents | |
| bsz = cond.shape[0] | |
| if do_classifier_free_guidance: | |
| bsz = bsz // 2 | |
| # latents = torch.randn( | |
| # (bsz, *shape), | |
| # generator=generator, | |
| # device=cond.device, | |
| # dtype=cond.dtype, | |
| # ) | |
| latents = diffusion_model.get_init_point(bsz) | |
| #latents = latents + init_offsets | |
| # scale the initial noise by the standard deviation required by the scheduler | |
| #latents = latents * ddim_scheduler.init_noise_sigma | |
| # set timesteps | |
| ddim_scheduler.set_timesteps(steps) | |
| timesteps = ddim_scheduler.timesteps.to(device) | |
| # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature | |
| # eta (η) is only used with the DDIMScheduler, and between [0, 1] | |
| extra_step_kwargs = { | |
| # "eta": eta, | |
| "generator": generator | |
| } | |
| #timesteps, num_inference_steps = retrieve_timesteps(ddim_scheduler, steps, device) | |
| #breakpoint() | |
| # reverse | |
| for i, t in enumerate(tqdm(timesteps, disable=disable_prog, desc="Euler Sampling:", leave=False)): | |
| # expand the latents if we are doing classifier free guidance | |
| latent_model_input = ( | |
| torch.cat([latents] * 2) | |
| if do_classifier_free_guidance | |
| else latents | |
| ) | |
| #breakpoint() | |
| # predict the noise residual | |
| timestep_tensor = torch.tensor(t.clone().detach(), dtype=torch.long, device=device) | |
| timestep_tensor = timestep_tensor.expand(latent_model_input.shape[0]) | |
| noise_pred = diffusion_model.forward(latent_model_input, timestep_tensor, cond, train_stage = False) | |
| # perform guidance | |
| if do_classifier_free_guidance: | |
| noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) | |
| noise_pred = noise_pred_uncond + guidance_scale * ( | |
| noise_pred_text - noise_pred_uncond | |
| ) | |
| # compute the previous noisy sample x_t -> x_t-1 | |
| latents = ddim_scheduler.step( | |
| noise_pred, t, latents, **extra_step_kwargs | |
| ).prev_sample | |
| yield latents, t | |
| def compute_snr(alphas_cumprod, timesteps): | |
| """ | |
| Computes SNR as per | |
| https://github.com/TiankaiHang/Min-SNR-Diffusion-Training/blob/521b624bd70c67cee4bdf49225915f5945a872e3/guided_diffusion/gaussian_diffusion.py#L847-L849 | |
| """ | |
| sqrt_alphas_cumprod = alphas_cumprod**0.5 | |
| sqrt_one_minus_alphas_cumprod = (1.0 - alphas_cumprod) ** 0.5 | |
| # Expand the tensors. | |
| # Adapted from https://github.com/TiankaiHang/Min-SNR-Diffusion-Training/blob/521b624bd70c67cee4bdf49225915f5945a872e3/guided_diffusion/gaussian_diffusion.py#L1026 | |
| sqrt_alphas_cumprod = sqrt_alphas_cumprod.to(device=timesteps.device)[timesteps].float() | |
| while len(sqrt_alphas_cumprod.shape) < len(timesteps.shape): | |
| sqrt_alphas_cumprod = sqrt_alphas_cumprod[..., None] | |
| alpha = sqrt_alphas_cumprod.expand(timesteps.shape) | |
| sqrt_one_minus_alphas_cumprod = sqrt_one_minus_alphas_cumprod.to(device=timesteps.device)[timesteps].float() | |
| while len(sqrt_one_minus_alphas_cumprod.shape) < len(timesteps.shape): | |
| sqrt_one_minus_alphas_cumprod = sqrt_one_minus_alphas_cumprod[..., None] | |
| sigma = sqrt_one_minus_alphas_cumprod.expand(timesteps.shape) | |
| # Compute SNR. | |
| snr = (alpha / sigma) ** 2 | |
| return snr | |
| def get_val_data(file_list, n_supervision=4096, geometry_type="occupancies"): | |
| batch_rand_points = [] | |
| batch_occupancies = [] | |
| for file in file_list: | |
| points = np.load(file) | |
| rand_points = np.asarray(points['points']) * 2 # range from -1.1 to 1.1 | |
| rand_points = torch.from_numpy(rand_points) | |
| rand_points = torch.split(rand_points, n_supervision, dim=0) | |
| rand_points = torch.stack(rand_points[0:-1]) | |
| occupancies = np.asarray(points[geometry_type]) | |
| occupancies = np.unpackbits(occupancies) | |
| occupancies = torch.from_numpy(occupancies) | |
| occupancies = torch.split(occupancies, n_supervision, dim=0) | |
| occupancies = torch.stack(occupancies[0:-1]) | |
| batch_rand_points.append(rand_points) | |
| batch_occupancies.append(occupancies) | |
| batch_rand_points = torch.stack(batch_rand_points) | |
| batch_occupancies= torch.stack(batch_occupancies) | |
| B, M, N, _ = batch_rand_points.shape | |
| return batch_rand_points.view(B*M, N, 3), batch_occupancies.view(B*M, N) | |
| def get_mvp_matrix(c2w:Tensor, proj_mtx:Tensor): | |
| ''' | |
| c2w: [batch_size, 4, 4] | |
| proj_mtx: [batch_size, 4, 4] | |
| mvp_mtx: [batch_size, 4, 4] | |
| ''' | |
| # calculate w2c from c2w: R' = Rt, t' = -Rt * t | |
| # mathematically equivalent to (c2w)^-1 | |
| w2c = torch.zeros(c2w.shape[0], 4, 4).to(c2w) | |
| w2c[:, :3, :3] = c2w[:, :3, :3].permute(0, 2, 1) | |
| w2c[:, :3, 3:] = -c2w[:, :3, :3].permute(0, 2, 1) @ c2w[:, :3, 3:] | |
| w2c[:, 3, 3] = 1.0 | |
| # calculate mvp matrix by proj_mtx @ w2c (mv_mtx) | |
| mvp_mtx = proj_mtx @ w2c | |
| return mvp_mtx.to(c2w) | |
| def get_projection_matrix_perspective(intrinsics:Tensor, near: float = 0.01, far: float = 100.0): | |
| ''' | |
| intrinsics: [batch_size, 3, 3], normalized | |
| proj_mtx: [batch_size, 4, 4] | |
| ''' | |
| batch_size = intrinsics.shape[0] | |
| proj_mtx = torch.zeros(batch_size, 4, 4, dtype=torch.float32).to(intrinsics) | |
| proj_mtx[:, 0, 0] = 2 * intrinsics[:, 0, 0] | |
| proj_mtx[:, 1, 1] = - 2 * intrinsics[:, 1, 1] # add a negative sign here as the y axis is flipped in nvdiffrast output | |
| proj_mtx[:, 2, 2] = -(far + near) / (far - near) | |
| proj_mtx[:, 2, 3] = -2.0 * far * near / (far - near) | |
| proj_mtx[:, 3, 2] = -1.0 | |
| return proj_mtx | |
| def render_mesh(mesh, cam2world_matrices, intrinsics, device, height=224, width=224, radius=1): | |
| import nvdiffrast.torch as dr | |
| ## world to ndc | |
| v_homo = torch.cat([mesh.v_pos, torch.ones([mesh.v_pos.shape[0], 1]).to(mesh.v_pos)], dim=-1) | |
| mvp_mtx = get_mvp_matrix(cam2world_matrices, get_projection_matrix_perspective(intrinsics)) | |
| v_pos_clip = torch.matmul(v_homo, mvp_mtx.permute(0, 2, 1)).float() | |
| v_nrm = mesh.v_nrm | |
| ## rasterize | |
| ctx = dr.RasterizeCudaContext(device) | |
| mesh.t_pos_idx = mesh.t_pos_idx.to(torch.int32) | |
| rast, _ = dr.rasterize(ctx, v_pos_clip, mesh.t_pos_idx, (height, width)) | |
| mask = rast[..., 3:] > 0 | |
| ## render alpha | |
| mask_aa = dr.antialias(mask.float(), rast, v_pos_clip, mesh.t_pos_idx) | |
| ## render depth | |
| gb_depth, _ = dr.interpolate(v_pos_clip[:, :, 3:].contiguous(), rast, mesh.t_pos_idx) | |
| gb_depth_aa = torch.lerp(torch.zeros_like(gb_depth), gb_depth, mask.float()) | |
| # gb_depth_aa = dr.antialias(gb_depth_aa, rast, v_pos_clip, mesh.t_pos_idx) | |
| ## render normal | |
| gb_normal, _ = dr.interpolate(v_nrm, rast, mesh.t_pos_idx) | |
| gb_normal = F.normalize(gb_normal, dim=-1) | |
| gb_normal_aa = torch.lerp(torch.full_like(gb_normal, fill_value=-1.0), gb_normal, mask.float()) | |
| gb_normal_aa = dr.antialias(gb_normal_aa, rast, v_pos_clip, mesh.t_pos_idx) | |
| ## render rgb | |
| selector = mask[..., 0] | |
| gb_pos, _ = dr.interpolate(mesh.v_pos, rast, mesh.t_pos_idx) | |
| positions = gb_pos[selector] | |
| gb_rgb_fg = torch.zeros(*mask_aa.shape[:3], 3).to(device) | |
| gb_rgb_fg[selector] = torch.ones(3).to(device) * 125 | |
| gb_rgb_bg = torch.ones_like(gb_rgb_fg).to(gb_rgb_fg) | |
| gb_rgb = torch.lerp(gb_rgb_bg, gb_rgb_fg, mask.to(gb_rgb_fg)) | |
| gb_rgb_aa = dr.antialias(gb_rgb, rast, v_pos_clip, mesh.t_pos_idx) | |
| # out["rgb"] = gb_rgb_aa | |
| out = { | |
| "alpha": mask_aa, | |
| "depth": gb_depth_aa, | |
| "rgb": gb_rgb_aa, | |
| "normal": gb_normal_aa | |
| } | |
| return out | |
| def get_camera(device, img_size=224, focal=5, distance=10): | |
| cam2world_matrices = torch.as_tensor([ | |
| [[1, 0, 0, 0], | |
| [0, 1, 0, 0], | |
| [0, 0, 1, distance], | |
| [0, 0, 0, 1]], # front to back | |
| [[0, 0, 1, distance], | |
| [0, 1, 0, 0], | |
| [1, 0, 0, 0], | |
| [0, 0, 0, 1]], # front to back | |
| [[-1, 0, 0, 0], | |
| [0, 1, 0, 0], | |
| [0, 0, -1, -distance], | |
| [0, 0, 0, 1]], # front to back | |
| [[0, 0, -1, -distance], | |
| [0, 1, 0, 0], | |
| [-1, 0, 0, 0], | |
| [0, 0, 0, 1]], # front to back | |
| ], dtype=torch.float32).to(device) | |
| intrinsics = torch.zeros([3, 3]).to(device) | |
| intrinsics[0, 0] = focal | |
| intrinsics[1, 1] = focal | |
| intrinsics[1, 2] = img_size // 2 | |
| intrinsics[0, 2] = img_size // 2 | |
| intrinsics = intrinsics.unsqueeze(0).repeat(4, 1 ,1).to(torch.float32) | |
| return cam2world_matrices, intrinsics | |
| def read_image(img, img_size=224): | |
| transform = transforms.Compose( | |
| [ | |
| transforms.Resize(img_size, transforms.InterpolationMode.BICUBIC, antialias=True), | |
| transforms.CenterCrop(img_size), # crop a (224, 224) square | |
| transforms.ToTensor() | |
| ] | |
| ) | |
| rgb = Image.open(img) | |
| rgb = transform(rgb)[:3,...].permute(1, 2, 0) | |
| return rgb | |
| def compute_metric(shape_model, sample_inputs, sample_outputs, device): | |
| threshold = 0 | |
| if len(sample_inputs['occupancy']) == 0: | |
| return 0, 0 | |
| queries, labels = get_val_data(sample_inputs['occupancy']) | |
| queries = queries.to(device) | |
| labels = labels.to(device) | |
| latent = sample_outputs[0][:len(sample_inputs['occupancy']),...] | |
| latent = latent.unsqueeze(1).repeat(1, queries.shape[0] // latent.shape[0], 1, 1).view(queries.shape[0], latent.shape[1], latent.shape[2]) | |
| outputs = shape_model.query(queries, latent) | |
| pred = torch.zeros_like(outputs) | |
| pred[outputs>=threshold] = 1 | |
| torch.cuda.empty_cache() | |
| accuracy = (pred==labels).float().sum(dim=1) / labels.shape[1] | |
| accuracy = accuracy.mean() | |
| intersection = (pred * labels).sum(dim=1) | |
| union = (pred + labels).gt(0).sum(dim=1) | |
| iou = intersection * 1.0 / union + 1e-5 | |
| iou = iou.mean() | |
| return accuracy, iou | |
| def compute_metric_from_sdf(shape_model, sample_inputs, sample_outputs, threshold, device): | |
| if len(sample_inputs['sdf']) == 0: | |
| return 0, 0 | |
| queries, sdf = get_val_data(sample_inputs['sdf']) | |
| queries = queries.to(device) | |
| labels = labels.to(device) | |
| latent = sample_outputs[0][:len(sample_inputs['occupancy']),...] | |
| latent = latent.unsqueeze(1).repeat(1, queries.shape[0] // latent.shape[0], 1, 1).view(queries.shape[0], latent.shape[1], latent.shape[2]) | |
| outputs = shape_model.query(queries, latent) | |
| pred = torch.zeros_like(outputs) | |
| pred[outputs>=threshold] = 1 | |
| torch.cuda.empty_cache() | |
| accuracy = (pred==labels).float().sum(dim=1) / labels.shape[1] | |
| accuracy = accuracy.mean() | |
| intersection = (pred * labels).sum(dim=1) | |
| union = (pred + labels).gt(0).sum(dim=1) | |
| iou = intersection * 1.0 / union + 1e-5 | |
| iou = iou.mean() | |
| return accuracy, iou | |
| def get_render(vertices, faces, device, img_size=224, focal=5, distance=12): | |
| cam2world_matrices, intrinsics = get_camera(device, img_size, focal, distance) | |
| mesh = Mesh( | |
| v_pos=torch.from_numpy(vertices).to(device), | |
| t_pos_idx=torch.from_numpy(faces).to(device), | |
| ) | |
| out = render_mesh(mesh, cam2world_matrices, intrinsics, device, img_size, img_size) | |
| v, h, w, _ = out['normal'].shape | |
| normal = out['normal'].transpose(1, 0).contiguous().view(h, w*v, 3) | |
| depth = out['depth'].transpose(1, 0).contiguous().view(h, w*v, 1) | |
| if depth[depth != 0].shape[0] != 0: | |
| depth[depth != 0] = (depth[depth != 0] - depth[depth != 0].min()) / (depth[depth != 0].max() - depth[depth != 0].min()) | |
| return normal, depth | |
| def ema_update(model_dest: nn.Module, model_src: nn.Module, rate): | |
| param_dict_src = dict(model_src.named_parameters()) | |
| for p_name, p_dest in model_dest.named_parameters(): | |
| p_src = param_dict_src[p_name] | |
| assert p_src is not p_dest | |
| p_dest.data.mul_(rate).add_((1 - rate) * p_src.data) | |
| def get_bbox(mask:np.ndarray): | |
| assert mask.ndim == 2 | |
| row = mask.sum(-1) | |
| col = mask.sum(-2) | |
| row_idx = np.where(row > 0)[0] | |
| col_idx = np.where(col > 0)[0] | |
| x1, y1, x2, y2 = col_idx.min(), row_idx.min(), col_idx.max(), row_idx.max() | |
| return np.array([x1, y1, x2, y2]) | |
| def preprocess(image:Image.Image,session) -> Image.Image: | |
| H_1 = W_1 = 512 | |
| scale = 0.8 | |
| color = 'white' | |
| image = ImageOps.exif_transpose(image) | |
| if image.mode == 'RGBA' and np.sum(np.array(image.getchannel('A')) > 0) < image.size[0] * image.size[1] - 8: | |
| rgba = image.copy() | |
| else: | |
| # https://github.com/pymatting/pymatting/issues/19 | |
| import rembg | |
| rgba = rembg.remove(image, alpha_matting=True, session=session) | |
| alpha = rgba.getchannel('A') | |
| bboxs = get_bbox(np.array(alpha)) | |
| x1, y1, x2, y2 = bboxs | |
| dy, dx = y2 - y1, x2 - x1 | |
| s = min(H_1 * scale / dy, W_1 * scale / dx) | |
| Ht, Wt = int(dy * s), int(dx * s) | |
| ox, oy = int((W_1 - Wt) / 2), int((H_1 - Ht) / 2) | |
| bboxt = np.array([ox, oy, ox+Wt, oy+Ht]) | |
| rgba = rgba.crop(bboxs).resize((Wt, Ht)) | |
| fg = rgba.convert('RGB') | |
| alpha = rgba.getchannel('A') | |
| alphat = Image.new('L', (W_1, H_1)) | |
| alphat.paste(alpha, bboxt) | |
| inp_1 = Image.new('RGBA', (W_1, H_1), color) | |
| inp_1.paste(fg, bboxt, alpha) | |
| inp_1.putalpha(alphat) | |
| return inp_1 | |
| # def preprocess(image:Image.Image,session) -> Image.Image: | |
| # H_1 = W_1 = 512 | |
| # color = 'white' | |
| # image = ImageOps.exif_transpose(image) | |
| # if image.mode == 'RGBA' and np.sum(np.array(image.getchannel('A')) > 0) < image.size[0] * image.size[1] - 8: | |
| # rgba = image.copy() | |
| # else: | |
| # # https://github.com/pymatting/pymatting/issues/19 | |
| # rgba = rembg.remove(image, alpha_matting=True, session=session) | |
| # fg = rgba.convert('RGB') | |
| # alpha = rgba.getchannel('A') | |
| # alphat = Image.new('L', (W_1, H_1)) | |
| # alphat.paste(alpha) | |
| # inp_1 = Image.new('RGBA', (W_1, H_1), color) | |
| # inp_1.paste(fg, mask = alpha) | |
| # inp_1.putalpha(alphat) | |
| # return inp_1 | |
| def TransformInput(image, c2w, fxfycxcy, patch_size=None): | |
| assert image.dim() == 5, f"image dim should be 5, but got {image.dim()}" # b, v, c, h, w | |
| assert ( | |
| fxfycxcy.dim() == 3 | |
| ), f"fxfycxcy dim should be 3, but got {fxfycxcy.dim()}" # b, v, 4 | |
| assert c2w.dim() == 4, f"c2w dim should be 4, but got {c2w.dim()}" # b, v, 4, 4 | |
| b, v, c, h, w = image.size() | |
| image = image.reshape(b * v, c, h * w) | |
| fxfycxcy = fxfycxcy.reshape(b * v, 4) | |
| c2w = c2w.reshape(b * v, 4, 4) # 进来就把 b 和 v 合并 | |
| # y - i - 行 - h, x - j - 列 - w | |
| # y 和 x 都是形状为 [h, w] 的矩阵, y 的每一行都是 0 到 w-1, x 的每一列都是 0 到 h-1 | |
| y, x = torch.meshgrid(torch.arange(h), torch.arange(w), indexing="ij") | |
| # print(f"y device: {y.device}, x device: {x.device}, image device: {image.device}") | |
| # print(f"y dtype: {y.dtype}, x dtype: {x.dtype}, image dtype: {image.dtype}") | |
| # print(f"y shape: {y.shape}, x shape: {x.shape}, image shape: {image.shape}") | |
| # stx() | |
| ''' | |
| 1. not eval: | |
| y device: cpu, x device: cpu, image device: cuda:0 | |
| y dtype: torch.int64, x dtype: torch.int64, image dtype: torch.float32 | |
| y shape: torch.Size([256, 256]), x shape: torch.Size([256, 256]), image shape: torch.Size([64, 3, 65536]) | |
| 2. eval: | |
| y device: cpu, x device: cpu, image device: cuda:0 | |
| y dtype: torch.int64, x dtype: torch.int64, image dtype: torch.float32 | |
| y shape: torch.Size([256, 256]), x shape: torch.Size([256, 256]), image shape: torch.Size([64, 3, 65536]) | |
| ''' | |
| y, x = y.to(image.device), x.to(image.device) | |
| # [h, w], normalize 到 [-1, 1] | |
| y_norm, x_norm = (y + 0.5) / h * 2 - 1, (x + 0.5) / w * 2 - 1 | |
| # [b, v, 2, h, w] | |
| xy_norm = torch.stack([x_norm, y_norm], dim=0)[None, None, :, :, :].expand( | |
| b, v, -1, -1, -1 | |
| ) | |
| x = x[None, :, :].expand(b * v, -1, -1).reshape(b * v, -1) | |
| y = y[None, :, :].expand(b * v, -1, -1).reshape(b * v, -1) | |
| # 到此处, x, y 是图像坐标 (u, v), 它与相机坐标 (x_c, y_c, z_c) 的转换关系是: | |
| # u = f_x * x_c / z_c + cx | |
| # v = f_y * y_c / z_c + cy | |
| # 所以反算 x_c, y_c 是 | |
| # x_c / z_c = (u - cx) / f_x, y_c / z_c = (v - cy) / f_y | |
| # 那么 per-pixel 的射线方向就是 (x_c, y_c, z_c) = (x_c / z_c, y_c / z_c, 1) | |
| x = (x + 0.5 - fxfycxcy[:, 2:3]) / fxfycxcy[:, 0:1] | |
| y = (y + 0.5 - fxfycxcy[:, 3:4]) / fxfycxcy[:, 1:2] | |
| z = torch.ones_like(x) | |
| # ray_d 是相机坐标系下的射线方向 | |
| ray_d = torch.stack([x, y, z], dim=2) # [b*v, h*w, 3] | |
| ray_d_cam = ray_d.clone() | |
| # bmm = batch matrix multiplication, 这里转成世界坐标系 | |
| ray_d = torch.bmm(ray_d.to(c2w), c2w[:, :3, :3].transpose(1, 2)) # [b*v, h*w, 3] | |
| # norm | |
| ray_d = ray_d / torch.norm(ray_d, dim=2, keepdim=True) # [b*v, h*w, 3] | |
| ray_d_cam = ray_d_cam / torch.norm(ray_d_cam, dim=2, keepdim=True) | |
| ray_o = c2w[:, :3, 3][:, None, :].expand_as(ray_d) # [b*v, h*w, 3] | |
| # fetch color, ray_o, ray_d for all patch centers | |
| ray_color_patch, ray_o_patch, ray_d_patch = None, None, None | |
| ray_xy_norm_patch, proj_mat = None, None | |
| if patch_size is not None: | |
| start_patch_center = patch_size / 2.0 | |
| y, x = torch.meshgrid( | |
| torch.arange(h // patch_size) * patch_size + start_patch_center, | |
| torch.arange(w // patch_size) * patch_size + start_patch_center, | |
| indexing="ij", | |
| ) | |
| y, x = y.to(image.device), x.to(image.device) | |
| x = x[None, :, :].expand(b * v, -1, -1).reshape(b * v, -1) | |
| y = y[None, :, :].expand(b * v, -1, -1).reshape(b * v, -1) | |
| ray_xy_norm_patch = torch.stack( | |
| [x / w, y / h], dim=2 | |
| ) # use [0,1] for patch center | |
| K_norm = ( | |
| torch.eye(3, device=image.device).unsqueeze(0).repeat(b * v, 1, 1) | |
| ) # [b*v, 3, 3] | |
| K_norm[:, 0, 0] = fxfycxcy[:, 0] / w | |
| K_norm[:, 1, 1] = fxfycxcy[:, 1] / h | |
| K_norm[:, 0, 2] = fxfycxcy[:, 2] / w | |
| K_norm[:, 1, 2] = fxfycxcy[:, 3] / h | |
| w2c = torch.inverse(c2w) # [b*v, 4, 4] | |
| proj_mat = torch.bmm(K_norm.to(w2c), w2c[:, :3, :4]) # [b*v, 3, 4] | |
| proj_mat = proj_mat.reshape(b * v, 12) | |
| proj_mat = proj_mat / (proj_mat.norm(dim=1, keepdim=True) + 1e-6) | |
| proj_mat = proj_mat.reshape(b * v, 3, 4) | |
| proj_mat = proj_mat * proj_mat[:, 0:1, 0:1].sign() | |
| # fetch colors for all patch centers | |
| ray_color_patch = ( | |
| F.grid_sample( | |
| image.reshape(b * v, c, h, w), | |
| torch.stack([x / w * 2.0 - 1.0, y / h * 2.0 - 1.0], dim=2).reshape( | |
| b * v, -1, 1, 2 | |
| ), | |
| align_corners=False, | |
| ) | |
| .squeeze(-1) | |
| .permute(0, 2, 1) | |
| ).contiguous() # [b*v, h'*w', c] | |
| x = (x - fxfycxcy[:, 2:3]) / fxfycxcy[:, 0:1] | |
| y = (y - fxfycxcy[:, 3:4]) / fxfycxcy[:, 1:2] | |
| z = torch.ones_like(x) | |
| ray_d_patch = torch.stack([x, y, z], dim=2) # [b*v, h'*w', 3] | |
| ray_d_patch = torch.bmm( | |
| ray_d_patch.to(c2w), c2w[:, :3, :3].transpose(1, 2) | |
| ) # [b*v, h'*w', 3] | |
| ray_d_patch = ray_d_patch / torch.norm( | |
| ray_d_patch, dim=2, keepdim=True | |
| ) # [b*v, h'*w', 3] | |
| ray_o_patch = c2w[:, :3, 3][:, None, :].expand_as( | |
| ray_d_patch | |
| ) # [b*v, h'*w', 3] | |
| n_patch = ray_color_patch.size(1) | |
| ray_color_patch = ray_color_patch.reshape(b, v, n_patch, c) | |
| ray_o_patch = ray_o_patch.reshape(b, v, n_patch, 3) | |
| ray_d_patch = ray_d_patch.reshape(b, v, n_patch, 3) | |
| ray_xy_norm_patch = ray_xy_norm_patch.reshape(b, v, n_patch, 2) | |
| proj_mat = proj_mat.reshape(b, v, 3, 4) | |
| ray_o = ray_o.reshape(b, v, h, w, 3).permute(0, 1, 4, 2, 3) | |
| ray_d = ray_d.reshape(b, v, h, w, 3).permute(0, 1, 4, 2, 3) | |
| ray_d_cam = ray_d_cam.reshape(b, v, h, w, 3).permute(0, 1, 4, 2, 3) | |
| image = image.reshape(b, v, c, h, w) | |
| fxfycxcy = fxfycxcy.reshape(b, v, 4) | |
| c2w = c2w.reshape(b, v, 4, 4) | |
| return ray_o, ray_d | |
| # 根据 timestep 把 x_t 做成 video 的可视化代码 | |
| def display_timestep_on_video(frames, timesteps): | |
| """ | |
| frames: [T, H, W, C] | |
| timesteps: [T, ] | |
| """ | |
| assert len(frames) == len( | |
| timesteps | |
| ), f"len(frames) {len(frames)} != len(timesteps) {len(timesteps)}" | |
| dtype = frames.dtype | |
| if dtype == np.float32: | |
| frames = (frames * 255.0).clip(0.0, 255.0).astype(np.uint8) | |
| # Use putText() method for | |
| # inserting text on video | |
| for i in range(len(frames)): | |
| # stx() | |
| frames[i] = cv2.putText( | |
| frames[i], | |
| f"t={timesteps[i]}", | |
| (50, 50), | |
| cv2.FONT_HERSHEY_SIMPLEX, | |
| 1, | |
| (0, 255, 255), | |
| 2, | |
| cv2.LINE_4, | |
| ) | |
| if dtype == np.float32: | |
| frames = frames.astype(np.float32) / 255.0 | |
| return frames | |