Linly-Talker / pytorch3d /tests /test_mesh_laplacian_smoothing.py
linxianzhong0128's picture
Upload folder using huggingface_hub
7088d16 verified
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.
import unittest
import torch
from pytorch3d.loss.mesh_laplacian_smoothing import mesh_laplacian_smoothing
from pytorch3d.structures.meshes import Meshes
class TestLaplacianSmoothing(unittest.TestCase):
@staticmethod
def laplacian_smoothing_naive_uniform(meshes):
"""
Naive implementation of laplacian smoothing with uniform weights.
"""
verts_packed = meshes.verts_packed() # (sum(V_n), 3)
faces_packed = meshes.faces_packed() # (sum(F_n), 3)
V = verts_packed.shape[0]
L = torch.zeros((V, V), dtype=torch.float32, device=meshes.device)
# filling L with the face pairs should be the same as edge pairs
for f in faces_packed:
L[f[0], f[1]] = 1
L[f[0], f[2]] = 1
L[f[1], f[2]] = 1
# symetric
L[f[1], f[0]] = 1
L[f[2], f[0]] = 1
L[f[2], f[1]] = 1
norm_w = L.sum(dim=1, keepdims=True)
idx = norm_w > 0
norm_w[idx] = 1.0 / norm_w[idx]
loss = (L.mm(verts_packed) * norm_w - verts_packed).norm(dim=1)
weights = torch.zeros(V, dtype=torch.float32, device=meshes.device)
for v in range(V):
weights[v] = meshes.num_verts_per_mesh()[
meshes.verts_packed_to_mesh_idx()[v]
]
weights = 1.0 / weights
loss = loss * weights
return loss.sum() / len(meshes)
@staticmethod
def laplacian_smoothing_naive_cot(meshes, method: str = "cot"):
"""
Naive implementation of laplacian smoothing wit cotangent weights.
"""
verts_packed = meshes.verts_packed() # (sum(V_n), 3)
faces_packed = meshes.faces_packed() # (sum(F_n), 3)
V = verts_packed.shape[0]
L = torch.zeros((V, V), dtype=torch.float32, device=meshes.device)
inv_areas = torch.zeros((V, 1), dtype=torch.float32, device=meshes.device)
for f in faces_packed:
v0 = verts_packed[f[0], :]
v1 = verts_packed[f[1], :]
v2 = verts_packed[f[2], :]
A = (v1 - v2).norm()
B = (v0 - v2).norm()
C = (v0 - v1).norm()
s = 0.5 * (A + B + C)
face_area = (s * (s - A) * (s - B) * (s - C)).clamp_(min=1e-12).sqrt()
inv_areas[f[0]] += face_area
inv_areas[f[1]] += face_area
inv_areas[f[2]] += face_area
A2, B2, C2 = A * A, B * B, C * C
cota = (B2 + C2 - A2) / face_area / 4.0
cotb = (A2 + C2 - B2) / face_area / 4.0
cotc = (A2 + B2 - C2) / face_area / 4.0
L[f[1], f[2]] += cota
L[f[2], f[0]] += cotb
L[f[0], f[1]] += cotc
# symetric
L[f[2], f[1]] += cota
L[f[0], f[2]] += cotb
L[f[1], f[0]] += cotc
idx = inv_areas > 0
inv_areas[idx] = 1.0 / inv_areas[idx]
norm_w = L.sum(dim=1, keepdims=True)
L_sum = norm_w.clone()
idx = norm_w > 0
norm_w[idx] = 1.0 / norm_w[idx]
if method == "cotcurv":
loss = (L.mm(verts_packed) - L_sum * verts_packed) * inv_areas * 0.25
loss = loss.norm(dim=1)
else:
loss = L.mm(verts_packed) * norm_w - verts_packed
loss = loss.norm(dim=1)
weights = torch.zeros(V, dtype=torch.float32, device=meshes.device)
for v in range(V):
weights[v] = meshes.num_verts_per_mesh()[
meshes.verts_packed_to_mesh_idx()[v]
]
weights = 1.0 / weights
loss = loss * weights
return loss.sum() / len(meshes)
@staticmethod
def init_meshes(num_meshes: int = 10, num_verts: int = 1000, num_faces: int = 3000):
device = torch.device("cuda:0")
verts_list = []
faces_list = []
for _ in range(num_meshes):
verts = (
torch.rand((num_verts, 3), dtype=torch.float32, device=device) * 2.0
- 1.0
) # verts in the space of [-1, 1]
faces = torch.stack(
[
torch.randperm(num_verts, device=device)[:3]
for _ in range(num_faces)
],
dim=0,
)
# avoids duplicate vertices in a face
verts_list.append(verts)
faces_list.append(faces)
meshes = Meshes(verts_list, faces_list)
return meshes
def test_laplacian_smoothing_uniform(self):
"""
Test Laplacian Smoothing with uniform weights.
"""
meshes = TestLaplacianSmoothing.init_meshes(10, 100, 300)
# feats in list
out = mesh_laplacian_smoothing(meshes, method="uniform")
naive_out = TestLaplacianSmoothing.laplacian_smoothing_naive_uniform(meshes)
self.assertTrue(torch.allclose(out, naive_out))
def test_laplacian_smoothing_cot(self):
"""
Test Laplacian Smoothing with cot weights.
"""
meshes = TestLaplacianSmoothing.init_meshes(10, 100, 300)
# feats in list
out = mesh_laplacian_smoothing(meshes, method="cot")
naive_out = TestLaplacianSmoothing.laplacian_smoothing_naive_cot(
meshes, method="cot"
)
self.assertTrue(torch.allclose(out, naive_out))
def test_laplacian_smoothing_cotcurv(self):
"""
Test Laplacian Smoothing with cotcurv weights.
"""
meshes = TestLaplacianSmoothing.init_meshes(10, 100, 300)
# feats in list
out = mesh_laplacian_smoothing(meshes, method="cotcurv")
naive_out = TestLaplacianSmoothing.laplacian_smoothing_naive_cot(
meshes, method="cotcurv"
)
self.assertTrue(torch.allclose(out, naive_out))
@staticmethod
def laplacian_smoothing_with_init(
num_meshes: int, num_verts: int, num_faces: int, device: str = "cpu"
):
device = torch.device(device)
verts_list = []
faces_list = []
for _ in range(num_meshes):
verts = torch.rand((num_verts, 3), dtype=torch.float32, device=device)
faces = torch.randint(
num_verts, size=(num_faces, 3), dtype=torch.int64, device=device
)
verts_list.append(verts)
faces_list.append(faces)
meshes = Meshes(verts_list, faces_list)
torch.cuda.synchronize()
def smooth():
mesh_laplacian_smoothing(meshes, method="cotcurv")
torch.cuda.synchronize()
return smooth