Spaces:
Runtime error
Runtime error
import numpy as np | |
import torch | |
import torch.nn.functional as F | |
import collections | |
from collections import defaultdict | |
import cv2 | |
import random | |
import math | |
import quaternion | |
from pytorch3d.structures import Meshes | |
from pytorch3d.renderer.mesh import TexturesVertex, TexturesUV, Textures | |
import os | |
import shutil | |
import imageio | |
from typing import Optional, List | |
def triangulate_pcd(sem_mask, semantic_pred, render_size, border_size): | |
#verts = torch.ones(render_size).nonzero().numpy() | |
verts = sem_mask.nonzero().numpy() | |
vert_id_map = defaultdict(dict) | |
for idx, vert in enumerate(verts): | |
vert_id_map[vert[0]][vert[1]] = idx# + len(verts) | |
height = render_size[0] - border_size * 2 | |
width = render_size[1] - border_size * 2 | |
semantic_pred = semantic_pred.numpy() | |
triangles = [] | |
for vert in verts: | |
# upper right triangle | |
if ( | |
vert[0] < height - 1 | |
and vert[1] < width - 1 | |
and sem_mask[vert[0] + 1][vert[1] + 1] | |
and sem_mask[vert[0]][vert[1] + 1] | |
and semantic_pred[vert[0]][vert[1]] == semantic_pred[vert[0] + 1][vert[1] + 1] | |
and semantic_pred[vert[0]][vert[1]] == semantic_pred[vert[0]][vert[1] + 1] | |
): | |
triangles.append( | |
[ | |
vert_id_map[vert[0]][vert[1]], | |
vert_id_map[vert[0] + 1][vert[1] + 1], | |
vert_id_map[vert[0]][vert[1] + 1], | |
] | |
) | |
# bottom left triangle | |
if ( | |
vert[0] < height - 1 | |
and vert[1] < width - 1 | |
and sem_mask[vert[0] + 1][vert[1] + 1] | |
and sem_mask[vert[0]][vert[1] + 1] | |
and semantic_pred[vert[0]][vert[1]] == semantic_pred[vert[0] + 1][vert[1]] | |
and semantic_pred[vert[0]][vert[1]] == semantic_pred[vert[0] + 1][vert[1] + 1] | |
): | |
triangles.append( | |
[ | |
vert_id_map[vert[0]][vert[1]], | |
vert_id_map[vert[0] + 1][vert[1]], | |
vert_id_map[vert[0] + 1][vert[1] + 1], | |
] | |
) | |
triangles = np.array(triangles) | |
triangles = torch.LongTensor(triangles) | |
return triangles | |
def get_pcd(verts, normal, offset, h=480, w=640, focal_length=517.97): | |
""" | |
Copy from | |
https://github.com/JasonQSY/Articulation3D/blob/master/articulation3d/articulation3d/utils/vis.py | |
convert 2d verts to 3d point cloud based on plane normal and offset | |
depth = offset / n \dot K^{-1}q | |
""" | |
offset_x = w/2 | |
offset_y = h/2 | |
K = [[focal_length, 0, offset_x], | |
[0, focal_length, offset_y], | |
[0, 0, 1]] | |
K_inv = np.linalg.inv(np.array(K)) | |
homogeneous = np.hstack((verts, np.ones(len(verts)).reshape(-1,1))) | |
ray = K_inv@homogeneous.T | |
depth = offset / np.dot(normal, ray) | |
pcd = depth.reshape(-1,1) * ray.T | |
#import pdb; pdb.set_trace() | |
return pcd | |
def fit_homography(src_pts, tgt_pts): | |
""" | |
Fit a homography from src_pts to tgt_pts. | |
src_pts: torch.LongTensor shape (N x 2) | |
tgt_pts: torch.LongTensor shape (N x 2) | |
""" | |
src_pts = src_pts.numpy().astype(np.float32) | |
tgt_pts = tgt_pts.numpy().astype(np.float32) | |
N = 4 | |
# randomly pick up 4 control points | |
#ids = random.sample(range(src_pts.shape[0]), 4) | |
#src_pts = | |
#import pdb; pdb.set_trace() | |
H, mask = cv2.findHomography(src_pts, tgt_pts, cv2.RANSAC, 5.0) | |
#H = cv2.getPerspectiveTransform(obj_mask.nonzero().cpu().numpy().astype(np.float32), pts_reproj[1].numpy().astype(np.float32)) | |
return H | |
def create_cylinder_mesh(radius, p0, p1, stacks=10, slices=10): | |
def compute_length_vec3(vec3): | |
return math.sqrt(vec3[0]*vec3[0] + vec3[1]*vec3[1] + vec3[2]*vec3[2]) | |
def rotation(axis, angle): | |
rot = np.eye(4) | |
c = np.cos(-angle) | |
s = np.sin(-angle) | |
t = 1.0 - c | |
axis /= compute_length_vec3(axis) | |
x = axis[0] | |
y = axis[1] | |
z = axis[2] | |
rot[0,0] = 1 + t*(x*x-1) | |
rot[0,1] = z*s+t*x*y | |
rot[0,2] = -y*s+t*x*z | |
rot[1,0] = -z*s+t*x*y | |
rot[1,1] = 1+t*(y*y-1) | |
rot[1,2] = x*s+t*y*z | |
rot[2,0] = y*s+t*x*z | |
rot[2,1] = -x*s+t*y*z | |
rot[2,2] = 1+t*(z*z-1) | |
return rot | |
verts = [] | |
indices = [] | |
diff = (p1 - p0).astype(np.float32) | |
height = compute_length_vec3(diff) | |
for i in range(stacks+1): | |
for i2 in range(slices): | |
theta = i2 * 2.0 * math.pi / slices | |
pos = np.array([radius*math.cos(theta), radius*math.sin(theta), height*i/stacks]) | |
verts.append(pos) | |
for i in range(stacks): | |
for i2 in range(slices): | |
i2p1 = math.fmod(i2 + 1, slices) | |
indices.append( np.array([(i + 1)*slices + i2, i*slices + i2, i*slices + i2p1], dtype=np.uint32) ) | |
indices.append( np.array([(i + 1)*slices + i2, i*slices + i2p1, (i + 1)*slices + i2p1], dtype=np.uint32) ) | |
transform = np.eye(4) | |
va = np.array([0, 0, 1], dtype=np.float32) | |
vb = diff | |
vb /= compute_length_vec3(vb) | |
axis = np.cross(vb, va) | |
angle = np.arccos(np.clip(np.dot(va, vb), -1, 1)) | |
if angle != 0: | |
if compute_length_vec3(axis) == 0: | |
dotx = va[0] | |
if (math.fabs(dotx) != 1.0): | |
axis = np.array([1,0,0]) - dotx * va | |
else: | |
axis = np.array([0,1,0]) - va[1] * va | |
axis /= compute_length_vec3(axis) | |
transform = rotation(axis, -angle) | |
transform[:3,3] += p0 | |
verts = [np.dot(transform, np.array([v[0], v[1], v[2], 1.0])) for v in verts] | |
verts = [np.array([v[0], v[1], v[2]]) / v[3] for v in verts] | |
return verts, indices | |
def create_arrow_mesh(radius, p0, p1, stacks=10, slices=10, arrow_height=0): | |
def compute_length_vec3(vec3): | |
return math.sqrt(vec3[0]*vec3[0] + vec3[1]*vec3[1] + vec3[2]*vec3[2]) | |
def rotation(axis, angle): | |
rot = np.eye(4) | |
c = np.cos(-angle) | |
s = np.sin(-angle) | |
t = 1.0 - c | |
axis /= compute_length_vec3(axis) | |
x = axis[0] | |
y = axis[1] | |
z = axis[2] | |
rot[0,0] = 1 + t*(x*x-1) | |
rot[0,1] = z*s+t*x*y | |
rot[0,2] = -y*s+t*x*z | |
rot[1,0] = -z*s+t*x*y | |
rot[1,1] = 1+t*(y*y-1) | |
rot[1,2] = x*s+t*y*z | |
rot[2,0] = y*s+t*x*z | |
rot[2,1] = -x*s+t*y*z | |
rot[2,2] = 1+t*(z*z-1) | |
return rot | |
verts = [] | |
indices = [] | |
diff = (p1 - p0).astype(np.float32) | |
height = compute_length_vec3(diff) | |
for i in range(stacks+2): | |
if i == stacks+1: | |
# arrow tip | |
cur_radius = 0 | |
cur_height = height | |
elif i == stacks: | |
# arrow base | |
cur_radius = radius*3 | |
cur_height = height * (1-arrow_height) * (i-1)/stacks | |
else: | |
# cylinder | |
cur_radius = radius | |
cur_height = height * (1-arrow_height) * i/stacks | |
for i2 in range(slices): | |
theta = i2 * 2.0 * math.pi / slices | |
pos = np.array([cur_radius*math.cos(theta), cur_radius*math.sin(theta), cur_height]) | |
verts.append(pos) | |
for i in range(stacks+1): | |
for i2 in range(slices): | |
i2p1 = math.fmod(i2 + 1, slices) | |
indices.append( np.array([(i + 1)*slices + i2, i*slices + i2, i*slices + i2p1], dtype=np.uint32) ) | |
indices.append( np.array([(i + 1)*slices + i2, i*slices + i2p1, (i + 1)*slices + i2p1], dtype=np.uint32) ) | |
transform = np.eye(4) | |
va = np.array([0, 0, 1], dtype=np.float32) | |
vb = diff | |
vb /= compute_length_vec3(vb) | |
axis = np.cross(vb, va) | |
angle = np.arccos(np.clip(np.dot(va, vb), -1, 1)) | |
if angle != 0: | |
if compute_length_vec3(axis) == 0: | |
dotx = va[0] | |
if (math.fabs(dotx) != 1.0): | |
axis = np.array([1,0,0]) - dotx * va | |
else: | |
axis = np.array([0,1,0]) - va[1] * va | |
axis /= compute_length_vec3(axis) | |
transform = rotation(axis, -angle) | |
transform[:3,3] += p0 | |
verts = [np.dot(transform, np.array([v[0], v[1], v[2], 1.0])) for v in verts] | |
verts = [np.array([v[0], v[1], v[2]]) / v[3] for v in verts] | |
return verts, indices | |
def get_camera_meshes(camera_list, radius=0.02): | |
verts_list = [] | |
faces_list = [] | |
color_list = [] | |
rots = np.array([quaternion.as_rotation_matrix(camera_info['rotation']) for camera_info in camera_list]) | |
# ai habitat frame | |
lookat = np.array([0,0,-1]) | |
vertical = np.array([0,1,0]) | |
positions = np.array([camera_info['position'] for camera_info in camera_list]) | |
lookats = rots@lookat.T | |
verticals = rots@vertical.T | |
predetermined_color = [ | |
[0.10196, 0.32157, 1.0], | |
[1.0, 0.0667, 0.1490],# [0.8314, 0.0667, 0.3490], | |
# [0.0, 0.4392156862745098, 0.7529411764705882], | |
# [0.3764705882352941, 0.08627450980392155, 0.47843137254901963], | |
] | |
for idx, (position, lookat, vertical, color) in enumerate(zip(positions, lookats, verticals, predetermined_color)): | |
cur_num_verts = 0 | |
# r, g, b = create_color_palette()[idx+10] | |
edges = get_cone_edges(position, lookat, vertical) | |
# color = [r/255.0,g/255.0,b/255.0] | |
cam_verts = [] | |
cam_inds = [] | |
for k in range(len(edges)): | |
cyl_verts, cyl_ind = create_cylinder_mesh(radius, edges[k][0], edges[k][1]) | |
cyl_verts = [x for x in cyl_verts] | |
cyl_ind = [x + cur_num_verts for x in cyl_ind] | |
cur_num_verts += len(cyl_verts) | |
cam_verts.extend(cyl_verts) | |
cam_inds.extend(cyl_ind) | |
# Create a textures object | |
verts_list.append(torch.tensor(cam_verts, dtype=torch.float32)) | |
faces_list.append(torch.tensor(cam_inds, dtype=torch.float32)) | |
color_list.append(color) | |
color_tensor = torch.tensor(color_list, dtype=torch.float32).unsqueeze_(1) | |
#tex = Textures(verts_uvs=None, faces_uvs=None, verts_rgb=color_tensor) | |
tex = TexturesVertex(verts_features=color_tensor) | |
# Initialise the mesh with textures | |
meshes = Meshes(verts=verts_list, faces=faces_list, textures=tex) | |
return meshes | |
def get_cone_edges(position, lookat, vertical): | |
def get_cone_verts(position, lookat, vertical): | |
vertical = np.array(vertical) / np.linalg.norm(vertical) | |
lookat = np.array(lookat) / np.linalg.norm(lookat) | |
right = np.cross(np.array(lookat), np.array(vertical)) | |
right = right / np.linalg.norm(right) | |
top = np.cross(right, lookat) | |
top = top / np.linalg.norm(top) | |
right *= .4 | |
lookat *= .4 | |
top *= .1 | |
verts = { | |
'topR': position + lookat + top + right, | |
'topL': position + lookat + top - right, | |
'center': position, | |
'bottomR': position + lookat - top + right, | |
'bottomL': position + lookat - top - right, | |
} | |
return verts | |
cone_verts = get_cone_verts(position, lookat, vertical) | |
edges = [ | |
(cone_verts['center'], cone_verts['topR']), | |
(cone_verts['center'], cone_verts['topL']), | |
(cone_verts['center'], cone_verts['bottomR']), | |
(cone_verts['center'], cone_verts['bottomL']), | |
(cone_verts['topR'], cone_verts['topL']), | |
(cone_verts['bottomR'], cone_verts['topR']), | |
(cone_verts['bottomR'], cone_verts['bottomL']), | |
(cone_verts['topL'], cone_verts['bottomL']), | |
] | |
return edges | |
def get_axis_mesh(radius, pt1, pt2): | |
verts_list = [] | |
faces_list = [] | |
color_list = [] | |
cyl_verts, cyl_ind = create_arrow_mesh(radius, pt1.numpy(), pt2.numpy()) | |
cyl_verts = [x for x in cyl_verts] | |
cyl_ind = [x for x in cyl_ind] | |
# Create a textures object | |
verts_list.append(torch.tensor(cyl_verts, dtype=torch.float32)) | |
faces_list.append(torch.tensor(cyl_ind, dtype=torch.float32)) | |
# color_list.append([0.10196, 0.32157, 1.0]) | |
# color_tensor = torch.tensor(color_list, dtype=torch.float32).unsqueeze_(1) | |
# Textures(verts_uvs=axis_verts_rgb, faces_uvs=axis_pt1.faces_list(), maps=torch.zeros((1,5,5,3)).cuda()) | |
# tex = TexturesVertex(verts_features=color_tensor) | |
# Initialise the mesh with textures | |
meshes = Meshes(verts=verts_list, faces=faces_list) | |
return meshes | |
def save_obj_articulation(folder, prefix, meshes, cam_meshes=None, decimal_places=None, blend_flag=False, map_files=None, uv_maps=None): | |
os.makedirs(folder, exist_ok=True) | |
# pytorch3d does not support map_files | |
#map_files = meshes.textures.map_files() | |
#assert map_files is not None | |
if map_files is None and uv_maps is None: | |
raise RuntimeError("either map_files or uv_maps should be set!") | |
# generate map_files from uv_map | |
if uv_maps is not None and map_files is None: | |
map_files = [] | |
uv_dir = os.path.join(folder, 'uv_maps') | |
if not os.path.exists(uv_dir): | |
os.mkdir(uv_dir) | |
for map_id, uv_map in enumerate(uv_maps): | |
uv_path = os.path.join(uv_dir, '{}_uv_plane_{}.png'.format(prefix, map_id)) | |
#pdb.set_trace() | |
imageio.imwrite(uv_path, uv_map) | |
map_files.append(uv_path) | |
#pdb.set_trace() | |
f_mtl = open(os.path.join(folder, prefix+'.mtl'), 'w') | |
f = open(os.path.join(folder, prefix+'.obj'), 'w') | |
try: | |
seen = set() | |
uniq_map_files = [m for m in list(map_files) if m not in seen and not seen.add(m)] | |
for map_id, map_file in enumerate(uniq_map_files): | |
if uv_maps is not None: | |
# we do not need to copy map_files, | |
# they are already in uv_maps/... | |
f_mtl.write(_get_mtl_map( | |
os.path.basename(map_file).split('.')[0], | |
os.path.join('uv_maps', os.path.basename(map_file)) | |
)) | |
continue | |
if not blend_flag: | |
shutil.copy(map_file, folder) | |
os.chmod(os.path.join(folder, os.path.basename(map_file)), 0o755) | |
f_mtl.write(_get_mtl_map(os.path.basename(map_file).split('.')[0], os.path.basename(map_file))) | |
else: | |
rgb = cv2.imread(map_file, cv2.IMREAD_COLOR) | |
if cam_meshes is not None: | |
blend_color = np.array(cam_meshes.textures.verts_features_packed().numpy().tolist()[map_id])*255 | |
else: | |
blend_color = np.array(create_color_palette()[map_id+10]) | |
alpha = 0.7 | |
blend = (rgb*alpha + blend_color[::-1]*(1-alpha)).astype(np.uint8) | |
cv2.imwrite(os.path.join(folder, os.path.basename(map_file).split('.')[0]+'_debug.png'), blend) | |
f_mtl.write(_get_mtl_map(os.path.basename(map_file).split('.')[0], os.path.basename(map_file).split('.')[0]+'_debug.png')) | |
f.write(f"mtllib {prefix}.mtl\n\n") | |
# we want [list] verts, vert_uvs, map_files; | |
# [packed] faces; | |
# face per mesh | |
verts_list = meshes.verts_list() | |
verts_uvs_list = meshes.textures.verts_uvs_list() | |
faces_list = meshes.faces_packed().split(meshes.num_faces_per_mesh().tolist(), dim=0) | |
#pdb.set_trace() | |
for idx, (verts, verts_uvs, faces, map_file) in enumerate(zip(verts_list, verts_uvs_list, faces_list, map_files)): | |
f.write(f"# mesh {idx}\n") | |
trunc_verts_uvs = verts_uvs[:verts.shape[0]] | |
_save(f, verts, faces, verts_uv=trunc_verts_uvs, map_file=map_file, idx=idx, decimal_places=decimal_places) | |
if cam_meshes: | |
face_offset = np.sum([len(v) for v in verts_list]) | |
cam_verts_list = cam_meshes.verts_list() | |
cam_verts_rgbs_list = cam_meshes.textures.verts_features_packed().numpy().tolist() | |
cam_faces_list = (cam_meshes.faces_packed()+face_offset).split(cam_meshes.num_faces_per_mesh().tolist(), dim=0) | |
assert(len(cam_verts_rgbs_list) == len(cam_verts_list)) | |
for idx, (verts, faces, rgb) in enumerate(zip(cam_verts_list, cam_faces_list, cam_verts_rgbs_list)): | |
f.write(f"# camera {idx}\n") | |
f_mtl.write(_get_mtl_rgb(idx, rgb)) | |
_save(f, verts, faces, rgb=rgb, idx=idx, decimal_places=decimal_places) | |
finally: | |
f.close() | |
f_mtl.close() | |
def _get_mtl_map(material_name, map_Kd): | |
return f"""newmtl {material_name} | |
map_Kd {map_Kd} | |
# Test colors | |
Ka 1.000 1.000 1.000 # white | |
Kd 1.000 1.000 1.000 # white | |
Ks 0.000 0.000 0.000 # black | |
Ns 10.0\n""" | |
def _get_mtl_rgb(material_idx, rgb): | |
return f"""newmtl color_{material_idx} | |
Kd {rgb[0]} {rgb[1]} {rgb[2]} | |
Ka 0.000 0.000 0.000\n""" | |
def _save(f, verts, faces, verts_uv=None, map_file=None, rgb=None, idx=None, double_sided=True, decimal_places: Optional[int] = None): | |
if decimal_places is None: | |
float_str = "%f" | |
else: | |
float_str = "%" + ".%df" % decimal_places | |
lines = "" | |
V, D = verts.shape | |
for i in range(V): | |
vert = [float_str % verts[i, j] for j in range(D)] | |
lines += "v %s\n" % " ".join(vert) | |
if verts_uv is not None: | |
V, D = verts_uv.shape | |
for i in range(V): | |
vert_uv = [float_str % verts_uv[i, j] for j in range(D)] | |
lines += "vt %s\n" % " ".join(vert_uv) | |
if map_file is not None: | |
lines += f"usemtl {os.path.basename(map_file).split('.')[0]}\n" | |
elif rgb is not None: | |
lines += f"usemtl color_{idx}\n" | |
if faces != []: | |
F, P = faces.shape | |
for i in range(F): | |
if verts_uv is not None: | |
face = ["%d/%d" % (faces[i, j] + 1, faces[i, j] + 1) for j in range(P)] | |
else: | |
face = ["%d" % (faces[i, j] + 1) for j in range(P)] | |
# if i + 1 < F: | |
lines += "f %s\n" % " ".join(face) | |
if double_sided: | |
if verts_uv is not None: | |
face = ["%d/%d" % (faces[i, j] + 1, faces[i, j] + 1) for j in reversed(range(P))] | |
else: | |
face = ["%d" % (faces[i, j] + 1) for j in reversed(range(P))] | |
lines += "f %s\n" % " ".join(face) | |
# elif i + 1 == F: | |
# # No newline at the end of the file. | |
# lines += "f %s" % " ".join(face) | |
else: | |
print(f"face = []") | |
f.write(lines) | |