Spaces:
Runtime error
Runtime error
| """ | |
| Copyright (c) Meta Platforms, Inc. and affiliates. | |
| All rights reserved. | |
| This source code is licensed under the license found in the | |
| LICENSE file in the root directory of this source tree. | |
| """ | |
| import logging | |
| from logging import Logger | |
| from typing import Any, Dict, Optional, Tuple, Union | |
| import igl | |
| import numpy as np | |
| import torch as th | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from visualize.ca_body.utils.geom import ( | |
| index_image_impaint, | |
| make_uv_barys, | |
| make_uv_vert_index, | |
| ) | |
| from trimesh import Trimesh | |
| from trimesh.triangles import points_to_barycentric | |
| logger: Logger = logging.getLogger(__name__) | |
| def face_normals_v2(v: th.Tensor, vi: th.Tensor, eps: float = 1e-5) -> th.Tensor: | |
| pts = v[:, vi] | |
| v0 = pts[:, :, 1] - pts[:, :, 0] | |
| v1 = pts[:, :, 2] - pts[:, :, 0] | |
| n = th.cross(v0, v1, dim=-1) | |
| norm = th.norm(n, dim=-1, keepdim=True) | |
| norm[norm < eps] = 1 | |
| n /= norm | |
| return n | |
| def vert_normals_v2(v: th.Tensor, vi: th.Tensor, eps: float = 1.0e-5) -> th.Tensor: | |
| fnorms = face_normals_v2(v, vi) | |
| fnorms = fnorms[:, :, None].expand(-1, -1, 3, -1).reshape(fnorms.shape[0], -1, 3) | |
| vi_flat = vi.view(1, -1).expand(v.shape[0], -1) | |
| vnorms = th.zeros_like(v) | |
| for j in range(3): | |
| vnorms[..., j].scatter_add_(1, vi_flat, fnorms[..., j]) | |
| norm = th.norm(vnorms, dim=-1, keepdim=True) | |
| norm[norm < eps] = 1 | |
| vnorms /= norm | |
| return vnorms | |
| def compute_neighbours( | |
| n_verts: int, vi: th.Tensor, n_max_values: int = 10 | |
| ) -> Tuple[th.Tensor, th.Tensor]: | |
| """Computes first-ring neighbours given vertices and faces.""" | |
| n_vi = vi.shape[0] | |
| adj = {i: set() for i in range(n_verts)} | |
| for i in range(n_vi): | |
| for idx in vi[i]: | |
| adj[idx] |= set(vi[i]) - {idx} | |
| nbs_idxs = np.tile(np.arange(n_verts)[:, np.newaxis], (1, n_max_values)) | |
| nbs_weights = np.zeros((n_verts, n_max_values), dtype=np.float32) | |
| for idx in range(n_verts): | |
| n_values = min(len(adj[idx]), n_max_values) | |
| nbs_idxs[idx, :n_values] = np.array(list(adj[idx]))[:n_values] | |
| nbs_weights[idx, :n_values] = -1.0 / n_values | |
| return nbs_idxs, nbs_weights | |
| def compute_v2uv(n_verts: int, vi: th.Tensor, vti: th.Tensor, n_max: int = 4) -> th.Tensor: | |
| """Computes mapping from vertex indices to texture indices. | |
| Args: | |
| vi: [F, 3], triangles | |
| vti: [F, 3], texture triangles | |
| n_max: int, max number of texture locations | |
| Returns: | |
| [n_verts, n_max], texture indices | |
| """ | |
| v2uv_dict = {} | |
| for i_v, i_uv in zip(vi.reshape(-1), vti.reshape(-1)): | |
| v2uv_dict.setdefault(i_v, set()).add(i_uv) | |
| assert len(v2uv_dict) == n_verts | |
| v2uv = np.zeros((n_verts, n_max), dtype=np.int32) | |
| for i in range(n_verts): | |
| vals = sorted(v2uv_dict[i]) | |
| v2uv[i, :] = vals[0] | |
| v2uv[i, : len(vals)] = np.array(vals) | |
| return v2uv | |
| def values_to_uv(values: th.Tensor, index_img: th.Tensor, bary_img: th.Tensor) -> th.Tensor: | |
| uv_size = index_img.shape[0] | |
| index_mask = th.all(index_img != -1, dim=-1) | |
| idxs_flat = index_img[index_mask].to(th.int64) | |
| bary_flat = bary_img[index_mask].to(th.float32) | |
| # NOTE: here we assume | |
| values_flat = th.sum(values[:, idxs_flat].permute(0, 3, 1, 2) * bary_flat, dim=-1) | |
| values_uv = th.zeros( | |
| values.shape[0], | |
| values.shape[-1], | |
| uv_size, | |
| uv_size, | |
| dtype=values.dtype, | |
| device=values.device, | |
| ) | |
| values_uv[:, :, index_mask] = values_flat | |
| return values_uv | |
| def sample_uv( | |
| values_uv: th.Tensor, | |
| uv_coords: th.Tensor, | |
| v2uv: Optional[th.Tensor] = None, | |
| mode: str = "bilinear", | |
| align_corners: bool = False, | |
| flip_uvs: bool = False, | |
| ) -> th.Tensor: | |
| batch_size = values_uv.shape[0] | |
| if flip_uvs: | |
| uv_coords = uv_coords.clone() | |
| uv_coords[:, 1] = 1.0 - uv_coords[:, 1] | |
| uv_coords_norm = (uv_coords * 2.0 - 1.0)[np.newaxis, :, np.newaxis].expand( | |
| batch_size, -1, -1, -1 | |
| ) | |
| values = ( | |
| F.grid_sample(values_uv, uv_coords_norm, align_corners=align_corners, mode=mode) | |
| .squeeze(-1) | |
| .permute((0, 2, 1)) | |
| ) | |
| if v2uv is not None: | |
| values_duplicate = values[:, v2uv] | |
| values = values_duplicate.mean(2) | |
| # if return_var: | |
| # values_var = values_duplicate.var(2) | |
| # return values, values_var | |
| return values | |
| def compute_tbn_uv( | |
| tri_xyz: th.Tensor, tri_uv: th.Tensor, eps: float = 1e-5 | |
| ) -> Tuple[th.Tensor, th.Tensor, th.Tensor]: | |
| """Compute tangents, bitangents, normals. | |
| Args: | |
| tri_xyz: [B,N,3,3] vertex coordinates | |
| tri_uv: [N,2] texture coordinates | |
| Returns: | |
| tangents, bitangents, normals | |
| """ | |
| tri_uv = tri_uv[np.newaxis] | |
| v01 = tri_xyz[:, :, 1] - tri_xyz[:, :, 0] | |
| v02 = tri_xyz[:, :, 2] - tri_xyz[:, :, 0] | |
| normals = th.cross(v01, v02, dim=-1) | |
| normals = normals / th.norm(normals, dim=-1, keepdim=True).clamp(min=eps) | |
| vt01 = tri_uv[:, :, 1] - tri_uv[:, :, 0] | |
| vt02 = tri_uv[:, :, 2] - tri_uv[:, :, 0] | |
| f = th.tensor([1.0], device=tri_xyz.device) / ( | |
| vt01[..., 0] * vt02[..., 1] - vt01[..., 1] * vt02[..., 0] | |
| ) | |
| tangents = f[..., np.newaxis] * ( | |
| v01 * vt02[..., 1][..., np.newaxis] - v02 * vt01[..., 1][..., np.newaxis] | |
| ) | |
| tangents = tangents / th.norm(tangents, dim=-1, keepdim=True).clamp(min=eps) | |
| bitangents = th.cross(normals, tangents, dim=-1) | |
| bitangents = bitangents / th.norm(bitangents, dim=-1, keepdim=True).clamp(min=eps).clamp( | |
| min=eps | |
| ) | |
| return tangents, bitangents, normals | |
| class GeometryModule(nn.Module): | |
| """This module encapsulates uv correspondences and vertex images.""" | |
| def __init__( | |
| self, | |
| vi: th.Tensor, | |
| vt: th.Tensor, | |
| vti: th.Tensor, | |
| v2uv: th.Tensor, | |
| uv_size: int, | |
| flip_uv: bool = False, | |
| impaint: bool = False, | |
| impaint_threshold: float = 100.0, | |
| device=None, | |
| ) -> None: | |
| super().__init__() | |
| self.register_buffer("vi", th.as_tensor(vi)) | |
| self.register_buffer("vt", th.as_tensor(vt)) | |
| self.register_buffer("vti", th.as_tensor(vti)) | |
| self.register_buffer("v2uv", th.as_tensor(v2uv)) | |
| self.uv_size: int = uv_size | |
| index_image = make_uv_vert_index( | |
| self.vt, | |
| self.vi, | |
| self.vti, | |
| uv_shape=uv_size, | |
| flip_uv=flip_uv, | |
| ).cpu() | |
| face_index, bary_image = make_uv_barys(self.vt, self.vti, uv_shape=uv_size, flip_uv=flip_uv) | |
| if impaint: | |
| # TODO: have an option to pre-compute this? | |
| assert isinstance(uv_size, int) | |
| if uv_size >= 1024: | |
| logger.info("impainting index image might take a while for sizes >= 1024") | |
| index_image, bary_image = index_image_impaint( | |
| index_image, bary_image, impaint_threshold | |
| ) | |
| self.register_buffer("index_image", index_image.cpu()) | |
| self.register_buffer("bary_image", bary_image.cpu()) | |
| self.register_buffer("face_index_image", face_index.cpu()) | |
| def render_index_images( | |
| self, uv_size: Union[Tuple[int, int], int], flip_uv: bool = False, impaint: bool = False | |
| ) -> Tuple[th.Tensor, th.Tensor]: | |
| index_image = make_uv_vert_index( | |
| self.vt, self.vi, self.vti, uv_shape=uv_size, flip_uv=flip_uv | |
| ) | |
| _, bary_image = make_uv_barys(self.vt, self.vti, uv_shape=uv_size, flip_uv=flip_uv) | |
| if impaint: | |
| index_image, bary_image = index_image_impaint( | |
| index_image, | |
| bary_image, | |
| ) | |
| return index_image, bary_image | |
| def vn(self, verts: th.Tensor) -> th.Tensor: | |
| return vert_normals_v2(verts, self.vi[np.newaxis].to(th.long)) | |
| def to_uv(self, values: th.Tensor) -> th.Tensor: | |
| return values_to_uv(values, self.index_image, self.bary_image) | |
| def from_uv(self, values_uv: th.Tensor) -> th.Tensor: | |
| # TODO: we need to sample this | |
| return sample_uv(values_uv, self.vt, self.v2uv.to(th.long)) | |
| def compute_view_cos(verts: th.Tensor, faces: th.Tensor, camera_pos: th.Tensor) -> th.Tensor: | |
| vn = F.normalize(vert_normals_v2(verts, faces), dim=-1) | |
| v2c = F.normalize(verts - camera_pos[:, np.newaxis], dim=-1) | |
| return th.einsum("bnd,bnd->bn", vn, v2c) | |
| def interpolate_values_mesh( | |
| src_values: th.Tensor, src_faces: th.Tensor, idxs: th.Tensor, weights: th.Tensor | |
| ) -> th.Tensor: | |
| """Interpolate values on the mesh.""" | |
| assert src_faces.dtype == th.long, "index should be torch.long" | |
| assert len(src_values.shape) in [2, 3], "supporting [N, F] and [B, N, F] only" | |
| if src_values.shape == 2: | |
| return (src_values[src_faces[idxs]] * weights[..., np.newaxis]).sum(dim=1) | |
| else: # src.verts.shape == 3: | |
| return (src_values[:, src_faces[idxs]] * weights[np.newaxis, ..., np.newaxis]).sum(dim=2) | |
| def depth_discontuity_mask( | |
| depth: th.Tensor, threshold: float = 40.0, kscale: float = 4.0, pool_ksize: int = 3 | |
| ) -> th.Tensor: | |
| device = depth.device | |
| with th.no_grad(): | |
| # TODO: pass the kernel? | |
| kernel = th.as_tensor( | |
| [ | |
| [[[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]], | |
| [[[-1, -2, -1], [0, 0, 0], [1, 2, 1]]], | |
| ], | |
| dtype=th.float32, | |
| device=device, | |
| ) | |
| disc_mask = (th.norm(F.conv2d(depth, kernel, bias=None, padding=1), dim=1) > threshold)[ | |
| :, np.newaxis | |
| ] | |
| disc_mask = ( | |
| F.avg_pool2d(disc_mask.float(), pool_ksize, stride=1, padding=pool_ksize // 2) > 0.0 | |
| ) | |
| return disc_mask | |
| def convert_camera_parameters(Rt: th.Tensor, K: th.Tensor) -> Dict[str, th.Tensor]: | |
| R = Rt[:, :3, :3] | |
| t = -R.permute(0, 2, 1).bmm(Rt[:, :3, 3].unsqueeze(2)).squeeze(2) | |
| return { | |
| "campos": t, | |
| "camrot": R, | |
| "focal": K[:, :2, :2], | |
| "princpt": K[:, :2, 2], | |
| } | |
| def closest_point(mesh: Trimesh, points: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: | |
| v = mesh.vertices | |
| vi = mesh.faces | |
| # pyre-ignore | |
| dist, face_idxs, p = igl.point_mesh_squared_distance(points, v, vi) | |
| return p, dist, face_idxs | |
| def closest_point_barycentrics( | |
| v: np.ndarray, vi: np.ndarray, points: np.ndarray | |
| ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: | |
| """Given a 3D mesh and a set of query points, return closest point barycentrics | |
| Args: | |
| v: np.array (float) | |
| [N, 3] mesh vertices | |
| vi: np.array (int) | |
| [N, 3] mesh triangle indices | |
| points: np.array (float) | |
| [M, 3] query points | |
| Returns: | |
| Tuple[approx, barys, interp_idxs, face_idxs] | |
| approx: [M, 3] approximated (closest) points on the mesh | |
| barys: [M, 3] barycentric weights that produce "approx" | |
| interp_idxs: [M, 3] vertex indices for barycentric interpolation | |
| face_idxs: [M] face indices for barycentric interpolation. interp_idxs = vi[face_idxs] | |
| """ | |
| mesh = Trimesh(vertices=v, faces=vi) | |
| p, _, face_idxs = closest_point(mesh, points) | |
| barys = points_to_barycentric(mesh.triangles[face_idxs], p) | |
| b0, b1, b2 = np.split(barys, 3, axis=1) | |
| interp_idxs = vi[face_idxs] | |
| v0 = v[interp_idxs[:, 0]] | |
| v1 = v[interp_idxs[:, 1]] | |
| v2 = v[interp_idxs[:, 2]] | |
| approx = b0 * v0 + b1 * v1 + b2 * v2 | |
| return approx, barys, interp_idxs, face_idxs | |
| def make_closest_uv_barys( | |
| vt: np.ndarray, | |
| vti: np.ndarray, | |
| uv_shape: Union[Tuple[int, int], int], | |
| flip_uv: bool = True, | |
| return_approx_dist: bool = False, | |
| ) -> Union[Tuple[th.Tensor, th.Tensor], Tuple[th.Tensor, th.Tensor, th.Tensor]]: | |
| """Compute a UV-space barycentric map where each texel contains barycentric | |
| coordinates for the closest point on a UV triangle. | |
| Args: | |
| vt: th.Tensor | |
| Texture coordinates. Shape = [n_texcoords, 2] | |
| vti: th.Tensor | |
| Face texture coordinate indices. Shape = [n_faces, 3] | |
| uv_shape: Tuple[int, int] or int | |
| Shape of the texture map. (HxW) | |
| flip_uv: bool | |
| Whether or not to flip UV coordinates along the V axis (OpenGL -> numpy/pytorch convention). | |
| return_approx_dist: bool | |
| Whether or not to include the distance to the nearest point. | |
| Returns: | |
| th.Tensor: index_img: Face index image, shape [uv_shape[0], uv_shape[1]] | |
| th.Tensor: Barycentric coordinate map, shape [uv_shape[0], uv_shape[1], 3] | |
| """ | |
| if isinstance(uv_shape, int): | |
| uv_shape = (uv_shape, uv_shape) | |
| if flip_uv: | |
| # Flip here because texture coordinates in some of our topo files are | |
| # stored in OpenGL convention with Y=0 on the bottom of the texture | |
| # unlike numpy/torch arrays/tensors. | |
| vt = vt.clone() | |
| vt[:, 1] = 1 - vt[:, 1] | |
| # Texel to UV mapping (as per OpenGL linear filtering) | |
| # https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf | |
| # Sect. 8.14, page 261 | |
| # uv=(0.5,0.5)/w is at the center of texel [0,0] | |
| # uv=(w-0.5, w-0.5)/w is the center of texel [w-1,w-1] | |
| # texel = floor(u*w - 0.5) | |
| # u = (texel+0.5)/w | |
| uv_grid = th.meshgrid( | |
| th.linspace(0.5, uv_shape[0] - 1 + 0.5, uv_shape[0]) / uv_shape[0], | |
| th.linspace(0.5, uv_shape[1] - 1 + 0.5, uv_shape[1]) / uv_shape[1], | |
| ) # HxW, v,u | |
| uv_grid = th.stack(uv_grid[::-1], dim=2) # HxW, u, v | |
| uv = uv_grid.reshape(-1, 2).data.to("cpu").numpy() | |
| vth = np.hstack((vt, vt[:, 0:1] * 0 + 1)) | |
| uvh = np.hstack((uv, uv[:, 0:1] * 0 + 1)) | |
| approx, barys, interp_idxs, face_idxs = closest_point_barycentrics(vth, vti, uvh) | |
| index_img = th.from_numpy(face_idxs.reshape(uv_shape[0], uv_shape[1])).long() | |
| bary_img = th.from_numpy(barys.reshape(uv_shape[0], uv_shape[1], 3)).float() | |
| if return_approx_dist: | |
| dist = np.linalg.norm(approx - uvh, axis=1) | |
| dist = th.from_numpy(dist.reshape(uv_shape[0], uv_shape[1])).float() | |
| return index_img, bary_img, dist | |
| else: | |
| return index_img, bary_img | |
| def compute_tbn( | |
| geom: th.Tensor, vt: th.Tensor, vi: th.Tensor, vti: th.Tensor | |
| ) -> Tuple[th.Tensor, th.Tensor, th.Tensor]: | |
| """Computes tangent, bitangent, and normal vectors given a mesh. | |
| Args: | |
| geom: [N, n_verts, 3] th.Tensor | |
| Vertex positions. | |
| vt: [n_uv_coords, 2] th.Tensor | |
| UV coordinates. | |
| vi: [..., 3] th.Tensor | |
| Face vertex indices. | |
| vti: [..., 3] th.Tensor | |
| Face UV indices. | |
| Returns: | |
| [..., 3] th.Tensors for T, B, N. | |
| """ | |
| v0 = geom[:, vi[..., 0]] | |
| v1 = geom[:, vi[..., 1]] | |
| v2 = geom[:, vi[..., 2]] | |
| vt0 = vt[vti[..., 0]] | |
| vt1 = vt[vti[..., 1]] | |
| vt2 = vt[vti[..., 2]] | |
| v01 = v1 - v0 | |
| v02 = v2 - v0 | |
| vt01 = vt1 - vt0 | |
| vt02 = vt2 - vt0 | |
| f = th.tensor([1.0], device=geom.device) / ( | |
| vt01[None, ..., 0] * vt02[None, ..., 1] - vt01[None, ..., 1] * vt02[None, ..., 0] | |
| ) | |
| tangent = f[..., None] * th.stack( | |
| [ | |
| v01[..., 0] * vt02[None, ..., 1] - v02[..., 0] * vt01[None, ..., 1], | |
| v01[..., 1] * vt02[None, ..., 1] - v02[..., 1] * vt01[None, ..., 1], | |
| v01[..., 2] * vt02[None, ..., 1] - v02[..., 2] * vt01[None, ..., 1], | |
| ], | |
| dim=-1, | |
| ) | |
| tangent = F.normalize(tangent, dim=-1) | |
| normal = F.normalize(th.cross(v01, v02, dim=3), dim=-1) | |
| bitangent = F.normalize(th.cross(tangent, normal, dim=3), dim=-1) | |
| return tangent, bitangent, normal | |
| def make_postex(v: th.Tensor, idxim: th.Tensor, barim: th.Tensor) -> th.Tensor: | |
| return ( | |
| barim[None, :, :, 0, None] * v[:, idxim[:, :, 0]] | |
| + barim[None, :, :, 1, None] * v[:, idxim[:, :, 1]] | |
| + barim[None, :, :, 2, None] * v[:, idxim[:, :, 2]] | |
| ).permute( | |
| 0, 3, 1, 2 | |
| ) # B x 3 x H x W | |
| def acos_safe_th(x: th.Tensor, eps: float = 1e-4) -> th.Tensor: | |
| slope = th.arccos(th.as_tensor(1 - eps)) / th.as_tensor(eps) | |
| # TODO: stop doing this allocation once sparse gradients with NaNs (like in | |
| # th.where) are handled differently. | |
| buf = th.empty_like(x) | |
| good = abs(x) <= 1 - eps | |
| bad = ~good | |
| sign = th.sign(x.data[bad]) | |
| buf[good] = th.acos(x[good]) | |
| buf[bad] = th.acos(sign * (1 - eps)) - slope * sign * (abs(x[bad]) - 1 + eps) | |
| return buf | |
| def invRodrigues(R: th.Tensor, eps: float = 1e-8) -> th.Tensor: | |
| """Computes the Rodrigues vectors r from the rotation matrices `R`""" | |
| # t = trace(R) | |
| # theta = rotational angle | |
| # [omega]_x = (R-R^T)/2 | |
| # r = theta/sin(theta)*omega | |
| assert R.shape[-2:] == (3, 3) | |
| t = R[..., 0, 0] + R[..., 1, 1] + R[..., 2, 2] | |
| theta = acos_safe_th((t - 1) / 2) | |
| omega = ( | |
| th.stack( | |
| ( | |
| R[..., 2, 1] - R[..., 1, 2], | |
| R[..., 0, 2] - R[..., 2, 0], | |
| R[..., 1, 0] - R[..., 0, 1], | |
| ), | |
| -1, | |
| ) | |
| / 2 | |
| ) | |
| # Edge Case 1: t >= 3 - eps | |
| inv_sinc = theta / th.sin(theta) | |
| inv_sinc_taylor_expansion = ( | |
| 1 | |
| + (1.0 / 6.0) * th.pow(theta, 2) | |
| + (7.0 / 360.0) * th.pow(theta, 4) | |
| + (31.0 / 15120.0) * th.pow(theta, 6) | |
| ) | |
| # Edge Case 2: t <= -1 + eps | |
| # From: https://math.stackexchange.com/questions/83874/efficient-and-accurate-numerical | |
| # -implementation-of-the-inverse-rodrigues-rotatio | |
| a = th.diagonal(R, 0, -2, -1).argmax(dim=-1) | |
| b = (a + 1) % 3 | |
| c = (a + 2) % 3 | |
| s = th.sqrt(R[..., a, a] - R[..., b, b] - R[..., c, c] + 1 + 1e-4) | |
| v = th.zeros_like(omega) | |
| v[..., a] = s / 2 | |
| v[..., b] = (R[..., b, a] + R[..., a, b]) / (2 * s) | |
| v[..., c] = (R[..., c, a] + R[..., a, c]) / (2 * s) | |
| norm = th.norm(v, dim=-1, keepdim=True).to(v.dtype).clamp(min=eps) | |
| pi_vnorm = np.pi * (v / norm) | |
| # use taylor expansion when R is close to a identity matrix (trace(R) ~= 3) | |
| r = th.where( | |
| t[:, None] > (3 - 1e-3), | |
| inv_sinc_taylor_expansion[..., None] * omega, | |
| th.where(t[:, None] < -1 + 1e-3, pi_vnorm, inv_sinc[..., None] * omega), | |
| ) | |
| return r | |
| def EulerXYZ_to_matrix(xyz: th.Tensor) -> th.Tensor: | |
| # R = Rz(φ)Ry(θ)Rx(ψ) = [ | |
| # cos θ cos φ sin ψ sin θ cos φ − cos ψ sin φ cos ψ sin θ cos φ + sin ψ sin φ | |
| # cos θ sin φ sin ψ sin θ sin φ + cos ψ cos φ cos ψ sin θ sin φ − sin ψ cos φ | |
| # − sin θ sin ψ cos θ cos ψ cos θ | |
| # ] | |
| ( | |
| x, | |
| y, | |
| z, | |
| ) = ( | |
| xyz[..., 0:1], | |
| xyz[..., 1:2], | |
| xyz[..., 2:3], | |
| ) | |
| sinx, cosx = th.sin(x), th.cos(x) | |
| siny, cosy = th.sin(y), th.cos(y) | |
| sinz, cosz = th.sin(z), th.cos(z) | |
| r1 = th.cat( | |
| ( | |
| cosy * cosz, | |
| sinx * siny * cosz | |
| - cosx * sinz, # th.sin(x) * th.sin(y) * th.cos(z) - th.cos(x) * th.sin(z), | |
| cosx * siny * cosz | |
| + sinx * sinz, # th.cos(x) * th.sin(y) * th.cos(z) + th.sin(x) * th.sin(z) | |
| ), | |
| -1, | |
| ) # [..., 3] | |
| r3 = th.cat( | |
| ( | |
| -siny, # -th.sin(y), | |
| sinx * cosy, # th.sin(x) * th.cos(y), | |
| cosx * cosy, # th.cos(x) * th.cos(y) | |
| ), | |
| -1, | |
| ) # [..., 3] | |
| r2 = th.cross(r3, r1, dim=-1) | |
| R = th.cat((r1.unsqueeze(-2), r2.unsqueeze(-2), r3.unsqueeze(-2)), -2) | |
| return R | |
| def axisangle_to_matrix(rvec: th.Tensor) -> th.Tensor: | |
| theta = th.sqrt(1e-5 + th.sum(th.pow(rvec, 2), dim=-1)) | |
| rvec = rvec / theta[..., None] | |
| costh = th.cos(theta) | |
| sinth = th.sin(theta) | |
| return th.stack( | |
| ( | |
| th.stack( | |
| ( | |
| th.pow(rvec[..., 0], 2) + (1.0 - th.pow(rvec[..., 0], 2)) * costh, | |
| rvec[..., 0] * rvec[..., 1] * (1.0 - costh) - rvec[..., 2] * sinth, | |
| rvec[..., 0] * rvec[..., 2] * (1.0 - costh) + rvec[..., 1] * sinth, | |
| ), | |
| dim=-1, | |
| ), | |
| th.stack( | |
| ( | |
| rvec[..., 0] * rvec[..., 1] * (1.0 - costh) + rvec[..., 2] * sinth, | |
| th.pow(rvec[..., 1], 2) + (1.0 - th.pow(rvec[..., 1], 2)) * costh, | |
| rvec[..., 1] * rvec[..., 2] * (1.0 - costh) - rvec[..., 0] * sinth, | |
| ), | |
| dim=-1, | |
| ), | |
| th.stack( | |
| ( | |
| rvec[..., 0] * rvec[..., 2] * (1.0 - costh) - rvec[..., 1] * sinth, | |
| rvec[..., 1] * rvec[..., 2] * (1.0 - costh) + rvec[..., 0] * sinth, | |
| th.pow(rvec[..., 2], 2) + (1.0 - th.pow(rvec[..., 2], 2)) * costh, | |
| ), | |
| dim=-1, | |
| ), | |
| ), | |
| dim=-2, | |
| ) | |
| def compute_view_cond_tbnrefl( | |
| geom: th.Tensor, campos: th.Tensor, geo_fn: GeometryModule | |
| ) -> th.Tensor: | |
| B = int(geom.shape[0]) | |
| S = geo_fn.uv_size | |
| device = geom.device | |
| # TODO: this can be pre-computed, or we can assume no invalid pixels? | |
| mask = (geo_fn.index_image != -1).any(dim=-1) | |
| idxs = geo_fn.index_image[mask] | |
| tri_uv = geo_fn.vt[geo_fn.v2uv[idxs, 0].to(th.long)] | |
| tri_xyz = geom[:, idxs] | |
| t, b, n = compute_tbn_uv(tri_xyz, tri_uv) | |
| tbn_rot = th.stack((t, -b, n), dim=-2) | |
| tbn_rot_uv = th.zeros( | |
| (B, S, S, 3, 3), | |
| dtype=th.float32, | |
| device=device, | |
| ) | |
| tbn_rot_uv[:, mask] = tbn_rot | |
| view = F.normalize(campos[:, np.newaxis] - geom, dim=-1) | |
| v_uv = geo_fn.to_uv(values=view) | |
| tbn_uv = th.einsum("bhwij,bjhw->bihw", tbn_rot_uv, v_uv) | |
| # reflectance vector | |
| n_uv = th.zeros((B, 3, S, S), dtype=th.float32, device=device) | |
| n_uv[..., mask] = n.permute(0, 2, 1) | |
| n_dot_v = (v_uv * n_uv).sum(dim=1, keepdim=True) | |
| r_uv = 2.0 * n_uv * n_dot_v - v_uv | |
| return th.cat([tbn_uv, r_uv], dim=1) | |
| def get_barys_for_uvs( | |
| topology: Dict[str, Any], uv_correspondences: np.ndarray | |
| ) -> Tuple[np.ndarray, np.ndarray]: | |
| """ | |
| Given a topology along with uv correspondences for the topology (eg. keypoints correspondences in uv space), | |
| this function will produce a tuple with the bary coordinates for each uv correspondece along with the vertex index. | |
| Parameters: | |
| ---------- | |
| topology: Input mesh that contains vertices, faces and texture coordinates info. | |
| uv_correspondences: N X 2 uv locations that describe the uv correspondence to the topology | |
| Returns: | |
| ------- | |
| bary: (N X 3 float) | |
| For each uv correspondence returns the bary corrdinates for the uv pixel | |
| triangles: (N X 3 int) | |
| For each uv correspondence returns the face (i.e vertices of the faces) for that pixel. | |
| """ | |
| vi: np.ndarray = topology["vi"] | |
| vt: np.ndarray = topology["vt"] | |
| vti: np.ndarray = topology["vti"] | |
| # # No up-down flip here | |
| # Here we pad the texture cordinates and correspondences with a 0 | |
| vth = np.hstack((vt[:, :2], vt[:, :1] * 0)) | |
| kp_uv_h = np.hstack((uv_correspondences, uv_correspondences[:, :1] * 0)) | |
| _, kp_barys, _, face_indices = closest_point_barycentrics(vth, vti, kp_uv_h) | |
| kp_verts = vi[face_indices] | |
| return kp_barys, kp_verts | |