def test_tensor_fail2(self, tensor, shape, wrong_dtype, device): with pytest.raises( TypeError, match="tensor dtype is torch.float32, should be torch.int64"): testing.check_tensor(tensor, shape, wrong_dtype, device) assert not testing.check_tensor( tensor, shape, wrong_dtype, device, throw=False)
def test_tensor_fail1(self, tensor, wrong_shape, dtype, device): with pytest.raises( ValueError, match= r"tensor shape is torch.Size\(\[4, 4\]\), should be \(3, 3\)"): testing.check_tensor(tensor, wrong_shape, dtype, device) assert not testing.check_tensor( tensor, wrong_shape, dtype, device, throw=False)
def test_module_conv3d(self, height, width, depth, in_channels, out_channels, with_bias, octrees, lengths, coalescent_features, max_level, pyramids, exsum, point_hierarchies, kernel_vectors, jump, with_spc_to_dict): conv = spc.Conv3d(in_channels, out_channels, kernel_vectors, jump, bias=with_bias).cuda() params = dict(conv.named_parameters()) weight = params['weight'] check_tensor(weight, shape=(kernel_vectors.shape[0], in_channels, out_channels), dtype=torch.float, device='cuda') if with_bias: assert len(params) == 2 bias = params['bias'] check_tensor(bias, shape=(out_channels, ), dtype=torch.float, device='cuda') else: assert len(params) == 1 bias = None buffers = dict(conv.named_buffers()) assert len(buffers) == 1 assert torch.equal(buffers['kernel_vectors'], kernel_vectors) assert repr(conv) == f'Conv3d(in={in_channels}, out={out_channels}, ' \ f'kernel_vector_size={kernel_vectors.shape[0]})' if with_spc_to_dict: input_spc = Spc(octrees, lengths) output, output_level = conv(**input_spc.to_dict(), level=max_level, input=coalescent_features) else: output, output_level = conv(octrees, point_hierarchies, max_level, pyramids, exsum, coalescent_features) expected_output, expected_output_level = spc.conv3d( octrees, point_hierarchies, max_level, pyramids, exsum, coalescent_features, weight, kernel_vectors, jump=jump, bias=bias) assert torch.equal(output, expected_output) assert output_level == expected_output_level
def test_sparse_1d_texture_mapping(self, sparse_coords_batch, texture_map_1d, mode): interop = texture_mapping(texture_coordinates=sparse_coords_batch, texture_maps=texture_map_1d, mode=mode) if mode == 'nearest': expected = torch.tensor([[41, 15, 11, 33], [133, 111, 115, 141]]).unsqueeze(-1) elif mode == 'bilinear': expected = torch.tensor([[41, 15, 11, 28], [128, 111, 115, 141]]).unsqueeze(-1) expected = expected.to(texture_map_1d.device).type(texture_map_1d.dtype) assert check_tensor(interop, shape=(2,4,1), dtype=texture_map_1d.dtype) assert torch.equal(interop, expected)
def test_sparse_3d_texture_mapping(self, sparse_coords_batch, texture_map_3d, mode): interop = texture_mapping(texture_coordinates=sparse_coords_batch, texture_maps=texture_map_3d, mode=mode) if mode == 'nearest': expected_d1 = torch.tensor([[41, 15, 11, 33], [133, 111, 115, 141]]) expected_d2 = -torch.tensor([[41, 15, 11, 33], [133, 111, 115, 141]]) expected_d3 = torch.tensor([[41, 15, 11, 33], [133, 111, 115, 141]]) expected = torch.stack([expected_d1, expected_d2, expected_d3], dim=-1) elif mode == 'bilinear': expected_d1 = torch.tensor([[41, 15, 11, 28], [128, 111, 115, 141]]) expected_d2 = -torch.tensor([[41, 15, 11, 28], [128, 111, 115, 141]]) expected_d3 = torch.tensor([[41, 15, 11, 28], [128, 111, 115, 141]]) expected = torch.stack([expected_d1, expected_d2, expected_d3], dim=-1) expected = expected.to(texture_map_3d.device).type(texture_map_3d.dtype) assert check_tensor(interop, shape=(2,4,3), dtype=texture_map_3d.dtype) assert torch.equal(interop, expected)
def test_sample_points(self, vertices, faces, device, dtype): batch_size, num_vertices = vertices.shape[:2] num_faces = faces.shape[0] num_samples = 1000 points, face_choices = mesh.sample_points(vertices, faces, num_samples) check_tensor(points, shape=(batch_size, num_samples, 3), dtype=dtype, device=device) check_tensor(face_choices, shape=(batch_size, num_samples), dtype=torch.long, device=device) # check that all faces are sampled num_0 = torch.sum(face_choices == 0, dim=1) assert torch.all(num_0 + torch.sum(face_choices == 1, dim=1) == num_samples) sampling_prob = num_samples / 3. tolerance = sampling_prob * 0.1 assert torch.all(num_0 < sampling_prob + tolerance) and \ torch.all(num_0 > sampling_prob - tolerance) face_vertices = mesh.index_vertices_by_faces(vertices, faces) face_vertices_choices = torch.gather( face_vertices, 1, face_choices[:, :, None, None].repeat(1, 1, 3, 3)) # compute distance from the point to the plan of the face picked face_normals = mesh.face_normals(face_vertices_choices, unit=True) v0_p = points - face_vertices_choices[:, :, 0] # batch_size x num_points x 3 len_v0_p = torch.sqrt(torch.sum(v0_p**2, dim=-1)) cos_a = torch.matmul(v0_p.reshape(-1, 1, 3), face_normals.reshape(-1, 3, 1)).reshape( batch_size, num_samples) / len_v0_p point_to_face_dist = len_v0_p * cos_a if dtype == torch.half: atol = 1e-2 rtol = 1e-3 else: atol = 1e-4 rtol = 1e-5 # check that the point is close to the plan assert torch.allclose(point_to_face_dist, torch.zeros((batch_size, num_samples), device=device, dtype=dtype), atol=atol, rtol=rtol) # check that the point lie in the triangle edges0 = face_vertices_choices[:, :, 1] - face_vertices_choices[:, :, 0] edges1 = face_vertices_choices[:, :, 2] - face_vertices_choices[:, :, 1] edges2 = face_vertices_choices[:, :, 0] - face_vertices_choices[:, :, 2] v0_p = points - face_vertices_choices[:, :, 0] v1_p = points - face_vertices_choices[:, :, 1] v2_p = points - face_vertices_choices[:, :, 2] # Normals of the triangle formed by an edge and the point normals1 = torch.cross(edges0, v0_p) normals2 = torch.cross(edges1, v1_p) normals3 = torch.cross(edges2, v2_p) # cross-product of those normals with the face normals must be positive margin = -5e-3 if dtype == torch.half else 0. assert torch.all( torch.matmul(normals1.reshape(-1, 1, 3), face_normals.reshape(-1, 3, 1)) >= margin) assert torch.all( torch.matmul(normals2.reshape(-1, 1, 3), face_normals.reshape(-1, 3, 1)) >= margin) assert torch.all( torch.matmul(normals3.reshape(-1, 1, 3), face_normals.reshape(-1, 3, 1)) >= margin)
def test_packed_sample_points(self, packed_vertices_info, packed_faces_info, device, dtype): vertices, first_idx_vertices = packed_vertices_info faces, num_faces_per_mesh = packed_faces_info total_num_vertices = vertices.shape[0] total_num_faces = faces.shape[0] batch_size = num_faces_per_mesh.shape[0] num_samples = 1000 points, face_choices = mesh.packed_sample_points( vertices, first_idx_vertices, faces, num_faces_per_mesh, num_samples) check_tensor(points, shape=(batch_size, num_samples, 3), dtype=dtype, device=device) check_tensor(face_choices, shape=(batch_size, num_samples), dtype=torch.long, device=device) # check that all faces are sampled assert torch.all(face_choices[1] == 2) num_0 = torch.sum(face_choices[0] == 0) assert num_0 + torch.sum(face_choices[0] == 1) == num_samples sampling_prob = num_samples / 3. tolerance = sampling_prob * 0.1 assert (num_0 < sampling_prob + tolerance) and \ (num_0 > sampling_prob - tolerance) merged_faces = faces + tile_to_packed( first_idx_vertices[:-1].to(vertices.device), num_faces_per_mesh) face_vertices = torch.index_select(vertices, 0, merged_faces.reshape(-1)).reshape( total_num_faces, 3, 3) face_vertices_choices = torch.gather( face_vertices, 0, face_choices.reshape(-1, 1, 1).repeat(1, 3, 3)).reshape(batch_size, num_samples, 3, 3) # compute distance from the point to the plan of the face picked face_normals = mesh.face_normals(face_vertices_choices, unit=True) v0_p = points - face_vertices_choices[:, :, 0] # batch_size x num_points x 3 len_v0_p = torch.sqrt(torch.sum(v0_p**2, dim=-1)) cos_a = torch.matmul(v0_p.reshape(-1, 1, 3), face_normals.reshape(-1, 3, 1)).reshape( batch_size, num_samples) / len_v0_p point_to_face_dist = len_v0_p * cos_a if dtype == torch.half: atol = 1e-2 rtol = 1e-3 else: atol = 1e-4 rtol = 1e-5 # check that the point is close to the plan assert torch.allclose(point_to_face_dist, torch.zeros((batch_size, num_samples), device=device, dtype=dtype), atol=atol, rtol=rtol) # check that the point lie in the triangle edges0 = face_vertices_choices[:, :, 1] - face_vertices_choices[:, :, 0] edges1 = face_vertices_choices[:, :, 2] - face_vertices_choices[:, :, 1] edges2 = face_vertices_choices[:, :, 0] - face_vertices_choices[:, :, 2] v0_p = points - face_vertices_choices[:, :, 0] v1_p = points - face_vertices_choices[:, :, 1] v2_p = points - face_vertices_choices[:, :, 2] # Normals of the triangle formed by an edge and the point normals1 = torch.cross(edges0, v0_p) normals2 = torch.cross(edges1, v1_p) normals3 = torch.cross(edges2, v2_p) # cross-product of those normals with the face normals must be positive margin = -2e-3 if dtype == torch.half else 0. assert torch.all( torch.matmul(normals1.reshape(-1, 1, 3), face_normals.reshape(-1, 3, 1)) >= margin) assert torch.all( torch.matmul(normals2.reshape(-1, 1, 3), face_normals.reshape(-1, 3, 1)) >= margin) assert torch.all( torch.matmul(normals3.reshape(-1, 1, 3), face_normals.reshape(-1, 3, 1)) >= margin)
def test_random_tensor(low, high, shape, dtype, device): tensor = random_tensor(low, high, shape, dtype, device) check_tensor(tensor, shape, dtype, device) assert (low <= tensor).all() assert (tensor <= high).all()
def test_tensor_fail3(self, tensor, shape, dtype, wrong_device): with pytest.raises(TypeError, match="tensor device is cpu, should be cuda"): testing.check_tensor(tensor, shape, dtype, wrong_device) assert not testing.check_tensor( tensor, shape, dtype, wrong_device, throw=False)
def test_tensor_default_success(self, tensor): assert testing.check_tensor(tensor)
def test_tensor_partial_shape_success(self, tensor, partial_shape, dtype, device): assert testing.check_tensor(tensor, partial_shape, dtype, device)
def test_tensor_success(self, tensor, shape, dtype, device): assert testing.check_tensor(tensor, shape, dtype, device)
def test_sample_points(self, vertices, faces, face_features, use_features, device, dtype): batch_size, num_vertices = vertices.shape[:2] num_faces = faces.shape[0] num_samples = 1000 if use_features: points, face_choices, interpolated_features = mesh.sample_points( vertices, faces, num_samples, face_features=face_features) else: points, face_choices = mesh.sample_points(vertices, faces, num_samples) check_tensor(points, shape=(batch_size, num_samples, 3), dtype=dtype, device=device) check_tensor(face_choices, shape=(batch_size, num_samples), dtype=torch.long, device=device) # check that all faces are sampled num_0 = torch.sum(face_choices == 0, dim=1) assert torch.all(num_0 + torch.sum(face_choices == 1, dim=1) == num_samples) sampling_prob = num_samples / 2 tolerance = sampling_prob * 0.2 assert torch.all(num_0 < sampling_prob + tolerance) and \ torch.all(num_0 > sampling_prob - tolerance) face_vertices = mesh.index_vertices_by_faces(vertices, faces) face_vertices_choices = torch.gather( face_vertices, 1, face_choices[:, :, None, None].repeat(1, 1, 3, 3)) # compute distance from the point to the plan of the face picked face_normals = mesh.face_normals(face_vertices_choices, unit=True) v0_p = points - face_vertices_choices[:, :, 0] # batch_size x num_points x 3 len_v0_p = torch.sqrt(torch.sum(v0_p**2, dim=-1)) cos_a = torch.matmul(v0_p.reshape(-1, 1, 3), face_normals.reshape(-1, 3, 1)).reshape( batch_size, num_samples) / len_v0_p point_to_face_dist = len_v0_p * cos_a if dtype == torch.half: atol = 1e-2 rtol = 1e-3 else: atol = 1e-4 rtol = 1e-5 # check that the point is close to the plan assert torch.allclose(point_to_face_dist, torch.zeros((batch_size, num_samples), device=device, dtype=dtype), atol=atol, rtol=rtol) # check that the point lie in the triangle edges0 = face_vertices_choices[:, :, 1] - face_vertices_choices[:, :, 0] edges1 = face_vertices_choices[:, :, 2] - face_vertices_choices[:, :, 1] edges2 = face_vertices_choices[:, :, 0] - face_vertices_choices[:, :, 2] v0_p = points - face_vertices_choices[:, :, 0] v1_p = points - face_vertices_choices[:, :, 1] v2_p = points - face_vertices_choices[:, :, 2] # Normals of the triangle formed by an edge and the point normals1 = torch.cross(edges0, v0_p) normals2 = torch.cross(edges1, v1_p) normals3 = torch.cross(edges2, v2_p) # cross-product of those normals with the face normals must be positive margin = -5e-3 if dtype == torch.half else 0. assert torch.all( torch.matmul(normals1.reshape(-1, 1, 3), face_normals.reshape(-1, 3, 1)) >= margin) assert torch.all( torch.matmul(normals2.reshape(-1, 1, 3), face_normals.reshape(-1, 3, 1)) >= margin) assert torch.all( torch.matmul(normals3.reshape(-1, 1, 3), face_normals.reshape(-1, 3, 1)) >= margin) if use_features: feat_dim = face_features.shape[-1] check_tensor(interpolated_features, shape=(batch_size, num_samples, feat_dim), dtype=dtype, device=device) # face_vertices_choices (batch_size, num_samples, 3, 3) # points (batch_size, num_samples, 3) ax = face_vertices_choices[:, :, 0, 0] ay = face_vertices_choices[:, :, 0, 1] bx = face_vertices_choices[:, :, 1, 0] by = face_vertices_choices[:, :, 1, 1] cx = face_vertices_choices[:, :, 2, 0] cy = face_vertices_choices[:, :, 2, 1] m = bx - ax p = by - ay n = cx - ax q = cy - ay s = points[:, :, 0] - ax t = points[:, :, 1] - ay # sum_weights = torch.sum(weights, dim=-1) # zeros_idxs = torch.where(sum_weights == 0) #weights = weights / torch.sum(weights, keepdims=True, dim=-1) k1 = s * q - n * t k2 = m * t - s * p k3 = m * q - n * p w1 = k1 / (k3 + 1e-7) w2 = k2 / (k3 + 1e-7) w0 = (1. - w1) - w2 weights = torch.stack([w0, w1, w2], dim=-1) gt_points = torch.sum(face_vertices_choices * weights.unsqueeze(-1), dim=-2) assert torch.allclose(points, gt_points, atol=atol, rtol=rtol) _face_choices = face_choices[..., None, None].repeat(1, 1, 3, feat_dim) face_features_choices = torch.gather(face_features, 1, _face_choices) gt_interpolated_features = torch.sum(face_features_choices * weights.unsqueeze(-1), dim=-2) assert torch.allclose(interpolated_features, gt_interpolated_features, atol=atol, rtol=rtol)
def test_module_conv_transpose3d(self, height, width, depth, in_channels, out_channels, with_bias, octrees, lengths, max_level, pyramids, exsum, point_hierarchies, kernel_size, kernel_vectors, jump, with_spc_to_dict): stride = 2**jump if stride > kernel_size: pytest.skip('stride higher than kernel_size is not tested') in_level = max_level - jump in_num_nodes = torch.sum(pyramids[:, 0, -(2 + jump)]) coalescent_features = torch.rand((in_num_nodes, in_channels), device='cuda', requires_grad=True) conv = spc.ConvTranspose3d(in_channels, out_channels, kernel_vectors, jump, bias=with_bias).cuda() params = dict(conv.named_parameters()) weight = params['weight'] check_tensor(weight, shape=(kernel_vectors.shape[0], in_channels, out_channels), dtype=torch.float, device='cuda') if with_bias: assert len(params) == 2 bias = params['bias'] check_tensor(bias, shape=(out_channels, ), dtype=torch.float, device='cuda') else: assert len(params) == 1 bias = None buffers = dict(conv.named_buffers()) assert len(buffers) == 1 assert torch.equal(buffers['kernel_vectors'], kernel_vectors) assert repr(conv) == f'ConvTranspose3d(in={in_channels}, ' \ f'out={out_channels}, ' \ f'kernel_vector_size={kernel_vectors.shape[0]})' if with_spc_to_dict: input_spc = Spc(octrees, lengths) output, output_level = conv(**input_spc.to_dict(), level=in_level, input=coalescent_features) else: output, output_level = conv(octrees, point_hierarchies, in_level, pyramids, exsum, coalescent_features) expected_output, expected_output_level = spc.conv_transpose3d( octrees, point_hierarchies, in_level, pyramids, exsum, coalescent_features, weight, kernel_vectors, jump=jump, bias=bias) assert torch.equal(output, expected_output) assert output_level == expected_output_level