Skip to content

Commit c6519f2

Browse files
bottlerfacebook-github-bot
authored andcommitted
chamfer for empty pointclouds #1174
Summary: Fix divide by zero for empty pointcloud in chamfer. Also for empty batches. In process, needed to regularize num_points_per_cloud for empty batches. Reviewed By: kjchalup Differential Revision: D36311330 fbshipit-source-id: 3378ab738bee77ecc286f2110a5c8dc445960340
1 parent a42a89a commit c6519f2

File tree

5 files changed

+24
-8
lines changed

5 files changed

+24
-8
lines changed

pytorch3d/implicitron/models/model_dbir.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def forward(
9999
mask_fg[is_known_idx],
100100
)
101101

102-
pcl_size = int(point_cloud.num_points_per_cloud())
102+
pcl_size = point_cloud.num_points_per_cloud().item()
103103
if (self.max_points > 0) and (pcl_size > self.max_points):
104104
prm = torch.randperm(pcl_size)[: self.max_points]
105105
point_cloud = Pointclouds(

pytorch3d/loss/chamfer.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,13 @@ def chamfer_distance(
197197
cham_norm_x = cham_norm_x.sum(1) # (N,)
198198
cham_norm_y = cham_norm_y.sum(1) # (N,)
199199
if point_reduction == "mean":
200-
cham_x /= x_lengths
201-
cham_y /= y_lengths
200+
x_lengths_clamped = x_lengths.clamp(min=1)
201+
y_lengths_clamped = y_lengths.clamp(min=1)
202+
cham_x /= x_lengths_clamped
203+
cham_y /= y_lengths_clamped
202204
if return_normals:
203-
cham_norm_x /= x_lengths
204-
cham_norm_y /= y_lengths
205+
cham_norm_x /= x_lengths_clamped
206+
cham_norm_y /= y_lengths_clamped
205207

206208
if batch_reduction is not None:
207209
# batch_reduction == "sum"
@@ -211,7 +213,7 @@ def chamfer_distance(
211213
cham_norm_x = cham_norm_x.sum()
212214
cham_norm_y = cham_norm_y.sum()
213215
if batch_reduction == "mean":
214-
div = weights.sum() if weights is not None else N
216+
div = weights.sum() if weights is not None else max(N, 1)
215217
cham_x /= div
216218
cham_y /= div
217219
if return_normals:

pytorch3d/loss/point_mesh_distance.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ def point_mesh_edge_distance(meshes: Meshes, pcls: Pointclouds):
303303
# weight each example by the inverse of number of points in the example
304304
point_to_cloud_idx = pcls.packed_to_cloud_idx() # (sum(P_i), )
305305
num_points_per_cloud = pcls.num_points_per_cloud() # (N,)
306+
# pyre-ignore[16]: `torch.Tensor` has no attribute `gather`
306307
weights_p = num_points_per_cloud.gather(0, point_to_cloud_idx)
307308
weights_p = 1.0 / weights_p.float()
308309
point_to_edge = point_to_edge * weights_p
@@ -377,6 +378,7 @@ def point_mesh_face_distance(
377378
# weight each example by the inverse of number of points in the example
378379
point_to_cloud_idx = pcls.packed_to_cloud_idx() # (sum(P_i),)
379380
num_points_per_cloud = pcls.num_points_per_cloud() # (N,)
381+
# pyre-ignore[16]: `torch.Tensor` has no attribute `gather`
380382
weights_p = num_points_per_cloud.gather(0, point_to_cloud_idx)
381383
weights_p = 1.0 / weights_p.float()
382384
point_to_face = point_to_face * weights_p

pytorch3d/structures/pointclouds.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,6 @@ def __init__(self, points, normals=None, features=None) -> None:
185185
self._points_list = points
186186
self._N = len(self._points_list)
187187
self.valid = torch.zeros((self._N,), dtype=torch.bool, device=self.device)
188-
self._num_points_per_cloud = []
189188

190189
if self._N > 0:
191190
self.device = self._points_list[0].device
@@ -208,6 +207,8 @@ def __init__(self, points, normals=None, features=None) -> None:
208207
if len(num_points_per_cloud.unique()) == 1:
209208
self.equisized = True
210209
self._num_points_per_cloud = num_points_per_cloud
210+
else:
211+
self._num_points_per_cloud = torch.tensor([], dtype=torch.int64)
211212

212213
elif torch.is_tensor(points):
213214
if points.dim() != 3 or points.shape[2] != 3:
@@ -525,7 +526,7 @@ def cloud_to_packed_first_idx(self):
525526
self._compute_packed()
526527
return self._cloud_to_packed_first_idx
527528

528-
def num_points_per_cloud(self):
529+
def num_points_per_cloud(self) -> torch.Tensor:
529530
"""
530531
Return a 1D tensor x with length equal to the number of clouds giving
531532
the number of points in each cloud.

tests/test_chamfer.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,17 @@ def test_invalid_norm(self):
778778
with self.assertRaisesRegex(ValueError, "Support for 1 or 2 norm."):
779779
chamfer_distance(p1, p2, norm=3)
780780

781+
def test_empty_clouds(self):
782+
# Check that point_reduction doesn't divide by zero
783+
points1 = Pointclouds(points=[torch.zeros(0, 3), torch.zeros(10, 3)])
784+
points2 = Pointclouds(points=torch.ones(2, 40, 3))
785+
loss, _ = chamfer_distance(points1, points2, batch_reduction=None)
786+
self.assertClose(loss, torch.tensor([0.0, 6.0]))
787+
788+
# Check that batch_reduction doesn't divide by zero
789+
loss2, _ = chamfer_distance(Pointclouds([]), Pointclouds([]))
790+
self.assertClose(loss2, torch.tensor(0.0))
791+
781792
@staticmethod
782793
def chamfer_with_init(
783794
batch_size: int,

0 commit comments

Comments
 (0)