Skip to content

Commit 78fd5af

Browse files
bottlerfacebook-github-bot
authored andcommitted
make points2volumes feature rescaling optional
Summary: Add option to not rescale the features, giving more control. #1137 Reviewed By: nikhilaravi Differential Revision: D35219577 fbshipit-source-id: cbbb643b91b71bc908cedc6dac0f63f6d1355c85
1 parent 0a7c354 commit 78fd5af

File tree

3 files changed

+47
-8
lines changed

3 files changed

+47
-8
lines changed

pytorch3d/ops/points_to_volumes.py

+17-5
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ def add_pointclouds_to_volumes(
192192
initial_volumes: "Volumes",
193193
mode: str = "trilinear",
194194
min_weight: float = 1e-4,
195+
rescale_features: bool = True,
195196
_python: bool = False,
196197
) -> "Volumes":
197198
"""
@@ -250,6 +251,10 @@ def add_pointclouds_to_volumes(
250251
min_weight: A scalar controlling the lowest possible total per-voxel
251252
weight used to normalize the features accumulated in a voxel.
252253
Only active for `mode==trilinear`.
254+
rescale_features: If False, output features are just the sum of input and
255+
added points. If True, they are averaged. In both cases,
256+
output densities are just summed without rescaling, so
257+
you may need to rescale them afterwards.
253258
_python: Set to True to use a pure Python implementation, e.g. for test
254259
purposes, which requires more memory and may be slower.
255260
@@ -286,6 +291,7 @@ def add_pointclouds_to_volumes(
286291
grid_sizes=initial_volumes.get_grid_sizes(),
287292
mask=mask,
288293
mode=mode,
294+
rescale_features=rescale_features,
289295
_python=_python,
290296
)
291297

@@ -303,6 +309,7 @@ def add_points_features_to_volume_densities_features(
303309
min_weight: float = 1e-4,
304310
mask: Optional[torch.Tensor] = None,
305311
grid_sizes: Optional[torch.LongTensor] = None,
312+
rescale_features: bool = True,
306313
_python: bool = False,
307314
) -> Tuple[torch.Tensor, torch.Tensor]:
308315
"""
@@ -345,6 +352,10 @@ def add_points_features_to_volume_densities_features(
345352
grid_sizes: `LongTensor` of shape (minibatch, 3) representing the
346353
spatial resolutions of each of the the non-flattened `volumes` tensors,
347354
or None to indicate the whole volume is used for every batch element.
355+
rescale_features: If False, output features are just the sum of input and
356+
added points. If True, they are averaged. In both cases,
357+
output densities are just summed without rescaling, so
358+
you may need to rescale them afterwards.
348359
_python: Set to True to use a pure Python implementation.
349360
Returns:
350361
volume_features: Output volume of shape `(minibatch, feature_dim, D, H, W)`
@@ -401,12 +412,13 @@ def add_points_features_to_volume_densities_features(
401412
True, # align_corners
402413
splat,
403414
)
404-
if splat:
405-
# divide each feature by the total weight of the votes
406-
volume_features = volume_features / volume_densities.clamp(min_weight)
407-
else:
415+
416+
if rescale_features:
408417
# divide each feature by the total weight of the votes
409-
volume_features = volume_features / volume_densities.clamp(1.0)
418+
if splat:
419+
volume_features = volume_features / volume_densities.clamp(min_weight)
420+
else:
421+
volume_features = volume_features / volume_densities.clamp(1.0)
410422

411423
return volume_features, volume_densities
412424

tests/common_testing.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,22 @@ def assertClose(
192192
self.fail(f"{msg} {err}")
193193
self.fail(err)
194194

195-
def assertConstant(self, input: TensorOrArray, value: Real) -> None:
195+
def assertConstant(
196+
self, input: TensorOrArray, value: Real, *, atol: float = 0
197+
) -> None:
196198
"""
197199
Asserts input is entirely filled with value.
198200
199201
Args:
200202
input: tensor or array
203+
value: expected value
204+
atol: tolerance
201205
"""
202-
self.assertEqual(input.min(), value)
203-
self.assertEqual(input.max(), value)
206+
mn, mx = input.min(), input.max()
207+
msg = f"values in range [{mn}, {mx}], not {value}, shape {input.shape}"
208+
if atol == 0:
209+
self.assertEqual(input.min(), value, msg=msg)
210+
self.assertEqual(input.max(), value, msg=msg)
211+
else:
212+
self.assertGreater(input.min(), value - atol, msg=msg)
213+
self.assertLess(input.max(), value + atol, msg=msg)

tests/test_points_to_volumes.py

+17
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,23 @@ def test_defaulted_arguments(self):
387387
)
388388
self.assertClose(torch.sum(densities), torch.tensor(30 * 1000.0), atol=0.1)
389389

390+
def test_unscaled(self):
391+
D = 5
392+
P = 1000
393+
B, C, H, W = 2, 3, D, D
394+
densities = torch.zeros(B, 1, D, H, W)
395+
features = torch.zeros(B, C, D, H, W)
396+
volumes = Volumes(densities=densities, features=features)
397+
points = torch.rand(B, 1000, 3) * (D - 1) - ((D - 1) * 0.5)
398+
point_features = torch.rand(B, 1000, C)
399+
pointclouds = Pointclouds(points=points, features=point_features)
400+
401+
volumes2 = add_pointclouds_to_volumes(
402+
pointclouds, volumes, rescale_features=False
403+
)
404+
self.assertConstant(volumes2.densities().sum([2, 3, 4]) / P, 1, atol=1e-5)
405+
self.assertConstant(volumes2.features().sum([2, 3, 4]) / P, 0.5, atol=0.03)
406+
390407
def _check_volume_slice_color_density(
391408
self, V, split_dim, interp_mode, clr_gt, slice_type, border=3
392409
):

0 commit comments

Comments
 (0)