| |
| |
| |
| |
| |
|
|
| import unittest |
|
|
| import numpy as np |
| import torch |
| from pytorch3d import _C |
| from pytorch3d.loss import point_mesh_edge_distance, point_mesh_face_distance |
| from pytorch3d.structures import Meshes, packed_to_list, Pointclouds |
|
|
| from .common_testing import get_random_cuda_device, TestCaseMixin |
|
|
|
|
| class TestPointMeshDistance(TestCaseMixin, unittest.TestCase): |
| def setUp(self) -> None: |
| np.random.seed(42) |
| torch.manual_seed(42) |
|
|
| @staticmethod |
| def eps(): |
| return 1e-8 |
|
|
| @staticmethod |
| def min_triangle_area(): |
| return 5e-3 |
|
|
| @staticmethod |
| def init_meshes_clouds( |
| batch_size: int = 10, |
| num_verts: int = 1000, |
| num_faces: int = 3000, |
| num_points: int = 3000, |
| device: str = "cuda:0", |
| ): |
| device = torch.device(device) |
| nump = torch.randint(low=1, high=num_points, size=(batch_size,)) |
| numv = torch.randint(low=3, high=num_verts, size=(batch_size,)) |
| numf = torch.randint(low=1, high=num_faces, size=(batch_size,)) |
| verts_list = [] |
| faces_list = [] |
| points_list = [] |
| for i in range(batch_size): |
| |
| verts = torch.rand((numv[i], 3), dtype=torch.float32, device=device) |
| verts.requires_grad_(True) |
|
|
| |
| |
| |
| |
| |
| faces, allf = [], 0 |
| validf = numv[i].item() - numv[i].item() % 3 |
| while allf < numf[i]: |
| ff = torch.randperm(numv[i], device=device)[:validf].view(-1, 3) |
| faces.append(ff) |
| allf += ff.shape[0] |
| faces = torch.cat(faces, 0) |
| if faces.shape[0] > numf[i]: |
| faces = faces[: numf[i]] |
|
|
| verts_list.append(verts) |
| faces_list.append(faces) |
|
|
| |
| points = torch.rand((nump[i], 3), dtype=torch.float32, device=device) |
| points.requires_grad_(True) |
|
|
| points_list.append(points) |
|
|
| meshes = Meshes(verts_list, faces_list) |
| pcls = Pointclouds(points_list) |
|
|
| return meshes, pcls |
|
|
| @staticmethod |
| def _point_to_bary(point: torch.Tensor, tri: torch.Tensor) -> torch.Tensor: |
| """ |
| Computes the barycentric coordinates of point wrt triangle (tri) |
| Note that point needs to live in the space spanned by tri = (a, b, c), |
| i.e. by taking the projection of an arbitrary point on the space spanned by tri |
| |
| Args: |
| point: FloatTensor of shape (3) |
| tri: FloatTensor of shape (3, 3) |
| Returns: |
| bary: FloatTensor of shape (3) |
| """ |
| assert point.dim() == 1 and point.shape[0] == 3 |
| assert tri.dim() == 2 and tri.shape[0] == 3 and tri.shape[1] == 3 |
|
|
| a, b, c = tri.unbind(0) |
|
|
| v0 = b - a |
| v1 = c - a |
| v2 = point - a |
|
|
| d00 = v0.dot(v0) |
| d01 = v0.dot(v1) |
| d11 = v1.dot(v1) |
| d20 = v2.dot(v0) |
| d21 = v2.dot(v1) |
|
|
| denom = d00 * d11 - d01 * d01 + TestPointMeshDistance.eps() |
| s2 = (d11 * d20 - d01 * d21) / denom |
| s3 = (d00 * d21 - d01 * d20) / denom |
| s1 = 1.0 - s2 - s3 |
|
|
| bary = torch.tensor([s1, s2, s3]) |
| return bary |
|
|
| @staticmethod |
| def _is_inside_triangle(point: torch.Tensor, tri: torch.Tensor) -> torch.Tensor: |
| """ |
| Computes whether point is inside triangle tri |
| Note that point needs to live in the space spanned by tri = (a, b, c) |
| i.e. by taking the projection of an arbitrary point on the space spanned by tri |
| |
| Args: |
| point: FloatTensor of shape (3) |
| tri: FloatTensor of shape (3, 3) |
| Returns: |
| inside: BoolTensor of shape (1) |
| """ |
| v0 = tri[1] - tri[0] |
| v1 = tri[2] - tri[0] |
| area = torch.cross(v0, v1).norm() / 2.0 |
|
|
| |
| if area < 5e-3: |
| return False |
| bary = TestPointMeshDistance._point_to_bary(point, tri) |
| inside = ((bary >= 0.0) * (bary <= 1.0)).all() |
| return inside |
|
|
| @staticmethod |
| def _point_to_edge_distance( |
| point: torch.Tensor, edge: torch.Tensor |
| ) -> torch.Tensor: |
| """ |
| Computes the squared euclidean distance of points to edges |
| Args: |
| point: FloatTensor of shape (3) |
| edge: FloatTensor of shape (2, 3) |
| Returns: |
| dist: FloatTensor of shape (1) |
| |
| If a, b are the start and end points of the segments, we |
| parametrize a point p as |
| x(t) = a + t * (b - a) |
| To find t which describes p we minimize (x(t) - p) ^ 2 |
| Note that p does not need to live in the space spanned by (a, b) |
| """ |
| s0, s1 = edge.unbind(0) |
|
|
| s01 = s1 - s0 |
| norm_s01 = s01.dot(s01) |
|
|
| same_edge = norm_s01 < TestPointMeshDistance.eps() |
| if same_edge: |
| dist = 0.5 * (point - s0).dot(point - s0) + 0.5 * (point - s1).dot( |
| point - s1 |
| ) |
| return dist |
|
|
| t = s01.dot(point - s0) / norm_s01 |
| t = torch.clamp(t, min=0.0, max=1.0) |
| x = s0 + t * s01 |
| dist = (x - point).dot(x - point) |
| return dist |
|
|
| @staticmethod |
| def _point_to_tri_distance(point: torch.Tensor, tri: torch.Tensor) -> torch.Tensor: |
| """ |
| Computes the squared euclidean distance of points to edges |
| Args: |
| point: FloatTensor of shape (3) |
| tri: FloatTensor of shape (3, 3) |
| Returns: |
| dist: FloatTensor of shape (1) |
| """ |
| a, b, c = tri.unbind(0) |
| cross = torch.cross(b - a, c - a) |
| norm = cross.norm() |
| normal = torch.nn.functional.normalize(cross, dim=0) |
|
|
| |
| |
| |
| tt = normal.dot(a) - normal.dot(point) |
| p0 = point + tt * normal |
| dist_p = tt * tt |
|
|
| |
| e01_dist = TestPointMeshDistance._point_to_edge_distance(point, tri[[0, 1]]) |
| e02_dist = TestPointMeshDistance._point_to_edge_distance(point, tri[[0, 2]]) |
| e12_dist = TestPointMeshDistance._point_to_edge_distance(point, tri[[1, 2]]) |
|
|
| with torch.no_grad(): |
| inside_tri = TestPointMeshDistance._is_inside_triangle(p0, tri) |
|
|
| if inside_tri and (norm > TestPointMeshDistance.eps()): |
| return dist_p |
| else: |
| if e01_dist.le(e02_dist) and e01_dist.le(e12_dist): |
| return e01_dist |
| elif e02_dist.le(e01_dist) and e02_dist.le(e12_dist): |
| return e02_dist |
| else: |
| return e12_dist |
|
|
| def test_point_edge_array_distance(self): |
| """ |
| Test CUDA implementation for PointEdgeArrayDistanceForward |
| & PointEdgeArrayDistanceBackward |
| """ |
| P, E = 16, 32 |
| device = get_random_cuda_device() |
| points = torch.rand((P, 3), dtype=torch.float32, device=device) |
| edges = torch.rand((E, 2, 3), dtype=torch.float32, device=device) |
|
|
| |
| same = torch.rand((E,), dtype=torch.float32, device=device) > 0.5 |
| edges[same, 1] = edges[same, 0].clone().detach() |
|
|
| points_cpu = points.clone().cpu() |
| edges_cpu = edges.clone().cpu() |
|
|
| points.requires_grad = True |
| edges.requires_grad = True |
| grad_dists = torch.rand((P, E), dtype=torch.float32, device=device) |
|
|
| |
| dists_naive = torch.zeros((P, E), dtype=torch.float32, device=device) |
| for p in range(P): |
| for e in range(E): |
| dist = self._point_to_edge_distance(points[p], edges[e]) |
| dists_naive[p, e] = dist |
|
|
| |
| dists_cuda = _C.point_edge_array_dist_forward(points, edges) |
| dists_cpu = _C.point_edge_array_dist_forward(points_cpu, edges_cpu) |
|
|
| |
| self.assertClose(dists_naive.cpu(), dists_cuda.cpu()) |
| self.assertClose(dists_naive.cpu(), dists_cpu) |
|
|
| |
| grad_points_cuda, grad_edges_cuda = _C.point_edge_array_dist_backward( |
| points, edges, grad_dists |
| ) |
| grad_points_cpu, grad_edges_cpu = _C.point_edge_array_dist_backward( |
| points_cpu, edges_cpu, grad_dists.cpu() |
| ) |
|
|
| dists_naive.backward(grad_dists) |
| grad_points_naive = points.grad.cpu() |
| grad_edges_naive = edges.grad.cpu() |
|
|
| |
| self.assertClose(grad_points_naive, grad_points_cuda.cpu()) |
| self.assertClose(grad_edges_naive, grad_edges_cuda.cpu()) |
| self.assertClose(grad_points_naive, grad_points_cpu) |
| self.assertClose(grad_edges_naive, grad_edges_cpu) |
|
|
| def test_point_edge_distance(self): |
| """ |
| Test CUDA implementation for PointEdgeDistanceForward |
| & PointEdgeDistanceBackward |
| """ |
| device = get_random_cuda_device() |
| N, V, F, P = 4, 32, 16, 24 |
| meshes, pcls = self.init_meshes_clouds(N, V, F, P, device=device) |
|
|
| |
| points_packed = pcls.points_packed().detach().clone() |
|
|
| points_first_idx = pcls.cloud_to_packed_first_idx() |
| max_p = pcls.num_points_per_cloud().max().item() |
|
|
| |
| verts_packed = meshes.verts_packed() |
| edges_packed = verts_packed[meshes.edges_packed()] |
| edges_packed = edges_packed.clone().detach() |
|
|
| edges_first_idx = meshes.mesh_to_edges_packed_first_idx() |
|
|
| |
| points_packed.requires_grad = True |
| edges_packed.requires_grad = True |
| grad_dists = torch.rand( |
| (points_packed.shape[0],), dtype=torch.float32, device=device |
| ) |
|
|
| |
| dists_cuda, idx_cuda = _C.point_edge_dist_forward( |
| points_packed, points_first_idx, edges_packed, edges_first_idx, max_p |
| ) |
| |
| grad_points_cuda, grad_edges_cuda = _C.point_edge_dist_backward( |
| points_packed, edges_packed, idx_cuda, grad_dists |
| ) |
| |
| dists_cpu, idx_cpu = _C.point_edge_dist_forward( |
| points_packed.cpu(), |
| points_first_idx.cpu(), |
| edges_packed.cpu(), |
| edges_first_idx.cpu(), |
| max_p, |
| ) |
|
|
| |
| |
| grad_points_cpu, grad_edges_cpu = _C.point_edge_dist_backward( |
| points_packed.cpu(), edges_packed.cpu(), idx_cuda.cpu(), grad_dists.cpu() |
| ) |
|
|
| |
| edges_list = packed_to_list(edges_packed, meshes.num_edges_per_mesh().tolist()) |
| dists_naive = [] |
| for i in range(N): |
| points = pcls.points_list()[i] |
| edges = edges_list[i] |
| dists_temp = torch.zeros( |
| (points.shape[0], edges.shape[0]), dtype=torch.float32, device=device |
| ) |
| for p in range(points.shape[0]): |
| for e in range(edges.shape[0]): |
| dist = self._point_to_edge_distance(points[p], edges[e]) |
| dists_temp[p, e] = dist |
| |
| |
| |
| |
| |
| |
|
|
| start = points_first_idx[i] |
| end = points_first_idx[i + 1] if i < N - 1 else points_packed.shape[0] |
|
|
| min_idx = idx_cuda[start:end] - edges_first_idx[i] |
| iidx = torch.arange(points.shape[0], device=device) |
| min_dist = dists_temp[iidx, min_idx] |
|
|
| dists_naive.append(min_dist) |
|
|
| dists_naive = torch.cat(dists_naive) |
|
|
| |
| self.assertClose(dists_naive.cpu(), dists_cuda.cpu()) |
| self.assertClose(dists_naive.cpu(), dists_cpu) |
|
|
| |
| dists_naive.backward(grad_dists) |
| grad_points_naive = torch.cat([cloud.grad for cloud in pcls.points_list()]) |
| grad_edges_naive = edges_packed.grad.cpu() |
|
|
| |
| self.assertClose(grad_points_naive.cpu(), grad_points_cuda.cpu(), atol=1e-7) |
| self.assertClose(grad_edges_naive, grad_edges_cuda.cpu(), atol=5e-7) |
| self.assertClose(grad_points_naive.cpu(), grad_points_cpu, atol=1e-7) |
| self.assertClose(grad_edges_naive, grad_edges_cpu, atol=5e-7) |
|
|
| def test_edge_point_distance(self): |
| """ |
| Test CUDA implementation for EdgePointDistanceForward |
| & EdgePointDistanceBackward |
| """ |
| device = get_random_cuda_device() |
| N, V, F, P = 4, 32, 16, 24 |
| meshes, pcls = self.init_meshes_clouds(N, V, F, P, device=device) |
|
|
| |
| points_packed = pcls.points_packed().detach().clone() |
|
|
| points_first_idx = pcls.cloud_to_packed_first_idx() |
|
|
| |
| verts_packed = meshes.verts_packed() |
| edges_packed = verts_packed[meshes.edges_packed()] |
| edges_packed = edges_packed.clone().detach() |
|
|
| edges_first_idx = meshes.mesh_to_edges_packed_first_idx() |
| max_e = meshes.num_edges_per_mesh().max().item() |
|
|
| |
| points_packed.requires_grad = True |
| edges_packed.requires_grad = True |
| grad_dists = torch.rand( |
| (edges_packed.shape[0],), dtype=torch.float32, device=device |
| ) |
|
|
| |
| dists_cuda, idx_cuda = _C.edge_point_dist_forward( |
| points_packed, points_first_idx, edges_packed, edges_first_idx, max_e |
| ) |
|
|
| |
| grad_points_cuda, grad_edges_cuda = _C.edge_point_dist_backward( |
| points_packed, edges_packed, idx_cuda, grad_dists |
| ) |
|
|
| |
| dists_cpu, idx_cpu = _C.edge_point_dist_forward( |
| points_packed.cpu(), |
| points_first_idx.cpu(), |
| edges_packed.cpu(), |
| edges_first_idx.cpu(), |
| max_e, |
| ) |
|
|
| |
| grad_points_cpu, grad_edges_cpu = _C.edge_point_dist_backward( |
| points_packed.cpu(), edges_packed.cpu(), idx_cpu, grad_dists.cpu() |
| ) |
|
|
| |
| edges_list = packed_to_list(edges_packed, meshes.num_edges_per_mesh().tolist()) |
| dists_naive = [] |
| for i in range(N): |
| points = pcls.points_list()[i] |
| edges = edges_list[i] |
| dists_temp = torch.zeros( |
| (edges.shape[0], points.shape[0]), dtype=torch.float32, device=device |
| ) |
| for e in range(edges.shape[0]): |
| for p in range(points.shape[0]): |
| dist = self._point_to_edge_distance(points[p], edges[e]) |
| dists_temp[e, p] = dist |
|
|
| |
| |
| |
| |
| |
| |
|
|
| start = edges_first_idx[i] |
| end = edges_first_idx[i + 1] if i < N - 1 else edges_packed.shape[0] |
|
|
| min_idx = idx_cuda.cpu()[start:end] - points_first_idx[i].cpu() |
| iidx = torch.arange(edges.shape[0], device=device) |
| min_dist = dists_temp[iidx, min_idx] |
|
|
| dists_naive.append(min_dist) |
|
|
| dists_naive = torch.cat(dists_naive) |
|
|
| |
| self.assertClose(dists_naive.cpu(), dists_cuda.cpu()) |
| self.assertClose(dists_naive.cpu(), dists_cpu) |
|
|
| |
| dists_naive.backward(grad_dists) |
| grad_points_naive = torch.cat([cloud.grad for cloud in pcls.points_list()]) |
| grad_edges_naive = edges_packed.grad.cpu() |
|
|
| |
| self.assertClose(grad_points_naive.cpu(), grad_points_cuda.cpu(), atol=1e-7) |
| self.assertClose(grad_edges_naive, grad_edges_cuda.cpu(), atol=5e-7) |
| self.assertClose(grad_points_naive.cpu(), grad_points_cpu, atol=1e-7) |
| self.assertClose(grad_edges_naive, grad_edges_cpu, atol=5e-7) |
|
|
| def test_point_mesh_edge_distance(self): |
| """ |
| Test point_mesh_edge_distance from pytorch3d.loss |
| """ |
| device = get_random_cuda_device() |
| N, V, F, P = 4, 32, 16, 24 |
| meshes, pcls = self.init_meshes_clouds(N, V, F, P, device=device) |
|
|
| |
| verts_op = [verts.clone().detach() for verts in meshes.verts_list()] |
| for i in range(N): |
| verts_op[i].requires_grad = True |
|
|
| faces_op = [faces.clone().detach() for faces in meshes.faces_list()] |
| meshes_op = Meshes(verts=verts_op, faces=faces_op) |
| points_op = [points.clone().detach() for points in pcls.points_list()] |
| for i in range(N): |
| points_op[i].requires_grad = True |
| pcls_op = Pointclouds(points_op) |
|
|
| |
| loss_op = point_mesh_edge_distance(meshes_op, pcls_op) |
|
|
| |
| edges_packed = meshes.edges_packed() |
| edges_list = packed_to_list(edges_packed, meshes.num_edges_per_mesh().tolist()) |
| loss_naive = torch.zeros(N, dtype=torch.float32, device=device) |
| for i in range(N): |
| points = pcls.points_list()[i] |
| verts = meshes.verts_list()[i] |
| v_first_idx = meshes.mesh_to_verts_packed_first_idx()[i] |
| edges = verts[edges_list[i] - v_first_idx] |
|
|
| num_p = points.shape[0] |
| num_e = edges.shape[0] |
| dists = torch.zeros((num_p, num_e), dtype=torch.float32, device=device) |
| for p in range(num_p): |
| for e in range(num_e): |
| dist = self._point_to_edge_distance(points[p], edges[e]) |
| dists[p, e] = dist |
|
|
| min_dist_p, min_idx_p = dists.min(1) |
| min_dist_e, min_idx_e = dists.min(0) |
|
|
| loss_naive[i] = min_dist_p.mean() + min_dist_e.mean() |
| loss_naive = loss_naive.mean() |
|
|
| |
| |
| |
| |
|
|
| |
| self.assertClose(loss_op, loss_naive) |
|
|
| |
| rand_val = torch.rand(1).item() |
| grad_dist = torch.tensor(rand_val, dtype=torch.float32, device=device) |
|
|
| loss_naive.backward(grad_dist) |
| loss_op.backward(grad_dist) |
|
|
| |
| for i in range(N): |
| self.assertClose( |
| meshes.verts_list()[i].grad, meshes_op.verts_list()[i].grad |
| ) |
| self.assertClose(pcls.points_list()[i].grad, pcls_op.points_list()[i].grad) |
|
|
| def test_point_face_array_distance(self): |
| """ |
| Test CUDA implementation for PointFaceArrayDistanceForward |
| & PointFaceArrayDistanceBackward |
| """ |
| P, T = 16, 32 |
| device = get_random_cuda_device() |
| points = torch.rand((P, 3), dtype=torch.float32, device=device) |
| tris = torch.rand((T, 3, 3), dtype=torch.float32, device=device) |
| points_cpu = points.clone().cpu() |
| tris_cpu = tris.clone().cpu() |
|
|
| points.requires_grad = True |
| tris.requires_grad = True |
| grad_dists = torch.rand((P, T), dtype=torch.float32, device=device) |
|
|
| points_temp = points.clone().detach() |
| points_temp.requires_grad = True |
| tris_temp = tris.clone().detach() |
| tris_temp.requires_grad = True |
|
|
| |
| dists_naive = torch.zeros((P, T), dtype=torch.float32, device=device) |
| for p in range(P): |
| for t in range(T): |
| dist = self._point_to_tri_distance(points[p], tris[t]) |
| dists_naive[p, t] = dist |
|
|
| |
| dists_naive.backward(grad_dists) |
| grad_points_naive = points.grad.cpu() |
| grad_tris_naive = tris.grad.cpu() |
|
|
| |
| dists_cuda = _C.point_face_array_dist_forward( |
| points, tris, TestPointMeshDistance.min_triangle_area() |
| ) |
| dists_cpu = _C.point_face_array_dist_forward( |
| points_cpu, tris_cpu, TestPointMeshDistance.min_triangle_area() |
| ) |
|
|
| |
| self.assertClose(dists_naive.cpu(), dists_cuda.cpu()) |
| self.assertClose(dists_naive.cpu(), dists_cpu) |
|
|
| |
| grad_points_cuda, grad_tris_cuda = _C.point_face_array_dist_backward( |
| points, tris, grad_dists, TestPointMeshDistance.min_triangle_area() |
| ) |
| grad_points_cpu, grad_tris_cpu = _C.point_face_array_dist_backward( |
| points_cpu, |
| tris_cpu, |
| grad_dists.cpu(), |
| TestPointMeshDistance.min_triangle_area(), |
| ) |
|
|
| |
| self.assertClose(grad_points_naive, grad_points_cuda.cpu()) |
| self.assertClose(grad_tris_naive, grad_tris_cuda.cpu(), atol=5e-6) |
| self.assertClose(grad_points_naive, grad_points_cpu) |
| self.assertClose(grad_tris_naive, grad_tris_cpu, atol=5e-6) |
|
|
| def test_point_face_distance(self): |
| """ |
| Test CUDA implementation for PointFaceDistanceForward |
| & PointFaceDistanceBackward |
| """ |
| device = get_random_cuda_device() |
| N, V, F, P = 4, 32, 16, 24 |
| meshes, pcls = self.init_meshes_clouds(N, V, F, P, device=device) |
|
|
| |
| points_packed = pcls.points_packed().detach().clone() |
|
|
| points_first_idx = pcls.cloud_to_packed_first_idx() |
| max_p = pcls.num_points_per_cloud().max().item() |
|
|
| |
| verts_packed = meshes.verts_packed() |
| faces_packed = verts_packed[meshes.faces_packed()] |
| faces_packed = faces_packed.clone().detach() |
|
|
| faces_first_idx = meshes.mesh_to_faces_packed_first_idx() |
|
|
| |
| points_packed.requires_grad = True |
| faces_packed.requires_grad = True |
| grad_dists = torch.rand( |
| (points_packed.shape[0],), dtype=torch.float32, device=device |
| ) |
|
|
| |
| dists_cuda, idx_cuda = _C.point_face_dist_forward( |
| points_packed, |
| points_first_idx, |
| faces_packed, |
| faces_first_idx, |
| max_p, |
| TestPointMeshDistance.min_triangle_area(), |
| ) |
|
|
| |
| grad_points_cuda, grad_faces_cuda = _C.point_face_dist_backward( |
| points_packed, |
| faces_packed, |
| idx_cuda, |
| grad_dists, |
| TestPointMeshDistance.min_triangle_area(), |
| ) |
|
|
| |
| dists_cpu, idx_cpu = _C.point_face_dist_forward( |
| points_packed.cpu(), |
| points_first_idx.cpu(), |
| faces_packed.cpu(), |
| faces_first_idx.cpu(), |
| max_p, |
| TestPointMeshDistance.min_triangle_area(), |
| ) |
|
|
| |
| |
| grad_points_cpu, grad_faces_cpu = _C.point_face_dist_backward( |
| points_packed.cpu(), |
| faces_packed.cpu(), |
| idx_cuda.cpu(), |
| grad_dists.cpu(), |
| TestPointMeshDistance.min_triangle_area(), |
| ) |
|
|
| |
| faces_list = packed_to_list(faces_packed, meshes.num_faces_per_mesh().tolist()) |
| dists_naive = [] |
| for i in range(N): |
| points = pcls.points_list()[i] |
| tris = faces_list[i] |
| dists_temp = torch.zeros( |
| (points.shape[0], tris.shape[0]), dtype=torch.float32, device=device |
| ) |
| for p in range(points.shape[0]): |
| for t in range(tris.shape[0]): |
| dist = self._point_to_tri_distance(points[p], tris[t]) |
| dists_temp[p, t] = dist |
|
|
| |
| |
| |
| |
| |
| |
|
|
| start = points_first_idx[i] |
| end = points_first_idx[i + 1] if i < N - 1 else points_packed.shape[0] |
|
|
| min_idx = idx_cuda.cpu()[start:end] - faces_first_idx[i].cpu() |
| iidx = torch.arange(points.shape[0], device=device) |
| min_dist = dists_temp[iidx, min_idx] |
|
|
| dists_naive.append(min_dist) |
|
|
| dists_naive = torch.cat(dists_naive) |
|
|
| |
| self.assertClose(dists_naive.cpu(), dists_cuda.cpu()) |
| self.assertClose(dists_naive.cpu(), dists_cpu) |
|
|
| |
| dists_naive.backward(grad_dists) |
| grad_points_naive = torch.cat([cloud.grad for cloud in pcls.points_list()]) |
| grad_faces_naive = faces_packed.grad.cpu() |
|
|
| |
| self.assertClose(grad_points_naive.cpu(), grad_points_cuda.cpu(), atol=1e-7) |
| self.assertClose(grad_faces_naive, grad_faces_cuda.cpu(), atol=5e-7) |
| self.assertClose(grad_points_naive.cpu(), grad_points_cpu, atol=1e-7) |
| self.assertClose(grad_faces_naive, grad_faces_cpu, atol=5e-7) |
|
|
| def test_face_point_distance(self): |
| """ |
| Test CUDA implementation for FacePointDistanceForward |
| & FacePointDistanceBackward |
| """ |
| device = get_random_cuda_device() |
| N, V, F, P = 4, 32, 16, 24 |
| meshes, pcls = self.init_meshes_clouds(N, V, F, P, device=device) |
|
|
| |
| points_packed = pcls.points_packed().detach().clone() |
|
|
| points_first_idx = pcls.cloud_to_packed_first_idx() |
|
|
| |
| verts_packed = meshes.verts_packed() |
| faces_packed = verts_packed[meshes.faces_packed()] |
| faces_packed = faces_packed.clone().detach() |
|
|
| faces_first_idx = meshes.mesh_to_faces_packed_first_idx() |
| max_f = meshes.num_faces_per_mesh().max().item() |
|
|
| |
| points_packed.requires_grad = True |
| faces_packed.requires_grad = True |
| grad_dists = torch.rand( |
| (faces_packed.shape[0],), dtype=torch.float32, device=device |
| ) |
|
|
| |
| dists_cuda, idx_cuda = _C.face_point_dist_forward( |
| points_packed, |
| points_first_idx, |
| faces_packed, |
| faces_first_idx, |
| max_f, |
| TestPointMeshDistance.min_triangle_area(), |
| ) |
|
|
| |
| grad_points_cuda, grad_faces_cuda = _C.face_point_dist_backward( |
| points_packed, |
| faces_packed, |
| idx_cuda, |
| grad_dists, |
| TestPointMeshDistance.min_triangle_area(), |
| ) |
|
|
| |
| dists_cpu, idx_cpu = _C.face_point_dist_forward( |
| points_packed.cpu(), |
| points_first_idx.cpu(), |
| faces_packed.cpu(), |
| faces_first_idx.cpu(), |
| max_f, |
| TestPointMeshDistance.min_triangle_area(), |
| ) |
|
|
| |
| grad_points_cpu, grad_faces_cpu = _C.face_point_dist_backward( |
| points_packed.cpu(), |
| faces_packed.cpu(), |
| idx_cpu, |
| grad_dists.cpu(), |
| TestPointMeshDistance.min_triangle_area(), |
| ) |
|
|
| |
| faces_list = packed_to_list(faces_packed, meshes.num_faces_per_mesh().tolist()) |
| dists_naive = [] |
| for i in range(N): |
| points = pcls.points_list()[i] |
| tris = faces_list[i] |
| dists_temp = torch.zeros( |
| (tris.shape[0], points.shape[0]), dtype=torch.float32, device=device |
| ) |
| for t in range(tris.shape[0]): |
| for p in range(points.shape[0]): |
| dist = self._point_to_tri_distance(points[p], tris[t]) |
| dists_temp[t, p] = dist |
|
|
| |
| |
| |
| |
| |
| |
|
|
| start = faces_first_idx[i] |
| end = faces_first_idx[i + 1] if i < N - 1 else faces_packed.shape[0] |
|
|
| min_idx = idx_cuda.cpu()[start:end] - points_first_idx[i].cpu() |
| iidx = torch.arange(tris.shape[0], device=device) |
| min_dist = dists_temp[iidx, min_idx] |
|
|
| dists_naive.append(min_dist) |
|
|
| dists_naive = torch.cat(dists_naive) |
|
|
| |
| self.assertClose(dists_naive.cpu(), dists_cuda.cpu()) |
| self.assertClose(dists_naive.cpu(), dists_cpu) |
|
|
| |
| dists_naive.backward(grad_dists) |
| grad_points_naive = torch.cat([cloud.grad for cloud in pcls.points_list()]) |
| grad_faces_naive = faces_packed.grad |
|
|
| |
| self.assertClose(grad_points_naive.cpu(), grad_points_cuda.cpu(), atol=1e-7) |
| self.assertClose(grad_faces_naive.cpu(), grad_faces_cuda.cpu(), atol=5e-7) |
| self.assertClose(grad_points_naive.cpu(), grad_points_cpu, atol=1e-7) |
| self.assertClose(grad_faces_naive.cpu(), grad_faces_cpu, atol=5e-7) |
|
|
| def test_point_mesh_face_distance(self): |
| """ |
| Test point_mesh_face_distance from pytorch3d.loss |
| """ |
| device = get_random_cuda_device() |
| N, V, F, P = 4, 32, 16, 24 |
| meshes, pcls = self.init_meshes_clouds(N, V, F, P, device=device) |
|
|
| |
| verts_op = [verts.clone().detach() for verts in meshes.verts_list()] |
| for i in range(N): |
| verts_op[i].requires_grad = True |
|
|
| faces_op = [faces.clone().detach() for faces in meshes.faces_list()] |
| meshes_op = Meshes(verts=verts_op, faces=faces_op) |
| points_op = [points.clone().detach() for points in pcls.points_list()] |
| for i in range(N): |
| points_op[i].requires_grad = True |
| pcls_op = Pointclouds(points_op) |
|
|
| |
| loss_naive = torch.zeros(N, dtype=torch.float32, device=device) |
| for i in range(N): |
| points = pcls.points_list()[i] |
| verts = meshes.verts_list()[i] |
| faces = meshes.faces_list()[i] |
| tris = verts[faces] |
|
|
| num_p = points.shape[0] |
| num_t = tris.shape[0] |
| dists = torch.zeros((num_p, num_t), dtype=torch.float32, device=device) |
| for p in range(num_p): |
| for t in range(num_t): |
| dist = self._point_to_tri_distance(points[p], tris[t]) |
| dists[p, t] = dist |
|
|
| min_dist_p, min_idx_p = dists.min(1) |
| min_dist_t, min_idx_t = dists.min(0) |
|
|
| loss_naive[i] = min_dist_p.mean() + min_dist_t.mean() |
| loss_naive = loss_naive.mean() |
|
|
| |
| loss_op = point_mesh_face_distance(meshes_op, pcls_op) |
|
|
| |
| self.assertClose(loss_op, loss_naive) |
|
|
| |
| rand_val = torch.rand(1).item() |
| grad_dist = torch.tensor(rand_val, dtype=torch.float32, device=device) |
|
|
| loss_naive.backward(grad_dist) |
| loss_op.backward(grad_dist) |
|
|
| |
| for i in range(N): |
| self.assertClose( |
| meshes.verts_list()[i].grad, meshes_op.verts_list()[i].grad |
| ) |
| self.assertClose(pcls.points_list()[i].grad, pcls_op.points_list()[i].grad) |
|
|
| def test_small_faces_case(self): |
| for device in [torch.device("cpu"), torch.device("cuda:0")]: |
| mesh_vertices = torch.tensor( |
| [ |
| [-0.0021, -0.3769, 0.7146], |
| [-0.0161, -0.3771, 0.7146], |
| [-0.0021, -0.3771, 0.7147], |
| ], |
| dtype=torch.float32, |
| device=device, |
| ) |
| mesh1_faces = torch.tensor([[0, 2, 1]], device=device) |
| mesh2_faces = torch.tensor([[2, 0, 1]], device=device) |
| pcd_points = torch.tensor([[-0.3623, -0.5340, 0.7727]], device=device) |
| mesh1 = Meshes(verts=[mesh_vertices], faces=[mesh1_faces]) |
| mesh2 = Meshes(verts=[mesh_vertices], faces=[mesh2_faces]) |
| pcd = Pointclouds(points=[pcd_points]) |
|
|
| loss1 = point_mesh_face_distance(mesh1, pcd) |
| loss2 = point_mesh_face_distance(mesh2, pcd) |
| self.assertClose(loss1, loss2) |
|
|
| @staticmethod |
| def point_mesh_edge(N: int, V: int, F: int, P: int, device: str): |
| device = torch.device(device) |
| meshes, pcls = TestPointMeshDistance.init_meshes_clouds( |
| N, V, F, P, device=device |
| ) |
| torch.cuda.synchronize() |
|
|
| def loss(): |
| point_mesh_edge_distance(meshes, pcls) |
| torch.cuda.synchronize() |
|
|
| return loss |
|
|
| @staticmethod |
| def point_mesh_face(N: int, V: int, F: int, P: int, device: str): |
| device = torch.device(device) |
| meshes, pcls = TestPointMeshDistance.init_meshes_clouds( |
| N, V, F, P, device=device |
| ) |
| torch.cuda.synchronize() |
|
|
| def loss(): |
| point_mesh_face_distance(meshes, pcls) |
| torch.cuda.synchronize() |
|
|
| return loss |
|
|