def test_face_normals_preset(self, test_inputs, test_outputs): """Tests the computation of mesh face normals.""" faces = normals.gather_faces(*test_inputs[:2]) test_inputs = [faces] + list(test_inputs[2:]) self.assert_output_is_correct( normals.face_normals, test_inputs, test_outputs, tile=False)
def test_face_normals_random(self): """Tests the computation of mesh face normals in each axis.""" tensor_vertex_size = np.random.randint(1, 3) tensor_out_shape = np.random.randint(1, 5, size=tensor_vertex_size) tensor_out_shape = tensor_out_shape.tolist() tensor_vertex_shape = list(tensor_out_shape) tensor_vertex_shape[-1] *= 3 tensor_index_shape = tensor_out_shape[-1] for i in range(3): vertices = np.random.random(size=tensor_vertex_shape + [3]) indices = np.arange(tensor_vertex_shape[-1]) np.random.shuffle(indices) indices = np.reshape(indices, newshape=[1] * (tensor_vertex_size - 1) \ + [tensor_index_shape, 3]) indices = np.tile(indices, tensor_vertex_shape[:-1] + [1, 1]) vertices[..., i] = 0. expected = np.zeros(shape=tensor_out_shape + [3], dtype=vertices.dtype) expected[..., i] = 1. faces = normals.gather_faces(vertices, indices) self.assertAllClose(tf.abs(normals.face_normals(faces)), expected, rtol=1e-3)
def test_face_normals_jacobian_random(self): """Test the Jacobian of the face normals function.""" tensor_vertex_size = np.random.randint(1, 3) tensor_out_shape = np.random.randint(1, 5, size=tensor_vertex_size) tensor_out_shape = tensor_out_shape.tolist() tensor_vertex_shape = list(tensor_out_shape) tensor_vertex_shape[-1] *= 3 tensor_index_shape = tensor_out_shape[-1] vertex_init = np.random.random(size=tensor_vertex_shape + [3]) index_init = np.arange(tensor_vertex_shape[-1]) np.random.shuffle(index_init) index_init = np.reshape(index_init, newshape=[1] * (tensor_vertex_size - 1) \ + [tensor_index_shape, 3]) index_init = np.tile(index_init, tensor_vertex_shape[:-1] + [1, 1]) vertex_tensor = tf.convert_to_tensor(value=vertex_init) index_tensor = tf.convert_to_tensor(value=index_init) face_tensor = normals.gather_faces(vertex_tensor, index_tensor) y = normals.face_normals(face_tensor) self.assert_jacobian_is_correct(vertex_tensor, vertex_init, y, atol=1e-4, delta=1e-9)
def test_gather_faces_random(self): """Tests the extraction of mesh faces.""" tensor_size = np.random.randint(3, 5) tensor_shape = np.random.randint(1, 5, size=tensor_size).tolist() vertices = np.random.random(size=tensor_shape) indices = np.arange(tensor_shape[-2]) indices = indices.reshape([1] * (tensor_size - 1) + [-1]) indices = np.tile(indices, tensor_shape[:-2] + [1, 1]) expected = np.expand_dims(vertices, -3) self.assertAllClose( normals.gather_faces(vertices, indices), expected, rtol=1e-3)
def test_gather_faces_jacobian_random(self): """Test the Jacobian of the face extraction function.""" tensor_size = np.random.randint(2, 5) tensor_shape = np.random.randint(1, 5, size=tensor_size).tolist() vertex_init = np.random.random(size=tensor_shape) indices_init = np.random.randint(0, tensor_shape[-2], size=tensor_shape) vertex_tensor = tf.convert_to_tensor(value=vertex_init) indices_tensor = tf.convert_to_tensor(value=indices_init) y = normals.gather_faces(vertex_tensor, indices_tensor) self.assert_jacobian_is_correct(vertex_tensor, vertex_init, y)
def area_weighted_random_sample_triangle_mesh(vertex_attributes, faces, num_samples, vertex_positions=None, seed=None, stateless=False, name=None): """Performs a face area weighted random sampling of a tri mesh. Note: In the following, A1 to An are optional batch dimensions. Args: vertex_attributes: A `float` tensor of shape `[A1, ..., An, V, D]`, where V is the number of vertices, and D is dimensionality of a feature defined on each vertex. If `vertex_positions` is not provided, then first 3 dimensions of `vertex_attributes` denote the vertex positions. faces: A `int` tensor of shape `[A1, ..., An, F, 3]`, where F is the number of faces. num_samples: An `int` scalar denoting number of samples to be drawn from each mesh. vertex_positions: An optional `float` tensor of shape `[A1, ..., An, V, 3]`, where V is the number of vertices. If None, then vertex_attributes[..., :3] is used as vertex positions. seed: Optional random seed. stateless: Optional flag to use stateless random sampler. If stateless=True, then seed must be provided as shape `[2]` int tensor. Stateless random sampling is useful for testing to generate same sequence across calls. name: Name for op. Defaults to "area_weighted_random_sample_triangle_mesh". Returns: sample_pts: A `float` tensor of shape `[A1, ..., An, num_samples, D]`, where D is dimensionality of each sampled point. sample_face_indices: A `int` tensor of shape `[A1, ..., An, num_samples]`. """ with tf.compat.v1.name_scope(name, "area_weighted_random_sample_triangle_mesh"): faces = tf.convert_to_tensor(value=faces) vertex_attributes = tf.convert_to_tensor(value=vertex_attributes) num_samples = tf.convert_to_tensor(value=num_samples) shape.check_static( tensor=vertex_attributes, tensor_name="vertex_attributes", has_rank_greater_than=1) shape.check_static( tensor=vertex_attributes, tensor_name="vertex_attributes", has_dim_greater_than=(-1, 2)) if vertex_positions is not None: vertex_positions = tf.convert_to_tensor(value=vertex_positions) else: vertex_positions = vertex_attributes[..., :3] shape.check_static( tensor=vertex_positions, tensor_name="vertex_positions", has_rank_greater_than=1) shape.check_static( tensor=vertex_positions, tensor_name="vertex_positions", has_dim_equals=(-1, 3)) triangle_vertex_positions = normals.gather_faces(vertex_positions, faces) triangle_areas = triangle_area(triangle_vertex_positions[..., 0, :], triangle_vertex_positions[..., 1, :], triangle_vertex_positions[..., 2, :]) return weighted_random_sample_triangle_mesh( vertex_attributes, faces, num_samples, face_weights=triangle_areas, seed=seed, stateless=stateless)
def gather_faces(vertex_tensor): return normals.gather_faces(vertex_tensor, indices_tensor)
def face_normals(vertex_tensor): face_tensor = normals.gather_faces(vertex_tensor, index_tensor) return normals.face_normals(face_tensor)