mirror of
https://github.com/facebookresearch/pytorch3d.git
synced 2025-08-02 20:02:49 +08:00
initialize pointcloud from list containing Nones
Summary: The following snippet should work in more cases. point_cloud = Pointclouds( [pcl.points_packed() for pcl in point_clouds], features=[pcl.features_packed() for pcl in point_clouds], ) We therefore allow features and normals inputs to be lists which contain some (but not all) Nones. The initialization of a Pointclouds from empty data is also made a bit better now at working out how many feature channels there are. Reviewed By: davnov134 Differential Revision: D31795089 fbshipit-source-id: 54bf941ba80672d699ffd5ac28927740e830f8ab
This commit is contained in:
parent
9640560541
commit
fc4dd80208
@ -266,30 +266,8 @@ class Pointclouds:
|
|||||||
aux_input_C = None
|
aux_input_C = None
|
||||||
|
|
||||||
if isinstance(aux_input, list):
|
if isinstance(aux_input, list):
|
||||||
if len(aux_input) != self._N:
|
return self._parse_auxiliary_input_list(aux_input)
|
||||||
raise ValueError("Points and auxiliary input must be the same length.")
|
if torch.is_tensor(aux_input):
|
||||||
for p, d in zip(self._num_points_per_cloud, aux_input):
|
|
||||||
if p != d.shape[0]:
|
|
||||||
raise ValueError(
|
|
||||||
"A cloud has mismatched numbers of points and inputs"
|
|
||||||
)
|
|
||||||
if d.device != self.device:
|
|
||||||
raise ValueError(
|
|
||||||
"All auxiliary inputs must be on the same device as the points."
|
|
||||||
)
|
|
||||||
if p > 0:
|
|
||||||
if d.dim() != 2:
|
|
||||||
raise ValueError(
|
|
||||||
"A cloud auxiliary input must be of shape PxC or empty"
|
|
||||||
)
|
|
||||||
if aux_input_C is None:
|
|
||||||
aux_input_C = d.shape[1]
|
|
||||||
if aux_input_C != d.shape[1]:
|
|
||||||
raise ValueError(
|
|
||||||
"The clouds must have the same number of channels"
|
|
||||||
)
|
|
||||||
return aux_input, None, aux_input_C
|
|
||||||
elif torch.is_tensor(aux_input):
|
|
||||||
if aux_input.dim() != 3:
|
if aux_input.dim() != 3:
|
||||||
raise ValueError("Auxiliary input tensor has incorrect dimensions.")
|
raise ValueError("Auxiliary input tensor has incorrect dimensions.")
|
||||||
if self._N != aux_input.shape[0]:
|
if self._N != aux_input.shape[0]:
|
||||||
@ -312,6 +290,72 @@ class Pointclouds:
|
|||||||
points in a cloud."
|
points in a cloud."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _parse_auxiliary_input_list(
|
||||||
|
self, aux_input: list
|
||||||
|
) -> Tuple[Optional[List[torch.Tensor]], None, Optional[int]]:
|
||||||
|
"""
|
||||||
|
Interpret the auxiliary inputs (normals, features) given to __init__,
|
||||||
|
if a list.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
aux_input:
|
||||||
|
- List where each element is a tensor of shape (num_points, C)
|
||||||
|
containing the features for the points in the cloud.
|
||||||
|
For normals, C = 3
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
3-element tuple of list, padded=None, num_channels.
|
||||||
|
If aux_input is list, then padded is None. If aux_input is a tensor,
|
||||||
|
then list is None.
|
||||||
|
"""
|
||||||
|
aux_input_C = None
|
||||||
|
good_empty = None
|
||||||
|
needs_fixing = False
|
||||||
|
|
||||||
|
if len(aux_input) != self._N:
|
||||||
|
raise ValueError("Points and auxiliary input must be the same length.")
|
||||||
|
for p, d in zip(self._num_points_per_cloud, aux_input):
|
||||||
|
valid_but_empty = p == 0 and d is not None and d.ndim == 2
|
||||||
|
if p > 0 or valid_but_empty:
|
||||||
|
if p != d.shape[0]:
|
||||||
|
raise ValueError(
|
||||||
|
"A cloud has mismatched numbers of points and inputs"
|
||||||
|
)
|
||||||
|
if d.dim() != 2:
|
||||||
|
raise ValueError(
|
||||||
|
"A cloud auxiliary input must be of shape PxC or empty"
|
||||||
|
)
|
||||||
|
if aux_input_C is None:
|
||||||
|
aux_input_C = d.shape[1]
|
||||||
|
elif aux_input_C != d.shape[1]:
|
||||||
|
raise ValueError("The clouds must have the same number of channels")
|
||||||
|
if d.device != self.device:
|
||||||
|
raise ValueError(
|
||||||
|
"All auxiliary inputs must be on the same device as the points."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
needs_fixing = True
|
||||||
|
|
||||||
|
if aux_input_C is None:
|
||||||
|
# We found nothing useful
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
|
# If we have empty but "wrong" inputs we want to store "fixed" versions.
|
||||||
|
if needs_fixing:
|
||||||
|
if good_empty is None:
|
||||||
|
good_empty = torch.zeros((0, aux_input_C), device=self.device)
|
||||||
|
aux_input_out = []
|
||||||
|
for p, d in zip(self._num_points_per_cloud, aux_input):
|
||||||
|
valid_but_empty = p == 0 and d is not None and d.ndim == 2
|
||||||
|
if p > 0 or valid_but_empty:
|
||||||
|
aux_input_out.append(d)
|
||||||
|
else:
|
||||||
|
aux_input_out.append(good_empty)
|
||||||
|
else:
|
||||||
|
aux_input_out = aux_input
|
||||||
|
|
||||||
|
return aux_input_out, None, aux_input_C
|
||||||
|
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return self._N
|
return self._N
|
||||||
|
|
||||||
|
@ -383,6 +383,43 @@ class TestPointclouds(TestCaseMixin, unittest.TestCase):
|
|||||||
self.assertTrue(features_padded[n, p:, :].eq(0).all())
|
self.assertTrue(features_padded[n, p:, :].eq(0).all())
|
||||||
self.assertTrue(points_per_cloud[n] == p)
|
self.assertTrue(points_per_cloud[n] == p)
|
||||||
|
|
||||||
|
def test_list_someempty(self):
|
||||||
|
# We want
|
||||||
|
# point_cloud = Pointclouds(
|
||||||
|
# [pcl.points_packed() for pcl in point_clouds],
|
||||||
|
# features=[pcl.features_packed() for pcl in point_clouds],
|
||||||
|
# )
|
||||||
|
# to work if point_clouds is a list of pointclouds with some empty and some not.
|
||||||
|
points_list = [torch.rand(30, 3), torch.zeros(0, 3)]
|
||||||
|
features_list = [torch.rand(30, 3), None]
|
||||||
|
pcls = Pointclouds(points=points_list, features=features_list)
|
||||||
|
self.assertEqual(len(pcls), 2)
|
||||||
|
self.assertClose(
|
||||||
|
pcls.points_padded(),
|
||||||
|
torch.stack([points_list[0], torch.zeros_like(points_list[0])]),
|
||||||
|
)
|
||||||
|
self.assertClose(pcls.points_packed(), points_list[0])
|
||||||
|
self.assertClose(
|
||||||
|
pcls.features_padded(),
|
||||||
|
torch.stack([features_list[0], torch.zeros_like(points_list[0])]),
|
||||||
|
)
|
||||||
|
self.assertClose(pcls.features_packed(), features_list[0])
|
||||||
|
|
||||||
|
points_list = [torch.zeros(0, 3), torch.rand(30, 3)]
|
||||||
|
features_list = [None, torch.rand(30, 3)]
|
||||||
|
pcls = Pointclouds(points=points_list, features=features_list)
|
||||||
|
self.assertEqual(len(pcls), 2)
|
||||||
|
self.assertClose(
|
||||||
|
pcls.points_padded(),
|
||||||
|
torch.stack([torch.zeros_like(points_list[1]), points_list[1]]),
|
||||||
|
)
|
||||||
|
self.assertClose(pcls.points_packed(), points_list[1])
|
||||||
|
self.assertClose(
|
||||||
|
pcls.features_padded(),
|
||||||
|
torch.stack([torch.zeros_like(points_list[1]), features_list[1]]),
|
||||||
|
)
|
||||||
|
self.assertClose(pcls.features_packed(), features_list[1])
|
||||||
|
|
||||||
def test_clone_list(self):
|
def test_clone_list(self):
|
||||||
N = 5
|
N = 5
|
||||||
clouds = self.init_cloud(N, 100, 5)
|
clouds = self.init_cloud(N, 100, 5)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user