Lingteng Qiu (邱陵腾)
rm assets & wheels
434b0b0
import torch
import io
import numpy as np
from pathlib import Path
import re
import trimesh
import imageio
def to_numpy(*args):
def convert(a):
if isinstance(a, torch.Tensor):
return a.detach().cpu().numpy()
assert a is None or isinstance(a, np.ndarray)
return a
return convert(args[0]) if len(args) == 1 else tuple(convert(a) for a in args)
def save_obj(vertices: torch.Tensor, faces: torch.Tensor, filename: Path):
filename = Path(filename)
bytes_io = io.BytesIO()
np.savetxt(bytes_io, vertices.detach().cpu().numpy(), "v %.4f %.4f %.4f")
np.savetxt(bytes_io, faces.cpu().numpy() + 1, "f %d %d %d") # 1-based indexing
obj_path = filename.with_suffix(".obj")
with open(obj_path, "w") as file:
file.write(bytes_io.getvalue().decode("UTF-8"))
def load_obj(filename: Path, device="cuda") -> tuple[torch.Tensor, torch.Tensor]:
filename = Path(filename)
obj_path = filename.with_suffix(".obj")
with open(obj_path) as file:
obj_text = file.read()
num = r"([0-9\.\-eE]+)"
v = re.findall(f"(v {num} {num} {num})", obj_text)
vertices = np.array(v)[:, 1:].astype(np.float32)
all_faces = []
f = re.findall(f"(f {num} {num} {num})", obj_text)
if f:
all_faces.append(np.array(f)[:, 1:].astype(np.long).reshape(-1, 3, 1)[..., :1])
f = re.findall(f"(f {num}/{num} {num}/{num} {num}/{num})", obj_text)
if f:
all_faces.append(np.array(f)[:, 1:].astype(np.long).reshape(-1, 3, 2)[..., :2])
f = re.findall(
f"(f {num}/{num}/{num} {num}/{num}/{num} {num}/{num}/{num})", obj_text
)
if f:
all_faces.append(np.array(f)[:, 1:].astype(np.long).reshape(-1, 3, 3)[..., :2])
f = re.findall(f"(f {num}//{num} {num}//{num} {num}//{num})", obj_text)
if f:
all_faces.append(np.array(f)[:, 1:].astype(np.long).reshape(-1, 3, 2)[..., :1])
all_faces = np.concatenate(all_faces, axis=0)
all_faces -= 1 # 1-based indexing
faces = all_faces[:, :, 0]
vertices = torch.tensor(vertices, dtype=torch.float32, device=device)
faces = torch.tensor(faces, dtype=torch.long, device=device)
return vertices, faces
def save_ply(
filename: Path,
vertices: torch.Tensor, # V,3
faces: torch.Tensor, # F,3
vertex_colors: torch.Tensor = None, # V,3
vertex_normals: torch.Tensor = None, # V,3
):
filename = Path(filename).with_suffix(".ply")
vertices, faces, vertex_colors = to_numpy(vertices, faces, vertex_colors)
assert (
np.all(np.isfinite(vertices))
and faces.min() == 0
and faces.max() == vertices.shape[0] - 1
)
header = "ply\nformat ascii 1.0\n"
header += "element vertex " + str(vertices.shape[0]) + "\n"
header += "property double x\n"
header += "property double y\n"
header += "property double z\n"
if vertex_normals is not None:
header += "property double nx\n"
header += "property double ny\n"
header += "property double nz\n"
if vertex_colors is not None:
assert vertex_colors.shape[0] == vertices.shape[0]
color = (vertex_colors * 255).astype(np.uint8)
header += "property uchar red\n"
header += "property uchar green\n"
header += "property uchar blue\n"
header += "element face " + str(faces.shape[0]) + "\n"
header += "property list int int vertex_indices\n"
header += "end_header\n"
with open(filename, "w") as file:
file.write(header)
for i in range(vertices.shape[0]):
s = f"{vertices[i,0]} {vertices[i,1]} {vertices[i,2]}"
if vertex_normals is not None:
s += f" {vertex_normals[i,0]} {vertex_normals[i,1]} {vertex_normals[i,2]}"
if vertex_colors is not None:
s += f" {color[i,0]:03d} {color[i,1]:03d} {color[i,2]:03d}"
file.write(s + "\n")
for i in range(faces.shape[0]):
file.write(f"3 {faces[i,0]} {faces[i,1]} {faces[i,2]}\n")
full_verts = vertices[faces] # F,3,3
def save_images(
images: torch.Tensor, # B,H,W,CH
dir: Path,
):
dir = Path(dir)
dir.mkdir(parents=True, exist_ok=True)
for i in range(images.shape[0]):
imageio.imwrite(
dir / f"{i:02d}.png",
(images.detach()[i, :, :, :3] * 255)
.clamp(max=255)
.type(torch.uint8)
.cpu()
.numpy(),
)
def normalize_vertices(
vertices: torch.Tensor, # V,3
):
"""shift and resize mesh to fit into a unit sphere"""
vertices -= (vertices.min(dim=0)[0] + vertices.max(dim=0)[0]) / 2
vertices /= torch.norm(vertices, dim=-1).max()
return vertices
def laplacian(num_verts: int, edges: torch.Tensor) -> torch.Tensor: # E,2 # sparse V,V
"""create sparse Laplacian matrix"""
V = num_verts
E = edges.shape[0]
# adjacency matrix,
idx = torch.cat([edges, edges.fliplr()], dim=0).type(torch.long).T # (2, 2*E)
ones = torch.ones(2 * E, dtype=torch.float32, device=edges.device)
A = torch.sparse.FloatTensor(idx, ones, (V, V))
# degree matrix
deg = torch.sparse.sum(A, dim=1).to_dense()
idx = torch.arange(V, device=edges.device)
idx = torch.stack([idx, idx], dim=0)
D = torch.sparse.FloatTensor(idx, deg, (V, V))
return D - A
def _translation(x, y, z, device):
return torch.tensor(
[[1.0, 0, 0, x], [0, 1, 0, y], [0, 0, 1, z], [0, 0, 0, 1]], device=device
) # 4,4
def _projection(r, device, l=None, t=None, b=None, n=1.0, f=50.0, flip_y=True):
if l is None:
l = -r
if t is None:
t = r
if b is None:
b = -t
p = torch.zeros([4, 4], device=device)
p[0, 0] = 2 * n / (r - l)
p[0, 2] = (r + l) / (r - l)
p[1, 1] = 2 * n / (t - b) * (-1 if flip_y else 1)
p[1, 2] = (t + b) / (t - b)
p[2, 2] = -(f + n) / (f - n)
p[2, 3] = -(2 * f * n) / (f - n)
p[3, 2] = -1
return p # 4,4
def make_star_cameras(
az_count,
pol_count,
distance: float = 10.0,
r=None,
image_size=[512, 512],
device="cuda",
):
if r is None:
r = 1 / distance
A = az_count
P = pol_count
C = A * P
phi = torch.arange(0, A) * (2 * torch.pi / A)
phi_rot = torch.eye(3, device=device)[None, None].expand(A, 1, 3, 3).clone()
phi_rot[:, 0, 2, 2] = phi.cos()
phi_rot[:, 0, 2, 0] = -phi.sin()
phi_rot[:, 0, 0, 2] = phi.sin()
phi_rot[:, 0, 0, 0] = phi.cos()
theta = torch.arange(1, P + 1) * (torch.pi / (P + 1)) - torch.pi / 2
theta_rot = torch.eye(3, device=device)[None, None].expand(1, P, 3, 3).clone()
theta_rot[0, :, 1, 1] = theta.cos()
theta_rot[0, :, 1, 2] = -theta.sin()
theta_rot[0, :, 2, 1] = theta.sin()
theta_rot[0, :, 2, 2] = theta.cos()
mv = torch.empty((C, 4, 4), device=device)
mv[:] = torch.eye(4, device=device)
mv[:, :3, :3] = (theta_rot @ phi_rot).reshape(C, 3, 3)
mv = _translation(0, 0, -distance, device) @ mv
return mv, _projection(r, device)
def make_sphere(
level: int = 2, radius=1.0, device="cuda"
) -> tuple[torch.Tensor, torch.Tensor]:
sphere = trimesh.creation.icosphere(subdivisions=level, radius=1.0, color=None)
vertices = (
torch.tensor(sphere.vertices, device=device, dtype=torch.float32) * radius
)
faces = torch.tensor(sphere.faces, device=device, dtype=torch.long)
return vertices, faces