feifeifeiliu's picture
Upload 97 files
faeda5e
raw
history blame
19.6 kB
#!/usr/bin/env python
# encoding: utf-8
# Copyright (c) 2012 Max Planck Society. All rights reserved.
"""
Mesh module
-----------
"""
import os
from functools import reduce
import numpy as np
from . import colors
from . import search
try:
from .serialization import serialization
except ImportError:
pass
from . import landmarks
from . import texture
from . import processing
__all__ = ["Mesh"]
class Mesh(object):
"""3d Triangulated Mesh class
Attributes:
v: Vx3 array of vertices
f: Fx3 array of faces
Optional attributes:
fc: Fx3 array of face colors
vc: Vx3 array of vertex colors
vn: Vx3 array of vertex normals
segm: dictionary of part names to triangle indices
"""
def __init__(self,
v=None,
f=None,
segm=None,
filename=None,
ppfilename=None,
lmrkfilename=None,
basename=None,
vc=None,
fc=None,
vscale=None,
landmarks=None):
"""
:param v: vertices
:param f: faces
:param filename: a filename from which a mesh is loaded
"""
if filename is not None:
self.load_from_file(filename)
if hasattr(self, 'f'):
self.f = np.require(self.f, dtype=np.uint32)
self.v = np.require(self.v, dtype=np.float64)
self.filename = filename
if vscale is not None:
self.v *= vscale
if v is not None:
self.v = np.array(v, dtype=np.float64)
if vscale is not None:
self.v *= vscale
if f is not None:
self.f = np.require(f, dtype=np.uint32)
self.basename = basename
if self.basename is None and filename is not None:
self.basename = os.path.splitext(os.path.basename(filename))[0]
if segm is not None:
self.segm = segm
if landmarks is not None:
self.set_landmark_indices_from_any(landmarks)
if ppfilename is not None:
self.set_landmark_indices_from_ppfile(ppfilename)
if lmrkfilename is not None:
self.set_landmark_indices_from_lmrkfile(lmrkfilename)
if vc is not None:
self.set_vertex_colors(vc)
if fc is not None:
self.set_face_colors(fc)
def __del__(self):
if hasattr(self, 'textureID'):
from OpenGL.GL import glDeleteTextures
glDeleteTextures([self.textureID])
def edges_as_lines(self, copy_vertices=False):
from .lines import Lines
edges = self.f[:, [0, 1, 1, 2, 2, 0]].flatten().reshape(-1, 2)
verts = self.v.copy() if copy_vertices else self.v
return Lines(v=verts, e=edges)
def show(self, mv=None, meshes=[], lines=[]):
from .meshviewer import MeshViewer
from .utils import row
if mv is None:
mv = MeshViewer(keepalive=True)
if hasattr(self, 'landm'):
from .sphere import Sphere
sphere = Sphere(np.zeros((3)), 1.).to_mesh()
scalefactor = 1e-2 * np.max(np.max(self.v) - np.min(self.v)) / np.max(np.max(sphere.v) - np.min(sphere.v))
sphere.v = sphere.v * scalefactor
spheres = [Mesh(vc='SteelBlue', f=sphere.f, v=sphere.v + row(np.array(self.landm_raw_xyz[k]))) for k in self.landm.keys()]
mv.set_dynamic_meshes([self] + spheres + meshes, blocking=True)
else:
mv.set_dynamic_meshes([self] + meshes, blocking=True)
mv.set_dynamic_lines(lines)
return mv
def colors_like(self, color, arr=None):
from .utils import row, col
if arr is None:
arr = np.zeros(self.v.shape)
# if arr is single-dim, reshape it
if arr.ndim == 1 or arr.shape[1] == 1:
arr = arr.reshape(-1, 3)
if isinstance(color, str):
color = colors.name_to_rgb[color]
elif isinstance(color, list):
color = np.array(color)
if color.shape[0] == arr.shape[0] and color.shape[0] == color.size:
def jet(v):
fourValue = 4 * v
red = min(fourValue - 1.5, -fourValue + 4.5)
green = min(fourValue - 0.5, -fourValue + 3.5)
blue = min(fourValue + 0.5, -fourValue + 2.5)
result = np.array([red, green, blue])
result[result > 1.0] = 1.0
result[result < 0.0] = 0.0
return row(result)
color = col(color)
color = np.concatenate([jet(color[i]) for i in range(color.size)], axis=0)
return np.ones_like(arr) * color
def set_vertex_colors(self, vc, vertex_indices=None):
if vertex_indices is not None:
self.vc[vertex_indices] = self.colors_like(vc, self.v[vertex_indices])
else:
self.vc = self.colors_like(vc, self.v)
return self
def set_vertex_colors_from_weights(self, weights, scale_to_range_1=True, color=True):
# from numpy import ones_like
if weights is None:
return self
if scale_to_range_1:
weights = weights - np.min(weights)
weights = (1.0 - 0.0) * weights / np.max(weights) + 0.0
if color:
from matplotlib import cm
self.vc = cm.jet(weights)[:, :3]
else:
self.vc = np.tile(np.reshape(weights, (len(weights), 1)), (1, 3)) # *ones_like(self.v)
return self
def scale_vertex_colors(self, weights, w_min=0.0, w_max=1.0):
if weights is None:
return self
weights = weights - np.min(weights)
weights = (w_max - w_min) * weights / np.max(weights) + w_min
self.vc = (weights * self.vc.T).T if weights is not None else self.vc
return self
def set_face_colors(self, fc):
self.fc = self.colors_like(fc, self.f)
return self
def faces_by_vertex(self, as_sparse_matrix=False):
import scipy.sparse as sp
if not as_sparse_matrix:
faces_by_vertex = [[] for i in range(len(self.v))]
for i, face in enumerate(self.f):
faces_by_vertex[face[0]].append(i)
faces_by_vertex[face[1]].append(i)
faces_by_vertex[face[2]].append(i)
else:
row = self.f.flatten()
col = np.array([range(self.f.shape[0])] * 3).T.flatten()
data = np.ones(len(col))
faces_by_vertex = sp.csr_matrix((data, (row, col)), shape=(self.v.shape[0], self.f.shape[0]))
return faces_by_vertex
def estimate_vertex_normals(self, face_to_verts_sparse_matrix=None):
from .geometry.tri_normals import TriNormalsScaled
face_normals = TriNormalsScaled(self.v, self.f).reshape(-1, 3)
ftov = face_to_verts_sparse_matrix if face_to_verts_sparse_matrix else self.faces_by_vertex(as_sparse_matrix=True)
non_scaled_normals = ftov * face_normals
norms = (np.sum(non_scaled_normals ** 2.0, axis=1) ** 0.5).T
norms[norms == 0] = 1.0
return (non_scaled_normals.T / norms).T
def barycentric_coordinates_for_points(self, points, face_indices):
from .geometry.barycentric_coordinates_of_projection import barycentric_coordinates_of_projection
vertex_indices = self.f[face_indices.flatten(), :]
tri_vertices = np.array([self.v[vertex_indices[:, 0]], self.v[vertex_indices[:, 1]], self.v[vertex_indices[:, 2]]])
return vertex_indices, barycentric_coordinates_of_projection(points, tri_vertices[0, :], tri_vertices[1, :] - tri_vertices[0, :], tri_vertices[2, :] - tri_vertices[0, :])
def transfer_segm(self, mesh, exclude_empty_parts=True):
self.segm = {}
if hasattr(mesh, 'segm'):
face_centers = np.array([self.v[face, :].mean(axis=0) for face in self.f])
(closest_faces, closest_points) = mesh.closest_faces_and_points(face_centers)
mesh_parts_by_face = mesh.parts_by_face()
parts_by_face = [mesh_parts_by_face[face] for face in closest_faces.flatten()]
self.segm = dict([(part, []) for part in mesh.segm.keys()])
for face, part in enumerate(parts_by_face):
self.segm[part].append(face)
for part in self.segm.keys():
self.segm[part].sort()
if exclude_empty_parts and not self.segm[part]:
del self.segm[part]
@property
def verts_by_segm(self):
return dict((segment, sorted(set(self.f[indices].flatten()))) for segment, indices in self.segm.items())
def parts_by_face(self):
segments_by_face = [''] * len(self.f)
for part in self.segm.keys():
for face in self.segm[part]:
segments_by_face[face] = part
return segments_by_face
def verts_in_common(self, segments):
"""
returns array of all vertex indices common to each segment in segments"""
return sorted(reduce(lambda s0, s1: s0.intersection(s1),
[set(self.verts_by_segm[segm]) for segm in segments]))
# # indices of vertices in the faces of the first segment
# indices = self.verts_by_segm[segments[0]]
# for segment in segments[1:] :
# indices = sorted([index for index in self.verts_by_segm[segment] if index in indices]) # Intersect current segment with current indices
# return sorted(set(indices))
@property
def joint_names(self):
return self.joint_regressors.keys()
@property
def joint_xyz(self):
joint_locations = {}
for name in self.joint_names:
joint_locations[name] = self.joint_regressors[name]['offset'] + \
np.sum(self.v[self.joint_regressors[name]['v_indices']].T * self.joint_regressors[name]['coeff'], axis=1)
return joint_locations
# creates joint_regressors from a list of joint names and a per joint list of vertex indices (e.g. a ring of vertices)
# For the regression coefficients, all vertices for a given joint are given equal weight
def set_joints(self, joint_names, vertex_indices):
self.joint_regressors = {}
for name, indices in zip(joint_names, vertex_indices):
self.joint_regressors[name] = {'v_indices': indices,
'coeff': [1.0 / len(indices)] * len(indices),
'offset': np.array([0., 0., 0.])}
def vertex_visibility(self, camera, normal_threshold=None, omni_directional_camera=False, binary_visiblity=True):
vis, n_dot_cam = self.vertex_visibility_and_normals(camera, omni_directional_camera)
if normal_threshold is not None:
vis = np.logical_and(vis, n_dot_cam > normal_threshold)
return np.squeeze(vis) if binary_visiblity else np.squeeze(vis * n_dot_cam)
def vertex_visibility_and_normals(self, camera, omni_directional_camera=False):
from .visibility import visibility_compute
arguments = {'v': self.v,
'f': self.f,
'cams': np.array([camera.origin.flatten()])}
if not omni_directional_camera:
arguments['sensors'] = np.array([camera.sensor_axis.flatten()])
arguments['n'] = self.vn if hasattr(self, 'vn') else self.estimate_vertex_normals()
return(visibility_compute(**arguments))
def visibile_mesh(self, camera=[0.0, 0.0, 0.0]):
vis = self.vertex_visibility(camera)
faces_to_keep = filter(lambda face: vis[face[0]] * vis[face[1]] * vis[face[2]], self.f)
vertex_indices_to_keep = np.nonzero(vis)[0]
vertices_to_keep = self.v[vertex_indices_to_keep]
old_to_new_indices = np.zeros(len(vis))
old_to_new_indices[vertex_indices_to_keep] = range(len(vertex_indices_to_keep))
return Mesh(v=vertices_to_keep, f=np.array([old_to_new_indices[face] for face in faces_to_keep]))
def estimate_circumference(self, plane_normal, plane_distance, partNamesAllowed=None, want_edges=False):
raise Exception('estimate_circumference function has moved to body.mesh.metrics.circumferences')
# ######################################################
# Processing
def reset_normals(self, face_to_verts_sparse_matrix=None, reset_face_normals=False):
return processing.reset_normals(self, face_to_verts_sparse_matrix, reset_face_normals)
def reset_face_normals(self):
return processing.reset_face_normals(self)
def uniquified_mesh(self):
"""This function returns a copy of the mesh in which vertices are copied such that
each vertex appears in only one face, and hence has only one texture"""
return processing.uniquified_mesh(self)
def keep_vertices(self, keep_list):
return processing.keep_vertices(self, keep_list)
def remove_vertices(self, v_list):
return self.keep_vertices(np.setdiff1d(np.arange(self.v.shape[0]), v_list))
def point_cloud(self):
return Mesh(v=self.v, f=[], vc=self.vc) if hasattr(self, 'vc') else Mesh(v=self.v, f=[])
def remove_faces(self, face_indices_to_remove):
return processing.remove_faces(self, face_indices_to_remove)
def scale_vertices(self, scale_factor):
return processing.scale_vertices(self, scale_factor)
def rotate_vertices(self, rotation):
return processing.rotate_vertices(self, rotation)
def translate_vertices(self, translation):
return processing.translate_vertices(self, translation)
def flip_faces(self):
return processing.flip_faces(self)
def simplified(self, factor=None, n_verts_desired=None):
from .topology import qslim_decimator
return qslim_decimator(self, factor, n_verts_desired)
def subdivide_triangles(self):
return processing.subdivide_triangles(self)
def concatenate_mesh(self, mesh):
return processing.concatenate_mesh(self, mesh)
# new_ordering specifies the new index of each vertex. If new_ordering[i] = j,
# vertex i should now be the j^th vertex. As such, each entry in new_ordering should be unique.
def reorder_vertices(self, new_ordering, new_normal_ordering=None):
processing.reorder_vertices(self, new_ordering, new_normal_ordering)
# ######################################################
# Landmark methods
@property
def landm_names(self):
names = []
if hasattr(self, 'landm_regressors') or hasattr(self, 'landm'):
names = self.landm_regressors.keys() if hasattr(self, 'landm_regressors') else self.landm.keys()
return list(names)
@property
def landm_xyz(self, ordering=None):
landmark_order = ordering if ordering else self.landm_names
landmark_vertex_locations = (self.landm_xyz_linear_transform(landmark_order) * self.v.flatten()).reshape(-1, 3) if landmark_order else np.zeros((0, 0))
return dict([(landmark_order[i], xyz) for i, xyz in enumerate(landmark_vertex_locations)]) if landmark_order else {}
def set_landmarks_from_xyz(self, landm_raw_xyz):
self.landm_raw_xyz = landm_raw_xyz if hasattr(landm_raw_xyz, 'keys') else dict((str(i), l) for i, l in enumerate(landm_raw_xyz))
self.recompute_landmark_indices()
def landm_xyz_linear_transform(self, ordering=None):
return landmarks.landm_xyz_linear_transform(self, ordering)
def recompute_landmark_xyz(self):
self.landm_raw_xyz = dict((name, self.v[ind]) for name, ind in self.landm.items())
def recompute_landmark_indices(self, landmark_fname=None, safe_mode=True):
landmarks.recompute_landmark_indices(self, landmark_fname, safe_mode)
def set_landmarks_from_regressors(self, regressors):
self.landm_regressors = regressors
def set_landmark_indices_from_any(self, landmark_file_or_values):
serialization.set_landmark_indices_from_any(self, landmark_file_or_values)
def set_landmarks_from_raw(self, landmark_file_or_values):
landmarks.set_landmarks_from_raw(self, landmark_file_or_values)
#######################################################
# Texture methods
@property
def texture_image(self):
if not hasattr(self, '_texture_image'):
self.reload_texture_image()
return self._texture_image
def set_texture_image(self, path_to_texture):
self.texture_filepath = path_to_texture
def texture_coordinates_by_vertex(self):
return texture.texture_coordinates_by_vertex(self)
def reload_texture_image(self):
texture.reload_texture_image(self)
def transfer_texture(self, mesh_with_texture):
texture.transfer_texture(self, mesh_with_texture)
def load_texture(self, texture_version):
texture.load_texture(self, texture_version)
def texture_rgb(self, texture_coordinate):
return texture.texture_rgb(self, texture_coordinate)
def texture_rgb_vec(self, texture_coordinates):
return texture.texture_rgb_vec(self, texture_coordinates)
#######################################################
# Search methods
def compute_aabb_tree(self):
return search.AabbTree(self)
def compute_aabb_normals_tree(self):
return search.AabbNormalsTree(self)
def compute_closest_point_tree(self, use_cgal=False):
return search.CGALClosestPointTree(self) if use_cgal else search.ClosestPointTree(self)
def closest_vertices(self, vertices, use_cgal=False):
return self.compute_closest_point_tree(use_cgal).nearest(vertices)
def closest_points(self, vertices):
return self.closest_faces_and_points(vertices)[1]
def closest_faces_and_points(self, vertices):
return self.compute_aabb_tree().nearest(vertices)
#######################################################
# Serialization methods
def load_from_file(self, filename):
serialization.load_from_file(self, filename)
def load_from_ply(self, filename):
serialization.load_from_ply(self, filename)
def load_from_obj(self, filename):
serialization.load_from_obj(self, filename)
def write_json(self, filename, header="", footer="", name="", include_faces=True, texture_mode=True):
serialization.write_json(self, filename, header, footer, name, include_faces, texture_mode)
def write_three_json(self, filename, name=""):
serialization.write_three_json(self, filename, name)
def write_ply(self, filename, flip_faces=False, ascii=False, little_endian=True, comments=[]):
serialization.write_ply(self, filename, flip_faces, ascii, little_endian, comments)
def write_mtl(self, path, material_name, texture_name):
"""Serializes a material attributes file"""
serialization.write_mtl(self, path, material_name, texture_name)
def write_obj(self, filename, flip_faces=False, group=False, comments=None):
serialization.write_obj(self, filename, flip_faces, group, comments)
def load_from_obj_cpp(self, filename):
serialization.load_from_obj_cpp(self, filename)
def set_landmark_indices_from_ppfile(self, ppfilename):
serialization.set_landmark_indices_from_ppfile(self, ppfilename)
def set_landmark_indices_from_lmrkfile(self, lmrkfilename):
serialization.set_landmark_indices_from_lmrkfile(self, lmrkfilename)